@masslessai/push-todo 3.0.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.
@@ -0,0 +1,5 @@
1
+ {
2
+ "name": "push-todo",
3
+ "version": "3.0.0",
4
+ "description": "Voice tasks from Push iOS app"
5
+ }
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 MasslessAI
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,107 @@
1
+ # @masslessai/push-todo
2
+
3
+ Voice tasks from the [Push iOS app](https://pushto.do) for Claude Code.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install -g @masslessai/push-todo
9
+ ```
10
+
11
+ ## Quick Start
12
+
13
+ ```bash
14
+ # Authenticate and set up
15
+ push-todo connect
16
+
17
+ # List your tasks
18
+ push-todo
19
+
20
+ # Work on a specific task
21
+ push-todo 427
22
+ ```
23
+
24
+ ## Features
25
+
26
+ - **Voice Tasks**: Tasks captured by voice on your iPhone sync to your terminal
27
+ - **Project Filtering**: Automatically shows tasks relevant to your current git repo
28
+ - **E2EE Support**: End-to-end encrypted tasks are decrypted using your iCloud Keychain
29
+ - **Claude Code Integration**: Works as a Claude Code plugin with `/push-todo` command
30
+ - **Daemon Execution**: Background task execution with progress monitoring
31
+
32
+ ## Commands
33
+
34
+ | Command | Description |
35
+ |---------|-------------|
36
+ | `push-todo` | List active tasks |
37
+ | `push-todo <number>` | View specific task |
38
+ | `push-todo connect` | Authenticate and set up |
39
+ | `push-todo search <query>` | Search tasks |
40
+ | `push-todo status` | Show connection status |
41
+ | `push-todo --watch` | Live monitoring UI |
42
+
43
+ ## Claude Code Integration
44
+
45
+ This package works as a Claude Code plugin:
46
+
47
+ ```
48
+ /push-todo List your voice tasks
49
+ /push-todo 427 Work on task #427
50
+ /push-todo connect Run diagnostics
51
+ ```
52
+
53
+ ### Session Hooks
54
+
55
+ - **Session Start**: Shows task count notification
56
+ - **Session End**: Reports session completion
57
+
58
+ ## Options
59
+
60
+ ```bash
61
+ push-todo --all-projects # Tasks from all projects
62
+ push-todo --backlog # Show backlog items
63
+ push-todo --include-backlog # Include backlog in list
64
+ push-todo --completed # Show completed items
65
+ push-todo --json # Output as JSON
66
+ push-todo --queue 1,2,3 # Queue tasks for daemon
67
+ ```
68
+
69
+ ## Configuration
70
+
71
+ Config stored at `~/.config/push/config`:
72
+
73
+ ```bash
74
+ push-todo setting # Show all settings
75
+ push-todo setting auto-commit # Toggle auto-commit
76
+ ```
77
+
78
+ ## Requirements
79
+
80
+ - Node.js 18+
81
+ - macOS (for E2EE features)
82
+ - [Push iOS app](https://apps.apple.com/app/push-todo/id6738972839)
83
+
84
+ ## API
85
+
86
+ ```javascript
87
+ import { listTasks, showTask, searchTasks } from '@masslessai/push-todo';
88
+
89
+ // List tasks
90
+ const tasks = await listTasks({ allProjects: true });
91
+
92
+ // Get specific task
93
+ const task = await showTask(427);
94
+
95
+ // Search
96
+ const results = await searchTasks('bug');
97
+ ```
98
+
99
+ ## Documentation
100
+
101
+ - [Skill Documentation](./SKILL.md)
102
+ - [Push Website](https://pushto.do)
103
+ - [Support](mailto:support@pushto.do)
104
+
105
+ ## License
106
+
107
+ MIT © [MasslessAI](https://masslessai.com)
package/SKILL.md ADDED
@@ -0,0 +1,180 @@
1
+ # Push Todo Skill
2
+
3
+ This skill enables Claude Code to fetch and work on voice tasks captured via the Push iOS app.
4
+
5
+ ## Quick Start
6
+
7
+ 1. Run `push-todo connect` to authenticate
8
+ 2. Run `push-todo` to list active tasks
9
+ 3. Run `push-todo <number>` to view and work on a specific task
10
+
11
+ ## Commands
12
+
13
+ ### List Tasks
14
+
15
+ ```bash
16
+ push-todo # Active tasks for current project
17
+ push-todo --all-projects # Tasks from all projects
18
+ push-todo --backlog # Backlog items only
19
+ push-todo --include-backlog # Active + backlog
20
+ push-todo --completed # Completed items only
21
+ push-todo --json # Output as JSON
22
+ ```
23
+
24
+ ### View Specific Task
25
+
26
+ ```bash
27
+ push-todo 427 # View task #427
28
+ push-todo 427 --json # As JSON
29
+ ```
30
+
31
+ ### Search Tasks
32
+
33
+ ```bash
34
+ push-todo search "auth bug" # Search for tasks
35
+ push-todo --search "fix" # Alternative syntax
36
+ ```
37
+
38
+ ### Manage Tasks
39
+
40
+ ```bash
41
+ push-todo --queue 427,428 # Queue tasks for daemon
42
+ push-todo --queue-batch # Auto-queue a batch
43
+ push-todo --mark-completed <uuid> --completion-comment "Fixed the bug"
44
+ ```
45
+
46
+ ### Connection & Status
47
+
48
+ ```bash
49
+ push-todo connect # Run diagnostics, authenticate
50
+ push-todo status # Show connection status
51
+ push-todo setting # Show all settings
52
+ push-todo setting auto-commit # Toggle a setting
53
+ ```
54
+
55
+ ### Monitor
56
+
57
+ ```bash
58
+ push-todo --watch # Live terminal UI
59
+ push-todo --watch --json # JSON status output
60
+ ```
61
+
62
+ ## Task Format
63
+
64
+ Tasks include:
65
+
66
+ - **summary**: Brief title
67
+ - **content/normalizedContent**: Full task description (AI-extracted)
68
+ - **originalTranscript**: Raw voice recording text
69
+ - **displayNumber**: Human-readable number (#1, #2...)
70
+ - **projectHint**: Associated project (git remote)
71
+ - **screenshotAttachments**: Any attached screenshots
72
+ - **linkAttachments**: Any attached links
73
+
74
+ ## Batch Processing
75
+
76
+ The CLI supports batch task processing:
77
+
78
+ 1. Fetch multiple tasks with `push-todo`
79
+ 2. Tasks marked for batch will show `BATCH_OFFER` format
80
+ 3. Use `--queue` to add tasks to the daemon queue
81
+
82
+ ## Integration with Claude Code
83
+
84
+ ### Session Start Hook
85
+
86
+ When Claude Code starts, the hook shows:
87
+ ```
88
+ [Push] You have 5 active tasks from your iPhone. Say 'push-todo' to see them.
89
+ ```
90
+
91
+ ### Session End Hook
92
+
93
+ Reports session completion to the Push backend.
94
+
95
+ ### Slash Command
96
+
97
+ Use `/push-todo` in Claude Code as a shortcut:
98
+ - `/push-todo` - List tasks
99
+ - `/push-todo 427` - Work on task #427
100
+ - `/push-todo connect` - Run diagnostics
101
+
102
+ ## Settings
103
+
104
+ | Setting | Default | Description |
105
+ |---------|---------|-------------|
106
+ | `AUTO_COMMIT` | `true` | Auto-commit changes after task completion |
107
+ | `MAX_BATCH_SIZE` | `5` | Maximum tasks in batch offer |
108
+
109
+ Toggle with: `push-todo setting auto-commit`
110
+
111
+ ## Project Registration
112
+
113
+ Projects are identified by their git remote URL. Register with:
114
+
115
+ ```bash
116
+ push-todo connect
117
+ ```
118
+
119
+ This maps the git remote to the local path, enabling:
120
+ - Automatic project filtering
121
+ - Daemon task routing
122
+ - Cross-machine synchronization
123
+
124
+ ## E2EE (End-to-End Encryption)
125
+
126
+ If enabled on the Push app:
127
+ - Tasks are encrypted on device
128
+ - Decryption uses iCloud Keychain
129
+ - Requires macOS with keychain access
130
+
131
+ Check status: `push-todo status`
132
+
133
+ ## Configuration
134
+
135
+ Config file: `~/.config/push/config`
136
+
137
+ ```bash
138
+ export PUSH_KEY="your-api-key"
139
+ export PUSH_USER_ID="user-uuid"
140
+ export AUTO_COMMIT="true"
141
+ export MAX_BATCH_SIZE="5"
142
+ ```
143
+
144
+ ## Troubleshooting
145
+
146
+ ### "No API key configured"
147
+ Run `push-todo connect` to authenticate.
148
+
149
+ ### "E2EE not available"
150
+ The keychain helper binary may not be installed. Check:
151
+ - macOS only (not Linux/Windows)
152
+ - Binary at `node_modules/@masslessai/push-todo/bin/push-keychain-helper`
153
+
154
+ ### "Invalid API key"
155
+ Your key may have expired. Run `push-todo connect` to re-authenticate.
156
+
157
+ ### Tasks not showing for project
158
+ Run `push-todo connect` to register the current project, or use `--all-projects`.
159
+
160
+ ## API Reference
161
+
162
+ The CLI also exports a programmatic API:
163
+
164
+ ```javascript
165
+ import {
166
+ listTasks,
167
+ showTask,
168
+ searchTasks,
169
+ markComplete
170
+ } from '@masslessai/push-todo';
171
+
172
+ // List tasks
173
+ const tasks = await listTasks({ allProjects: true });
174
+
175
+ // Search
176
+ const results = await searchTasks('bug fix');
177
+
178
+ // Mark complete
179
+ await markComplete(taskId, 'Fixed the issue');
180
+ ```
@@ -0,0 +1,23 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Push Todo CLI
5
+ *
6
+ * Voice tasks from Push iOS app for Claude Code.
7
+ * This is the main CLI entry point.
8
+ *
9
+ * Usage:
10
+ * push-todo # List active tasks
11
+ * push-todo 427 # Get specific task
12
+ * push-todo connect # Setup connection
13
+ * push-todo status # Show status
14
+ * push-todo watch # Live daemon monitor
15
+ */
16
+
17
+ import { run } from '../lib/cli.js';
18
+
19
+ // Run CLI with arguments
20
+ run(process.argv.slice(2)).catch(error => {
21
+ console.error(`Error: ${error.message}`);
22
+ process.exit(1);
23
+ });
@@ -0,0 +1,78 @@
1
+ # Push Todo - Voice Tasks from iPhone
2
+
3
+ Fetch and work on voice tasks captured via the Push iOS app.
4
+
5
+ ## How to Use
6
+
7
+ Run `/push-todo` to see your active tasks, or `/push-todo <number>` to work on a specific task.
8
+
9
+ ## Commands
10
+
11
+ | Command | Description |
12
+ |---------|-------------|
13
+ | `/push-todo` | List all active tasks for current project |
14
+ | `/push-todo <number>` | Work on specific task (e.g., `/push-todo 427`) |
15
+ | `/push-todo --all-projects` | List tasks from all projects |
16
+ | `/push-todo --backlog` | Show backlog items |
17
+ | `/push-todo connect` | Run connection diagnostics |
18
+ | `/push-todo search <query>` | Search tasks |
19
+ | `/push-todo status` | Show connection status |
20
+
21
+ ## Task Output Format
22
+
23
+ When you fetch a task, you'll see:
24
+
25
+ ```
26
+ ## Task: #427 Fix authentication bug
27
+
28
+ **Project:** github.com/user/repo
29
+
30
+ ### Content
31
+ Users are getting logged out randomly. Need to investigate the session token expiration logic.
32
+
33
+ ### Original Voice Transcript
34
+ > "There's a bug where users get logged out randomly, I think it's the session token expiration"
35
+
36
+ **Task ID:** `550e8400-e29b-41d4-a716-446655440000`
37
+ **Display Number:** #427
38
+ **Status:** Active
39
+ **Created:** 2026-01-15T10:30:00Z
40
+ ```
41
+
42
+ ## Batch Offer Format
43
+
44
+ When multiple tasks are available, you may see a batch offer:
45
+
46
+ ```
47
+ ==================================================
48
+ BATCH_OFFER: 3
49
+ BATCH_TASKS: 427,428,429
50
+ #427 - Fix authentication bug
51
+ #428 - Add dark mode support
52
+ #429 - Update API documentation
53
+ ==================================================
54
+ ```
55
+
56
+ ## Working on Tasks
57
+
58
+ When you fetch a specific task:
59
+
60
+ 1. Read the **Content** section for the full task description
61
+ 2. Check the **Original Voice Transcript** for additional context
62
+ 3. Implement the requested changes
63
+ 4. Mark the task as completed when done
64
+
65
+ ## Completion
66
+
67
+ After completing a task, the task will be marked as done and synced back to the Push app on your iPhone.
68
+
69
+ ## Project Context
70
+
71
+ Tasks are associated with git repositories. The CLI automatically detects your current project and shows only relevant tasks.
72
+
73
+ - Use `--all-projects` to see tasks from all registered projects
74
+ - Run `push-todo connect` to register the current project
75
+
76
+ ## E2EE Support
77
+
78
+ If you have End-to-End Encryption enabled on the Push app, the CLI will automatically decrypt task content using your iCloud Keychain.
@@ -0,0 +1,26 @@
1
+ {
2
+ "hooks": {
3
+ "SessionStart": [
4
+ {
5
+ "matcher": "",
6
+ "hooks": [
7
+ {
8
+ "type": "command",
9
+ "command": "node ${CLAUDE_PLUGIN_ROOT}/hooks/session-start.js"
10
+ }
11
+ ]
12
+ }
13
+ ],
14
+ "SessionEnd": [
15
+ {
16
+ "hooks": [
17
+ {
18
+ "type": "command",
19
+ "command": "node ${CLAUDE_PLUGIN_ROOT}/hooks/session-end.js",
20
+ "timeout": 15
21
+ }
22
+ ]
23
+ }
24
+ ]
25
+ }
26
+ }
@@ -0,0 +1,99 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Session end hook for Push CLI.
4
+ *
5
+ * Reports session completion status to the backend.
6
+ */
7
+
8
+ import { existsSync, readFileSync } from 'fs';
9
+ import { homedir } from 'os';
10
+ import { join } from 'path';
11
+
12
+ const CONFIG_FILE = join(homedir(), '.config', 'push', 'config');
13
+ const API_BASE = 'https://jxuzqcbqhiaxmfitzxlo.supabase.co/functions/v1';
14
+
15
+ /**
16
+ * Get the API key from config.
17
+ */
18
+ function getApiKey() {
19
+ if (process.env.PUSH_API_KEY) {
20
+ return process.env.PUSH_API_KEY;
21
+ }
22
+
23
+ if (!existsSync(CONFIG_FILE)) {
24
+ return null;
25
+ }
26
+
27
+ try {
28
+ const content = readFileSync(CONFIG_FILE, 'utf8');
29
+ const match = content.match(/^export\s+PUSH_KEY\s*=\s*["']?([^"'\n]+)["']?/m);
30
+ return match ? match[1] : null;
31
+ } catch {
32
+ return null;
33
+ }
34
+ }
35
+
36
+ /**
37
+ * Get the machine ID.
38
+ */
39
+ function getMachineId() {
40
+ const machineIdFile = join(homedir(), '.config', 'push', 'machine_id');
41
+
42
+ if (existsSync(machineIdFile)) {
43
+ try {
44
+ return readFileSync(machineIdFile, 'utf8').trim();
45
+ } catch {
46
+ return null;
47
+ }
48
+ }
49
+
50
+ return null;
51
+ }
52
+
53
+ /**
54
+ * Report session end to backend.
55
+ */
56
+ async function reportSessionEnd(apiKey, machineId, exitReason) {
57
+ try {
58
+ const response = await fetch(`${API_BASE}/session-end`, {
59
+ method: 'POST',
60
+ headers: {
61
+ 'Authorization': `Bearer ${apiKey}`,
62
+ 'Content-Type': 'application/json'
63
+ },
64
+ body: JSON.stringify({
65
+ machine_id: machineId,
66
+ exit_reason: exitReason,
67
+ timestamp: new Date().toISOString()
68
+ }),
69
+ signal: AbortSignal.timeout(10000)
70
+ });
71
+
72
+ return response.ok;
73
+ } catch {
74
+ return false;
75
+ }
76
+ }
77
+
78
+ /**
79
+ * Main entry point.
80
+ */
81
+ async function main() {
82
+ const apiKey = getApiKey();
83
+ const machineId = getMachineId();
84
+
85
+ if (!apiKey || !machineId) {
86
+ // Not configured - silent exit
87
+ process.exit(0);
88
+ }
89
+
90
+ // Get exit reason from environment (Claude Code may set this)
91
+ const exitReason = process.env.CLAUDE_EXIT_REASON || 'normal';
92
+
93
+ // Report to backend
94
+ await reportSessionEnd(apiKey, machineId, exitReason);
95
+
96
+ process.exit(0);
97
+ }
98
+
99
+ main().catch(() => process.exit(0));
@@ -0,0 +1,134 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Session start hook for Push CLI.
4
+ *
5
+ * Displays task count notification when Claude Code starts.
6
+ */
7
+
8
+ import { execSync } from 'child_process';
9
+ import { existsSync, readFileSync } from 'fs';
10
+ import { homedir } from 'os';
11
+ import { join } from 'path';
12
+
13
+ const CONFIG_FILE = join(homedir(), '.config', 'push', 'config');
14
+ const API_BASE = 'https://jxuzqcbqhiaxmfitzxlo.supabase.co/functions/v1';
15
+
16
+ /**
17
+ * Get the API key from config.
18
+ */
19
+ function getApiKey() {
20
+ // Check environment variable first
21
+ if (process.env.PUSH_API_KEY) {
22
+ return process.env.PUSH_API_KEY;
23
+ }
24
+
25
+ // Read from config file
26
+ if (!existsSync(CONFIG_FILE)) {
27
+ return null;
28
+ }
29
+
30
+ try {
31
+ const content = readFileSync(CONFIG_FILE, 'utf8');
32
+ const match = content.match(/^export\s+PUSH_KEY\s*=\s*["']?([^"'\n]+)["']?/m);
33
+ return match ? match[1] : null;
34
+ } catch {
35
+ return null;
36
+ }
37
+ }
38
+
39
+ /**
40
+ * Get the git remote for the current directory.
41
+ */
42
+ function getGitRemote() {
43
+ try {
44
+ const result = execSync('git remote get-url origin', {
45
+ encoding: 'utf8',
46
+ timeout: 5000,
47
+ stdio: ['pipe', 'pipe', 'pipe']
48
+ });
49
+
50
+ let url = result.trim();
51
+ if (!url) return null;
52
+
53
+ // Normalize
54
+ const prefixes = ['https://', 'http://', 'git@', 'ssh://git@'];
55
+ for (const prefix of prefixes) {
56
+ if (url.startsWith(prefix)) {
57
+ url = url.slice(prefix.length);
58
+ break;
59
+ }
60
+ }
61
+
62
+ if (url.includes(':') && !url.includes('://')) {
63
+ url = url.replace(':', '/');
64
+ }
65
+
66
+ if (url.endsWith('.git')) {
67
+ url = url.slice(0, -4);
68
+ }
69
+
70
+ return url;
71
+ } catch {
72
+ return null;
73
+ }
74
+ }
75
+
76
+ /**
77
+ * Fetch task count from the API.
78
+ */
79
+ async function fetchTaskCount(apiKey, gitRemote) {
80
+ try {
81
+ const params = new URLSearchParams();
82
+ if (gitRemote) {
83
+ params.set('git_remote', gitRemote);
84
+ }
85
+ params.set('count_only', 'true');
86
+
87
+ const url = `${API_BASE}/synced-todos?${params}`;
88
+ const response = await fetch(url, {
89
+ headers: {
90
+ 'Authorization': `Bearer ${apiKey}`,
91
+ 'Content-Type': 'application/json'
92
+ },
93
+ signal: AbortSignal.timeout(5000)
94
+ });
95
+
96
+ if (!response.ok) {
97
+ return null;
98
+ }
99
+
100
+ const data = await response.json();
101
+ return data.count || 0;
102
+ } catch {
103
+ return null;
104
+ }
105
+ }
106
+
107
+ /**
108
+ * Main entry point.
109
+ */
110
+ async function main() {
111
+ const apiKey = getApiKey();
112
+
113
+ if (!apiKey) {
114
+ // No API key - silent exit
115
+ process.exit(0);
116
+ }
117
+
118
+ const gitRemote = getGitRemote();
119
+ const count = await fetchTaskCount(apiKey, gitRemote);
120
+
121
+ if (count === null) {
122
+ // API error - silent exit
123
+ process.exit(0);
124
+ }
125
+
126
+ if (count > 0) {
127
+ // Output notification for Claude Code
128
+ console.log(`[Push] You have ${count} active task${count !== 1 ? 's' : ''} from your iPhone. Say 'push-todo' to see them.`);
129
+ }
130
+
131
+ process.exit(0);
132
+ }
133
+
134
+ main().catch(() => process.exit(0));