@nueldotdev/amnesiac 1.0.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.
Files changed (4) hide show
  1. package/.npmignore +24 -0
  2. package/README.md +228 -0
  3. package/bin/cli.js +276 -0
  4. package/package.json +39 -0
package/.npmignore ADDED
@@ -0,0 +1,24 @@
1
+ # Ignore all files so that nothing is published to npm except what is explicitly whitelisted in package.json "files" or .npmignore negations
2
+
3
+ *
4
+ !.npmignore
5
+ !bin/
6
+ !lib/
7
+ !package.json
8
+ !README.md
9
+ !LICENSE
10
+
11
+ # Ignore config, test, and local files
12
+ amnesiac.config.*
13
+ .env
14
+ .DS_Store
15
+ node_modules/
16
+ test/
17
+ tests/
18
+ coverage/
19
+ .idea/
20
+ .vscode/
21
+ *.log
22
+ *.local
23
+ *.swp
24
+ *.swo
package/README.md ADDED
@@ -0,0 +1,228 @@
1
+ # Amnesiac
2
+
3
+ Amnesiac is a CLI tool for keeping track of what’s changed in your code. It looks at your recent Git commits and diffs, uses Gemini to summarize them, and spits out a tidy, bullet-point for your changelog.
4
+
5
+ ## Table of Contents
6
+
7
+ - [Features](#features)
8
+ - [Installation](#installation)
9
+ - [Getting Started](#getting-started)
10
+ - [Configuration & Profiles](#configuration--profiles)
11
+ - [Configuration Loading Hierarchy](#configuration-loading-hierarchy)
12
+ - [Global Configuration (`~/.amnesiac/config.json`)](#global-configuration-amnesiacconfigjson)
13
+ - [Local Project Configuration (`amnesiac.config.js`)](#local-project-configuration-amnesiacconfigjs)
14
+ - [Commands](#commands)
15
+ - [`amnesiac`](#amnesiac)
16
+ - [`amnesiac init`](#amnesiac-init)
17
+ - [`amnesiac config`](#amnesiac-config)
18
+ - [`amnesiac use <profile_name>`](#amnesiac-use-profile_name)
19
+ - [`amnesiac status`](#amnesiac-status)
20
+ - [`amnesiac reset`](#amnesiac-reset)
21
+ - [`amnesiac --version`](#amnesiac---version)
22
+ - [API Key and Model Information](#api-key-and-model-information)
23
+ - [Getting Your API Key](#getting-your-api-key)
24
+ - [Keeping Your API Key Secure](#keeping-your-api-key-secure)
25
+ - [Understanding Gemini Models](#understanding-gemini-models)
26
+
27
+ ## Features
28
+
29
+ - **Automated Changelog Generation:** Generate changelog entries from Git diffs and commit messages.
30
+ - **Flexible Configuration:** Supports global profiles, local project-specific configurations, and CLI overrides.
31
+ - **Profile Management:** Create, edit, list, and delete multiple configuration profiles.
32
+ - **Active Profile Switching:** Easily switch between global profiles.
33
+ - **Secure API Key Handling:** API key input is masked, and the status command redacts it.
34
+ - **Intuitive CLI:** User-friendly commands for initialization, configuration, and status checks.
35
+
36
+ ## Installation
37
+
38
+ To install Amnesiac, you will first need to have [Node.js](https://nodejs.org/) and [npm](https://www.npmjs.com/) installed on your system.
39
+
40
+ Then, you can install Amnesiac globally via npm:
41
+
42
+ ```bash
43
+ npm install -g amnesiac
44
+ ```
45
+
46
+ This will make the `amnesiac` command available from any directory in your terminal.
47
+
48
+ ### Updating Amnesiac
49
+
50
+ To update your globally installed Amnesiac to the latest version, simply run:
51
+
52
+ ```bash
53
+ npm update -g amnesiac
54
+ ```
55
+
56
+ ## Getting Started
57
+
58
+ 1. **Initialize your project (optional, but recommended for project-specific settings):**
59
+ ```bash
60
+ amnesiac init
61
+ ```
62
+ This command creates an `amnesiac.config.js` file in your project root with default settings.
63
+
64
+ 2. **Configure your API Key and Model:**
65
+ Run `amnesiac config` to set up your global default profile or create a new one. You'll be prompted for your Gemini API key and preferred model.
66
+ ```bash
67
+ amnesiac config
68
+ ```
69
+ *(See [Configuration & Profiles](#configuration--profiles) for more details on managing profiles.)*
70
+
71
+ 3. **Generate Changelog Entry:**
72
+ Once configured, you can generate a changelog entry by running:
73
+ ```bash
74
+ amnesiac
75
+ ```
76
+ This command will:
77
+ - Detect recent Git changes (staged, uncommitted, and committed).
78
+ - Send the diff to the Gemini API using your active configuration.
79
+ - Receive a concise changelog entry.
80
+ - Prepend the new entry to your `CHANGELOG.md` file (or the file specified in your config).
81
+
82
+ ## Configuration & Profiles
83
+
84
+ Amnesiac offers a flexible configuration system that allows you to manage settings at different levels: per-project, globally, or as a one-off CLI override.
85
+
86
+ ### Configuration Loading Hierarchy
87
+
88
+ Amnesiac loads configurations with the following priority (highest to lowest):
89
+
90
+ 1. **CLI Overrides:** Options passed directly to the `amnesiac` command (e.g., `--use <profile>`, `--prompt <text>`, `--model <name>`). These are temporary for a single run.
91
+ 2. **Local Project Configuration:** An `amnesiac.config.js` file in your current project's root directory. This takes precedence over your global active profile for that specific project.
92
+ 3. **Global Active Profile:** The profile marked as `activeProfile` in your `~/.amnesiac/config.json` file. This is your default fallback.
93
+
94
+ ### Local Project Configuration (`amnesiac.config.js`)
95
+
96
+ You can create an `amnesiac.config.js` file in the root of your project to define project-specific settings. This configuration will override your global active profile settings when you run `amnesiac` within that project, unless a `--use` flag is provided via CLI.
97
+
98
+ **Example `amnesiac.config.js`:**
99
+
100
+ ```javascript
101
+ export default {
102
+ apiKey: process.env.GEMINI_API_KEY, // Can be sourced from environment variables
103
+ model: "gemini-1.5-pro",
104
+ outputFile: "PROJECT_CHANGELOG.md",
105
+ prompt: `
106
+ Generate a comprehensive changelog entry focusing on new features and bug fixes for this project.
107
+ Include PR numbers if available.
108
+ `
109
+ };
110
+ ```
111
+
112
+ ## Commands
113
+
114
+ Amnesiac provides several commands to manage your configurations and generate changelogs.
115
+
116
+ ### `amnesiac`
117
+
118
+ The main command to generate a changelog entry. It uses the configuration determined by the [loading hierarchy](#configuration-loading-hierarchy).
119
+
120
+ **Usage:**
121
+ ```bash
122
+ amnesiac
123
+ amnesiac -p "Custom prompt for this run" # Override prompt for a single run
124
+ amnesiac -m "gemini-pro" # Override model for a single run
125
+ amnesiac -u work # Use 'work' profile for a single run
126
+ ```
127
+
128
+ ### `amnesiac init`
129
+
130
+ Initializes a local `amnesiac.config.js` file in the current project directory with default settings. It will prompt for confirmation if the file already exists.
131
+
132
+ **Usage:**
133
+ ```bash
134
+ amnesiac init
135
+ ```
136
+
137
+ ### `amnesiac config`
138
+
139
+ Manages your global Amnesiac profiles (stored in `~/.amnesiac/config.json`).
140
+
141
+ **Usage:**
142
+
143
+ - **Set up or edit a profile (will prompt for details):**
144
+ ```bash
145
+ amnesiac config
146
+ amnesiac config --profile my-dev-profile # Directly specify a profile to create/edit
147
+ ```
148
+ When prompted for the API key, your input will be masked for security.
149
+
150
+ - **List all available profiles:**
151
+ ```bash
152
+ amnesiac config --list
153
+ # or
154
+ amnesiac config -l
155
+ ```
156
+ This will also indicate which profile is currently active.
157
+
158
+ - **Delete a specified profile:**
159
+ ```bash
160
+ amnesiac config --delete my-old-profile
161
+ # or
162
+ amnesiac config -d my-old-profile
163
+ ```
164
+
165
+ ### `amnesiac use <profile_name>`
166
+
167
+ Sets the specified profile as the globally active profile in `~/.amnesiac/config.json`. This profile will be used by default for subsequent `amnesiac` runs unless overridden by a local config or CLI flag.
168
+
169
+ **Usage:**
170
+ ```bash
171
+ amnesiac use work
172
+ amnesiac use personal
173
+ ```
174
+
175
+ ### `amnesiac status`
176
+
177
+ Displays the currently active Amnesiac configuration, including the active profile, model, output file, and a snippet of the default prompt. The API key is masked for security.
178
+
179
+ **Usage:**
180
+ ```bash
181
+ amnesiac status
182
+ ```
183
+
184
+ ### `amnesiac reset`
185
+
186
+ Deletes the entire global Amnesiac configuration file (`~/.amnesiac/config.json`), effectively removing all saved profiles and resetting global settings. This command requires user confirmation.
187
+
188
+ **Usage:**
189
+ ```bash
190
+ amnesiac reset
191
+ ```
192
+
193
+ ### `amnesiac --version`
194
+
195
+ Displays the current version of the Amnesiac CLI tool.
196
+
197
+ **Usage:**
198
+ ```bash
199
+ amnesiac --version
200
+ # or
201
+ amnesiac -V
202
+ ```
203
+
204
+ ## API Key and Model Information
205
+
206
+ ### Getting Your API Key
207
+
208
+ Amnesiac uses the Google Gemini API to generate changelog entries. You'll need an API key to use the tool.
209
+
210
+ 1. Visit [Google AI Studio](https://aistudio.google.com/app/apikey).
211
+ 2. Follow the instructions to create a new API key.
212
+ 3. Once you have your key, you can configure it using `amnesiac config` or by setting it in your local `amnesiac.config.js` file or as an environment variable (e.g., `GEMINI_API_KEY`).
213
+
214
+ ### Keeping Your API Key Secure
215
+
216
+ Your API key is a sensitive credential. Treat it like a password:
217
+ - **Do not commit it directly into your project's `amnesiac.config.js`** if the file is shared publicly. Instead, use environment variables (`process.env.GEMINI_API_KEY`) as shown in the local config example.
218
+ - When entering your API key via `amnesiac config`, the input is masked.
219
+ - When viewing your configuration with `amnesiac status`, the API key is partially masked.
220
+
221
+ ### Understanding Gemini Models
222
+
223
+ The `model` parameter (e.g., `gemini-1.5-flash`, `gemini-1.5-pro`) determines which Gemini model Amnesiac uses for content generation.
224
+
225
+ - **`gemini-1.5-flash`:** A faster, more cost-effective model, suitable for many common tasks. This is the default.
226
+ - **`gemini-1.5-pro`:** A more capable model for complex tasks, offering higher quality but potentially at a higher latency or cost.
227
+
228
+ You can specify the model in your global or local configurations, or override it for a single run using `amnesiac -m <model_name>`. Refer to the [Gemini API documentation](https://ai.google.dev/models/gemini) for the latest information on available models and their capabilities.
package/bin/cli.js ADDED
@@ -0,0 +1,276 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from "commander";
3
+ import fs from "fs";
4
+ import path from "path";
5
+ import os from "os";
6
+ import inquirer from "inquirer";
7
+ import { generateDocs } from "../lib/doc_generator.js";
8
+ import { loadConfig } from "../lib/config.js";
9
+ import { readFileSync } from 'fs';
10
+
11
+ const program = new Command();
12
+ const packageJson = JSON.parse(readFileSync(new URL('../package.json', import.meta.url)));
13
+
14
+ // Handle Ctrl+C gracefully
15
+ process.on('SIGINT', () => {
16
+ console.log('\nOperation cancelled by user.');
17
+ process.exit(1);
18
+ });
19
+
20
+ program.version(packageJson.version);
21
+
22
+ program
23
+ .command("init")
24
+ .description("Initialize a local amnesiac.config.js file")
25
+ .action(async () => {
26
+ const localConfigPath = path.resolve(process.cwd(), "amnesiac.config.js");
27
+ const defaultConfigContent = `export default {
28
+ apiKey: process.env.GEMINI_API_KEY || "", // Consider adding a placeholder for API key
29
+ model: "gemini-1.5-flash",
30
+ outputFile: "CHANGELOG.md",
31
+ prompt: \`
32
+ You are an assistant that generates clean, developer-friendly changelog entries.
33
+ Summarize commit messages and diffs into concise bullet points.
34
+ Output only valid markdown for a CHANGELOG.md file.
35
+ \`
36
+ };
37
+ `;
38
+
39
+ if (fs.existsSync(localConfigPath)) {
40
+ const { overwrite } = await inquirer.prompt([
41
+ {
42
+ type: "confirm",
43
+ name: "overwrite",
44
+ message: "amnesiac.config.js already exists. Overwrite?",
45
+ default: false,
46
+ },
47
+ ]);
48
+ if (!overwrite) {
49
+ console.log("Initialization cancelled.");
50
+ return;
51
+ }
52
+ }
53
+
54
+ try {
55
+ fs.writeFileSync(localConfigPath, defaultConfigContent.trim());
56
+ console.log(`βœ… Created amnesiac.config.js at ${localConfigPath}`);
57
+ console.log(`πŸ’‘ Remember to add your Gemini API key to your environment variables (e.g., GEMINI_API_KEY=YOUR_KEY) or directly into amnesiac.config.js`);
58
+ } catch (error) {
59
+ console.error(`❌ Failed to create amnesiac.config.js: ${error.message}`);
60
+ process.exit(1);
61
+ }
62
+ });
63
+
64
+ program
65
+ .command("reset")
66
+ .description("Delete the global Amnesiac config file and all profiles")
67
+ .action(async () => {
68
+ const globalConfigPath = path.join(os.homedir(), ".amnesiac", "config.json");
69
+
70
+ if (!fs.existsSync(globalConfigPath)) {
71
+ console.log("πŸ€·β€β™€οΈ No global config file found to reset.");
72
+ return;
73
+ }
74
+
75
+ const { confirmReset } = await inquirer.prompt([
76
+ {
77
+ type: "confirm",
78
+ name: "confirmReset",
79
+ message: "This will delete your global Amnesiac config file (~/.amnesiac/config.json) and ALL saved profiles. Are you sure?",
80
+ default: false,
81
+ },
82
+ ]);
83
+
84
+ if (confirmReset) {
85
+ try {
86
+ fs.unlinkSync(globalConfigPath);
87
+ console.log("βœ… Global Amnesiac config and all profiles have been reset.");
88
+ } catch (error) {
89
+ console.error("❌ Failed to delete global config file:", error.message);
90
+ }
91
+ } else {
92
+ console.log("Reset cancelled.");
93
+ }
94
+ });
95
+
96
+ program
97
+ .command("status")
98
+ .description("Display the currently active Amnesiac configuration")
99
+ .action(async () => {
100
+ try {
101
+ const config = await loadConfig({}); // Load config without CLI overrides for status display
102
+
103
+ console.log("\n--- Amnesiac Configuration Status ---\n");
104
+ console.log(`Active Profile: ${config.activeProfile || 'N/A (using local/CLI defaults)'}`);
105
+ console.log(`API Key: ${config.apiKey ? '********' + config.apiKey.slice(-4) : 'Not Set'}`); // Mask API key
106
+ console.log(`Model: ${config.model}`);
107
+ console.log(`Output File: ${config.outputFile}`);
108
+ console.log(`Default Prompt: ${config.prompt.split('\n')[0]}...`); // Show first line of prompt
109
+ console.log("\n-------------------------------------\n");
110
+ } catch (error) {
111
+ console.error(`\n❌ An error occurred while fetching status: ${error.message}`);
112
+ process.exit(1);
113
+ }
114
+ });
115
+
116
+ program
117
+ .command("config")
118
+ .description("Set up or edit Amnesiac config")
119
+ .option("-p, --profile <name>", "Specify a profile name", "default")
120
+ .option("-l, --list", "List all available profiles")
121
+ .option("-d, --delete <name>", "Delete a specified profile")
122
+ .action(async (options) => {
123
+ try {
124
+ const dir = path.join(os.homedir(), ".amnesiac");
125
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir);
126
+
127
+ const globalConfigPath = path.join(dir, "config.json");
128
+ let globalConfig = { activeProfile: "default", profiles: {} };
129
+
130
+ if (fs.existsSync(globalConfigPath)) {
131
+ try {
132
+ globalConfig = JSON.parse(fs.readFileSync(globalConfigPath, "utf-8"));
133
+ } catch (e) {
134
+ console.error("❌ Error reading global config file. It might be corrupted. You can reset it with 'amnesiac reset'.", e.message);
135
+ // Continue with default empty config if file is corrupted, but alert user
136
+ }
137
+ }
138
+
139
+ if (options.list) {
140
+ console.log("Available profiles:");
141
+ const profiles = Object.keys(globalConfig.profiles);
142
+ if (profiles.length > 0) {
143
+ profiles.forEach(profile => {
144
+ const activeIndicator = profile === globalConfig.activeProfile ? " (active)" : "";
145
+ console.log(`- ${profile}${activeIndicator}`);
146
+ });
147
+ } else {
148
+ console.log("No profiles found.");
149
+ }
150
+ return; // Exit after listing profiles
151
+ }
152
+
153
+ if (options.delete) {
154
+ const profileToDelete = options.delete;
155
+ if (globalConfig.profiles[profileToDelete]) {
156
+ delete globalConfig.profiles[profileToDelete];
157
+ if (globalConfig.activeProfile === profileToDelete) {
158
+ globalConfig.activeProfile = "default"; // Reset active profile if deleted
159
+ }
160
+ fs.writeFileSync(globalConfigPath, JSON.stringify(globalConfig, null, 2));
161
+ console.log(`βœ… Profile "${profileToDelete}" deleted successfully.`);
162
+ } else {
163
+ console.log(`❌ Profile "${profileToDelete}" not found.`);
164
+ }
165
+ return; // Exit after deleting profile
166
+ }
167
+
168
+ // Ask for profile name if not provided via flag
169
+ let profile = options.profile;
170
+ if (profile === 'default') {
171
+ const answers = await inquirer.prompt([
172
+ {
173
+ type: "input",
174
+ name: "profile",
175
+ message: "Profile name (default = 'default'):",
176
+ default: "default",
177
+ },
178
+ ]);
179
+ profile = answers.profile;
180
+ }
181
+
182
+ // Load existing profile config for editing
183
+ let existingConfig = globalConfig.profiles[profile] || {};
184
+ if (Object.keys(existingConfig).length > 0) {
185
+ console.log(`πŸ”„ Editing existing profile "${profile}"...`);
186
+ } else {
187
+ console.log(`πŸ†• Creating new profile "${profile}"...`);
188
+ }
189
+
190
+ const answers = await inquirer.prompt([
191
+ {
192
+ type: "password",
193
+ name: "apiKey",
194
+ message: "Enter your API key:",
195
+ default: existingConfig.apiKey || "",
196
+ validate: (input) =>
197
+ input.trim() !== "" ? true : "API key is required.",
198
+ },
199
+ {
200
+ type: "input",
201
+ name: "model",
202
+ message: "Enter model name:",
203
+ default: existingConfig.model || "gemini-1.5-flash",
204
+ },
205
+ {
206
+ type: "input",
207
+ name: "prompt",
208
+ message: "Enter your default prompt (leave blank to use default):",
209
+ default:
210
+ existingConfig.prompt ||
211
+ "Generate a clear changelog entry for these changes.",
212
+ },
213
+ {
214
+ type: "input",
215
+ name: "outputFile",
216
+ message: "Enter output file name:",
217
+ default: existingConfig.outputFile || "CHANGELOG.md",
218
+ },
219
+ ]);
220
+
221
+ globalConfig.profiles[profile] = answers;
222
+ fs.writeFileSync(globalConfigPath, JSON.stringify(globalConfig, null, 2));
223
+ console.log(`βœ… Config saved at ${globalConfigPath}`);
224
+ } catch (error) {
225
+ if (error.name === 'ExitPromptError') {
226
+ console.log('\nOperation cancelled by user.');
227
+ process.exit(1);
228
+ } else {
229
+ console.error(`\n❌ An unexpected error occurred during config: ${error.message}`);
230
+ process.exit(1);
231
+ }
232
+ }
233
+ });
234
+
235
+ program
236
+ .command("use <profile_name>")
237
+ .description("Set the active profile for Amnesiac")
238
+ .action(async (profile_name) => {
239
+ const dir = path.join(os.homedir(), ".amnesiac");
240
+ const globalConfigPath = path.join(dir, "config.json");
241
+ let globalConfig = { activeProfile: "default", profiles: {} };
242
+
243
+ if (fs.existsSync(globalConfigPath)) {
244
+ try {
245
+ globalConfig = JSON.parse(fs.readFileSync(globalConfigPath, "utf-8"));
246
+ } catch (e) {
247
+ console.error("❌ Error reading global config file. It might be corrupted. You can reset it with 'amnesiac reset'.", e.message);
248
+ return; // Exit if global config is unreadable for 'use' command
249
+ }
250
+ }
251
+
252
+ if (globalConfig.profiles[profile_name]) {
253
+ globalConfig.activeProfile = profile_name;
254
+ fs.writeFileSync(globalConfigPath, JSON.stringify(globalConfig, null, 2));
255
+ console.log(`βœ… Active profile set to "${profile_name}".`);
256
+ } else {
257
+ console.log(`❌ Profile "${profile_name}" not found. Please create it first using 'amnesiac config --profile ${profile_name}'.`);
258
+ }
259
+ });
260
+
261
+ program
262
+ .option("-u, --use <profile>", "Use a specific profile for this run")
263
+ .option("-p, --prompt <text>", "Override prompt")
264
+ .option("-m, --model <name>", "Override model")
265
+ // Future: .option("-u, --use <profile>", "Use specific profile")
266
+ .action(async (opts) => {
267
+ try {
268
+ const config = await loadConfig(opts);
269
+ await generateDocs(config);
270
+ } catch (error) {
271
+ console.error(`\n❌ An error occurred: ${error.message}`);
272
+ process.exit(1);
273
+ }
274
+ });
275
+
276
+ program.parse(process.argv);
package/package.json ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "@nueldotdev/amnesiac",
3
+ "version": "1.0.0",
4
+ "description": "A command-line tool to automatically generate documentation for recent codebase changes using the Gemini API.",
5
+ "keywords": [
6
+ "cli-tool",
7
+ "documentation-generator",
8
+ "changelog",
9
+ "auto-docs",
10
+ "git-diff",
11
+ "git-integration",
12
+ "ai-powered",
13
+ "gemini-api",
14
+ "llm",
15
+ "google-ai",
16
+ "developer-tools",
17
+ "productivity"
18
+ ],
19
+ "homepage": "https://github.com/nueldotdev/amnesiac#readme",
20
+ "bugs": {
21
+ "url": "https://github.com/nueldotdev/amnesiac/issues"
22
+ },
23
+ "repository": {
24
+ "type": "git",
25
+ "url": "git+https://github.com/nueldotdev/amnesiac.git"
26
+ },
27
+ "license": "MIT",
28
+ "author": "nueldotdev",
29
+ "type": "module",
30
+ "bin": {
31
+ "amnesiac": "bin/cli.js"
32
+ },
33
+ "dependencies": {
34
+ "@google/generative-ai": "^0.24.1",
35
+ "commander": "^14.0.1",
36
+ "inquirer": "^12.9.6",
37
+ "simple-git": "^3.28.0"
38
+ }
39
+ }