@masslessai/push-todo 4.2.3 → 4.2.5
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 +68 -47
- package/lib/daemon.js +48 -0
- package/lib/launchagent.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,69 +1,94 @@
|
|
|
1
1
|
# @masslessai/push-todo
|
|
2
2
|
|
|
3
|
-
Voice tasks from the [Push iOS app](https://pushto.do) for Claude Code.
|
|
3
|
+
Voice tasks from the [Push iOS app](https://pushto.do) for Claude Code, Codex, and OpenClaw.
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
8
|
npm install -g @masslessai/push-todo
|
|
9
|
+
push-todo connect --auto
|
|
9
10
|
```
|
|
10
11
|
|
|
12
|
+
The install sets up skill integrations for all detected agents (Claude Code, Codex, OpenClaw). The `connect --auto` command handles authentication, project discovery, and LaunchAgent installation in one step.
|
|
13
|
+
|
|
11
14
|
## Quick Start
|
|
12
15
|
|
|
13
16
|
```bash
|
|
14
|
-
# Authenticate and set up
|
|
15
|
-
push-todo connect
|
|
16
|
-
|
|
17
17
|
# List your tasks
|
|
18
18
|
push-todo
|
|
19
19
|
|
|
20
20
|
# Work on a specific task
|
|
21
21
|
push-todo 427
|
|
22
|
+
|
|
23
|
+
# Create a task from the terminal
|
|
24
|
+
push-todo create "Fix the auth redirect bug"
|
|
25
|
+
|
|
26
|
+
# Queue tasks for background execution
|
|
27
|
+
push-todo --queue 1,2,3
|
|
22
28
|
```
|
|
23
29
|
|
|
24
30
|
## Features
|
|
25
31
|
|
|
26
32
|
- **Voice Tasks**: Tasks captured by voice on your iPhone sync to your terminal
|
|
27
|
-
- **
|
|
28
|
-
- **
|
|
29
|
-
- **
|
|
30
|
-
- **
|
|
33
|
+
- **Background Daemon**: Executes tasks automatically via Claude Code (or Codex/OpenClaw) in git worktrees
|
|
34
|
+
- **LaunchAgent**: Daemon starts on login and restarts on crash — no manual babysitting
|
|
35
|
+
- **Multi-Agent**: Same CLI works with Claude Code, OpenAI Codex, and OpenClaw
|
|
36
|
+
- **Project Routing**: AI routes voice tasks to the right project using learned vocabulary
|
|
37
|
+
- **E2EE Support**: End-to-end encrypted tasks decrypted via iCloud Keychain
|
|
38
|
+
- **Cron Jobs**: Schedule recurring notifications and health checks
|
|
31
39
|
|
|
32
40
|
## Commands
|
|
33
41
|
|
|
34
42
|
| Command | Description |
|
|
35
43
|
|---------|-------------|
|
|
36
|
-
| `push-todo` | List active tasks |
|
|
44
|
+
| `push-todo` | List active tasks for current project |
|
|
37
45
|
| `push-todo <number>` | View specific task |
|
|
38
|
-
| `push-todo
|
|
46
|
+
| `push-todo create <title>` | Create a new todo |
|
|
47
|
+
| `push-todo connect --auto` | One-command setup (auth + projects + daemon) |
|
|
48
|
+
| `push-todo connect` | Run connection diagnostics |
|
|
39
49
|
| `push-todo search <query>` | Search tasks |
|
|
40
|
-
| `push-todo
|
|
50
|
+
| `push-todo review` | Review and mark completed tasks |
|
|
51
|
+
| `push-todo update` | Update CLI and check agent versions |
|
|
41
52
|
| `push-todo --watch` | Live monitoring UI |
|
|
53
|
+
| `push-todo --queue 1,2,3` | Queue tasks for daemon execution |
|
|
54
|
+
| `push-todo --queue-batch` | Auto-queue a batch of tasks |
|
|
55
|
+
| `push-todo --resume <number>` | Resume daemon's Claude session for a task |
|
|
56
|
+
| `push-todo --all-projects` | Tasks from all projects |
|
|
57
|
+
| `push-todo --backlog` | Show backlog items |
|
|
58
|
+
| `push-todo --completed` | Show completed items |
|
|
59
|
+
| `push-todo --json` | Output as JSON |
|
|
42
60
|
|
|
43
|
-
##
|
|
44
|
-
|
|
45
|
-
This package works as a Claude Code plugin:
|
|
61
|
+
## Daemon Management
|
|
46
62
|
|
|
47
|
-
```
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
63
|
+
```bash
|
|
64
|
+
push-todo --daemon-status # Show daemon + LaunchAgent status
|
|
65
|
+
push-todo --daemon-install # Install LaunchAgent (auto-start on login)
|
|
66
|
+
push-todo --daemon-uninstall # Remove LaunchAgent
|
|
67
|
+
push-todo --daemon-start # Start daemon manually
|
|
68
|
+
push-todo --daemon-stop # Stop daemon
|
|
51
69
|
```
|
|
52
70
|
|
|
53
|
-
|
|
71
|
+
The daemon self-updates hourly when idle. Two-layer reliability: LaunchAgent for OS-level lifecycle, self-healing for edge cases.
|
|
54
72
|
|
|
55
|
-
|
|
56
|
-
- **Session End**: Reports session completion
|
|
57
|
-
|
|
58
|
-
## Options
|
|
73
|
+
## Cron Jobs
|
|
59
74
|
|
|
60
75
|
```bash
|
|
61
|
-
push-todo --
|
|
62
|
-
push-todo --
|
|
63
|
-
push-todo --
|
|
64
|
-
push-todo
|
|
65
|
-
push-todo
|
|
66
|
-
|
|
76
|
+
push-todo cron add --name "standup" --every 24h --notify "Time for standup"
|
|
77
|
+
push-todo cron add --name "weekly-review" --cron "0 9 * * 1" --create-todo "Weekly code review"
|
|
78
|
+
push-todo cron add --name "check-deps" --every 7d --health-check /path/to/project --scope deps
|
|
79
|
+
push-todo cron list
|
|
80
|
+
push-todo cron remove <id>
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Claude Code Integration
|
|
84
|
+
|
|
85
|
+
This package installs as a Claude Code skill (not plugin) for a clean `/push-todo` command:
|
|
86
|
+
|
|
87
|
+
```
|
|
88
|
+
/push-todo List your voice tasks
|
|
89
|
+
/push-todo 427 Work on task #427
|
|
90
|
+
/push-todo review Review completed tasks
|
|
91
|
+
/push-todo setup Configure connection
|
|
67
92
|
```
|
|
68
93
|
|
|
69
94
|
## Configuration
|
|
@@ -71,37 +96,33 @@ push-todo --queue 1,2,3 # Queue tasks for daemon
|
|
|
71
96
|
Config stored at `~/.config/push/config`:
|
|
72
97
|
|
|
73
98
|
```bash
|
|
74
|
-
push-todo setting
|
|
75
|
-
push-todo setting auto-commit
|
|
99
|
+
push-todo setting # Show all settings
|
|
100
|
+
push-todo setting auto-commit # Toggle auto-commit
|
|
101
|
+
push-todo setting auto-update # Toggle daemon self-update
|
|
76
102
|
```
|
|
77
103
|
|
|
78
|
-
##
|
|
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
|
|
104
|
+
## Programmatic API
|
|
85
105
|
|
|
86
106
|
```javascript
|
|
87
107
|
import { listTasks, showTask, searchTasks } from '@masslessai/push-todo';
|
|
88
108
|
|
|
89
|
-
// List tasks
|
|
90
109
|
const tasks = await listTasks({ allProjects: true });
|
|
91
|
-
|
|
92
|
-
// Get specific task
|
|
93
110
|
const task = await showTask(427);
|
|
94
|
-
|
|
95
|
-
// Search
|
|
96
|
-
const results = await searchTasks('bug');
|
|
111
|
+
const results = await searchTasks('auth bug');
|
|
97
112
|
```
|
|
98
113
|
|
|
114
|
+
## Requirements
|
|
115
|
+
|
|
116
|
+
- Node.js 18+
|
|
117
|
+
- macOS (for LaunchAgent and E2EE)
|
|
118
|
+
- [Push iOS app](https://pushto.do)
|
|
119
|
+
|
|
99
120
|
## Documentation
|
|
100
121
|
|
|
101
|
-
- [Skill
|
|
122
|
+
- [Skill Instructions](./SKILL.md) — how the AI agent uses push-todo
|
|
102
123
|
- [Push Website](https://pushto.do)
|
|
103
|
-
- [
|
|
124
|
+
- [GitHub Issues](https://github.com/MasslessAI/push-todo-cli/issues)
|
|
104
125
|
|
|
105
126
|
## License
|
|
106
127
|
|
|
107
|
-
MIT
|
|
128
|
+
MIT
|
package/lib/daemon.js
CHANGED
|
@@ -91,6 +91,7 @@ const LOG_FILE = join(PUSH_DIR, 'daemon.log');
|
|
|
91
91
|
const STATUS_FILE = join(PUSH_DIR, 'daemon_status.json');
|
|
92
92
|
const VERSION_FILE = join(PUSH_DIR, 'daemon.version');
|
|
93
93
|
const LOCK_FILE = join(PUSH_DIR, 'daemon.lock');
|
|
94
|
+
const COMPLETED_FILE = join(PUSH_DIR, 'completed_tasks.json');
|
|
94
95
|
const CONFIG_FILE = join(CONFIG_DIR, 'config');
|
|
95
96
|
const MACHINE_ID_FILE = join(CONFIG_DIR, 'machine_id');
|
|
96
97
|
const REGISTRY_FILE = join(CONFIG_DIR, 'projects.json');
|
|
@@ -110,6 +111,25 @@ function trackCompleted(entry) {
|
|
|
110
111
|
if (completedToday.length > COMPLETED_TODAY_MAX) {
|
|
111
112
|
completedToday.splice(0, completedToday.length - COMPLETED_TODAY_MAX);
|
|
112
113
|
}
|
|
114
|
+
// Persist to disk so new daemon instances skip already-completed tasks
|
|
115
|
+
try {
|
|
116
|
+
writeFileSync(COMPLETED_FILE, JSON.stringify(completedToday));
|
|
117
|
+
} catch {}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function loadCompletedTasks() {
|
|
121
|
+
try {
|
|
122
|
+
if (!existsSync(COMPLETED_FILE)) return;
|
|
123
|
+
const data = JSON.parse(readFileSync(COMPLETED_FILE, 'utf8'));
|
|
124
|
+
if (!Array.isArray(data)) return;
|
|
125
|
+
// Only load entries from the last 24 hours
|
|
126
|
+
const cutoff = Date.now() - 86400000;
|
|
127
|
+
for (const entry of data) {
|
|
128
|
+
if (entry.completedAt && new Date(entry.completedAt).getTime() > cutoff) {
|
|
129
|
+
completedToday.push(entry);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
} catch {}
|
|
113
133
|
}
|
|
114
134
|
const taskLastOutput = new Map(); // displayNumber -> timestamp
|
|
115
135
|
const taskStdoutBuffer = new Map(); // displayNumber -> lines[]
|
|
@@ -886,6 +906,16 @@ function createPRForTask(displayNumber, summary, projectPath) {
|
|
|
886
906
|
const gitCwd = projectPath || process.cwd();
|
|
887
907
|
|
|
888
908
|
try {
|
|
909
|
+
// Verify branch exists before comparing (worktree may have been auto-cleaned)
|
|
910
|
+
try {
|
|
911
|
+
execFileSync('git', ['rev-parse', '--verify', branch], {
|
|
912
|
+
cwd: gitCwd, timeout: 5000, stdio: 'pipe'
|
|
913
|
+
});
|
|
914
|
+
} catch {
|
|
915
|
+
log(`Branch ${branch} does not exist, skipping PR creation`);
|
|
916
|
+
return null;
|
|
917
|
+
}
|
|
918
|
+
|
|
889
919
|
// Check if branch has commits
|
|
890
920
|
const logResult = execSync(`git log HEAD..${branch} --oneline`, {
|
|
891
921
|
cwd: gitCwd,
|
|
@@ -1666,6 +1696,8 @@ function respawnWithInjectedMessage(displayNumber) {
|
|
|
1666
1696
|
const claudeArgs = [
|
|
1667
1697
|
'--continue', sessionId,
|
|
1668
1698
|
'-p', injectionPrompt,
|
|
1699
|
+
'--verbose',
|
|
1700
|
+
'--worktree', getWorktreeName(displayNumber),
|
|
1669
1701
|
'--allowedTools', allowedTools,
|
|
1670
1702
|
'--output-format', 'stream-json',
|
|
1671
1703
|
'--permission-mode', 'bypassPermissions',
|
|
@@ -2330,6 +2362,8 @@ async function executeTask(task) {
|
|
|
2330
2362
|
? [
|
|
2331
2363
|
'--continue', previousSessionId,
|
|
2332
2364
|
'-p', prompt,
|
|
2365
|
+
'--verbose',
|
|
2366
|
+
'--worktree', worktreeName,
|
|
2333
2367
|
'--allowedTools', allowedTools,
|
|
2334
2368
|
'--output-format', 'stream-json',
|
|
2335
2369
|
'--permission-mode', 'bypassPermissions',
|
|
@@ -2337,6 +2371,8 @@ async function executeTask(task) {
|
|
|
2337
2371
|
]
|
|
2338
2372
|
: [
|
|
2339
2373
|
'-p', prompt,
|
|
2374
|
+
'--verbose',
|
|
2375
|
+
'--worktree', worktreeName,
|
|
2340
2376
|
'--allowedTools', allowedTools,
|
|
2341
2377
|
'--output-format', 'stream-json',
|
|
2342
2378
|
'--permission-mode', 'bypassPermissions',
|
|
@@ -3059,6 +3095,7 @@ If dependencies are current, say "All dependencies up to date."`,
|
|
|
3059
3095
|
|
|
3060
3096
|
const claudeArgs = [
|
|
3061
3097
|
'-p', prompt,
|
|
3098
|
+
'--verbose',
|
|
3062
3099
|
'--allowedTools', allowedTools,
|
|
3063
3100
|
'--output-format', 'stream-json',
|
|
3064
3101
|
'--permission-mode', 'bypassPermissions',
|
|
@@ -3196,6 +3233,14 @@ async function recoverOrphanedTasks() {
|
|
|
3196
3233
|
for (const task of orphaned) {
|
|
3197
3234
|
const dn = task.displayNumber || task.display_number;
|
|
3198
3235
|
const tid = task.id || task.todo_id || null;
|
|
3236
|
+
|
|
3237
|
+
// Skip tasks already completed by the previous daemon instance
|
|
3238
|
+
// (race condition: API may return stale 'running' status after session_finished update)
|
|
3239
|
+
if (completedToday.some(c => c.displayNumber === dn)) {
|
|
3240
|
+
log(`Task #${dn}: skipping orphan recovery — already completed by previous daemon`);
|
|
3241
|
+
continue;
|
|
3242
|
+
}
|
|
3243
|
+
|
|
3199
3244
|
log(`Task #${dn}: resetting from 'running' to 'queued' (orphaned by restart)`);
|
|
3200
3245
|
await updateTaskStatus(dn, 'queued', {
|
|
3201
3246
|
event: {
|
|
@@ -3255,6 +3300,9 @@ async function mainLoop() {
|
|
|
3255
3300
|
writeFileSync(VERSION_FILE, getVersion());
|
|
3256
3301
|
} catch {}
|
|
3257
3302
|
|
|
3303
|
+
// Load completed tasks from previous daemon instance (prevents re-execution)
|
|
3304
|
+
loadCompletedTasks();
|
|
3305
|
+
|
|
3258
3306
|
// Recover orphaned tasks from previous daemon instance
|
|
3259
3307
|
// When the daemon restarts (self-update, crash, reboot), tasks may be stuck
|
|
3260
3308
|
// in 'running' with no process actually working on them. Reset them to 'queued'
|
package/lib/launchagent.js
CHANGED
|
@@ -68,7 +68,7 @@ function generatePlist() {
|
|
|
68
68
|
<key>PUSH_DAEMON</key>
|
|
69
69
|
<string>1</string>
|
|
70
70
|
<key>PATH</key>
|
|
71
|
-
<string>/usr/local/bin:/usr/bin:/bin:/opt/homebrew/bin:${dirname(nodeBin)}</string>
|
|
71
|
+
<string>/usr/local/bin:/usr/bin:/bin:/opt/homebrew/bin:${dirname(nodeBin)}:${join(homedir(), '.local', 'bin')}</string>
|
|
72
72
|
</dict>
|
|
73
73
|
|
|
74
74
|
<key>RunAtLoad</key>
|