@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 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
- - **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
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 connect` | Authenticate and set up |
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 status` | Show connection status |
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
- ## Claude Code Integration
44
-
45
- This package works as a Claude Code plugin:
61
+ ## Daemon Management
46
62
 
47
- ```
48
- /push-todo List your voice tasks
49
- /push-todo 427 Work on task #427
50
- /push-todo connect Run diagnostics
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
- ### Session Hooks
71
+ The daemon self-updates hourly when idle. Two-layer reliability: LaunchAgent for OS-level lifecycle, self-healing for edge cases.
54
72
 
55
- - **Session Start**: Shows task count notification
56
- - **Session End**: Reports session completion
57
-
58
- ## Options
73
+ ## Cron Jobs
59
74
 
60
75
  ```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
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 # Show all settings
75
- push-todo setting auto-commit # Toggle 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
- ## 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
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 Documentation](./SKILL.md)
122
+ - [Skill Instructions](./SKILL.md) — how the AI agent uses push-todo
102
123
  - [Push Website](https://pushto.do)
103
- - [Support](mailto:support@pushto.do)
124
+ - [GitHub Issues](https://github.com/MasslessAI/push-todo-cli/issues)
104
125
 
105
126
  ## License
106
127
 
107
- MIT © [MasslessAI](https://masslessai.com)
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'
@@ -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>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@masslessai/push-todo",
3
- "version": "4.2.3",
3
+ "version": "4.2.5",
4
4
  "description": "Voice tasks from Push iOS app for Claude Code",
5
5
  "type": "module",
6
6
  "bin": {