inboxd 1.0.12 → 1.1.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.
@@ -18,7 +18,10 @@ You are an inbox management assistant. Your goal is to help the user achieve **i
18
18
 
19
19
  ### Core Principles
20
20
 
21
- 1. **Be proactive, not reactive** - After every action, suggest the next step. Don't wait for the user to ask "what now?"
21
+ 1. **Be proactive, not reactive** - After every action, **suggest** the next step. Don't wait for the user to ask "what now?"
22
+ - **Proactive means:** "I found 12 newsletters - want me to delete them?"
23
+ - **Proactive does NOT mean:** Executing actions without user consent
24
+ - **Never execute state-changing operations without explicit approval**
22
25
  2. **Prioritize by impact** - Tackle the most cluttered account first. Surface emails that need ACTION before FYI emails.
23
26
  3. **Minimize decisions** - Group similar items, suggest batch actions. Don't make the user review 50 emails individually.
24
27
  4. **Respect their time** - Old emails (>30 days) rarely need individual review. Summarize, don't itemize.
@@ -67,6 +70,42 @@ Use when: Heavy inbox (>30 unread), user wants thoroughness, language like "what
67
70
 
68
71
  ---
69
72
 
73
+ ## Inbox Zero Philosophy
74
+
75
+ > [!NOTE]
76
+ > "Inbox Zero" is a user preference, not a default goal.
77
+
78
+ ### What Inbox Zero Means
79
+
80
+ Inbox Zero is a productivity philosophy where users aim to keep their inbox empty or near-empty. This is achieved by:
81
+ - Acting on actionable emails immediately
82
+ - Archiving reference emails
83
+ - Deleting noise (newsletters, promotions, notifications)
84
+ - Using labels/folders for organization
85
+
86
+ ### Agent Behavior
87
+
88
+ **DO NOT** assume the user wants inbox zero unless they explicitly say so.
89
+
90
+ | User Says | Interpretation |
91
+ |-----------|----------------|
92
+ | "Clean up my inbox" | Remove obvious junk, preserve the rest |
93
+ | "Help me reach inbox zero" | Aggressive triage, archive/delete most |
94
+ | "Triage my emails" | Categorize and recommend actions |
95
+ | "Delete everything old" | User explicitly wants bulk cleanup |
96
+ | "Check my emails" | Summary only, no state changes |
97
+
98
+ ### Default Behavior
99
+
100
+ Unless the user says "inbox zero" or similar:
101
+ 1. **Preserve by default** - Keep emails unless clearly deletable
102
+ 2. **Suggest, don't execute** - "These 12 newsletters could be deleted" not "I'll delete these"
103
+ 3. **Ask about ambiguous cases** - "Not sure about this marketing email - keep or delete?"
104
+ 4. **Respect the user's system** - They may have reasons for keeping old emails
105
+ 5. **Never mark as read without asking** - Unread status is user's to-do list
106
+
107
+ ---
108
+
70
109
  ## Heavy Inbox Strategy
71
110
 
72
111
  When a user has a heavy inbox (>20 unread emails), use this optimized workflow:
@@ -242,22 +281,22 @@ This will guide you through:
242
281
 
243
282
  ### Optional: Automatic Background Monitoring
244
283
 
245
- Users can enable automatic inbox checking with macOS notifications:
284
+ Users can enable automatic inbox checking with notifications:
246
285
 
247
286
  ```bash
248
287
  inbox install-service # Check every 5 minutes
249
288
  inbox install-service --interval 10 # Check every 10 minutes
289
+ inbox install-service --uninstall # Remove service
250
290
  ```
251
291
 
252
292
  This installs and starts a background service that:
253
293
  - Checks for new emails automatically
254
- - Sends macOS notifications when new emails arrive
294
+ - Sends desktop notifications when new emails arrive
255
295
  - Starts on login
256
296
 
257
- To stop: `launchctl unload ~/Library/LaunchAgents/com.yourname.inboxd.plist`
297
+ **macOS:** Uses launchd. To stop: `launchctl unload ~/Library/LaunchAgents/com.danielparedes.inboxd.plist`
258
298
 
259
- > [!NOTE]
260
- > This is macOS-only. Linux users can set up a cron job instead.
299
+ **Linux:** Uses systemd. To stop: `systemctl --user stop inboxd.timer`
261
300
 
262
301
  ## Command Reference
263
302
 
@@ -290,8 +329,18 @@ To stop: `launchctl unload ~/Library/LaunchAgents/com.yourname.inboxd.plist`
290
329
  | `inbox restore --last N` | Restore last N deleted emails |
291
330
  | `inbox restore --ids "id1,id2"` | Restore specific emails |
292
331
  | `inbox mark-read --ids "id1,id2"` | Mark emails as read (remove UNREAD label) |
332
+ | `inbox mark-unread --ids "id1,id2"` | Mark emails as unread (add UNREAD label) |
293
333
  | `inbox archive --ids "id1,id2" --confirm` | Archive emails (remove from inbox, keep in All Mail) |
334
+ | `inbox unarchive --last N` | Undo last N archived emails |
335
+ | `inbox unarchive --ids "id1,id2"` | Unarchive specific emails |
336
+ | `inbox stats` | Show email activity dashboard (deletions, sent counts) |
337
+ | `inbox stats --days 7 --json` | Get stats as JSON for custom period |
338
+ | `inbox cleanup-suggest` | Get smart cleanup suggestions based on deletion patterns |
294
339
  | `inbox deletion-log` | View recent deletions |
340
+ | `inbox deletion-log --json` | Get deletion log as JSON |
341
+ | `inbox accounts --json` | List accounts as JSON |
342
+ | `inbox delete --dry-run --json` | Preview deletion as structured JSON |
343
+ | `inbox restore --json` | Get restore results as JSON |
295
344
 
296
345
  ### Smart Filtering Options
297
346
 
@@ -550,6 +599,8 @@ When user has job-related emails (LinkedIn, Indeed, recruiters) and wants to eva
550
599
  | "Delete [sender]'s emails" | Bulk sender cleanup | Two-step pattern with `--sender` filter |
551
600
  | "Delete the security emails" | Subject-based cleanup | `--match "security" --dry-run` → confirm → `--ids` |
552
601
  | "What senders have the most emails?" | Inbox analysis | `inbox analyze --group-by sender` |
602
+ | "Show my email stats" | Activity summary | `inbox stats` |
603
+ | "What should I clean up?" | Pattern analysis | `inbox cleanup-suggest` |
553
604
  | "What links are in this email?" | Extract URLs | `inbox read --id <id> --links` |
554
605
  | "Find my old emails" / "Clean up old stuff" | Stale email review | `inbox analyze --older-than 30d` |
555
606
  | "I keep getting these" | Recurring annoyance | Suggest unsubscribe/filter, then delete batch |
@@ -565,13 +616,29 @@ When user has job-related emails (LinkedIn, Indeed, recruiters) and wants to eva
565
616
  > [!CAUTION]
566
617
  > These constraints are non-negotiable.
567
618
 
619
+ ### Deletion Safety
568
620
  1. **NEVER auto-delete** - Always confirm before deletion, but adapt confirmation style to batch size
569
621
  2. **NEVER delete Action Required emails** - Surface them, let user decide
570
622
  3. **NEVER delete without --confirm flag** - Command will hang otherwise
571
623
  4. **Always remind about undo** - After every deletion, mention `inbox restore --last N`
572
- 5. **Preserve by default** - When in doubt about classification, keep the email
573
- 6. **Multi-Account Safety** - Always use `--account <name>` for `delete` and `analyze` commands
574
- 7. **Respect user preferences** - If they say "don't list everything", remember and adapt
624
+
625
+ ### State Change Safety
626
+ 5. **Confirm before mark-read** - Marking as read can hide important emails. Confirm batch operations (3+ emails)
627
+ 6. **Remind about mark-unread undo** - After mark-read, mention: "To undo: `inbox mark-unread --ids \"id1,id2\"`"
628
+ 7. **Confirm before archive** - Archiving removes emails from inbox view. Always use `--confirm` flag
629
+ 8. **Never batch mark-read silently** - Show what will be marked read before executing
630
+
631
+ ### General Safety
632
+ 9. **Preserve by default** - When in doubt about classification, keep the email
633
+ 10. **Multi-Account Safety** - Always use `--account <name>` for `delete`, `mark-read`, `mark-unread`, and `archive` commands
634
+ 11. **Respect user preferences** - If they say "don't list everything", remember and adapt
635
+
636
+ ### Undo Commands Reference
637
+ | Action | Undo Command |
638
+ |--------|--------------|
639
+ | Deleted emails | `inbox restore --last N` |
640
+ | Marked as read | `inbox mark-unread --ids "id1,id2,..."` |
641
+ | Archived | `inbox unarchive --last N` |
575
642
 
576
643
  ---
577
644
 
@@ -659,6 +726,82 @@ Reply with the number or describe which one.
659
726
 
660
727
  ---
661
728
 
729
+ ## Interaction Model
730
+
731
+ > [!IMPORTANT]
732
+ > This section defines how to present multi-step plans to users. Following this model prevents executing without user understanding.
733
+
734
+ ### Plan-Before-Execute Pattern
735
+
736
+ When a task involves multiple actions, **always present the plan first**:
737
+
738
+ 1. **Announce the plan** - State what you intend to do in numbered steps
739
+ 2. **Wait for approval** - Ask "Should I proceed?" or "Ready to start?"
740
+ 3. **Execute incrementally** - Complete one step, report, then ask before next step
741
+ 4. **Summarize at end** - Show what was done with undo options
742
+
743
+ ### Example: Multi-Step Triage
744
+
745
+ **Bad (executes without consent):**
746
+ ```
747
+ Looking at your inbox...
748
+ [Analyzes 47 emails]
749
+ I've classified your emails. Here's the breakdown:
750
+ - 12 newsletters (marked as read)
751
+ - 8 LinkedIn alerts (deleted)
752
+ - 27 remaining
753
+
754
+ Done! To undo deletions: inbox restore --last 8
755
+ ```
756
+
757
+ **Good (plan-first approach):**
758
+ ```
759
+ Looking at your inbox...
760
+
761
+ ## Triage Plan for work@company.com (47 unread)
762
+
763
+ I'll process your inbox in these steps:
764
+ 1. **Group by sender** - Find batch cleanup opportunities
765
+ 2. **Identify deletables** - Newsletters, job alerts, promotions
766
+ 3. **Surface action items** - Emails needing your response
767
+ 4. **Propose cleanup** - Show what I'd delete, get your OK
768
+
769
+ Ready to start?
770
+ ```
771
+
772
+ After user says "yes":
773
+ ```
774
+ Step 1 complete. Found 3 high-volume senders:
775
+ - linkedin.com (12 emails)
776
+ - substack.com (8 emails)
777
+ - github.com (6 notifications)
778
+
779
+ Step 2: These 20 emails are cleanup candidates (newsletters + job alerts).
780
+ Want me to list them, or proceed to Step 3 (find action items)?
781
+ ```
782
+
783
+ ### Confirmation Thresholds
784
+
785
+ | Batch Size | Confirmation Approach |
786
+ |------------|----------------------|
787
+ | 1-3 emails | Inline confirmation, can proceed quickly |
788
+ | 4-10 emails | Show summary, ask "Delete these 7?" |
789
+ | 11-25 emails | Show categorized summary, ask "Proceed with cleanup?" |
790
+ | 25+ emails | Present full plan, confirm before any execution |
791
+
792
+ ### State Changes Require Explicit Approval
793
+
794
+ **Actions that modify email state (always confirm):**
795
+ - `delete` - Always requires confirmation
796
+ - `mark-read` - Confirm if batch (3+), mention undo
797
+ - `archive` - Confirm always, warn about no CLI undo
798
+ - `send` / `reply` - Requires `--confirm` flag
799
+
800
+ **Read-only actions (no confirmation needed):**
801
+ - `summary`, `analyze`, `search`, `read`, `accounts`
802
+
803
+ ---
804
+
662
805
  ## Feedback Loop
663
806
 
664
807
  If the user encounters a bug, friction point, or suggests a feature:
@@ -679,6 +822,10 @@ If the user encounters a bug, friction point, or suggests a feature:
679
822
  | Skipping pre-flight check | Tool may not be installed | Always run `inbox --version` first |
680
823
  | Forgetting `--account` flag | Ambiguity errors with multi-account | Always specify account |
681
824
  | Being passive after actions | User has to drive every step | Proactively suggest next step |
825
+ | Executing mark-read on batch without confirmation | User loses unread status on important emails | Confirm 3+ emails, always mention undo |
826
+ | Assuming user wants inbox zero | May delete emails user wanted to keep | Ask first, preserve by default |
827
+ | Executing multi-step plan without presenting it | User doesn't know what happened or why | Use plan-before-execute pattern |
828
+ | Auto-archiving "FYI" emails | User may want them visible in inbox | Archive only on explicit request |
682
829
 
683
830
  ---
684
831
 
package/CLAUDE.md CHANGED
@@ -11,7 +11,7 @@ inbox setup # First-time setup wizard
11
11
  inbox auth -a <name> # Add account
12
12
  inbox summary # Check all inboxes
13
13
  inbox check -q # Background check
14
- inbox install-service # Install launchd service (macOS only)
14
+ inbox install-service # Install background service (macOS/Linux)
15
15
  ```
16
16
 
17
17
  ## Architecture
@@ -20,9 +20,10 @@ inbox install-service # Install launchd service (macOS only)
20
20
  src/
21
21
  ├── cli.js # Entry point, command definitions (commander)
22
22
  ├── gmail-auth.js # OAuth2 flow, token storage, multi-account management
23
- ├── gmail-monitor.js # Gmail API: fetch, count, trash, restore
23
+ ├── gmail-monitor.js # Gmail API: fetch, count, trash, restore, archive
24
24
  ├── state.js # Tracks seen emails per account
25
25
  ├── deletion-log.js # Logs deleted emails for restore capability
26
+ ├── archive-log.js # Logs archived emails for unarchive capability
26
27
  ├── sent-log.js # Logs sent emails for audit trail
27
28
  ├── notifier.js # macOS notifications (node-notifier)
28
29
  └── skill-installer.js # Copies skill to ~/.claude/skills/
@@ -54,6 +55,7 @@ All user data lives in `~/.config/inboxd/`:
54
55
  | `token-<name>.json` | OAuth refresh/access tokens |
55
56
  | `state-<name>.json` | `{ seenEmailIds, lastCheck, lastNotifiedAt }` |
56
57
  | `deletion-log.json` | Audit log for deleted emails |
58
+ | `archive-log.json` | Audit log for archived emails |
57
59
  | `sent-log.json` | Audit log for sent emails |
58
60
 
59
61
  ## Code Patterns
@@ -70,7 +72,10 @@ All user data lives in `~/.config/inboxd/`:
70
72
  - `inbox check` marks emails as seen after notifying
71
73
  - `inbox delete` logs to `deletion-log.json` before trashing
72
74
  - `inbox restore` moves from Trash to Inbox, removes log entry
73
- - `install-service` creates and automatically enables launchd service (macOS only, warns on other platforms)
75
+ - `inbox archive` logs to `archive-log.json` before archiving
76
+ - `inbox unarchive` moves archived emails back to Inbox, removes log entry
77
+ - `inbox send/reply` prompts for interactive confirmation (or use `--confirm` to skip)
78
+ - `install-service` creates and enables launchd (macOS) or systemd (Linux) service
74
79
 
75
80
  ## OAuth Notes
76
81
 
@@ -80,11 +85,25 @@ All user data lives in `~/.config/inboxd/`:
80
85
 
81
86
  ## Release Process
82
87
 
83
- 1. Bump version in `package.json`
84
- 2. Commit changes
85
- 3. Create a GitHub Release (e.g., `gh release create v1.0.3`)
88
+ **After merging a feature/fix PR to main, always release:**
89
+
90
+ 1. `npm version patch` (or `minor`/`major` as appropriate)
91
+ 2. Commit and push: `git add package*.json && git commit -m "chore: bump version to X.X.X" && git push`
92
+ 3. Create release with quality notes:
93
+ ```bash
94
+ gh release create vX.X.X --title "vX.X.X" --notes "$(cat <<'EOF'
95
+ ## What's New
96
+ - Feature 1: description
97
+ - Feature 2: description
98
+
99
+ ## Fixes
100
+ - Fix 1: description
101
+ EOF
102
+ )"
103
+ ```
86
104
  4. The `publish.yml` workflow will automatically test and publish to npm
87
- - Note: `src/cli.js` dynamically imports version from `package.json` to ensure consistency
105
+
106
+ Note: `src/cli.js` dynamically imports version from `package.json` to ensure consistency.
88
107
 
89
108
  ## AI Agent Integration
90
109
 
@@ -153,7 +172,18 @@ scripts/postinstall.js # npm postinstall hint about install-skill
153
172
  | `inbox search -q <query>` | Search using Gmail query syntax |
154
173
  | `inbox send -t <to> -s <subj> -b <body> --confirm` | Send email (requires --confirm) |
155
174
  | `inbox reply --id <id> -b <body> --confirm` | Reply to email (requires --confirm) |
175
+ | `inbox mark-read --ids "id1,id2"` | Mark emails as read |
176
+ | `inbox mark-unread --ids "id1,id2"` | Mark emails as unread (undo mark-read) |
177
+ | `inbox archive --ids "id1,id2" --confirm` | Archive emails (remove from inbox) |
178
+ | `inbox unarchive --last N` | Undo last N archives |
179
+ | `inbox stats` | Show email activity dashboard (deletions, sent) |
180
+ | `inbox stats --json` | Get stats as JSON |
181
+ | `inbox cleanup-suggest` | Get smart cleanup suggestions based on patterns |
182
+ | `inbox accounts --json` | List accounts as JSON |
183
+ | `inbox deletion-log --json` | Get deletion log as JSON |
184
+ | `inbox delete --dry-run --json` | Preview deletion as JSON |
156
185
  | `inbox install-skill` | Install/update the Claude Code skill |
186
+ | `inbox install-service --uninstall` | Remove background service |
157
187
 
158
188
  ### Smart Filtering Options
159
189
  | Option | Description |
@@ -166,8 +196,9 @@ scripts/postinstall.js # npm postinstall hint about install-skill
166
196
 
167
197
  ### Send/Reply Safety
168
198
  The `send` and `reply` commands have built-in safety features:
199
+ - **Interactive confirmation**: Prompts "Send this email? (y/N)" when no flags provided
169
200
  - **`--dry-run`**: Preview the email without sending
170
- - **`--confirm`**: Required flag to actually send (prevents accidental sends)
201
+ - **`--confirm`**: Skip the interactive prompt (for automation/scripts)
171
202
  - **Audit logging**: All sent emails are logged to `~/.config/inboxd/sent-log.json`
172
203
  - **Account resolution**: Prompts for account selection when multiple accounts exist
173
204
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "inboxd",
3
- "version": "1.0.12",
3
+ "version": "1.1.0",
4
4
  "description": "CLI assistant for Gmail monitoring with multi-account support and AI-ready JSON output",
5
5
  "main": "src/cli.js",
6
6
  "bin": {
@@ -0,0 +1,104 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const { TOKEN_DIR } = require('./gmail-auth');
4
+ const { atomicWriteJsonSync } = require('./utils');
5
+
6
+ const LOG_DIR = TOKEN_DIR;
7
+ const LOG_FILE = path.join(LOG_DIR, 'archive-log.json');
8
+
9
+ /**
10
+ * Ensures the log directory exists
11
+ */
12
+ function ensureLogDir() {
13
+ if (!fs.existsSync(LOG_DIR)) {
14
+ fs.mkdirSync(LOG_DIR, { recursive: true });
15
+ }
16
+ }
17
+
18
+ /**
19
+ * Reads the current archive log
20
+ * @returns {Array} Array of archive entries
21
+ */
22
+ function readArchiveLog() {
23
+ ensureLogDir();
24
+ if (!fs.existsSync(LOG_FILE)) {
25
+ return [];
26
+ }
27
+ try {
28
+ const content = fs.readFileSync(LOG_FILE, 'utf8');
29
+ return JSON.parse(content);
30
+ } catch (_err) {
31
+ return [];
32
+ }
33
+ }
34
+
35
+ /**
36
+ * Logs archived emails to the archive log
37
+ * @param {Array} emails - Array of email objects with id, threadId, account, from, subject, snippet
38
+ */
39
+ function logArchives(emails) {
40
+ ensureLogDir();
41
+ const log = readArchiveLog();
42
+ const timestamp = new Date().toISOString();
43
+
44
+ for (const email of emails) {
45
+ log.push({
46
+ archivedAt: timestamp,
47
+ account: email.account,
48
+ id: email.id,
49
+ threadId: email.threadId,
50
+ from: email.from,
51
+ subject: email.subject,
52
+ snippet: email.snippet,
53
+ });
54
+ }
55
+
56
+ atomicWriteJsonSync(LOG_FILE, log);
57
+ }
58
+
59
+ /**
60
+ * Gets recent archives from the log
61
+ * @param {number} days - Number of days to look back (default: 30)
62
+ * @returns {Array} Array of archive entries within the time range
63
+ */
64
+ function getRecentArchives(days = 30) {
65
+ const log = readArchiveLog();
66
+ const cutoff = new Date();
67
+ cutoff.setDate(cutoff.getDate() - days);
68
+
69
+ return log.filter((entry) => {
70
+ const archivedAt = new Date(entry.archivedAt);
71
+ return archivedAt >= cutoff;
72
+ });
73
+ }
74
+
75
+ /**
76
+ * Gets the path to the log file (for display purposes)
77
+ * @returns {string} The log file path
78
+ */
79
+ function getArchiveLogPath() {
80
+ return LOG_FILE;
81
+ }
82
+
83
+ /**
84
+ * Removes entries from the archive log (e.g., after unarchiving)
85
+ * @param {Array<string>} ids - Array of message IDs to remove
86
+ */
87
+ function removeArchiveLogEntries(ids) {
88
+ ensureLogDir();
89
+ const log = readArchiveLog();
90
+
91
+ const newLog = log.filter(entry => !ids.includes(entry.id));
92
+
93
+ if (log.length !== newLog.length) {
94
+ atomicWriteJsonSync(LOG_FILE, newLog);
95
+ }
96
+ }
97
+
98
+ module.exports = {
99
+ logArchives,
100
+ getRecentArchives,
101
+ getArchiveLogPath,
102
+ readArchiveLog,
103
+ removeArchiveLogEntries,
104
+ };