trakt-tools 1.5.1

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 ADDED
@@ -0,0 +1,42 @@
1
+ # openclaw-trakt
2
+
3
+ OpenClaw plugin for [Trakt.tv](https://trakt.tv). Track movies and TV shows, view watch history, manage your watchlist, and check show progress.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ openclaw plugins install trakt-tools
9
+ ```
10
+
11
+ ## Prerequisites
12
+
13
+ The `trakt-cli` Go binary must be installed and on your PATH:
14
+
15
+ ```bash
16
+ go install github.com/omarshahine/trakt-plugin@latest
17
+ ```
18
+
19
+ Or set `TRAKT_CLI_PATH` environment variable, or configure `cliPath` in plugin settings.
20
+
21
+ ## Configuration
22
+
23
+ | Setting | Description |
24
+ |---------|-------------|
25
+ | `cliPath` | Path to trakt-cli binary (auto-detected on PATH) |
26
+ | `clientId` | Trakt API client ID (from https://trakt.tv/oauth/applications) |
27
+ | `clientSecret` | Trakt API client secret |
28
+
29
+ ## Available Tools
30
+
31
+ | Tool | Description |
32
+ |------|-------------|
33
+ | `trakt_search` | Search for movies and TV shows |
34
+ | `trakt_history` | View watch history |
35
+ | `trakt_history_add` | Mark movies/shows as watched |
36
+ | `trakt_watchlist` | View watchlist |
37
+ | `trakt_progress` | Show watch progress for TV shows |
38
+ | `trakt_auth` | Set up Trakt.tv authentication |
39
+
40
+ ## License
41
+
42
+ MIT
@@ -0,0 +1,42 @@
1
+ {
2
+ "id": "trakt-tools",
3
+ "name": "Trakt",
4
+ "description": "Track movies and TV shows using trakt.tv",
5
+ "version": "1.0.0",
6
+ "configSchema": {
7
+ "type": "object",
8
+ "properties": {
9
+ "cliPath": {
10
+ "type": "string",
11
+ "description": "Path to trakt-cli binary",
12
+ "default": "trakt-cli"
13
+ },
14
+ "clientId": {
15
+ "type": "string",
16
+ "description": "Trakt API client ID (from https://trakt.tv/oauth/applications)"
17
+ },
18
+ "clientSecret": {
19
+ "type": "string",
20
+ "description": "Trakt API client secret"
21
+ }
22
+ },
23
+ "additionalProperties": false
24
+ },
25
+ "uiHints": {
26
+ "cliPath": {
27
+ "label": "CLI Path",
28
+ "help": "Path or command name for the trakt-cli binary. Defaults to finding it on PATH.",
29
+ "placeholder": "trakt-cli"
30
+ },
31
+ "clientId": {
32
+ "label": "Client ID",
33
+ "help": "Trakt API client ID from https://trakt.tv/oauth/applications",
34
+ "sensitive": true
35
+ },
36
+ "clientSecret": {
37
+ "label": "Client Secret",
38
+ "help": "Trakt API client secret from https://trakt.tv/oauth/applications",
39
+ "sensitive": true
40
+ }
41
+ }
42
+ }
package/package.json ADDED
@@ -0,0 +1,47 @@
1
+ {
2
+ "name": "trakt-tools",
3
+ "version": "1.5.1",
4
+ "description": "OpenClaw plugin for Trakt.tv - track movies and TV shows",
5
+ "type": "module",
6
+ "openclaw": {
7
+ "extensions": [
8
+ "./src/index.ts"
9
+ ]
10
+ },
11
+ "files": [
12
+ "src/",
13
+ "skills/",
14
+ "openclaw.plugin.json"
15
+ ],
16
+ "keywords": [
17
+ "openclaw",
18
+ "openclaw-plugin",
19
+ "trakt",
20
+ "movies",
21
+ "tv-shows",
22
+ "watchlist"
23
+ ],
24
+ "author": {
25
+ "name": "Omar Shahine",
26
+ "email": "omar@shahine.com"
27
+ },
28
+ "license": "MIT",
29
+ "repository": {
30
+ "type": "git",
31
+ "url": "https://github.com/omarshahine/trakt-plugin.git"
32
+ },
33
+ "homepage": "https://github.com/omarshahine/trakt-plugin#readme",
34
+ "bugs": {
35
+ "url": "https://github.com/omarshahine/trakt-plugin/issues"
36
+ },
37
+ "engines": {
38
+ "node": ">=18"
39
+ },
40
+ "dependencies": {
41
+ "openclaw": "^2026.3.23"
42
+ },
43
+ "devDependencies": {
44
+ "@types/node": "^25.5.0",
45
+ "typescript": "^5.9.3"
46
+ }
47
+ }
@@ -0,0 +1,97 @@
1
+ ---
2
+ name: trakt
3
+ description: |
4
+ Search movies/shows, view watch history, check watchlist, track progress, and mark items as watched on Trakt.tv.
5
+ Use when the user asks what they've been watching, what's on their watchlist, what's in progress,
6
+ wants to find a movie or show, mark something as watched, or asks about their Trakt activity.
7
+ license: MIT
8
+ metadata:
9
+ author: Omar Shahine
10
+ version: 2.0.0
11
+ openclaw:
12
+ requires:
13
+ bins: [trakt-cli]
14
+ ---
15
+
16
+ # Trakt Skill
17
+
18
+ View watch history, watchlist, progress, search, and mark items as watched on Trakt.tv.
19
+
20
+ All commands support `--json` for machine-readable output. **Always use `--json` for data processing.**
21
+
22
+ ## Commands
23
+
24
+ ### Progress (In-Progress Shows)
25
+
26
+ Shows which watchlist shows are started but not finished, not started, or completed.
27
+
28
+ ```bash
29
+ trakt-cli progress --json
30
+ trakt-cli progress --all --json
31
+ ```
32
+
33
+ - Default: shows only in-progress items + summary counts
34
+ - `--all`: includes not_started and completed lists
35
+ - JSON output: `{ "in_progress": [...], "summary": { "in_progress": N, "not_started": N, "completed": N } }`
36
+ - Each item: `{ "title", "year", "trakt_id", "aired", "watched", "remaining", "percent", "status", "next_episode" }`
37
+
38
+ ### Watchlist
39
+
40
+ ```bash
41
+ trakt-cli watchlist --json
42
+ trakt-cli watchlist --type shows --limit 100 --json
43
+ trakt-cli watchlist --type movies --json
44
+ ```
45
+
46
+ - JSON output: `{ "items": [{ "type", "title", "year", "trakt_id", "added_at" }], "page", "page_count", "item_count" }`
47
+ - `--type`: filter by `movies` or `shows`
48
+ - `--limit`: items per page (default 10)
49
+ - `--page`: page number
50
+
51
+ ### Watch History
52
+
53
+ ```bash
54
+ trakt-cli history --json
55
+ trakt-cli history --type shows --limit 20 --json
56
+ trakt-cli history --type movies --json
57
+ ```
58
+
59
+ - JSON output: `{ "items": [{ "type", "title", "year", "watched_at", "season", "episode", "show_title" }], ... }`
60
+ - Episodes include `show_title`, `season`, `episode` fields
61
+
62
+ ### Mark as Watched
63
+
64
+ ```bash
65
+ trakt-cli history add "Pluribus" --json
66
+ trakt-cli history add "The Sopranos" "The Wire" --json
67
+ trakt-cli history add --type movie "The Godfather" --json
68
+ trakt-cli history add --watched-at 2025-06-15 "Dark" --json
69
+ ```
70
+
71
+ - Searches by name, prefers exact title matches
72
+ - `--type show` (default) or `--type movie`
73
+ - `--watched-at`: RFC3339 or YYYY-MM-DD (defaults to now)
74
+ - Accepts multiple titles in one call
75
+ - JSON output: `{ "added_episodes": N, "added_movies": N, "not_found_movies": N, "not_found_shows": N }`
76
+
77
+ ### Search
78
+
79
+ ```bash
80
+ trakt-cli search "Shogun" --json
81
+ trakt-cli search "Inception" --type movie --json
82
+ ```
83
+
84
+ - JSON output: `{ "items": [{ "type", "title", "year", "trakt_id", "imdb" }] }`
85
+ - `--type`: `movie`, `show`, or `movie,show` (default)
86
+
87
+ ## Notes
88
+
89
+ - Always use `--json` flag — raw table output is for human use only
90
+ - No shell constructs (pipes, redirects, chaining)
91
+ - Auth stored in `~/.trakt.yaml` (OAuth device flow)
92
+
93
+ ## Changelog
94
+
95
+ - **v2.0.0** — Add `progress` command, `--json` flag for all commands (agent-friendly output)
96
+ - **v1.1.0** — Add `watchlist` command, `--type` filter for `history`, `history add` with `--watched-at`
97
+ - **v1.0.0** — Initial skill (upstream `history` and `search` only)
package/src/index.ts ADDED
@@ -0,0 +1,311 @@
1
+ /**
2
+ * OpenClaw plugin entry for trakt-cli.
3
+ *
4
+ * Registers tools that shell out to the `trakt-cli` binary.
5
+ * Each tool maps to a CLI subcommand (search, history, watchlist, progress).
6
+ */
7
+
8
+ import { definePluginEntry } from 'openclaw/plugin-sdk/plugin-entry';
9
+ import { Type } from '@sinclair/typebox';
10
+ import { execFileSync, execFile } from 'child_process';
11
+ import { promisify } from 'util';
12
+ import { existsSync } from 'fs';
13
+ import { homedir } from 'os';
14
+ import { join } from 'path';
15
+
16
+ const execFileAsync = promisify(execFile);
17
+
18
+ interface PluginConfig {
19
+ cliPath?: string;
20
+ clientId?: string;
21
+ clientSecret?: string;
22
+ }
23
+
24
+ interface ToolDef {
25
+ name: string;
26
+ command: string;
27
+ subcommand?: string;
28
+ description: string;
29
+ parameters: ReturnType<typeof Type.Object>;
30
+ }
31
+
32
+ // Tool metadata — each maps to a CLI subcommand
33
+ const TOOLS: ToolDef[] = [
34
+ {
35
+ name: 'trakt_search',
36
+ command: 'search',
37
+ description:
38
+ 'Search Trakt.tv for movies and TV shows. Returns title, year, trakt_id, and imdb.',
39
+ parameters: Type.Object({
40
+ query: Type.String({ description: 'Search query (movie or show title)' }),
41
+ type: Type.Optional(
42
+ Type.Union(
43
+ [Type.Literal('movie'), Type.Literal('show'), Type.Literal('movie,show')],
44
+ { description: 'Filter by type (default: movie,show)' }
45
+ )
46
+ ),
47
+ }),
48
+ },
49
+ {
50
+ name: 'trakt_history',
51
+ command: 'history',
52
+ description:
53
+ 'View Trakt.tv watch history. Returns recently watched movies and episodes with timestamps.',
54
+ parameters: Type.Object({
55
+ type: Type.Optional(
56
+ Type.Union([Type.Literal('movies'), Type.Literal('shows')], {
57
+ description: 'Filter by type',
58
+ })
59
+ ),
60
+ limit: Type.Optional(Type.Number({ description: 'Items per page (default 10)' })),
61
+ page: Type.Optional(Type.Number({ description: 'Page number' })),
62
+ }),
63
+ },
64
+ {
65
+ name: 'trakt_history_add',
66
+ command: 'history',
67
+ subcommand: 'add',
68
+ description:
69
+ 'Mark movies or shows as watched on Trakt.tv. Searches by title and adds to history. Accepts multiple titles.',
70
+ parameters: Type.Object({
71
+ titles: Type.Array(Type.String(), { description: 'Title(s) to mark as watched' }),
72
+ type: Type.Optional(
73
+ Type.Union([Type.Literal('movie'), Type.Literal('show')], {
74
+ description: 'Content type (default: show)',
75
+ })
76
+ ),
77
+ watched_at: Type.Optional(
78
+ Type.String({
79
+ description: 'When watched (YYYY-MM-DD or RFC3339, defaults to now)',
80
+ })
81
+ ),
82
+ }),
83
+ },
84
+ {
85
+ name: 'trakt_watchlist',
86
+ command: 'watchlist',
87
+ description:
88
+ 'View Trakt.tv watchlist. Returns items the user wants to watch, with type, title, year, and added date.',
89
+ parameters: Type.Object({
90
+ type: Type.Optional(
91
+ Type.Union([Type.Literal('movies'), Type.Literal('shows')], {
92
+ description: 'Filter by type',
93
+ })
94
+ ),
95
+ limit: Type.Optional(Type.Number({ description: 'Items per page (default 10)' })),
96
+ page: Type.Optional(Type.Number({ description: 'Page number' })),
97
+ }),
98
+ },
99
+ {
100
+ name: 'trakt_progress',
101
+ command: 'progress',
102
+ description:
103
+ 'Show progress of watchlist TV shows. Returns in-progress shows with episode counts, percentage, and next episode to watch.',
104
+ parameters: Type.Object({
105
+ all: Type.Optional(
106
+ Type.Boolean({
107
+ description: 'Include not_started and completed shows (default: in-progress only)',
108
+ })
109
+ ),
110
+ }),
111
+ },
112
+ {
113
+ name: 'trakt_auth',
114
+ command: 'auth',
115
+ description:
116
+ 'Set up Trakt.tv authentication. Initiates OAuth device flow using configured client credentials. Only needed for initial setup.',
117
+ parameters: Type.Object({}),
118
+ },
119
+ ];
120
+
121
+ /** Helper to build a tool result with the required content + details shape. */
122
+ function toolResult(text: string) {
123
+ return {
124
+ content: [{ type: 'text' as const, text }],
125
+ details: undefined,
126
+ };
127
+ }
128
+
129
+ /**
130
+ * Look up a binary on PATH, cross-platform.
131
+ */
132
+ function whichBinary(name: string): string | null {
133
+ const cmd = process.platform === 'win32' ? 'where.exe' : 'which';
134
+ try {
135
+ const result = execFileSync(cmd, [name], { encoding: 'utf8' }).trim();
136
+ const first = result.split('\n')[0]?.trim();
137
+ return first || null;
138
+ } catch {
139
+ return null;
140
+ }
141
+ }
142
+
143
+ /**
144
+ * Resolve the CLI binary path:
145
+ * 1. Plugin config cliPath
146
+ * 2. Env var TRAKT_CLI_PATH
147
+ * 3. PATH lookup (try both `trakt-cli` and `trakt-plugin`)
148
+ */
149
+ function resolveCliPath(config?: PluginConfig): string {
150
+ if (config?.cliPath && existsSync(config.cliPath)) {
151
+ return config.cliPath;
152
+ }
153
+
154
+ const envPath = process.env.TRAKT_CLI_PATH;
155
+ if (envPath && existsSync(envPath)) {
156
+ return envPath;
157
+ }
158
+
159
+ for (const name of ['trakt-cli', 'trakt-plugin']) {
160
+ const found = whichBinary(name);
161
+ if (found) return found;
162
+ }
163
+
164
+ throw new Error(
165
+ 'trakt-cli not found. Install with: go install github.com/omarshahine/trakt-plugin@latest\n' +
166
+ 'Or set TRAKT_CLI_PATH or configure cliPath in plugin settings.'
167
+ );
168
+ }
169
+
170
+ function isAuthConfigured(): boolean {
171
+ return existsSync(join(homedir(), '.trakt.yaml'));
172
+ }
173
+
174
+ /**
175
+ * Build CLI arguments from tool parameters.
176
+ * Always appends --json for machine-readable output.
177
+ */
178
+ function buildCliArgs(
179
+ command: string,
180
+ subcommand: string | undefined,
181
+ params: Record<string, unknown>,
182
+ config?: PluginConfig
183
+ ): string[] {
184
+ const args: string[] = [command];
185
+
186
+ if (command === 'auth') {
187
+ if (config?.clientId) args.push('--client-id', config.clientId);
188
+ if (config?.clientSecret) args.push('--client-secret', config.clientSecret);
189
+ return args;
190
+ }
191
+
192
+ if (subcommand) args.push(subcommand);
193
+
194
+ if (command === 'search') {
195
+ if (params.query) args.push(String(params.query));
196
+ }
197
+
198
+ if (command === 'history' && subcommand === 'add') {
199
+ const titles = params.titles as string[] | undefined;
200
+ if (titles) {
201
+ if (params.type) args.push('--type', String(params.type));
202
+ if (params.watched_at) args.push('--watched-at', String(params.watched_at));
203
+ for (const title of titles) {
204
+ args.push(title);
205
+ }
206
+ args.push('--json');
207
+ return args;
208
+ }
209
+ }
210
+
211
+ const skipKeys = new Set(['query', 'titles']);
212
+ for (const [key, value] of Object.entries(params)) {
213
+ if (skipKeys.has(key) || value === undefined || value === null || value === false) continue;
214
+ const flag = `--${key.replace(/_/g, '-')}`;
215
+ if (typeof value === 'boolean') {
216
+ args.push(flag);
217
+ } else {
218
+ args.push(flag, String(value));
219
+ }
220
+ }
221
+
222
+ args.push('--json');
223
+ return args;
224
+ }
225
+
226
+ export default definePluginEntry({
227
+ id: 'trakt-tools',
228
+ name: 'Trakt',
229
+ description: 'Track movies and TV shows using trakt.tv',
230
+
231
+ register(api) {
232
+ const config = api.pluginConfig as PluginConfig | undefined;
233
+
234
+ let cliPath: string;
235
+ try {
236
+ cliPath = resolveCliPath(config);
237
+ } catch (error) {
238
+ // Defer error to tool execution time — plugin still loads
239
+ const errorMessage = error instanceof Error ? error.message : String(error);
240
+
241
+ for (const tool of TOOLS) {
242
+ api.registerTool({
243
+ name: tool.name,
244
+ label: tool.name,
245
+ description: tool.description,
246
+ parameters: tool.parameters,
247
+ async execute() {
248
+ return toolResult(
249
+ JSON.stringify({ success: false, error: errorMessage }, null, 2)
250
+ );
251
+ },
252
+ });
253
+ }
254
+ return;
255
+ }
256
+
257
+ for (const tool of TOOLS) {
258
+ api.registerTool({
259
+ name: tool.name,
260
+ label: tool.name,
261
+ description: tool.description,
262
+ parameters: tool.parameters,
263
+
264
+ async execute(_id: string, params: Record<string, unknown>) {
265
+ if (tool.command !== 'auth' && !isAuthConfigured()) {
266
+ return toolResult(
267
+ JSON.stringify(
268
+ {
269
+ success: false,
270
+ error:
271
+ 'Trakt auth not configured. Run trakt_auth first, or manually: trakt-cli auth --client-id X --client-secret Y',
272
+ },
273
+ null,
274
+ 2
275
+ )
276
+ );
277
+ }
278
+
279
+ try {
280
+ const args = buildCliArgs(tool.command, tool.subcommand, params, config);
281
+ const { stdout } = await execFileAsync(cliPath, args, {
282
+ encoding: 'utf8',
283
+ timeout: 30_000,
284
+ maxBuffer: 1024 * 1024,
285
+ });
286
+
287
+ let result: unknown;
288
+ try {
289
+ result = JSON.parse(stdout);
290
+ } catch {
291
+ result = { output: stdout.trim() };
292
+ }
293
+
294
+ return toolResult(JSON.stringify(result, null, 2));
295
+ } catch (error: unknown) {
296
+ const message = error instanceof Error ? error.message : String(error);
297
+ const stderr =
298
+ error && typeof error === 'object' && 'stderr' in error
299
+ ? String((error as { stderr: unknown }).stderr).trim()
300
+ : '';
301
+ const errorOutput = stderr ? `${message}\n\nstderr: ${stderr}` : message;
302
+
303
+ return toolResult(
304
+ JSON.stringify({ success: false, error: errorOutput }, null, 2)
305
+ );
306
+ }
307
+ },
308
+ });
309
+ }
310
+ },
311
+ });