@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.
- package/.claude-plugin/plugin.json +5 -0
- package/LICENSE +21 -0
- package/README.md +107 -0
- package/SKILL.md +180 -0
- package/bin/push-todo.js +23 -0
- package/commands/push-todo.md +78 -0
- package/hooks/hooks.json +26 -0
- package/hooks/session-end.js +99 -0
- package/hooks/session-start.js +134 -0
- package/lib/api.js +325 -0
- package/lib/cli.js +191 -0
- package/lib/config.js +279 -0
- package/lib/connect.js +380 -0
- package/lib/encryption.js +201 -0
- package/lib/fetch.js +371 -0
- package/lib/index.js +114 -0
- package/lib/machine-id.js +101 -0
- package/lib/project-registry.js +279 -0
- package/lib/utils/colors.js +126 -0
- package/lib/utils/format.js +253 -0
- package/lib/utils/git.js +149 -0
- package/lib/watch.js +343 -0
- package/natives/KeychainHelper.swift +134 -0
- package/package.json +54 -0
- package/scripts/postinstall.js +136 -0
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
|
+
```
|
package/bin/push-todo.js
ADDED
|
@@ -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.
|
package/hooks/hooks.json
ADDED
|
@@ -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));
|