create-neomutt-gmail 0.1.1

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/LICENSE ADDED
File without changes
package/README.md ADDED
@@ -0,0 +1,265 @@
1
+ # create-neomutt-gmail
2
+
3
+ Interactive setup wizard for Neomutt + Gmail + OAuth2.0
4
+
5
+ [![npm version](https://badge.fury.io/js/create-neomutt-gmail.svg)](https://www.npmjs.com/package/create-neomutt-gmail)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
+
8
+ ## Features
9
+
10
+ - ✅ **Interactive wizard** - Answer a few questions, get a working config
11
+ - ✅ **Prerequisites check** - Verifies all dependencies before starting
12
+ - ✅ **Safe operations** - Automatic backups, confirmation prompts
13
+ - ✅ **Dry-run mode** - Preview changes without modifying files
14
+ - ✅ **Automatic downloads** - Fetches OAuth2 script from official source
15
+ - ✅ **Helpful guidance** - Clear next steps after installation
16
+
17
+ ## Quick Start
18
+
19
+ ```bash
20
+ npx create-neomutt-gmail
21
+ ```
22
+
23
+ That's it! Answer the questions and follow the on-screen instructions.
24
+
25
+ ## Prerequisites
26
+
27
+ The wizard checks for these automatically, but you'll need:
28
+
29
+ - **neomutt** - Email client
30
+ - **python3** - For OAuth2 script
31
+ - **gpg** - For token encryption
32
+
33
+ On macOS:
34
+ ```bash
35
+ brew install neomutt python3 gnupg
36
+ ```
37
+
38
+ On Linux:
39
+ ```bash
40
+ # Debian/Ubuntu
41
+ sudo apt install neomutt python3 gnupg
42
+
43
+ # Arch
44
+ sudo pacman -S neomutt python gnupg
45
+ ```
46
+
47
+ ### GPG Key Setup
48
+
49
+ If you don't have a GPG key yet:
50
+
51
+ ```bash
52
+ gpg --full-generate-key
53
+ ```
54
+
55
+ Choose:
56
+ - RSA and RSA
57
+ - 4096 bits
58
+ - No expiration (or as preferred)
59
+ - Enter your name and email
60
+
61
+ ## Usage
62
+
63
+ ### Standard Installation
64
+
65
+ ```bash
66
+ npx create-neomutt-gmail
67
+ ```
68
+
69
+ ### Dry Run (Preview Only)
70
+
71
+ ```bash
72
+ npx create-neomutt-gmail --dry-run
73
+ ```
74
+
75
+ Shows what would be created without actually creating any files.
76
+
77
+ ### Skip Prerequisites Check
78
+
79
+ ```bash
80
+ npx create-neomutt-gmail --skip-prereqs
81
+ ```
82
+
83
+ Skips the dependency verification (not recommended).
84
+
85
+ ### Help
86
+
87
+ ```bash
88
+ npx create-neomutt-gmail --help
89
+ ```
90
+
91
+ ## What It Does
92
+
93
+ The wizard will:
94
+
95
+ 1. **Check prerequisites** - Verify neomutt, python3, gpg are installed
96
+ 2. **Ask configuration questions**:
97
+ - Gmail address
98
+ - Your name
99
+ - Preferred editor (nvim, vim, nano, emacs)
100
+ - Gmail language (Japanese or English)
101
+ - GPG key ID (optional)
102
+ 3. **Create directories**:
103
+ - `~/.config/mutt/`
104
+ - `~/.config/mutt/accounts/`
105
+ - `~/.local/etc/oauth-tokens/`
106
+ - `~/.cache/mutt/`
107
+ 4. **Generate config files**:
108
+ - `~/.config/mutt/muttrc`
109
+ - `~/.config/mutt/accounts/YOUR_EMAIL.muttrc`
110
+ 5. **Download OAuth script**:
111
+ - `~/.config/mutt/mutt_oauth2.py`
112
+ 6. **Display next steps** for Google Cloud Console setup
113
+
114
+ ## After Installation
115
+
116
+ ### Step 1: Google Cloud Console
117
+
118
+ 1. Visit https://console.cloud.google.com/
119
+ 2. Create a new project
120
+ 3. Enable Gmail API
121
+ 4. Configure OAuth consent screen (External)
122
+ 5. Add scope: `https://mail.google.com/`
123
+ 6. Add your email as test user
124
+ 7. Create OAuth client ID (Desktop application)
125
+ 8. Note Client ID and Client Secret
126
+
127
+ ### Step 2: Obtain OAuth Token
128
+
129
+ Run the command shown by the wizard (it will look like this):
130
+
131
+ ```bash
132
+ python3 ~/.config/mutt/mutt_oauth2.py \
133
+ --authorize \
134
+ --authflow localhostauthcode \
135
+ --encryption-pipe 'gpg --encrypt --recipient your@email.com' \
136
+ --client-id YOUR_CLIENT_ID.apps.googleusercontent.com \
137
+ --client-secret YOUR_CLIENT_SECRET \
138
+ --provider google \
139
+ --email your@email.com \
140
+ ~/.local/etc/oauth-tokens/gmail.tokens
141
+ ```
142
+
143
+ ### Step 3: Launch Neomutt
144
+
145
+ ```bash
146
+ neomutt
147
+ ```
148
+
149
+ ## Keyboard Shortcuts
150
+
151
+ Once Neomutt is running:
152
+
153
+ | Key | Action |
154
+ |-----|--------|
155
+ | `gi` | Go to inbox |
156
+ | `gt` | Go to trash |
157
+ | `gs` | Go to sent mail |
158
+ | `gd` | Go to drafts |
159
+ | `m` | Compose new email |
160
+ | `r` | Reply |
161
+ | `q` | Quit |
162
+
163
+ ## Safety Features
164
+
165
+ ### Automatic Backups
166
+
167
+ Existing config files are backed up with timestamps:
168
+ ```
169
+ ~/.config/mutt/muttrc.backup-2026-01-26T12-34-56-789Z
170
+ ```
171
+
172
+ ### Confirmation Prompts
173
+
174
+ The wizard asks for confirmation before:
175
+ - Overwriting existing configurations
176
+ - Creating files
177
+ - Proceeding with installation
178
+
179
+ ### Dry-Run Mode
180
+
181
+ Preview all changes before applying them:
182
+ ```bash
183
+ npx create-neomutt-gmail --dry-run
184
+ ```
185
+
186
+ ## File Permissions
187
+
188
+ Created files have appropriate permissions:
189
+ - Config files: `0o600` (owner read/write only)
190
+ - OAuth script: `0o755` (owner rwx, others rx)
191
+
192
+ ## Troubleshooting
193
+
194
+ ### "Command not found: neomutt"
195
+
196
+ Install neomutt:
197
+ ```bash
198
+ brew install neomutt # macOS
199
+ sudo apt install neomutt # Linux
200
+ ```
201
+
202
+ ### "No GPG keys found"
203
+
204
+ Create a GPG key:
205
+ ```bash
206
+ gpg --full-generate-key
207
+ ```
208
+
209
+ ### "Failed to download mutt_oauth2.py"
210
+
211
+ Download manually:
212
+ ```bash
213
+ curl -o ~/.config/mutt/mutt_oauth2.py \
214
+ https://raw.githubusercontent.com/neomutt/neomutt/main/contrib/oauth2/mutt_oauth2.py
215
+ chmod +x ~/.config/mutt/mutt_oauth2.py
216
+ ```
217
+
218
+ ### OAuth Token Issues
219
+
220
+ See the [detailed troubleshooting guide](https://github.com/a-lost-social-misfit/terminal-blog) for OAuth-specific issues.
221
+
222
+ ## Powered By
223
+
224
+ This wizard uses:
225
+ - [mutt-config-core](https://www.npmjs.com/package/mutt-config-core) - Configuration generation
226
+ - [config-fs-utils](https://www.npmjs.com/package/config-fs-utils) - File system operations
227
+
228
+ ## Related Projects
229
+
230
+ - [mutt-config-core](https://github.com/a-lost-social-misfit/mutt-config-core) - Core config generator
231
+ - [config-fs-utils](https://github.com/a-lost-social-misfit/config-fs-utils) - FS utilities
232
+ - [terminal-blog](https://github.com/a-lost-social-misfit/terminal-blog) - Detailed setup guide
233
+
234
+ ## Contributing
235
+
236
+ Contributions are welcome! Please feel free to submit a Pull Request.
237
+
238
+ ## ⚠️ Important Notice
239
+
240
+ This tool modifies files in your home directory:
241
+ - `~/.config/mutt/`
242
+ - `~/.local/etc/oauth-tokens/`
243
+ - `~/.cache/mutt/`
244
+
245
+ Before running:
246
+ 1. ✅ Backup existing Neomutt configuration (if any)
247
+ 2. ✅ Review the code on GitHub
248
+ 3. ✅ Use `--dry-run` to preview changes
249
+
250
+ The tool creates automatic backups, but it's your responsibility to:
251
+ - Verify the configuration before use
252
+ - Keep OAuth tokens secure
253
+ - Review file permissions
254
+
255
+ **No warranty is provided. Use at your own risk.**
256
+
257
+ ## License
258
+
259
+ MIT © a-lost-social-misfit
260
+
261
+ ## Author
262
+
263
+ Created by [a-lost-social-misfit](https://github.com/a-lost-social-misfit)
264
+
265
+ Based on practical experience and lessons learned from setting up Neomutt with Gmail OAuth2.0 on macOS.
File without changes
package/bin/cli.js ADDED
@@ -0,0 +1,48 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * create-neomutt-gmail
5
+ * Interactive setup wizard for Neomutt + Gmail + OAuth2.0
6
+ */
7
+
8
+ import { run } from "../src/index.js";
9
+
10
+ // Parse command line arguments
11
+ const args = process.argv.slice(2);
12
+ const options = {
13
+ dryRun: args.includes("--dry-run"),
14
+ skipPrereqs: args.includes("--skip-prereqs"),
15
+ help: args.includes("--help") || args.includes("-h"),
16
+ };
17
+
18
+ // Show help
19
+ if (options.help) {
20
+ console.log(`
21
+ create-neomutt-gmail - Interactive setup wizard for Neomutt + Gmail
22
+
23
+ Usage:
24
+ npx create-neomutt-gmail [options]
25
+
26
+ Options:
27
+ --dry-run Preview changes without creating files
28
+ --skip-prereqs Skip prerequisites check
29
+ --help, -h Show this help message
30
+
31
+ Examples:
32
+ npx create-neomutt-gmail
33
+ npx create-neomutt-gmail --dry-run
34
+
35
+ For more information, visit:
36
+ https://github.com/a-lost-social-misfit/create-neomutt-gmail
37
+ `);
38
+ process.exit(0);
39
+ }
40
+
41
+ // Run the wizard
42
+ run(options).catch((error) => {
43
+ console.error("\n❌ Error:", error.message);
44
+ if (process.env.DEBUG) {
45
+ console.error(error.stack);
46
+ }
47
+ process.exit(1);
48
+ });
package/package.json ADDED
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "create-neomutt-gmail",
3
+ "version": "0.1.1",
4
+ "description": "Interactive setup wizard for Neomutt + Gmail + OAuth2.0",
5
+ "main": "src/index.js",
6
+ "bin": {
7
+ "create-neomutt-gmail": "./bin/cli.js"
8
+ },
9
+ "scripts": {
10
+ "test": "jest",
11
+ "test:watch": "jest --watch",
12
+ "dev": "node bin/cli.js"
13
+ },
14
+ "keywords": [
15
+ "neomutt",
16
+ "gmail",
17
+ "oauth2",
18
+ "email",
19
+ "cli",
20
+ "setup",
21
+ "wizard",
22
+ "mutt"
23
+ ],
24
+ "author": "a-lost-social-misfit",
25
+ "license": "MIT",
26
+ "repository": {
27
+ "type": "git",
28
+ "url": "git+https://github.com/a-lost-social-misfit/create-neomutt-gmail.git"
29
+ },
30
+ "bugs": {
31
+ "url": "https://github.com/a-lost-social-misfit/create-neomutt-gmail/issues"
32
+ },
33
+ "homepage": "https://github.com/a-lost-social-misfit/create-neomutt-gmail#readme",
34
+ "dependencies": {
35
+ "mutt-config-core": "^0.1.0",
36
+ "config-fs-utils": "^0.1.0",
37
+ "inquirer": "^13.2.1",
38
+ "chalk": "^5.6.2",
39
+ "ora": "^9.1.0",
40
+ "node-fetch": "^3.3.2"
41
+ },
42
+ "devDependencies": {
43
+ "jest": "^30.2.0"
44
+ },
45
+ "engines": {
46
+ "node": ">=18.0.0"
47
+ },
48
+ "type": "module"
49
+ }
package/src/display.js ADDED
@@ -0,0 +1,115 @@
1
+ /**
2
+ * Display functions for user output
3
+ */
4
+
5
+ import chalk from "chalk";
6
+ import os from "os";
7
+
8
+ /**
9
+ * Display next steps after installation
10
+ * @param {Object} answers - User configuration
11
+ */
12
+ export function displayNextSteps(answers) {
13
+ console.log(chalk.bold("📖 Next Steps:\n"));
14
+
15
+ // Step 1: Google Cloud Console
16
+ console.log(chalk.bold("1. Setup Google Cloud Console"));
17
+ console.log(
18
+ " Visit: " + chalk.cyan("https://console.cloud.google.com/") + "\n"
19
+ );
20
+ console.log(
21
+ " " + chalk.dim("a.") + ' Create a new project (e.g., "neomutt-gmail")'
22
+ );
23
+ console.log(" " + chalk.dim("b.") + " Enable Gmail API");
24
+ console.log(" " + chalk.dim("c.") + " Configure OAuth consent screen:");
25
+ console.log(" • User Type: External");
26
+ console.log(" • Add scope: https://mail.google.com/");
27
+ console.log(` • Add test user: ${chalk.cyan(answers.email)}`);
28
+ console.log(
29
+ " " + chalk.dim("d.") + " Create OAuth client ID (Desktop application)"
30
+ );
31
+ console.log(
32
+ " " + chalk.dim("e.") + " Note down Client ID and Client Secret\n"
33
+ );
34
+
35
+ // Step 2: Obtain OAuth token
36
+ console.log(chalk.bold("2. Obtain OAuth 2.0 token"));
37
+ console.log(" Run the following command:\n");
38
+
39
+ const command = `python3 ~/.config/mutt/mutt_oauth2.py \\
40
+ --authorize \\
41
+ --authflow localhostauthcode \\
42
+ --encryption-pipe 'gpg --encrypt --recipient ${answers.gpgKeyId || answers.email}' \\
43
+ --client-id YOUR_CLIENT_ID.apps.googleusercontent.com \\
44
+ --client-secret YOUR_CLIENT_SECRET \\
45
+ --provider google \\
46
+ --email ${answers.email} \\
47
+ ~/.local/etc/oauth-tokens/gmail.tokens`;
48
+
49
+ console.log(chalk.cyan(command) + "\n");
50
+ console.log(
51
+ chalk.dim(
52
+ " Replace YOUR_CLIENT_ID and YOUR_CLIENT_SECRET with actual values"
53
+ )
54
+ );
55
+ console.log(chalk.dim(" You will be prompted for your GPG passphrase\n"));
56
+
57
+ // Step 3: Launch Neomutt
58
+ console.log(chalk.bold("3. Launch Neomutt"));
59
+ console.log(" " + chalk.cyan("neomutt") + "\n");
60
+
61
+ // Additional resources
62
+ console.log(chalk.dim("───────────────────────────────────\n"));
63
+ console.log("📚 For detailed instructions, visit:");
64
+ console.log(" https://github.com/a-lost-social-misfit/terminal-blog\n");
65
+ console.log("💬 Need help? Open an issue:");
66
+ console.log(
67
+ " https://github.com/a-lost-social-misfit/create-neomutt-gmail/issues\n"
68
+ );
69
+
70
+ // Keyboard shortcuts reminder
71
+ console.log(chalk.bold("🎹 Neomutt Keyboard Shortcuts:\n"));
72
+ console.log(" " + chalk.cyan("gi") + " - Go to inbox");
73
+ console.log(" " + chalk.cyan("gt") + " - Go to trash");
74
+ console.log(" " + chalk.cyan("gs") + " - Go to sent mail");
75
+ console.log(" " + chalk.cyan("gd") + " - Go to drafts");
76
+ console.log(" " + chalk.cyan("m") + " - Compose new email");
77
+ console.log(" " + chalk.cyan("r") + " - Reply");
78
+ console.log(" " + chalk.cyan("q") + " - Quit");
79
+ }
80
+
81
+ /**
82
+ * Display dry-run preview
83
+ * @param {Object} answers - User configuration
84
+ * @param {Object} paths - Configuration paths
85
+ * @param {Object} configs - Generated configurations
86
+ */
87
+ export function displayDryRun(answers, paths, configs) {
88
+ const homeDir = os.homedir();
89
+
90
+ console.log(chalk.bold("📁 Directories to be created:\n"));
91
+ console.log(` ${homeDir}/.config/mutt/`);
92
+ console.log(` ${homeDir}/.config/mutt/accounts/`);
93
+ console.log(` ${homeDir}/.local/etc/oauth-tokens/`);
94
+ console.log(` ${homeDir}/.cache/mutt/headers/`);
95
+ console.log(` ${homeDir}/.cache/mutt/bodies/`);
96
+
97
+ console.log(chalk.bold("\n📄 Files to be created:\n"));
98
+ console.log(` ${homeDir}/${paths.accountMuttrc}`);
99
+ console.log(` ${homeDir}/${paths.mainMuttrc}`);
100
+ console.log(` ${homeDir}/.config/mutt/mutt_oauth2.py`);
101
+
102
+ console.log(chalk.bold("\n📝 Configuration preview:\n"));
103
+ console.log(chalk.dim("Account config (first 10 lines):"));
104
+ console.log(
105
+ chalk.dim(configs.accountMuttrc.split("\n").slice(0, 10).join("\n"))
106
+ );
107
+ console.log(chalk.dim(" ..."));
108
+
109
+ console.log(chalk.bold("\n⚙️ Settings:\n"));
110
+ console.log(` Email: ${chalk.cyan(answers.email)}`);
111
+ console.log(` Name: ${chalk.cyan(answers.realName)}`);
112
+ console.log(` Editor: ${chalk.cyan(answers.editor)}`);
113
+ console.log(` Locale: ${chalk.cyan(answers.locale)}`);
114
+ console.log(` GPG Key: ${chalk.cyan(answers.gpgKeyId || answers.email)}`);
115
+ }
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Download mutt_oauth2.py script
3
+ */
4
+
5
+ import fs from "fs/promises";
6
+ import path from "path";
7
+ import os from "os";
8
+ import fetch from "node-fetch";
9
+
10
+ const MUTT_OAUTH2_URL =
11
+ "https://raw.githubusercontent.com/neomutt/neomutt/main/contrib/oauth2/mutt_oauth2.py";
12
+
13
+ /**
14
+ * Download and install mutt_oauth2.py
15
+ * @returns {Promise<string>} Path to installed script
16
+ */
17
+ export async function downloadMuttOAuth2() {
18
+ const targetPath = path.join(
19
+ os.homedir(),
20
+ ".config",
21
+ "mutt",
22
+ "mutt_oauth2.py"
23
+ );
24
+
25
+ // Check if already exists
26
+ try {
27
+ await fs.access(targetPath);
28
+ // File exists, skip download
29
+ return targetPath;
30
+ } catch {
31
+ // File doesn't exist, proceed with download
32
+ }
33
+
34
+ // Download
35
+ const response = await fetch(MUTT_OAUTH2_URL);
36
+ if (!response.ok) {
37
+ throw new Error(`Failed to download: ${response.statusText}`);
38
+ }
39
+
40
+ const content = await response.text();
41
+
42
+ // Basic validation - check if it looks like a Python script
43
+ if (
44
+ !content.includes("#!/usr/bin/env python") &&
45
+ !content.includes("import")
46
+ ) {
47
+ throw new Error(
48
+ "Downloaded file does not appear to be a valid Python script"
49
+ );
50
+ }
51
+
52
+ // Write file
53
+ await fs.writeFile(targetPath, content, "utf8");
54
+
55
+ // Make executable
56
+ await fs.chmod(targetPath, 0o755);
57
+
58
+ return targetPath;
59
+ }
package/src/index.js ADDED
@@ -0,0 +1,167 @@
1
+ /**
2
+ * Main application logic
3
+ */
4
+
5
+ import { generateMuttConfigs, getConfigPaths } from "mutt-config-core";
6
+ import {
7
+ setupStandardMuttDirs,
8
+ writeConfigFiles,
9
+ exists,
10
+ } from "config-fs-utils";
11
+ import inquirer from "inquirer";
12
+ import chalk from "chalk";
13
+ import ora from "ora";
14
+
15
+ import { checkPrerequisites } from "./prerequisites.js";
16
+ import { askQuestions } from "./questions.js";
17
+ import { downloadMuttOAuth2 } from "./download.js";
18
+ import { displayNextSteps, displayDryRun } from "./display.js";
19
+
20
+ export async function run(options = {}) {
21
+ console.log(chalk.bold("\n🔧 Neomutt + Gmail Setup Wizard\n"));
22
+
23
+ // Prerequisites check
24
+ if (!options.skipPrereqs) {
25
+ console.log(chalk.blue("📋 Prerequisites Check"));
26
+ const prereqCheck = await checkPrerequisites();
27
+
28
+ if (!prereqCheck.allPassed) {
29
+ console.log(chalk.red("\n❌ Missing required dependencies."));
30
+ console.log("Please install them first and try again.\n");
31
+ process.exit(1);
32
+ }
33
+
34
+ console.log(chalk.dim("\n───────────────────────────────────\n"));
35
+ }
36
+
37
+ // Ask user questions
38
+ console.log(chalk.blue("📝 Configuration\n"));
39
+ const answers = await askQuestions();
40
+
41
+ console.log(chalk.dim("\n───────────────────────────────────\n"));
42
+
43
+ // Check for existing config and warn
44
+ const muttrcPath = `${process.env.HOME}/.config/mutt/muttrc`;
45
+ if (await exists(muttrcPath)) {
46
+ console.log(chalk.yellow("⚠️ Existing Neomutt configuration found."));
47
+ console.log(
48
+ chalk.yellow(" A backup will be created before overwriting.\n")
49
+ );
50
+
51
+ const { proceed } = await inquirer.prompt([
52
+ {
53
+ type: "confirm",
54
+ name: "proceed",
55
+ message: "Continue with setup?",
56
+ default: true,
57
+ },
58
+ ]);
59
+
60
+ if (!proceed) {
61
+ console.log(chalk.dim("\nSetup cancelled."));
62
+ process.exit(0);
63
+ }
64
+
65
+ console.log();
66
+ }
67
+
68
+ // Dry run mode - show what would happen
69
+ if (options.dryRun) {
70
+ console.log(
71
+ chalk.bold.cyan("🔍 DRY RUN MODE - No files will be created\n")
72
+ );
73
+ const configs = generateMuttConfigs(answers);
74
+ const paths = getConfigPaths(answers.email);
75
+ displayDryRun(answers, paths, configs);
76
+ console.log(chalk.dim("\nRun without --dry-run to actually create files."));
77
+ return;
78
+ }
79
+
80
+ // Final confirmation
81
+ console.log(chalk.bold("📦 Ready to create:\n"));
82
+ console.log(" • Configuration directories");
83
+ console.log(" • Neomutt config files");
84
+ console.log(" • OAuth2 script\n");
85
+
86
+ const { finalConfirm } = await inquirer.prompt([
87
+ {
88
+ type: "confirm",
89
+ name: "finalConfirm",
90
+ message: "Proceed with installation?",
91
+ default: true,
92
+ },
93
+ ]);
94
+
95
+ if (!finalConfirm) {
96
+ console.log(chalk.dim("\nSetup cancelled."));
97
+ process.exit(0);
98
+ }
99
+
100
+ console.log();
101
+
102
+ // Create directories
103
+ const spinner = ora("Creating directories...").start();
104
+ try {
105
+ const dirs = await setupStandardMuttDirs();
106
+ spinner.succeed("Directories created");
107
+
108
+ for (const [name, dirPath] of Object.entries(dirs)) {
109
+ console.log(chalk.green(` ✓ ${dirPath}`));
110
+ }
111
+ } catch (error) {
112
+ spinner.fail("Failed to create directories");
113
+ throw error;
114
+ }
115
+
116
+ console.log();
117
+
118
+ // Generate and write config files
119
+ spinner.text = "Generating configuration files...";
120
+ spinner.start();
121
+
122
+ try {
123
+ const configs = generateMuttConfigs(answers);
124
+ const paths = getConfigPaths(answers.email);
125
+
126
+ const results = await writeConfigFiles({
127
+ [`~/${paths.accountMuttrc}`]: configs.accountMuttrc,
128
+ [`~/${paths.mainMuttrc}`]: configs.mainMuttrc,
129
+ });
130
+
131
+ spinner.succeed("Configuration files created");
132
+
133
+ for (const result of results) {
134
+ console.log(chalk.green(` ✓ ${result.path}`));
135
+ if (result.backup) {
136
+ console.log(chalk.dim(` (backup: ${result.backup})`));
137
+ }
138
+ }
139
+ } catch (error) {
140
+ spinner.fail("Failed to create config files");
141
+ throw error;
142
+ }
143
+
144
+ console.log();
145
+
146
+ // Download mutt_oauth2.py
147
+ spinner.text = "Downloading mutt_oauth2.py...";
148
+ spinner.start();
149
+
150
+ try {
151
+ const scriptPath = await downloadMuttOAuth2();
152
+ spinner.succeed("mutt_oauth2.py downloaded");
153
+ console.log(chalk.green(` ✓ ${scriptPath}`));
154
+ } catch (error) {
155
+ spinner.fail("Failed to download mutt_oauth2.py");
156
+ console.log(chalk.yellow("\n⚠️ You can download it manually from:"));
157
+ console.log(
158
+ " https://raw.githubusercontent.com/neomutt/neomutt/main/contrib/oauth2/mutt_oauth2.py"
159
+ );
160
+ }
161
+
162
+ console.log(chalk.dim("\n───────────────────────────────────\n"));
163
+
164
+ // Display next steps
165
+ console.log(chalk.bold.green("🎉 Configuration complete!\n"));
166
+ displayNextSteps(answers);
167
+ }
@@ -0,0 +1,98 @@
1
+ /**
2
+ * Prerequisites checking
3
+ */
4
+
5
+ import { exec } from "child_process";
6
+ import { promisify } from "util";
7
+ import chalk from "chalk";
8
+
9
+ const execAsync = promisify(exec);
10
+
11
+ const REQUIRED_COMMANDS = [
12
+ {
13
+ cmd: "neomutt",
14
+ name: "neomutt",
15
+ required: true,
16
+ install: "brew install neomutt",
17
+ },
18
+ {
19
+ cmd: "python3",
20
+ name: "python3",
21
+ required: true,
22
+ install: "brew install python3",
23
+ },
24
+ {
25
+ cmd: "gpg",
26
+ name: "gpg",
27
+ required: true,
28
+ install: "brew install gnupg",
29
+ },
30
+ ];
31
+
32
+ /**
33
+ * Check if a command exists in PATH
34
+ * @param {string} command - Command to check
35
+ * @returns {Promise<boolean>}
36
+ */
37
+ async function checkCommand(command) {
38
+ try {
39
+ await execAsync(`which ${command}`);
40
+ return true;
41
+ } catch {
42
+ return false;
43
+ }
44
+ }
45
+
46
+ /**
47
+ * Check if GPG has any keys configured
48
+ * @returns {Promise<boolean>}
49
+ */
50
+ async function checkGPGKeys() {
51
+ try {
52
+ const { stdout } = await execAsync("gpg --list-secret-keys");
53
+ return stdout.trim().length > 0;
54
+ } catch {
55
+ return false;
56
+ }
57
+ }
58
+
59
+ /**
60
+ * Check all prerequisites
61
+ * @returns {Promise<Object>} Check results
62
+ */
63
+ export async function checkPrerequisites() {
64
+ const results = [];
65
+ let allPassed = true;
66
+
67
+ // Check required commands
68
+ for (const { cmd, name, required, install } of REQUIRED_COMMANDS) {
69
+ const found = await checkCommand(cmd);
70
+ results.push({ name, found, required, install });
71
+
72
+ if (found) {
73
+ console.log(chalk.green(`✓ ${name} found`));
74
+ } else {
75
+ console.log(chalk.red(`✗ ${name} not found`));
76
+ if (required) {
77
+ console.log(chalk.dim(` Install: ${install}`));
78
+ allPassed = false;
79
+ }
80
+ }
81
+ }
82
+
83
+ // Check GPG keys
84
+ const hasGPGKeys = await checkGPGKeys();
85
+ if (hasGPGKeys) {
86
+ console.log(chalk.green("✓ GPG keys configured"));
87
+ } else {
88
+ console.log(chalk.yellow("⚠ No GPG keys found"));
89
+ console.log(
90
+ chalk.dim(
91
+ " You will need to create a GPG key for OAuth token encryption"
92
+ )
93
+ );
94
+ console.log(chalk.dim(" Run: gpg --full-generate-key"));
95
+ }
96
+
97
+ return { results, allPassed, hasGPGKeys };
98
+ }
@@ -0,0 +1,68 @@
1
+ /**
2
+ * User interaction prompts
3
+ */
4
+
5
+ import inquirer from "inquirer";
6
+
7
+ /**
8
+ * Ask user for configuration details
9
+ * @returns {Promise<Object>} User answers
10
+ */
11
+ export async function askQuestions() {
12
+ const questions = [
13
+ {
14
+ type: "input",
15
+ name: "email",
16
+ message: "Gmail address:",
17
+ validate: (input) => {
18
+ const emailRegex = /^[^\s@]+@gmail\.com$/;
19
+ if (!emailRegex.test(input)) {
20
+ return "Please enter a valid Gmail address (e.g., user@gmail.com)";
21
+ }
22
+ return true;
23
+ },
24
+ },
25
+ {
26
+ type: "input",
27
+ name: "realName",
28
+ message: "Your name (for email headers):",
29
+ validate: (input) => {
30
+ if (input.trim().length === 0) {
31
+ return "Name is required";
32
+ }
33
+ return true;
34
+ },
35
+ },
36
+ {
37
+ type: "list",
38
+ name: "editor",
39
+ message: "Preferred text editor:",
40
+ choices: [
41
+ { name: "Neovim (nvim)", value: "nvim" },
42
+ { name: "Vim", value: "vim" },
43
+ { name: "Nano", value: "nano" },
44
+ { name: "Emacs", value: "emacs" },
45
+ ],
46
+ default: "nvim",
47
+ },
48
+ {
49
+ type: "list",
50
+ name: "locale",
51
+ message: "Gmail language setting:",
52
+ choices: [
53
+ { name: "日本語 (Japanese)", value: "ja" },
54
+ { name: "English", value: "en" },
55
+ ],
56
+ default: "ja",
57
+ },
58
+ {
59
+ type: "input",
60
+ name: "gpgKeyId",
61
+ message: "GPG key ID (press Enter to use email address):",
62
+ default: (answers) => answers.email,
63
+ when: () => true,
64
+ },
65
+ ];
66
+
67
+ return inquirer.prompt(questions);
68
+ }