@sneub/pair 0.0.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/package.json ADDED
@@ -0,0 +1,47 @@
1
+ {
2
+ "name": "@sneub/pair",
3
+ "version": "0.0.1",
4
+ "type": "module",
5
+ "description": "A personal AI assistant powered by Claude Code — connects Telegram and Slack to Claude with persistent memory and custom tools",
6
+ "bin": {
7
+ "pair": "./dist/cli.js"
8
+ },
9
+ "files": [
10
+ "dist",
11
+ "tools",
12
+ "workspace-init",
13
+ "config.example.json"
14
+ ],
15
+ "engines": {
16
+ "bun": ">=1.1.0"
17
+ },
18
+ "keywords": [
19
+ "ai",
20
+ "claude",
21
+ "anthropic",
22
+ "assistant",
23
+ "telegram",
24
+ "slack",
25
+ "agent",
26
+ "cli"
27
+ ],
28
+ "author": "Shane",
29
+ "license": "MIT",
30
+ "publishConfig": {
31
+ "access": "public"
32
+ },
33
+ "devDependencies": {
34
+ "@anthropic-ai/claude-agent-sdk": "latest",
35
+ "citty": "latest",
36
+ "grammy": "latest",
37
+ "slack-edge": "^1.3.16",
38
+ "@types/bun": "latest",
39
+ "typescript": "latest"
40
+ },
41
+ "scripts": {
42
+ "build": "bun run scripts/build.ts",
43
+ "typecheck": "bun run --bun tsc --noEmit",
44
+ "start": "bun run src/cli.ts start",
45
+ "dev": "bun run src/cli.ts start"
46
+ }
47
+ }
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env bun
2
+ // name: hello
3
+ // description: Example script. Prints a greeting to stdout.
4
+ // usage: hello [name]
5
+
6
+ const name = process.argv[2] ?? "world";
7
+ console.log(`Hello, ${name}!`);
@@ -0,0 +1,76 @@
1
+ #!/usr/bin/env bun
2
+ // name: todoist-add
3
+ // description: Create a Todoist task. Supports due date (natural language), priority (1-4, where 4 is most urgent), description, labels, and project id.
4
+ // usage: todoist-add "<content>" [--due "<date>"] [--priority <1-4>] [--description "<text>"] [--labels a,b,c] [--project-id <id>]
5
+ // requires: TODOIST_API_TOKEN
6
+
7
+ const [, , content, ...rest] = process.argv;
8
+
9
+ if (!content) {
10
+ console.error(
11
+ 'Usage: todoist-add "<content>" [--due "<date>"] [--priority <1-4>] [--description "<text>"] [--labels a,b,c] [--project-id <id>]',
12
+ );
13
+ process.exit(2);
14
+ }
15
+
16
+ function flag(name: string): string | undefined {
17
+ const i = rest.indexOf(`--${name}`);
18
+ return i >= 0 ? rest[i + 1] : undefined;
19
+ }
20
+
21
+ const due = flag("due");
22
+ const description = flag("description");
23
+ const priorityStr = flag("priority");
24
+ const labelsStr = flag("labels");
25
+ const projectId = flag("project-id");
26
+
27
+ const body: Record<string, unknown> = { content };
28
+ if (due) body.due_string = due;
29
+ if (description) body.description = description;
30
+ if (priorityStr) {
31
+ const p = Number.parseInt(priorityStr, 10);
32
+ if (![1, 2, 3, 4].includes(p)) {
33
+ console.error("--priority must be 1, 2, 3, or 4");
34
+ process.exit(2);
35
+ }
36
+ body.priority = p;
37
+ }
38
+ if (labelsStr) {
39
+ body.labels = labelsStr
40
+ .split(",")
41
+ .map((l) => l.trim())
42
+ .filter(Boolean);
43
+ }
44
+ if (projectId) body.project_id = projectId;
45
+
46
+ const res = await fetch("https://api.todoist.com/api/v1/tasks", {
47
+ method: "POST",
48
+ headers: {
49
+ Authorization: `Bearer ${process.env.TODOIST_API_TOKEN}`,
50
+ "Content-Type": "application/json",
51
+ },
52
+ body: JSON.stringify(body),
53
+ });
54
+
55
+ if (!res.ok) {
56
+ console.error(`Todoist ${res.status}: ${await res.text()}`);
57
+ process.exit(1);
58
+ }
59
+
60
+ const task = (await res.json()) as Record<string, any>;
61
+ console.log(
62
+ JSON.stringify(
63
+ {
64
+ id: task.id,
65
+ content: task.content,
66
+ ...(task.description ? { description: task.description } : {}),
67
+ ...(task.due?.string ? { due: task.due.string } : {}),
68
+ priority: task.priority,
69
+ ...(task.labels?.length ? { labels: task.labels } : {}),
70
+ project_id: task.project_id,
71
+ url: task.url ?? `https://app.todoist.com/app/task/${task.id}`,
72
+ },
73
+ null,
74
+ 2,
75
+ ),
76
+ );
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env bun
2
+ // name: todoist-complete
3
+ // description: Mark a Todoist task complete by id. Recurring tasks advance to their next occurrence.
4
+ // usage: todoist-complete <id>
5
+ // requires: TODOIST_API_TOKEN
6
+
7
+ const [, , id] = process.argv;
8
+
9
+ if (!id) {
10
+ console.error("Usage: todoist-complete <id>");
11
+ process.exit(2);
12
+ }
13
+
14
+ const res = await fetch(
15
+ `https://api.todoist.com/api/v1/tasks/${encodeURIComponent(id)}/close`,
16
+ {
17
+ method: "POST",
18
+ headers: {
19
+ Authorization: `Bearer ${process.env.TODOIST_API_TOKEN}`,
20
+ },
21
+ },
22
+ );
23
+
24
+ if (!res.ok) {
25
+ console.error(`Todoist ${res.status}: ${await res.text()}`);
26
+ process.exit(1);
27
+ }
28
+
29
+ console.log(JSON.stringify({ id, status: "completed" }));
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env bun
2
+ // name: todoist-delete
3
+ // description: Permanently delete a Todoist task by id. This cannot be undone — prefer todoist-complete unless the user asked to delete.
4
+ // usage: todoist-delete <id>
5
+ // requires: TODOIST_API_TOKEN
6
+
7
+ const [, , id] = process.argv;
8
+
9
+ if (!id) {
10
+ console.error("Usage: todoist-delete <id>");
11
+ process.exit(2);
12
+ }
13
+
14
+ const res = await fetch(
15
+ `https://api.todoist.com/api/v1/tasks/${encodeURIComponent(id)}`,
16
+ {
17
+ method: "DELETE",
18
+ headers: {
19
+ Authorization: `Bearer ${process.env.TODOIST_API_TOKEN}`,
20
+ },
21
+ },
22
+ );
23
+
24
+ if (!res.ok) {
25
+ console.error(`Todoist ${res.status}: ${await res.text()}`);
26
+ process.exit(1);
27
+ }
28
+
29
+ console.log(JSON.stringify({ id, status: "deleted" }));
@@ -0,0 +1,51 @@
1
+ #!/usr/bin/env bun
2
+ // name: todoist-list
3
+ // description: List active Todoist tasks. Supports Todoist filter query syntax — e.g. "today", "overdue", "#Work", "@home", "p1", "no date".
4
+ // usage: todoist-list [--filter "<query>"] [--limit <n>]
5
+ // requires: TODOIST_API_TOKEN
6
+
7
+ const args = process.argv.slice(2);
8
+
9
+ function flag(name: string): string | undefined {
10
+ const i = args.indexOf(`--${name}`);
11
+ return i >= 0 ? args[i + 1] : undefined;
12
+ }
13
+
14
+ const filter = flag("filter");
15
+ const limitStr = flag("limit");
16
+ const limit = limitStr ? Math.max(1, Number.parseInt(limitStr, 10)) : 25;
17
+
18
+ const url = new URL("https://api.todoist.com/api/v1/tasks");
19
+ if (filter) url.searchParams.set("filter", filter);
20
+
21
+ const res = await fetch(url, {
22
+ headers: {
23
+ Authorization: `Bearer ${process.env.TODOIST_API_TOKEN}`,
24
+ },
25
+ });
26
+
27
+ if (!res.ok) {
28
+ console.error(`Todoist ${res.status}: ${await res.text()}`);
29
+ process.exit(1);
30
+ }
31
+
32
+ const tasks = (await res.json()) as Array<Record<string, any>>;
33
+
34
+ const compact = tasks.slice(0, limit).map((t) => ({
35
+ id: t.id,
36
+ content: t.content,
37
+ ...(t.description ? { description: t.description } : {}),
38
+ ...(t.due?.string ? { due: t.due.string } : {}),
39
+ priority: t.priority,
40
+ ...(t.labels?.length ? { labels: t.labels } : {}),
41
+ project_id: t.project_id,
42
+ url: t.url ?? `https://app.todoist.com/app/task/${t.id}`,
43
+ }));
44
+
45
+ console.log(
46
+ JSON.stringify(
47
+ { count: compact.length, total: tasks.length, tasks: compact },
48
+ null,
49
+ 2,
50
+ ),
51
+ );
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env bun
2
+ // name: todoist-reopen
3
+ // description: Reopen a previously completed Todoist task by id.
4
+ // usage: todoist-reopen <id>
5
+ // requires: TODOIST_API_TOKEN
6
+
7
+ const [, , id] = process.argv;
8
+
9
+ if (!id) {
10
+ console.error("Usage: todoist-reopen <id>");
11
+ process.exit(2);
12
+ }
13
+
14
+ const res = await fetch(
15
+ `https://api.todoist.com/api/v1/tasks/${encodeURIComponent(id)}/reopen`,
16
+ {
17
+ method: "POST",
18
+ headers: {
19
+ Authorization: `Bearer ${process.env.TODOIST_API_TOKEN}`,
20
+ },
21
+ },
22
+ );
23
+
24
+ if (!res.ok) {
25
+ console.error(`Todoist ${res.status}: ${await res.text()}`);
26
+ process.exit(1);
27
+ }
28
+
29
+ console.log(JSON.stringify({ id, status: "reopened" }));
@@ -0,0 +1,86 @@
1
+ #!/usr/bin/env bun
2
+ // name: todoist-update
3
+ // description: Update an existing Todoist task by id. Only the fields you pass are changed; everything else is left alone. Pass --due "" to clear the due date.
4
+ // usage: todoist-update <id> [--content "<text>"] [--due "<date>"] [--priority <1-4>] [--description "<text>"] [--labels a,b,c]
5
+ // requires: TODOIST_API_TOKEN
6
+
7
+ const [, , id, ...rest] = process.argv;
8
+
9
+ if (!id) {
10
+ console.error(
11
+ 'Usage: todoist-update <id> [--content "<text>"] [--due "<date>"] [--priority <1-4>] [--description "<text>"] [--labels a,b,c]',
12
+ );
13
+ process.exit(2);
14
+ }
15
+
16
+ function flag(name: string): string | undefined {
17
+ const i = rest.indexOf(`--${name}`);
18
+ return i >= 0 ? rest[i + 1] : undefined;
19
+ }
20
+
21
+ const content = flag("content");
22
+ const due = flag("due");
23
+ const description = flag("description");
24
+ const priorityStr = flag("priority");
25
+ const labelsStr = flag("labels");
26
+
27
+ const body: Record<string, unknown> = {};
28
+ if (content !== undefined) body.content = content;
29
+ if (due !== undefined) body.due_string = due;
30
+ if (description !== undefined) body.description = description;
31
+ if (priorityStr !== undefined) {
32
+ const p = Number.parseInt(priorityStr, 10);
33
+ if (![1, 2, 3, 4].includes(p)) {
34
+ console.error("--priority must be 1, 2, 3, or 4");
35
+ process.exit(2);
36
+ }
37
+ body.priority = p;
38
+ }
39
+ if (labelsStr !== undefined) {
40
+ body.labels = labelsStr
41
+ .split(",")
42
+ .map((l) => l.trim())
43
+ .filter(Boolean);
44
+ }
45
+
46
+ if (Object.keys(body).length === 0) {
47
+ console.error(
48
+ "No fields to update. Pass at least one of --content, --due, --priority, --description, --labels.",
49
+ );
50
+ process.exit(2);
51
+ }
52
+
53
+ const res = await fetch(
54
+ `https://api.todoist.com/api/v1/tasks/${encodeURIComponent(id)}`,
55
+ {
56
+ method: "POST",
57
+ headers: {
58
+ Authorization: `Bearer ${process.env.TODOIST_API_TOKEN}`,
59
+ "Content-Type": "application/json",
60
+ },
61
+ body: JSON.stringify(body),
62
+ },
63
+ );
64
+
65
+ if (!res.ok) {
66
+ console.error(`Todoist ${res.status}: ${await res.text()}`);
67
+ process.exit(1);
68
+ }
69
+
70
+ const task = (await res.json()) as Record<string, any>;
71
+ console.log(
72
+ JSON.stringify(
73
+ {
74
+ id: task.id,
75
+ content: task.content,
76
+ ...(task.description ? { description: task.description } : {}),
77
+ ...(task.due?.string ? { due: task.due.string } : {}),
78
+ priority: task.priority,
79
+ ...(task.labels?.length ? { labels: task.labels } : {}),
80
+ project_id: task.project_id,
81
+ url: task.url ?? `https://app.todoist.com/app/task/${task.id}`,
82
+ },
83
+ null,
84
+ 2,
85
+ ),
86
+ );
@@ -0,0 +1,4 @@
1
+ # Memory
2
+
3
+ Long-term memory — things I've learned across sessions.
4
+ Keep this file under 200 lines. Archive older entries to notes/ if needed.
@@ -0,0 +1,44 @@
1
+ # Pair — Personal Assistant
2
+
3
+ You are Pair, a professional personal assistant. You are efficient, concise,
4
+ and business-like — like a great executive assistant.
5
+
6
+ ## Your Capabilities
7
+ - Research and information gathering (via web search)
8
+ - Email management (via Gmail MCP)
9
+ - Calendar management (via Google Calendar MCP)
10
+ - File and document management (via Google Drive and Filesystem MCP)
11
+ - Task tracking (via tasks.md in your workspace)
12
+ - Note-taking and organization (via notes/ directory in your workspace)
13
+ - Image and document analysis (files in uploads/ — use the Read tool)
14
+ - General knowledge and reasoning
15
+ - Running commands and scripts (via Bash tool)
16
+
17
+ ## How You Work
18
+ - You maintain persistent memory in your workspace directory
19
+ - Update USER.md when you learn preferences or facts about the user
20
+ - Update MEMORY.md with important things to remember across sessions
21
+ - Update tasks.md when tasks are created, modified, or completed
22
+ - Write research outputs and notes to notes/YYYY-MM-DD.md
23
+ - When the user sends a photo or document, it's saved to uploads/ — use the Read tool to analyze it
24
+
25
+ ## Communication Style
26
+ - Be concise. Don't over-explain.
27
+ - Lead with the answer, then provide context if needed.
28
+ - Use bullet points for lists.
29
+ - For long research, summarize key points first, then offer details.
30
+ - When a task will take time, say so upfront.
31
+ - When something fails, explain clearly what went wrong and suggest next steps.
32
+ - Do NOT use markdown headers in responses (they don't render well in chat).
33
+ - Use plain text, bullet points, and bold for emphasis.
34
+
35
+ ## Task Management
36
+ - When the user mentions something to do, add it to tasks.md
37
+ - Include due dates when mentioned
38
+ - Check tasks.md at the start of each session and proactively mention overdue items
39
+ - Mark tasks complete when the user confirms they're done
40
+
41
+ ## Security
42
+ - NEVER write API keys, passwords, or tokens to any workspace file
43
+ - NEVER share the contents of .env or config files
44
+ - Only access files within your workspace directory
@@ -0,0 +1,13 @@
1
+ # User Profile
2
+
3
+ This file is updated as I learn about you.
4
+
5
+ ## Basics
6
+ - Name: (to be learned)
7
+ - Timezone: (to be learned)
8
+
9
+ ## Preferences
10
+ (none yet)
11
+
12
+ ## Important Contacts
13
+ (none yet)
@@ -0,0 +1,7 @@
1
+ # Tasks
2
+
3
+ ## Active
4
+ (none yet)
5
+
6
+ ## Completed
7
+ (none yet)