inboxd 1.0.9 → 1.0.11

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.md CHANGED
@@ -106,16 +106,21 @@ inbox install-skill --uninstall # Remove
106
106
 
107
107
  The `inbox setup` wizard also offers to install the skill automatically.
108
108
 
109
- ### Skill Location & Versioning
109
+ ### Skill Location & Update Detection
110
110
 
111
111
  | Location | Purpose |
112
112
  |----------|---------|
113
113
  | `.claude/skills/inbox-assistant/SKILL.md` | Source (bundled with package) |
114
114
  | `~/.claude/skills/inbox-assistant/SKILL.md` | Installed (global for Claude Code) |
115
115
 
116
- The skill has a `version` field in its front matter. When updating:
117
- 1. Bump version in `SKILL.md`
118
- 2. Users run `inbox install-skill` to get the update
116
+ The skill uses content-hash detection (no version field). Updates are detected automatically:
117
+ - On `npm install`: Auto-updates if skill already installed
118
+ - Manual: Run `inbox install-skill` to update
119
+
120
+ Safety features:
121
+ - `source: inboxd` marker identifies ownership (won't overwrite user's own skills)
122
+ - Creates `SKILL.md.backup` before replacing modified files
123
+ - Use `--force` to override ownership check
119
124
 
120
125
  ### Architecture
121
126
 
@@ -135,10 +140,22 @@ scripts/postinstall.js # npm postinstall hint about install-skill
135
140
  |---------|---------|
136
141
  | `inbox summary --json` | Quick status check (unread counts) |
137
142
  | `inbox analyze --count 50` | Get email data as JSON for classification |
138
- | `inbox delete --ids "id1,id2" --confirm` | Delete with confirmation flag |
143
+ | `inbox analyze --group-by sender` | Group emails by sender domain |
144
+ | `inbox delete --ids "id1,id2" --confirm` | Delete specific emails by ID |
145
+ | `inbox delete --sender "pattern" --dry-run` | Preview deletion by sender filter |
146
+ | `inbox delete --match "pattern" --dry-run` | Preview deletion by subject filter |
139
147
  | `inbox restore --last N` | Undo last N deletions |
140
148
  | `inbox install-skill` | Install/update the Claude Code skill |
141
149
 
150
+ ### Smart Filtering Options
151
+ | Option | Description |
152
+ |--------|-------------|
153
+ | `--sender <pattern>` | Case-insensitive substring match on From field |
154
+ | `--match <pattern>` | Case-insensitive substring match on Subject field |
155
+ | `--limit <N>` | Max emails for filter operations (default: 50) |
156
+ | `--force` | Override safety warnings (short patterns, large batches) |
157
+ | `--dry-run` | Preview what would be deleted without deleting |
158
+
142
159
  ### Email Object Shape (from `analyze`)
143
160
  ```json
144
161
  {
package/README.md CHANGED
@@ -80,6 +80,8 @@ inbox summary
80
80
  | `inbox check` | Check for new emails + send notifications |
81
81
  | `inbox check -q` | Silent check (for background use) |
82
82
  | `inbox delete --ids <ids>` | Move emails to trash |
83
+ | `inbox delete --sender <pattern>` | Delete by sender (with confirmation) |
84
+ | `inbox delete --match <pattern>` | Delete by subject (with confirmation) |
83
85
  | `inbox restore --last 1` | Restore last deleted email |
84
86
  | `inbox deletion-log` | View deletion history |
85
87
  | `inbox logout --all` | Remove all accounts |
@@ -188,13 +190,18 @@ The skill provides:
188
190
 
189
191
  ### Updating the Skill
190
192
 
191
- When you update inboxd, run `inbox install-skill` again to get the latest skill version:
193
+ The skill auto-updates when you update inboxd (if already installed). You can also update manually:
192
194
 
193
195
  ```bash
194
196
  npm update -g inboxd
195
197
  inbox install-skill
196
198
  ```
197
199
 
200
+ Safety features:
201
+ - Won't overwrite skills not created by inboxd (uses `source: inboxd` marker)
202
+ - Creates `SKILL.md.backup` before replacing if you've modified the skill
203
+ - Use `inbox install-skill --force` to override ownership check
204
+
198
205
  ### CLI vs MCP
199
206
 
200
207
  Unlike an MCP server that exposes raw Gmail API primitives, `inboxd` provides **opinionated commands** with built-in safety:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "inboxd",
3
- "version": "1.0.9",
3
+ "version": "1.0.11",
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": {
@@ -62,10 +62,14 @@ function handleSkillUpdate() {
62
62
 
63
63
  if (result.success) {
64
64
  if (result.backedUp) {
65
- console.log(`\n✓ inbox-assistant skill updated (previous saved to SKILL.md.backup)\n`);
65
+ console.log(`\n✓ inbox-assistant skill updated`);
66
+ console.log(` Your previous version was saved to: SKILL.md.backup\n`);
66
67
  } else {
67
68
  console.log(`\n✓ inbox-assistant skill updated\n`);
68
69
  }
70
+ } else if (result.reason === 'backup_failed') {
71
+ console.log(`\n⚠️ Could not backup existing skill - update skipped`);
72
+ console.log(` Run 'inbox install-skill' manually to update\n`);
69
73
  }
70
74
  }
71
75
  // Up-to-date → silent (no message)
@@ -50,7 +50,7 @@ function getSkillSource(skillPath) {
50
50
  /**
51
51
  * Create a backup of the skill file
52
52
  * @param {string} skillDir - Directory containing SKILL.md
53
- * @returns {string|null} Backup path or null if failed
53
+ * @returns {{ success: boolean, tempPath: string|null, originalHash: string|null }} Backup result
54
54
  */
55
55
  function createBackup(skillDir) {
56
56
  const skillPath = path.join(skillDir, 'SKILL.md');
@@ -59,12 +59,21 @@ function createBackup(skillDir) {
59
59
 
60
60
  try {
61
61
  if (fs.existsSync(skillPath)) {
62
+ // Get hash of original before backup (for verification)
63
+ const originalHash = getFileHash(skillPath);
62
64
  fs.copyFileSync(skillPath, backupPath);
63
- return backupPath;
65
+
66
+ // Verify backup was created with correct content
67
+ const backupHash = getFileHash(backupPath);
68
+ if (backupHash === originalHash) {
69
+ return { success: true, tempPath: backupPath, originalHash };
70
+ }
71
+ // Backup hash doesn't match - something went wrong
72
+ return { success: false, tempPath: null, originalHash: null };
64
73
  }
65
- return null;
74
+ return { success: false, tempPath: null, originalHash: null };
66
75
  } catch {
67
- return null;
76
+ return { success: false, tempPath: null, originalHash: null };
68
77
  }
69
78
  }
70
79
 
@@ -194,13 +203,28 @@ function installSkill(options = {}) {
194
203
  const skillsDir = path.dirname(SKILL_DEST_DIR);
195
204
  fs.mkdirSync(skillsDir, { recursive: true });
196
205
 
197
- // Backup if replacing existing (user may have modified)
206
+ // Backup if replacing existing AND user modified it
207
+ // (Don't backup if it matches source - nothing worth preserving)
198
208
  let backedUp = false;
199
209
  let backupPath = null;
200
- let tempBackupPath = null;
201
- if (status.installed) {
202
- tempBackupPath = createBackup(SKILL_DEST_DIR);
210
+ let backupResult = null;
211
+ if (status.installed && updateInfo.hashMismatch) {
212
+ // Only backup if the installed version differs from our source
213
+ // This prevents overwriting a previous backup with an unmodified version
214
+ backupResult = createBackup(SKILL_DEST_DIR);
215
+
216
+ if (!backupResult.success) {
217
+ // Backup failed - don't proceed without protecting user's data
218
+ return {
219
+ success: false,
220
+ action: 'skipped',
221
+ reason: 'backup_failed',
222
+ path: SKILL_DEST_DIR
223
+ };
224
+ }
225
+ }
203
226
 
227
+ if (status.installed) {
204
228
  // Remove existing for clean update
205
229
  fs.rmSync(SKILL_DEST_DIR, { recursive: true, force: true });
206
230
  }
@@ -209,9 +233,18 @@ function installSkill(options = {}) {
209
233
  copyDirSync(SKILL_SOURCE_DIR, SKILL_DEST_DIR);
210
234
 
211
235
  // Move backup into the new skill directory
212
- if (tempBackupPath) {
213
- backupPath = moveBackupToSkillDir(tempBackupPath, SKILL_DEST_DIR);
236
+ if (backupResult && backupResult.tempPath) {
237
+ backupPath = moveBackupToSkillDir(backupResult.tempPath, SKILL_DEST_DIR);
214
238
  backedUp = !!backupPath;
239
+
240
+ // Verify the backup still has the original content after move
241
+ if (backedUp && backupResult.originalHash) {
242
+ const movedBackupHash = getFileHash(backupPath);
243
+ if (movedBackupHash !== backupResult.originalHash) {
244
+ // Something went wrong during move - backup is corrupted
245
+ backedUp = false;
246
+ }
247
+ }
215
248
  }
216
249
 
217
250
  const action = status.installed ? 'updated' : 'installed';