inboxd 1.0.13 → 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.
@@ -281,22 +281,22 @@ This will guide you through:
281
281
 
282
282
  ### Optional: Automatic Background Monitoring
283
283
 
284
- Users can enable automatic inbox checking with macOS notifications:
284
+ Users can enable automatic inbox checking with notifications:
285
285
 
286
286
  ```bash
287
287
  inbox install-service # Check every 5 minutes
288
288
  inbox install-service --interval 10 # Check every 10 minutes
289
+ inbox install-service --uninstall # Remove service
289
290
  ```
290
291
 
291
292
  This installs and starts a background service that:
292
293
  - Checks for new emails automatically
293
- - Sends macOS notifications when new emails arrive
294
+ - Sends desktop notifications when new emails arrive
294
295
  - Starts on login
295
296
 
296
- To stop: `launchctl unload ~/Library/LaunchAgents/com.yourname.inboxd.plist`
297
+ **macOS:** Uses launchd. To stop: `launchctl unload ~/Library/LaunchAgents/com.danielparedes.inboxd.plist`
297
298
 
298
- > [!NOTE]
299
- > This is macOS-only. Linux users can set up a cron job instead.
299
+ **Linux:** Uses systemd. To stop: `systemctl --user stop inboxd.timer`
300
300
 
301
301
  ## Command Reference
302
302
 
@@ -331,7 +331,16 @@ To stop: `launchctl unload ~/Library/LaunchAgents/com.yourname.inboxd.plist`
331
331
  | `inbox mark-read --ids "id1,id2"` | Mark emails as read (remove UNREAD label) |
332
332
  | `inbox mark-unread --ids "id1,id2"` | Mark emails as unread (add UNREAD label) |
333
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 |
334
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 |
335
344
 
336
345
  ### Smart Filtering Options
337
346
 
@@ -590,6 +599,8 @@ When user has job-related emails (LinkedIn, Indeed, recruiters) and wants to eva
590
599
  | "Delete [sender]'s emails" | Bulk sender cleanup | Two-step pattern with `--sender` filter |
591
600
  | "Delete the security emails" | Subject-based cleanup | `--match "security" --dry-run` → confirm → `--ids` |
592
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` |
593
604
  | "What links are in this email?" | Extract URLs | `inbox read --id <id> --links` |
594
605
  | "Find my old emails" / "Clean up old stuff" | Stale email review | `inbox analyze --older-than 30d` |
595
606
  | "I keep getting these" | Recurring annoyance | Suggest unsubscribe/filter, then delete batch |
@@ -627,7 +638,7 @@ When user has job-related emails (LinkedIn, Indeed, recruiters) and wants to eva
627
638
  |--------|--------------|
628
639
  | Deleted emails | `inbox restore --last N` |
629
640
  | Marked as read | `inbox mark-unread --ids "id1,id2,..."` |
630
- | Archived | No CLI undo - must use Gmail web |
641
+ | Archived | `inbox unarchive --last N` |
631
642
 
632
643
  ---
633
644
 
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
 
@@ -169,7 +174,16 @@ scripts/postinstall.js # npm postinstall hint about install-skill
169
174
  | `inbox reply --id <id> -b <body> --confirm` | Reply to email (requires --confirm) |
170
175
  | `inbox mark-read --ids "id1,id2"` | Mark emails as read |
171
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 |
172
185
  | `inbox install-skill` | Install/update the Claude Code skill |
186
+ | `inbox install-service --uninstall` | Remove background service |
173
187
 
174
188
  ### Smart Filtering Options
175
189
  | Option | Description |
@@ -182,8 +196,9 @@ scripts/postinstall.js # npm postinstall hint about install-skill
182
196
 
183
197
  ### Send/Reply Safety
184
198
  The `send` and `reply` commands have built-in safety features:
199
+ - **Interactive confirmation**: Prompts "Send this email? (y/N)" when no flags provided
185
200
  - **`--dry-run`**: Preview the email without sending
186
- - **`--confirm`**: Required flag to actually send (prevents accidental sends)
201
+ - **`--confirm`**: Skip the interactive prompt (for automation/scripts)
187
202
  - **Audit logging**: All sent emails are logged to `~/.config/inboxd/sent-log.json`
188
203
  - **Account resolution**: Prompts for account selection when multiple accounts exist
189
204
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "inboxd",
3
- "version": "1.0.13",
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
+ };