@snelusha/noto 0.4.1 → 1.0.0-beta.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/bin/noto.mjs CHANGED
@@ -1,4 +1,4 @@
1
1
  #!/usr/bin/env node
2
- 'use strict'
2
+ "use strict";
3
3
 
4
- import '../dist/cli.js'
4
+ import "../dist/index.js";
package/dist/index.js ADDED
@@ -0,0 +1,411 @@
1
+ // src/index.ts
2
+ import * as p5 from "@clack/prompts";
3
+ import color5 from "picocolors";
4
+
5
+ // src/utils/parser.ts
6
+ import arg from "arg";
7
+ var parse = (schema, raw) => {
8
+ const args = arg(schema, { argv: raw, permissive: true });
9
+ return {
10
+ command: args._[0],
11
+ options: args
12
+ };
13
+ };
14
+
15
+ // src/commands/noto.ts
16
+ import * as p3 from "@clack/prompts";
17
+ import color3 from "picocolors";
18
+
19
+ // src/middleware/auth.ts
20
+ import * as p from "@clack/prompts";
21
+ import color from "picocolors";
22
+ import dedent from "dedent";
23
+
24
+ // src/utils/storage.ts
25
+ import os from "os";
26
+ import { dirname, join, resolve } from "path";
27
+ import { promises as fs } from "fs";
28
+ import { z as z2 } from "zod";
29
+
30
+ // src/ai/types.ts
31
+ import { z } from "zod";
32
+ var AvailableModelsSchema = z.enum([
33
+ "gemini-1.5-flash",
34
+ "gemini-1.5-flash-latest",
35
+ "gemini-1.5-flash-8b",
36
+ "gemini-1.5-flash-8b-latest",
37
+ "gemini-1.5-pro",
38
+ "gemini-1.5-pro-latest",
39
+ "gemini-2.0-flash-lite-preview-02-05",
40
+ "gemini-2.0-flash-exp",
41
+ "gemini-2.0-pro-exp-02-05"
42
+ ]);
43
+
44
+ // src/utils/storage.ts
45
+ var StorageSchema = z2.object({
46
+ llm: z2.object({
47
+ apiKey: z2.string().optional(),
48
+ model: AvailableModelsSchema.optional()
49
+ }).optional()
50
+ });
51
+ var StorageManager = class {
52
+ static storagePath = resolve(
53
+ join(os.homedir(), ".noto"),
54
+ "storage.sithi"
55
+ );
56
+ static storage = {};
57
+ static async load() {
58
+ try {
59
+ await fs.access(this.storagePath);
60
+ const data = await fs.readFile(this.storagePath, "utf-8");
61
+ const json = data ? JSON.parse(data) : {};
62
+ const result = StorageSchema.safeParse(json);
63
+ if (!result.success) this.storage = {};
64
+ else this.storage = result.data;
65
+ } catch {
66
+ this.storage = {};
67
+ }
68
+ return this.storage;
69
+ }
70
+ static async save() {
71
+ try {
72
+ const directory = dirname(this.storagePath);
73
+ await fs.mkdir(directory, { recursive: true });
74
+ const data = JSON.stringify(this.storage, null, 2);
75
+ await fs.writeFile(this.storagePath, data, "utf-8");
76
+ } catch {
77
+ }
78
+ }
79
+ static async update(updater) {
80
+ try {
81
+ const updatedStorage = await updater(this.storage);
82
+ const result = StorageSchema.safeParse(updatedStorage);
83
+ if (!result.success) return this.storage;
84
+ this.storage = result.data;
85
+ await this.save();
86
+ } catch {
87
+ }
88
+ return this.storage;
89
+ }
90
+ static async get() {
91
+ await this.load();
92
+ return JSON.parse(JSON.stringify(this.storage));
93
+ }
94
+ };
95
+
96
+ // src/middleware/auth.ts
97
+ var withAuth = (fn) => {
98
+ return async (options) => {
99
+ const storage = await StorageManager.get();
100
+ if (!storage.llm?.apiKey) {
101
+ p.log.error(
102
+ dedent`${color.red("noto api key is missing.")}
103
+ ${color.dim(`run ${color.cyan("`noto config key`")} to set it up.`)}`
104
+ );
105
+ console.log();
106
+ process.exit(1);
107
+ }
108
+ return fn(options);
109
+ };
110
+ };
111
+
112
+ // src/middleware/git.ts
113
+ import * as p2 from "@clack/prompts";
114
+ import color2 from "picocolors";
115
+ import dedent2 from "dedent";
116
+
117
+ // src/utils/git.ts
118
+ import simpleGit from "simple-git";
119
+ var git = simpleGit();
120
+ var isGitRepository = async () => {
121
+ return git.checkIsRepo();
122
+ };
123
+ var getStagedDiff = async () => {
124
+ try {
125
+ return git.diff(["--cached"]);
126
+ } catch {
127
+ return null;
128
+ }
129
+ };
130
+
131
+ // src/middleware/git.ts
132
+ var withRepository = (fn) => {
133
+ return async (options) => {
134
+ const isRepo = await isGitRepository();
135
+ if (!isRepo) {
136
+ p2.log.error(
137
+ dedent2`${color2.red("no git repository found in cwd.")}
138
+ ${color2.dim(`run ${color2.cyan("`git init`")} to initialize a new repository.`)}`
139
+ );
140
+ console.log();
141
+ process.exit(1);
142
+ }
143
+ const diff = await getStagedDiff();
144
+ if (!diff) {
145
+ p2.log.error(
146
+ dedent2`${color2.red("no staged changes found.")}
147
+ ${color2.dim(`run ${color2.cyan("`git add <file>`")} or ${color2.cyan("`git add .`")} to stage changes.`)}`
148
+ );
149
+ console.log();
150
+ process.exit(1);
151
+ }
152
+ options.diff = diff;
153
+ return fn(options);
154
+ };
155
+ };
156
+
157
+ // src/errors.ts
158
+ var NotoError = class _NotoError extends Error {
159
+ code;
160
+ constructor(options) {
161
+ super(options.message);
162
+ this.code = options.code;
163
+ Object.setPrototypeOf(this, _NotoError.prototype);
164
+ }
165
+ };
166
+
167
+ // src/ai/index.ts
168
+ import { generateObject } from "ai";
169
+ import z3 from "zod";
170
+ import dedent3 from "dedent";
171
+
172
+ // src/ai/models.ts
173
+ import { createGoogleGenerativeAI } from "@ai-sdk/google";
174
+ var google = createGoogleGenerativeAI({
175
+ apiKey: (await StorageManager.get()).llm?.apiKey ?? "api-key"
176
+ });
177
+ var defaultModel = "gemini-2.0-flash-exp";
178
+ var models = {
179
+ "gemini-1.5-flash": google("gemini-1.5-flash"),
180
+ "gemini-1.5-flash-latest": google("gemini-1.5-flash-latest"),
181
+ "gemini-1.5-flash-8b": google("gemini-1.5-flash-8b"),
182
+ "gemini-1.5-flash-8b-latest": google("gemini-1.5-flash-8b-latest"),
183
+ "gemini-1.5-pro": google("gemini-1.5-pro"),
184
+ "gemini-1.5-pro-latest": google("gemini-1.5-pro-latest"),
185
+ "gemini-2.0-flash-lite-preview-02-05": google(
186
+ "gemini-2.0-flash-lite-preview-02-05"
187
+ ),
188
+ "gemini-2.0-flash-exp": google("gemini-2.0-flash-exp"),
189
+ "gemini-2.0-pro-exp-02-05": google("gemini-2.0-pro-exp-02-05")
190
+ };
191
+ var availableModels = Object.keys(models);
192
+ var getModel = async () => {
193
+ const model2 = (await StorageManager.get()).llm?.model ?? defaultModel;
194
+ if (!availableModels.includes(model2)) {
195
+ throw new NotoError({
196
+ code: "model-not-found",
197
+ message: `model "${model2}" not found.`
198
+ });
199
+ }
200
+ return models[model2];
201
+ };
202
+
203
+ // src/ai/index.ts
204
+ var generateCommitMessage = async (diff) => {
205
+ throw new Error("something");
206
+ const model2 = await getModel();
207
+ const { object } = await generateObject({
208
+ model: model2,
209
+ schema: z3.object({
210
+ message: z3.string()
211
+ }),
212
+ messages: [
213
+ {
214
+ role: "system",
215
+ content: dedent3`
216
+ You are a state-of-the-art AI model tasked with generating a precise Git commit message based on staged changes.
217
+ Adhere strictly to the following instructions, ranked by priority:
218
+
219
+ 1. Write the commit message in present tense, starting with a present-tense verb such as add, fix, update, remove, improve, or implement. This applies to all repositories, including Java.
220
+ 2. Summarize the key changes only, crafting a concise and clear commit message in the format "<type>: <description>".
221
+ 3. Use one of the following standardized types: feat, fix, refactor, docs, test, or chore.
222
+ 4. Ensure the commit message is a single line, fully lowercase, with no scope or body, and omit punctuation such as full stops at the end.
223
+ 5. Limit the length of the commit message to 72 characters.
224
+ 6. Avoid mentioning file names unless a file was renamed or is critical for understanding the changes.
225
+ 7. Prioritize clarity and focus on the most impactful changes for the commit.
226
+
227
+ You are expected to generate structured outputs that align with the provided guidelines and produce a message optimized for readability and accuracy. Strictly follow all constraints to ensure high-quality results.`
228
+ },
229
+ {
230
+ role: "user",
231
+ content: dedent3`generate a commit message for the following staged changes:
232
+ ${diff}`
233
+ }
234
+ ]
235
+ });
236
+ return object.message.trim();
237
+ };
238
+
239
+ // src/commands/noto.ts
240
+ var command = {
241
+ name: "noto",
242
+ description: "generate commit message",
243
+ usage: "noto [options]",
244
+ options: [],
245
+ execute: withAuth(
246
+ withRepository(async (options) => {
247
+ const spin = p3.spinner();
248
+ try {
249
+ const { diff } = options;
250
+ spin.start("generating commit message");
251
+ const message = await generateCommitMessage(diff);
252
+ spin.stop(color3.green(message));
253
+ console.log();
254
+ } catch {
255
+ spin.stop(color3.red("failed to generate commit message"), 1);
256
+ console.log();
257
+ process.exit(1);
258
+ }
259
+ })
260
+ )
261
+ };
262
+ var noto_default = command;
263
+
264
+ // src/commands/config.ts
265
+ import * as p4 from "@clack/prompts";
266
+ import color4 from "picocolors";
267
+ var key = {
268
+ name: "key",
269
+ description: "configure api key",
270
+ usage: "noto config key [options]",
271
+ execute: async (options) => {
272
+ if ((await StorageManager.get()).llm?.apiKey) {
273
+ const confirm2 = await p4.confirm({
274
+ message: "noto api key already configured, do you want to update it?"
275
+ });
276
+ if (p4.isCancel(confirm2) || !confirm2) {
277
+ p4.log.error(color4.red("nothing changed!"));
278
+ console.log();
279
+ process.exit(1);
280
+ }
281
+ }
282
+ let apiKey = options._[0];
283
+ if (!apiKey) {
284
+ const result = await p4.text({
285
+ message: "enter your noto api key"
286
+ });
287
+ if (p4.isCancel(result)) {
288
+ p4.log.error(color4.red("nothing changed!"));
289
+ console.log();
290
+ process.exit(1);
291
+ }
292
+ apiKey = result;
293
+ }
294
+ await StorageManager.update((current) => ({
295
+ ...current,
296
+ llm: {
297
+ ...current.llm,
298
+ apiKey
299
+ }
300
+ }));
301
+ p4.log.success(color4.green("noto api key configured!"));
302
+ console.log();
303
+ }
304
+ };
305
+ var model = {
306
+ name: "model",
307
+ description: "configure model",
308
+ usage: "noto config model [options]",
309
+ execute: async (options) => {
310
+ const model2 = await p4.select({
311
+ message: "select a model",
312
+ initialValue: (await StorageManager.get()).llm?.model,
313
+ options: Object.keys(models).map((model3) => ({
314
+ label: model3,
315
+ value: model3
316
+ }))
317
+ });
318
+ if (p4.isCancel(model2)) {
319
+ p4.log.error(color4.red("nothing changed!"));
320
+ console.log();
321
+ process.exit();
322
+ }
323
+ await StorageManager.update((current) => ({
324
+ ...current,
325
+ llm: {
326
+ ...current.llm,
327
+ model: model2
328
+ }
329
+ }));
330
+ p4.log.success(color4.green("model configured!"));
331
+ console.log();
332
+ }
333
+ };
334
+ var subCommands = [key, model];
335
+ var command2 = {
336
+ name: "config",
337
+ description: "configure noto",
338
+ usage: "noto config [subcommand]",
339
+ execute: async (options) => {
340
+ const command3 = await p4.select({
341
+ message: "Select a subcommand",
342
+ options: subCommands.map((cmd2) => ({
343
+ label: cmd2.description,
344
+ value: cmd2.name
345
+ }))
346
+ });
347
+ if (p4.isCancel(command3)) {
348
+ console.log();
349
+ process.exit(1);
350
+ }
351
+ const cmd = getCommand(command3, subCommands);
352
+ if (!cmd) {
353
+ p4.log.error(color4.red("unknown config command"));
354
+ console.log();
355
+ process.exit(1);
356
+ }
357
+ options._ = options._.slice(1);
358
+ cmd.execute(options);
359
+ },
360
+ subCommands
361
+ };
362
+ var config_default = command2;
363
+
364
+ // src/commands/index.ts
365
+ var commands = [noto_default, config_default];
366
+ var getCommand = (name, cmds = commands) => {
367
+ return cmds.find((cmd) => cmd.name === name);
368
+ };
369
+
370
+ // package.json
371
+ var version = "1.0.0-beta-1";
372
+
373
+ // src/index.ts
374
+ var globalSpec = {
375
+ "--version": Boolean,
376
+ "--help": Boolean,
377
+ "-v": "--version",
378
+ "-h": "--help"
379
+ };
380
+ function main() {
381
+ const args = process.argv.slice(2);
382
+ const { command: command3, options: globalOptions } = parse(globalSpec, args);
383
+ console.log();
384
+ p5.intro(`${color5.bgCyan(color5.black(" @snelusha/noto "))}`);
385
+ if (globalOptions["--version"]) return p5.outro(version);
386
+ const cmd = getCommand(command3) ?? getCommand("noto");
387
+ if (!cmd) return getCommand("noto")?.execute(globalOptions);
388
+ let commandArgs = command3 ? args.slice(1) : args;
389
+ let selectedCommand = cmd;
390
+ if (cmd.subCommands && commandArgs.length) {
391
+ const possibleCommand = commandArgs[0];
392
+ const subCommand = cmd.subCommands.find(
393
+ (cmd2) => cmd2.name === possibleCommand || cmd2.aliases && cmd2.aliases.includes(possibleCommand)
394
+ );
395
+ if (subCommand) {
396
+ selectedCommand = subCommand;
397
+ commandArgs = commandArgs.slice(1);
398
+ }
399
+ }
400
+ const commandSpec = (selectedCommand.options ?? []).reduce((acc, opt) => {
401
+ acc[opt.flag] = opt.type ?? Boolean;
402
+ if (Array.isArray(opt.alias))
403
+ opt.alias.forEach((alias) => acc[alias] = opt.flag);
404
+ else if (opt.alias) acc[opt.alias] = opt.flag;
405
+ return acc;
406
+ }, {});
407
+ const { options: commandOptions } = parse(commandSpec, commandArgs);
408
+ const options = { ...globalOptions, ...commandOptions };
409
+ selectedCommand.execute(options);
410
+ }
411
+ main();
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "@snelusha/noto",
3
- "type": "module",
4
- "version": "0.4.1",
5
- "description": "generate clean commit messages in a snap! ✨",
3
+ "version": "1.0.0-beta.1",
4
+ "description": "Generate clean commit messages in a snap! ✨",
6
5
  "license": "MIT",
6
+ "type": "module",
7
7
  "author": {
8
8
  "name": "Sithija Nelusha Silva",
9
9
  "email": "hello@snelusha.dev",
@@ -17,40 +17,38 @@
17
17
  "bugs": {
18
18
  "url": "https://github.com/snelusha/noto/issues"
19
19
  },
20
- "main": "dist/cli.js",
21
- "module": "dist/cli.js",
22
- "types": "dist/cli.d.ts",
20
+ "main": "dist/index.js",
21
+ "module": "dist/index.js",
22
+ "types": "dist/index.d.ts",
23
23
  "bin": {
24
- "noto": "bin/noto.mjs"
24
+ "noto": "./bin/noto.mjs"
25
25
  },
26
- "files": [
27
- "dist"
28
- ],
29
26
  "scripts": {
30
- "build": "tsup",
31
- "publish": "npm publish --public"
27
+ "clean": "rm -rf .turbo node_modules dist",
28
+ "build": "tsup --clean",
29
+ "dev": "tsup --clean --watch",
30
+ "test": "tsup --clean && vitest"
32
31
  },
32
+ "files": [
33
+ "dist",
34
+ "README.md"
35
+ ],
33
36
  "devDependencies": {
34
- "@types/bun": "latest",
35
- "@types/which": "^3.0.4",
36
- "@types/yargs": "^17.0.33",
37
- "esbuild-plugin-alias": "^0.2.1",
38
- "tsup": "^8.3.6"
39
- },
40
- "peerDependencies": {
41
- "typescript": "^5.0.0"
37
+ "@types/node": "^22.12.0",
38
+ "tsup": "^8.3.6",
39
+ "typescript": "^5.7.3"
42
40
  },
43
41
  "dependencies": {
44
- "@ai-sdk/google": "^1.1.10",
45
- "@posva/prompts": "^2.4.4",
46
- "ai": "^4.1.21",
47
- "clipboardy": "^4.0.0",
42
+ "@ai-sdk/google": "^1.1.14",
43
+ "@clack/prompts": "^0.10.0",
44
+ "@snelusha/noto": "link:@snelusha/noto",
45
+ "ai": "^4.1.40",
46
+ "arg": "^5.0.2",
48
47
  "dedent": "^1.5.3",
49
- "ora": "^8.2.0",
50
48
  "picocolors": "^1.1.1",
51
49
  "simple-git": "^3.27.0",
52
- "which": "^5.0.0",
53
- "yargs": "^17.7.2",
54
- "zod": "^3.24.1"
50
+ "tinyexec": "^0.3.2",
51
+ "vitest": "^3.0.5",
52
+ "zod": "^3.24.2"
55
53
  }
56
54
  }
package/LICENSE DELETED
@@ -1,21 +0,0 @@
1
- MIT License
2
-
3
- Copyright (c) 2024 Sithija Nelusha Silva<https://github.com/snelusha>
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
package/README.md DELETED
@@ -1,61 +0,0 @@
1
- <h1 align="center">noto ✨</h1>
2
- <p align="center"><sup>(/nōto/, <em>notebook</em> in Japanese)</sup></p>
3
- <img src="https://github.com/snelusha/static/blob/main/noto/banner-minimal.png?raw=true" align="center"></img>
4
-
5
- ## Features
6
-
7
- - **Instant Commit Messages**: Generate clear, context-aware messages based on staged changes.
8
-
9
- - **Seamless Git Integration**: Apply messages directly, skip the copy-paste.
10
-
11
- - **No Installation Required:** Use instantly with `npx @snelusha/noto` — just run and go!
12
-
13
- ## Getting Started
14
-
15
- 1. **Configuration:**
16
- Before diving in, run the following command to configure your `noto`:
17
-
18
- ```bash
19
- noto config
20
- ```
21
-
22
- Here, you’ll need to input your Google GenAI API Key.
23
-
24
- 2. **Generate commit messages**
25
- To generate a commit message, simply run:
26
-
27
- ```bash
28
- noto # generate a commit message
29
-
30
- # apply generated message
31
- noto --apply # apply the message to current commit
32
- noto -a # alias for apply
33
-
34
- # copy generated message
35
- noto --copy # copy the message to clipboard
36
- noto -c # alias for copy
37
-
38
- # access the previously generated message
39
- noto prev # view the last message
40
-
41
- noto prev --apply # apply the last message to current commit
42
- noto prev -a # alias for apply
43
-
44
- noto prev --copy # copy the last message to clipboard
45
- noto prev -c # alias for copy
46
- ```
47
-
48
- ## Pro Tips
49
-
50
- - 🚀 Get fast commits on the fly with `noto -a` to streamline your workflow!
51
-
52
- ## Contributing
53
-
54
- We welcome contributions and suggestions! If you have ideas or improvements, feel free to reach out or open a pull request.
55
-
56
- Thank you for using `noto`! If you have any feedback or suggestions, feel free to reach out or contribute to the project. ✨
57
-
58
- ## License
59
-
60
- This project is licensed under the MIT License.
61
- © 2024 [Sithija Nelusha Silva](https://github.com/snelusha)
package/dist/cli.cjs DELETED
@@ -1,15 +0,0 @@
1
- "use strict";var X=Object.create;var F=Object.defineProperty;var Q=Object.getOwnPropertyDescriptor;var V=Object.getOwnPropertyNames;var Z=Object.getPrototypeOf,ee=Object.prototype.hasOwnProperty;var te=(e,t,o,i)=>{if(t&&typeof t=="object"||typeof t=="function")for(let r of V(t))!ee.call(e,r)&&r!==o&&F(e,r,{get:()=>t[r],enumerable:!(i=Q(t,r))||i.enumerable});return e};var s=(e,t,o)=>(o=e!=null?X(Z(e)):{},te(t||!e||!e.__esModule?F(o,"default",{value:e,enumerable:!0}):o,e));var q=s(require("yargs"),1),L=require("yargs/helpers");var C=s(require("@posva/prompts"),1),y=s(require("picocolors"),1);var E=require("path"),P=require("fs");var M=s(require("os"),1),g=s(require("process"),1),ie=s(require("which"),1),w=s(require("ora"),1),a=s(require("picocolors"),1),d=require("path"),n=require("fs");var l=s(require("simple-git"),1);async function I(){try{return await(0,l.default)().checkIsRepo()}catch{return!1}}async function $(){try{let t=(await(0,l.default)().diff(["--cached","--name-only"])).split(`
2
- `).filter(Boolean),o=["*.lock","*.lockb","*.yaml.lock","*.hcl.lock","*.resolved"],i=t.filter(r=>!o.some(W=>new RegExp(W.replace("*",".*")).test(r)));return i.length===0?null:await(0,l.default)().diff(["--cached","--",...i])}catch{return null}}async function h(e){try{return(await(0,l.default)().commit(e)).summary.changes>0}catch{return!1}}async function oe(){try{let e=await(0,l.default)().raw(["rev-list","--count","HEAD"]);return Number(e.trim())}catch(e){return/(ambiguous argument.*HEAD|unknown revision or path.*HEAD)/i.test(e.message)?0:null}}async function K(){try{return await oe()===0}catch{return!1}}var u=(0,d.join)(M.default.homedir(),"snelusha-noto");var T=0;async function j(){(0,n.existsSync)(u)||await n.promises.mkdir(u,{recursive:!0});let e=(0,d.join)(u,`.${g.default.pid}.${T}`);return T+=1,n.promises.open(e,"wx").then(t=>({fd:t,path:e,cleanup(){t.close().then(()=>{(0,n.existsSync)(e)&&n.promises.unlink(e)})}})).catch(t=>{if(t&&t.code==="EEXIST")return j()})}async function D(e,t=""){let o=await j();if(o)try{await n.promises.writeFile(o.path,t);let i=(0,d.dirname)(e);return(0,n.existsSync)(i)||await n.promises.mkdir(i,{recursive:!0}),await n.promises.rename(o.path,e),!0}catch{return!1}finally{o.cleanup()}return!1}function b(){let e;return{start(t){e=(0,w.default)(t),e.spinner={interval:150,frames:["\u2736","\u2738","\u2739","\u273A","\u2739","\u2737"]},e.start()},fail(t){e||(e=(0,w.default)()),e.fail(t),e=void 0},success(t){e||(e=(0,w.default)()),e.succeed(t),e=void 0},stop(){e&&(e.stop(),e=void 0)}}}async function O(){(await c()).apiKey||(console.log(`Please run ${a.default.cyan(a.default.bold("`noto config`"))} to set your API key.`),g.default.exit(1))}async function x(){await I()||(console.log(a.default.red("Oops! No Git repository found in the current directory.")),console.log(a.default.dim(`You can initialize one by running ${a.default.cyan(a.default.bold("`git init`"))}`)),g.default.exit(1))}async function v(){let e=await $();if(e)return e;console.log(a.default.red("Oops! No staged changes found to commit.")),console.log(a.default.dim(`Stage changes with ${a.default.cyan(a.default.bold("`git add <file>`"))} or ${a.default.cyan(a.default.bold("`git add .`"))} for stage all files.`)),g.default.exit(1)}var m={},S=(0,E.resolve)(u,"storage.json");async function c(e){try{Object.keys(m).length||(m=(0,P.existsSync)(S)?JSON.parse(await P.promises.readFile(S,"utf-8")||"{}")||{}:{}),e&&await e(m)&&await f()}catch(t){console.error("error loading storage:",t),m={}}return m}async function f(){try{m&&await D(S,JSON.stringify(m,null,2))}catch(e){console.error("error saving storage:",e)}}async function R(){let e=await c();e.apiKey&&((await(0,C.default)({type:"confirm",name:"reset",message:"Do you want to reset your API key?"},{onCancel:()=>process.exit(0)})).reset||(console.log(`Use ${y.default.greenBright(y.default.bold("`noto`"))} to generate your commit message!`),process.exit(0)));let t=await(0,C.default)({type:"password",name:"apiKey",message:"Please enter your API key:",validate:o=>o?!0:"API key is required!"});t.apiKey&&(e.apiKey=t.apiKey,await f(),console.log("API key configured successfully!"),console.log(`Use ${y.default.greenBright(y.default.bold("`noto`"))} to generate your commit message!`))}var G=s(require("picocolors"),1),H=s(require("clipboardy"),1);var N=require("ai"),z=require("@ai-sdk/google"),A=s(require("dedent"),1),k=require("zod");async function B(e){if(await K())return"chore: init repo";let t=await c(),o=(0,z.createGoogleGenerativeAI)({apiKey:t.apiKey}),{object:i}=await(0,N.generateObject)({model:o("gemini-2.0-pro-exp-02-05",{structuredOutputs:!1}),schema:k.z.object({message:k.z.string()}),messages:[{role:"system",content:A.default`
3
- You are a state-of-the-art AI model tasked with generating a precise Git commit message based on staged changes.
4
- Adhere strictly to the following instructions, ranked by priority:
5
-
6
- 1. Write the commit message in present tense, starting with a present-tense verb such as add, fix, update, remove, improve, or implement. This applies to all repositories, including Java.
7
- 2. Summarize the key changes only, crafting a concise and clear commit message in the format "<type>: <description>".
8
- 3. Use one of the following standardized types: feat, fix, refactor, docs, test, or chore.
9
- 4. Ensure the commit message is a single line, fully lowercase, with no scope or body, and omit punctuation such as full stops at the end.
10
- 5. Limit the length of the commit message to 72 characters.
11
- 6. Avoid mentioning file names unless a file was renamed or is critical for understanding the changes.
12
- 7. Prioritize clarity and focus on the most impactful changes for the commit.
13
-
14
- You are expected to generate structured outputs that align with the provided guidelines and produce a message optimized for readability and accuracy. Strictly follow all constraints to ensure high-quality results.`},{role:"user",content:A.default`generate a commit message for the following staged changes:\n${e}`}]});return i.message.trim()}async function J(e){let t=await c();await O(),await x();let o=await v(),i=b();try{i.start("Generating commit message...");let r=await B(o);t.lastGeneratedMessage=r,await f(),i.success(`Commit Message: ${G.default.dim(G.default.bold(r))}`),e.copy&&(H.default.writeSync(r),i.success("Message copied to clipboard!")),e.apply&&(await h(r)||(i.fail("Failed to commit staged changes."),process.exit(1)),i.success("Staged changes committed!"))}catch{i.fail("Failed to generate commit message."),process.exit(1)}}var p=s(require("picocolors"),1),U=s(require("clipboardy"),1);async function Y(e){let t=await c();t.lastGeneratedMessage||(console.log(p.default.red("No previous commit message found.")),console.log(p.default.dim(`Generate a new message with ${p.default.cyan(p.default.bold("`noto`"))} command.`)),process.exit(1));let o=b(),i=t.lastGeneratedMessage;o.success(`Previous Commit Message: ${p.default.dim(p.default.bold(i))}`),e.copy&&(U.default.writeSync(i),o.success("Message copied to clipboard!")),e.apply&&(await x(),await v(),await h(i)||(o.fail("Failed to commit staged changes."),process.exit(1)),o.success("Staged changes committed!"))}var _="0.4.1";(0,q.default)((0,L.hideBin)(process.argv)).scriptName("noto").usage("$0 [args]").command("config","setup you API key to enable noto.",()=>{},R).command("prev","access previous commit message",e=>{e.option("copy",{alias:"c",type:"boolean",description:"Copy the previous commit message to the clipboard."}),e.option("apply",{alias:"a",type:"boolean",description:"Commit the staged changes with the previous message."})},Y).command("*","generate commit message",e=>{e.option("copy",{alias:"c",type:"boolean",description:"Copy the generated commit message to the clipboard."}),e.option("apply",{alias:"a",type:"boolean",description:"Commit the staged changes with the generated message."})},J).version("version",_).alias("-v","--version").alias("-h","--help").argv;
15
- //# sourceMappingURL=cli.cjs.map
package/dist/cli.cjs.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/cli.ts","../src/commands/config.ts","../src/storage.ts","../src/utils.ts","../src/git.ts","../src/commands/generate.ts","../src/ai.ts","../src/commands/prev.ts","../package.json"],"sourcesContent":["import yargs from \"yargs\";\n\nimport { hideBin } from \"yargs/helpers\";\n\nimport { config } from \"@/commands/config\";\nimport { generate } from \"@/commands/generate\";\nimport { prev } from \"@/commands/prev\";\n\nimport { version } from \"package\";\n\nyargs(hideBin(process.argv))\n .scriptName(\"noto\")\n .usage(\"$0 [args]\")\n .command(\"config\", \"setup you API key to enable noto.\", () => {}, config)\n .command(\n \"prev\",\n \"access previous commit message\",\n (args) => {\n args.option(\"copy\", {\n alias: \"c\",\n type: \"boolean\",\n description: \"Copy the previous commit message to the clipboard.\",\n });\n args.option(\"apply\", {\n alias: \"a\",\n type: \"boolean\",\n description: \"Commit the staged changes with the previous message.\",\n });\n },\n prev\n )\n .command(\n \"*\",\n \"generate commit message\",\n (args) => {\n args.option(\"copy\", {\n alias: \"c\",\n type: \"boolean\",\n description: \"Copy the generated commit message to the clipboard.\",\n });\n args.option(\"apply\", {\n alias: \"a\",\n type: \"boolean\",\n description: \"Commit the staged changes with the generated message.\",\n });\n },\n generate\n )\n .version(\"version\", version)\n .alias(\"-v\", \"--version\")\n .alias(\"-h\", \"--help\").argv;\n","import prompts from \"@posva/prompts\";\nimport pc from \"picocolors\";\n\nimport { load, dump } from \"@/storage\";\n\nexport async function config() {\n const storage = await load();\n\n if (storage.apiKey) {\n const response = await prompts(\n {\n type: \"confirm\",\n name: \"reset\",\n message: \"Do you want to reset your API key?\",\n },\n {\n onCancel: () => process.exit(0),\n }\n );\n\n if (!response.reset) {\n console.log(\n `Use ${pc.greenBright(\n pc.bold(\"`noto`\")\n )} to generate your commit message!`\n );\n process.exit(0);\n }\n }\n\n const response = await prompts({\n type: \"password\",\n name: \"apiKey\",\n message: \"Please enter your API key:\",\n validate: (value) => (value ? true : \"API key is required!\"),\n });\n if (response.apiKey) {\n storage.apiKey = response.apiKey;\n await dump();\n console.log(\"API key configured successfully!\");\n console.log(\n `Use ${pc.greenBright(pc.bold(\"`noto`\"))} to generate your commit message!`\n );\n }\n}\n","import { resolve } from \"node:path\";\nimport { existsSync, promises as fs } from \"node:fs\";\n\nimport { APP_DIR, writeFileSafe } from \"@/utils\";\n\nexport interface Storage {\n apiKey?: string;\n lastGeneratedMessage?: string;\n}\n\nlet storage: Storage = {};\n\nconst storagePath = resolve(APP_DIR, \"storage.json\");\n\nexport async function load(\n fn?: (storage: Storage) => Promise<boolean> | boolean\n): Promise<Storage> {\n try {\n if (!Object.keys(storage).length) {\n storage = existsSync(storagePath)\n ? JSON.parse((await fs.readFile(storagePath, \"utf-8\")) || \"{}\") || {}\n : {};\n }\n if (fn && (await fn(storage))) {\n await dump();\n }\n } catch (error) {\n console.error(\"error loading storage:\", error);\n storage = {};\n }\n\n return storage;\n}\n\nexport async function dump(): Promise<void> {\n try {\n if (storage) {\n await writeFileSafe(storagePath, JSON.stringify(storage, null, 2));\n }\n } catch (error) {\n console.error(\"error saving storage:\", error);\n }\n}\n","import os from \"node:os\";\nimport process from \"node:process\";\n\nimport which from \"which\";\nimport ora from \"ora\";\nimport pc from \"picocolors\";\n\nimport { dirname, join } from \"node:path\";\nimport { existsSync, promises as fs } from \"node:fs\";\n\nimport type { Buffer } from \"node:buffer\";\n\nimport type { Ora } from \"ora\";\n\nimport { load } from \"@/storage\";\nimport { getStagedDiff, isGitRepository } from \"@/git\";\n\nexport const APP_DIR = join(os.homedir(), \"snelusha-noto\");\n\nexport function remove<T>(arr: T[], v: T) {\n const index = arr.indexOf(v);\n if (index >= 0) arr.splice(index, 1);\n return arr;\n}\n\nexport function exclude<T>(arr: T[], ...v: T[]) {\n return arr.slice().filter((item) => !v.includes(item));\n}\n\nexport function cmdExists(cmd: string) {\n return which.sync(cmd, { nothrow: true }) !== null;\n}\n\ninterface TempFile {\n path: string;\n fd: fs.FileHandle;\n cleanup: () => void;\n}\n\nlet counter = 0;\n\nasync function openTemp(): Promise<TempFile | undefined> {\n if (!existsSync(APP_DIR)) await fs.mkdir(APP_DIR, { recursive: true });\n\n const competitivePath = join(APP_DIR, `.${process.pid}.${counter}`);\n counter += 1;\n\n return fs\n .open(competitivePath, \"wx\")\n .then((fd) => ({\n fd,\n path: competitivePath,\n cleanup() {\n fd.close().then(() => {\n if (existsSync(competitivePath)) fs.unlink(competitivePath);\n });\n },\n }))\n .catch((error: any) => {\n if (error && error.code === \"EEXIST\") return openTemp();\n else return undefined;\n });\n}\n\nexport async function writeFileSafe(\n path: string,\n data: string | Buffer = \"\"\n): Promise<boolean> {\n const temp = await openTemp();\n\n if (temp) {\n try {\n // @ts-expect-error eslint-disable-next-line\n await fs.writeFile(temp.path, data);\n const directory = dirname(path);\n if (!existsSync(directory))\n await fs.mkdir(directory, { recursive: true });\n await fs.rename(temp.path, path);\n return true;\n } catch {\n return false;\n } finally {\n temp.cleanup();\n }\n }\n\n return false;\n}\n\nexport function spinner() {\n let s: Ora | undefined;\n\n return {\n start(text: string) {\n s = ora(text);\n s.spinner = {\n interval: 150,\n frames: [\"✶\", \"✸\", \"✹\", \"✺\", \"✹\", \"✷\"],\n };\n s.start();\n },\n fail(text: string) {\n if (!s) {\n s = ora();\n }\n s.fail(text);\n s = undefined;\n },\n success(text: string) {\n if (!s) {\n s = ora();\n }\n s.succeed(text);\n s = undefined;\n },\n stop() {\n if (s) {\n s.stop();\n s = undefined;\n }\n },\n };\n}\n\nexport async function ensureApiKey() {\n const storage = await load();\n if (!storage.apiKey) {\n console.log(\n `Please run ${pc.cyan(pc.bold(\"`noto config`\"))} to set your API key.`\n );\n process.exit(1);\n }\n}\n\nexport async function ensureGitRepository() {\n if (await isGitRepository()) return;\n console.log(pc.red(\"Oops! No Git repository found in the current directory.\"));\n console.log(\n pc.dim(`You can initialize one by running ${pc.cyan(pc.bold(\"`git init`\"))}`)\n );\n process.exit(1);\n}\n\nexport async function ensureStagedChanges() {\n const diff = await getStagedDiff();\n if (diff) return diff;\n console.log(pc.red(\"Oops! No staged changes found to commit.\"));\n console.log(\n pc.dim(\n `Stage changes with ${pc.cyan(pc.bold(\"`git add <file>`\"))} or ${pc.cyan(\n pc.bold(\"`git add .`\")\n )} for stage all files.`\n )\n );\n process.exit(1);\n}\n","import simpleGit from \"simple-git\";\n\nexport async function isGitRepository() {\n try {\n return await simpleGit().checkIsRepo();\n } catch {\n return false;\n }\n}\n\nexport async function getStagedDiff(): Promise<string | null> {\n try {\n const stagedFiles = await simpleGit().diff([\"--cached\", \"--name-only\"]);\n\n const files = stagedFiles.split(\"\\n\").filter(Boolean);\n\n const excludedPatterns = [\n \"*.lock\",\n \"*.lockb\",\n \"*.yaml.lock\",\n \"*.hcl.lock\",\n \"*.resolved\",\n ];\n const filteredFiles = files.filter(\n (file) =>\n !excludedPatterns.some((pattern) => {\n const regex = new RegExp(pattern.replace(\"*\", \".*\"));\n return regex.test(file);\n })\n );\n\n if (filteredFiles.length === 0) return null;\n\n return await simpleGit().diff([\"--cached\", \"--\", ...filteredFiles]);\n } catch {\n return null;\n }\n}\n\nexport async function commit(message: string): Promise<boolean> {\n try {\n const result = await simpleGit().commit(message);\n return result.summary.changes > 0;\n } catch {\n return false;\n }\n}\n\nexport async function getCommitCount() {\n try {\n const count = await simpleGit().raw([\"rev-list\", \"--count\", \"HEAD\"]);\n return Number(count.trim());\n } catch (error) {\n if (\n /(ambiguous argument.*HEAD|unknown revision or path.*HEAD)/i.test(\n (error as Error).message\n )\n ) {\n return 0;\n }\n return null;\n }\n}\n\nexport async function isFirstCommit() {\n try {\n const count = await getCommitCount();\n return count === 0;\n } catch {\n return false;\n }\n}\n","import pc from \"picocolors\";\nimport clipboardy from \"clipboardy\";\n\nimport { load, dump } from \"@/storage\";\nimport { commit } from \"@/git\";\nimport { generateCommitMessage } from \"@/ai\";\nimport {\n ensureApiKey,\n ensureGitRepository,\n ensureStagedChanges,\n spinner,\n} from \"@/utils\";\n\nimport type { ArgumentsCamelCase } from \"yargs\";\n\nexport async function generate(args: ArgumentsCamelCase) {\n const storage = await load();\n\n await ensureApiKey();\n await ensureGitRepository();\n\n const diff = await ensureStagedChanges();\n\n const spin = spinner();\n\n try {\n spin.start(\"Generating commit message...\");\n\n const message = await generateCommitMessage(diff);\n\n storage.lastGeneratedMessage = message;\n await dump();\n\n spin.success(`Commit Message: ${pc.dim(pc.bold(message))}`);\n\n if (args.copy) {\n clipboardy.writeSync(message);\n spin.success(\"Message copied to clipboard!\");\n }\n\n if (args.apply) {\n if (!(await commit(message))) {\n spin.fail(\"Failed to commit staged changes.\");\n process.exit(1);\n }\n spin.success(\"Staged changes committed!\");\n }\n } catch (_) {\n spin.fail(\"Failed to generate commit message.\");\n process.exit(1);\n }\n}\n","import { generateObject } from \"ai\";\nimport { createGoogleGenerativeAI } from \"@ai-sdk/google\";\n\nimport dedent from \"dedent\";\n\nimport { z } from \"zod\";\n\nimport { load } from \"@/storage\";\nimport { isFirstCommit } from \"@/git\";\n\nexport async function generateCommitMessage(diff: string): Promise<string> {\n if (await isFirstCommit()) return \"chore: init repo\";\n\n const storage = await load();\n\n const google = createGoogleGenerativeAI({\n apiKey: storage.apiKey,\n });\n\n const { object } = await generateObject({\n model: google(\"gemini-2.0-pro-exp-02-05\", {\n structuredOutputs: false,\n }),\n schema: z.object({\n message: z.string(),\n }),\n messages: [\n {\n role: \"system\",\n content: dedent`\n You are a state-of-the-art AI model tasked with generating a precise Git commit message based on staged changes.\n Adhere strictly to the following instructions, ranked by priority:\n \n 1. Write the commit message in present tense, starting with a present-tense verb such as add, fix, update, remove, improve, or implement. This applies to all repositories, including Java.\n 2. Summarize the key changes only, crafting a concise and clear commit message in the format \"<type>: <description>\".\n 3. Use one of the following standardized types: feat, fix, refactor, docs, test, or chore.\n 4. Ensure the commit message is a single line, fully lowercase, with no scope or body, and omit punctuation such as full stops at the end.\n 5. Limit the length of the commit message to 72 characters.\n 6. Avoid mentioning file names unless a file was renamed or is critical for understanding the changes.\n 7. Prioritize clarity and focus on the most impactful changes for the commit.\n \n You are expected to generate structured outputs that align with the provided guidelines and produce a message optimized for readability and accuracy. Strictly follow all constraints to ensure high-quality results.`,\n },\n {\n role: \"user\",\n content: dedent`generate a commit message for the following staged changes:\\n${diff}`,\n },\n ],\n });\n\n return object.message.trim();\n}\n","import pc from \"picocolors\";\nimport clipboardy from \"clipboardy\";\n\nimport { load } from \"@/storage\";\nimport { commit } from \"@/git\";\n\nimport {\n ensureApiKey,\n ensureGitRepository,\n ensureStagedChanges,\n spinner,\n} from \"@/utils\";\n\nimport type { ArgumentsCamelCase } from \"yargs\";\n\nexport async function prev(args: ArgumentsCamelCase) {\n const storage = await load();\n\n if (!storage.lastGeneratedMessage) {\n console.log(pc.red(\"No previous commit message found.\"));\n console.log(\n pc.dim(`Generate a new message with ${pc.cyan(pc.bold(\"`noto`\"))} command.`)\n );\n process.exit(1);\n }\n const spin = spinner();\n\n const message = storage.lastGeneratedMessage;\n\n spin.success(`Previous Commit Message: ${pc.dim(pc.bold(message))}`);\n\n if (args.copy) {\n clipboardy.writeSync(message);\n spin.success(\"Message copied to clipboard!\");\n }\n\n if (args.apply) {\n await ensureGitRepository();\n\n await ensureStagedChanges();\n\n if (!(await commit(message))) {\n spin.fail(\"Failed to commit staged changes.\");\n process.exit(1);\n }\n\n spin.success(\"Staged changes committed!\");\n }\n}\n","{\n \"name\": \"@snelusha/noto\",\n \"type\": \"module\",\n \"version\": \"0.4.1\",\n \"description\": \"generate clean commit messages in a snap! ✨\",\n \"license\": \"MIT\",\n \"author\": {\n \"name\": \"Sithija Nelusha Silva\",\n \"email\": \"hello@snelusha.dev\",\n \"url\": \"https://snelusha.dev\"\n },\n \"homepage\": \"https://github.com/snelusha/noto\",\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git+https://github.com/snelusha/noto.git\"\n },\n \"bugs\": {\n \"url\": \"https://github.com/snelusha/noto/issues\"\n },\n \"main\": \"dist/cli.js\",\n \"module\": \"dist/cli.js\",\n \"types\": \"dist/cli.d.ts\",\n \"bin\": {\n \"noto\": \"bin/noto.mjs\"\n },\n \"files\": [\n \"dist\"\n ],\n \"scripts\": {\n \"build\": \"tsup\",\n \"publish\": \"npm publish --public\"\n },\n \"devDependencies\": {\n \"@types/bun\": \"latest\",\n \"@types/which\": \"^3.0.4\",\n \"@types/yargs\": \"^17.0.33\",\n \"esbuild-plugin-alias\": \"^0.2.1\",\n \"tsup\": \"^8.3.6\"\n },\n \"peerDependencies\": {\n \"typescript\": \"^5.0.0\"\n },\n \"dependencies\": {\n \"@ai-sdk/google\": \"^1.1.10\",\n \"@posva/prompts\": \"^2.4.4\",\n \"ai\": \"^4.1.21\",\n \"clipboardy\": \"^4.0.0\",\n \"dedent\": \"^1.5.3\",\n \"ora\": \"^8.2.0\",\n \"picocolors\": \"^1.1.1\",\n \"simple-git\": \"^3.27.0\",\n \"which\": \"^5.0.0\",\n \"yargs\": \"^17.7.2\",\n \"zod\": \"^3.24.1\"\n }\n}\n"],"mappings":"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","names":["import_yargs","import_helpers","import_prompts","import_picocolors","import_node_path","import_node_fs","import_node_os","import_node_process","import_which","import_ora","import_picocolors","import_node_path","import_node_fs","import_simple_git","isGitRepository","simpleGit","getStagedDiff","files","excludedPatterns","filteredFiles","file","pattern","commit","message","getCommitCount","count","error","isFirstCommit","APP_DIR","os","counter","openTemp","APP_DIR","fs","competitivePath","process","fd","error","writeFileSafe","path","data","temp","directory","spinner","s","text","ora","ensureApiKey","load","pc","ensureGitRepository","isGitRepository","ensureStagedChanges","diff","getStagedDiff","storage","storagePath","APP_DIR","load","fn","fs","dump","error","writeFileSafe","config","storage","load","prompts","pc","response","value","dump","import_picocolors","import_clipboardy","import_ai","import_google","import_dedent","import_zod","generateCommitMessage","diff","isFirstCommit","storage","load","google","object","dedent","generate","args","storage","load","ensureApiKey","ensureGitRepository","diff","ensureStagedChanges","spin","spinner","message","generateCommitMessage","dump","pc","clipboardy","commit","import_picocolors","import_clipboardy","prev","args","storage","load","pc","spin","spinner","message","clipboardy","ensureGitRepository","ensureStagedChanges","commit","version","yargs","config","args","prev","generate","version"]}
package/dist/cli.d.ts DELETED
@@ -1,2 +0,0 @@
1
-
2
- export { }
package/dist/cli.js DELETED
@@ -1,15 +0,0 @@
1
- import Q from"yargs";import{hideBin as V}from"yargs/helpers";import $ from"@posva/prompts";import w from"picocolors";import{resolve as J}from"node:path";import{existsSync as U,promises as Y}from"node:fs";import B from"node:os";import d from"node:process";import re from"which";import b from"ora";import s from"picocolors";import{dirname as H,join as k}from"node:path";import{existsSync as x,promises as c}from"node:fs";import p from"simple-git";async function P(){try{return await p().checkIsRepo()}catch{return!1}}async function S(){try{let t=(await p().diff(["--cached","--name-only"])).split(`
2
- `).filter(Boolean),o=["*.lock","*.lockb","*.yaml.lock","*.hcl.lock","*.resolved"],i=t.filter(a=>!o.some(N=>new RegExp(N.replace("*",".*")).test(a)));return i.length===0?null:await p().diff(["--cached","--",...i])}catch{return null}}async function g(e){try{return(await p().commit(e)).summary.changes>0}catch{return!1}}async function z(){try{let e=await p().raw(["rev-list","--count","HEAD"]);return Number(e.trim())}catch(e){return/(ambiguous argument.*HEAD|unknown revision or path.*HEAD)/i.test(e.message)?0:null}}async function C(){try{return await z()===0}catch{return!1}}var l=k(B.homedir(),"snelusha-noto");var A=0;async function G(){x(l)||await c.mkdir(l,{recursive:!0});let e=k(l,`.${d.pid}.${A}`);return A+=1,c.open(e,"wx").then(t=>({fd:t,path:e,cleanup(){t.close().then(()=>{x(e)&&c.unlink(e)})}})).catch(t=>{if(t&&t.code==="EEXIST")return G()})}async function F(e,t=""){let o=await G();if(o)try{await c.writeFile(o.path,t);let i=H(e);return x(i)||await c.mkdir(i,{recursive:!0}),await c.rename(o.path,e),!0}catch{return!1}finally{o.cleanup()}return!1}function f(){let e;return{start(t){e=b(t),e.spinner={interval:150,frames:["\u2736","\u2738","\u2739","\u273A","\u2739","\u2737"]},e.start()},fail(t){e||(e=b()),e.fail(t),e=void 0},success(t){e||(e=b()),e.succeed(t),e=void 0},stop(){e&&(e.stop(),e=void 0)}}}async function I(){(await r()).apiKey||(console.log(`Please run ${s.cyan(s.bold("`noto config`"))} to set your API key.`),d.exit(1))}async function y(){await P()||(console.log(s.red("Oops! No Git repository found in the current directory.")),console.log(s.dim(`You can initialize one by running ${s.cyan(s.bold("`git init`"))}`)),d.exit(1))}async function h(){let e=await S();if(e)return e;console.log(s.red("Oops! No staged changes found to commit.")),console.log(s.dim(`Stage changes with ${s.cyan(s.bold("`git add <file>`"))} or ${s.cyan(s.bold("`git add .`"))} for stage all files.`)),d.exit(1)}var n={},v=J(l,"storage.json");async function r(e){try{Object.keys(n).length||(n=U(v)?JSON.parse(await Y.readFile(v,"utf-8")||"{}")||{}:{}),e&&await e(n)&&await u()}catch(t){console.error("error loading storage:",t),n={}}return n}async function u(){try{n&&await F(v,JSON.stringify(n,null,2))}catch(e){console.error("error saving storage:",e)}}async function K(){let e=await r();e.apiKey&&((await $({type:"confirm",name:"reset",message:"Do you want to reset your API key?"},{onCancel:()=>process.exit(0)})).reset||(console.log(`Use ${w.greenBright(w.bold("`noto`"))} to generate your commit message!`),process.exit(0)));let t=await $({type:"password",name:"apiKey",message:"Please enter your API key:",validate:o=>o?!0:"API key is required!"});t.apiKey&&(e.apiKey=t.apiKey,await u(),console.log("API key configured successfully!"),console.log(`Use ${w.greenBright(w.bold("`noto`"))} to generate your commit message!`))}import D from"picocolors";import L from"clipboardy";import{generateObject as _}from"ai";import{createGoogleGenerativeAI as q}from"@ai-sdk/google";import T from"dedent";import{z as M}from"zod";async function j(e){if(await C())return"chore: init repo";let t=await r(),o=q({apiKey:t.apiKey}),{object:i}=await _({model:o("gemini-2.0-pro-exp-02-05",{structuredOutputs:!1}),schema:M.object({message:M.string()}),messages:[{role:"system",content:T`
3
- You are a state-of-the-art AI model tasked with generating a precise Git commit message based on staged changes.
4
- Adhere strictly to the following instructions, ranked by priority:
5
-
6
- 1. Write the commit message in present tense, starting with a present-tense verb such as add, fix, update, remove, improve, or implement. This applies to all repositories, including Java.
7
- 2. Summarize the key changes only, crafting a concise and clear commit message in the format "<type>: <description>".
8
- 3. Use one of the following standardized types: feat, fix, refactor, docs, test, or chore.
9
- 4. Ensure the commit message is a single line, fully lowercase, with no scope or body, and omit punctuation such as full stops at the end.
10
- 5. Limit the length of the commit message to 72 characters.
11
- 6. Avoid mentioning file names unless a file was renamed or is critical for understanding the changes.
12
- 7. Prioritize clarity and focus on the most impactful changes for the commit.
13
-
14
- You are expected to generate structured outputs that align with the provided guidelines and produce a message optimized for readability and accuracy. Strictly follow all constraints to ensure high-quality results.`},{role:"user",content:T`generate a commit message for the following staged changes:\n${e}`}]});return i.message.trim()}async function O(e){let t=await r();await I(),await y();let o=await h(),i=f();try{i.start("Generating commit message...");let a=await j(o);t.lastGeneratedMessage=a,await u(),i.success(`Commit Message: ${D.dim(D.bold(a))}`),e.copy&&(L.writeSync(a),i.success("Message copied to clipboard!")),e.apply&&(await g(a)||(i.fail("Failed to commit staged changes."),process.exit(1)),i.success("Staged changes committed!"))}catch{i.fail("Failed to generate commit message."),process.exit(1)}}import m from"picocolors";import W from"clipboardy";async function E(e){let t=await r();t.lastGeneratedMessage||(console.log(m.red("No previous commit message found.")),console.log(m.dim(`Generate a new message with ${m.cyan(m.bold("`noto`"))} command.`)),process.exit(1));let o=f(),i=t.lastGeneratedMessage;o.success(`Previous Commit Message: ${m.dim(m.bold(i))}`),e.copy&&(W.writeSync(i),o.success("Message copied to clipboard!")),e.apply&&(await y(),await h(),await g(i)||(o.fail("Failed to commit staged changes."),process.exit(1)),o.success("Staged changes committed!"))}var R="0.4.1";Q(V(process.argv)).scriptName("noto").usage("$0 [args]").command("config","setup you API key to enable noto.",()=>{},K).command("prev","access previous commit message",e=>{e.option("copy",{alias:"c",type:"boolean",description:"Copy the previous commit message to the clipboard."}),e.option("apply",{alias:"a",type:"boolean",description:"Commit the staged changes with the previous message."})},E).command("*","generate commit message",e=>{e.option("copy",{alias:"c",type:"boolean",description:"Copy the generated commit message to the clipboard."}),e.option("apply",{alias:"a",type:"boolean",description:"Commit the staged changes with the generated message."})},O).version("version",R).alias("-v","--version").alias("-h","--help").argv;
15
- //# sourceMappingURL=cli.js.map
package/dist/cli.js.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/cli.ts","../src/commands/config.ts","../src/storage.ts","../src/utils.ts","../src/git.ts","../src/commands/generate.ts","../src/ai.ts","../src/commands/prev.ts","../package.json"],"sourcesContent":["import yargs from \"yargs\";\n\nimport { hideBin } from \"yargs/helpers\";\n\nimport { config } from \"@/commands/config\";\nimport { generate } from \"@/commands/generate\";\nimport { prev } from \"@/commands/prev\";\n\nimport { version } from \"package\";\n\nyargs(hideBin(process.argv))\n .scriptName(\"noto\")\n .usage(\"$0 [args]\")\n .command(\"config\", \"setup you API key to enable noto.\", () => {}, config)\n .command(\n \"prev\",\n \"access previous commit message\",\n (args) => {\n args.option(\"copy\", {\n alias: \"c\",\n type: \"boolean\",\n description: \"Copy the previous commit message to the clipboard.\",\n });\n args.option(\"apply\", {\n alias: \"a\",\n type: \"boolean\",\n description: \"Commit the staged changes with the previous message.\",\n });\n },\n prev\n )\n .command(\n \"*\",\n \"generate commit message\",\n (args) => {\n args.option(\"copy\", {\n alias: \"c\",\n type: \"boolean\",\n description: \"Copy the generated commit message to the clipboard.\",\n });\n args.option(\"apply\", {\n alias: \"a\",\n type: \"boolean\",\n description: \"Commit the staged changes with the generated message.\",\n });\n },\n generate\n )\n .version(\"version\", version)\n .alias(\"-v\", \"--version\")\n .alias(\"-h\", \"--help\").argv;\n","import prompts from \"@posva/prompts\";\nimport pc from \"picocolors\";\n\nimport { load, dump } from \"@/storage\";\n\nexport async function config() {\n const storage = await load();\n\n if (storage.apiKey) {\n const response = await prompts(\n {\n type: \"confirm\",\n name: \"reset\",\n message: \"Do you want to reset your API key?\",\n },\n {\n onCancel: () => process.exit(0),\n }\n );\n\n if (!response.reset) {\n console.log(\n `Use ${pc.greenBright(\n pc.bold(\"`noto`\")\n )} to generate your commit message!`\n );\n process.exit(0);\n }\n }\n\n const response = await prompts({\n type: \"password\",\n name: \"apiKey\",\n message: \"Please enter your API key:\",\n validate: (value) => (value ? true : \"API key is required!\"),\n });\n if (response.apiKey) {\n storage.apiKey = response.apiKey;\n await dump();\n console.log(\"API key configured successfully!\");\n console.log(\n `Use ${pc.greenBright(pc.bold(\"`noto`\"))} to generate your commit message!`\n );\n }\n}\n","import { resolve } from \"node:path\";\nimport { existsSync, promises as fs } from \"node:fs\";\n\nimport { APP_DIR, writeFileSafe } from \"@/utils\";\n\nexport interface Storage {\n apiKey?: string;\n lastGeneratedMessage?: string;\n}\n\nlet storage: Storage = {};\n\nconst storagePath = resolve(APP_DIR, \"storage.json\");\n\nexport async function load(\n fn?: (storage: Storage) => Promise<boolean> | boolean\n): Promise<Storage> {\n try {\n if (!Object.keys(storage).length) {\n storage = existsSync(storagePath)\n ? JSON.parse((await fs.readFile(storagePath, \"utf-8\")) || \"{}\") || {}\n : {};\n }\n if (fn && (await fn(storage))) {\n await dump();\n }\n } catch (error) {\n console.error(\"error loading storage:\", error);\n storage = {};\n }\n\n return storage;\n}\n\nexport async function dump(): Promise<void> {\n try {\n if (storage) {\n await writeFileSafe(storagePath, JSON.stringify(storage, null, 2));\n }\n } catch (error) {\n console.error(\"error saving storage:\", error);\n }\n}\n","import os from \"node:os\";\nimport process from \"node:process\";\n\nimport which from \"which\";\nimport ora from \"ora\";\nimport pc from \"picocolors\";\n\nimport { dirname, join } from \"node:path\";\nimport { existsSync, promises as fs } from \"node:fs\";\n\nimport type { Buffer } from \"node:buffer\";\n\nimport type { Ora } from \"ora\";\n\nimport { load } from \"@/storage\";\nimport { getStagedDiff, isGitRepository } from \"@/git\";\n\nexport const APP_DIR = join(os.homedir(), \"snelusha-noto\");\n\nexport function remove<T>(arr: T[], v: T) {\n const index = arr.indexOf(v);\n if (index >= 0) arr.splice(index, 1);\n return arr;\n}\n\nexport function exclude<T>(arr: T[], ...v: T[]) {\n return arr.slice().filter((item) => !v.includes(item));\n}\n\nexport function cmdExists(cmd: string) {\n return which.sync(cmd, { nothrow: true }) !== null;\n}\n\ninterface TempFile {\n path: string;\n fd: fs.FileHandle;\n cleanup: () => void;\n}\n\nlet counter = 0;\n\nasync function openTemp(): Promise<TempFile | undefined> {\n if (!existsSync(APP_DIR)) await fs.mkdir(APP_DIR, { recursive: true });\n\n const competitivePath = join(APP_DIR, `.${process.pid}.${counter}`);\n counter += 1;\n\n return fs\n .open(competitivePath, \"wx\")\n .then((fd) => ({\n fd,\n path: competitivePath,\n cleanup() {\n fd.close().then(() => {\n if (existsSync(competitivePath)) fs.unlink(competitivePath);\n });\n },\n }))\n .catch((error: any) => {\n if (error && error.code === \"EEXIST\") return openTemp();\n else return undefined;\n });\n}\n\nexport async function writeFileSafe(\n path: string,\n data: string | Buffer = \"\"\n): Promise<boolean> {\n const temp = await openTemp();\n\n if (temp) {\n try {\n // @ts-expect-error eslint-disable-next-line\n await fs.writeFile(temp.path, data);\n const directory = dirname(path);\n if (!existsSync(directory))\n await fs.mkdir(directory, { recursive: true });\n await fs.rename(temp.path, path);\n return true;\n } catch {\n return false;\n } finally {\n temp.cleanup();\n }\n }\n\n return false;\n}\n\nexport function spinner() {\n let s: Ora | undefined;\n\n return {\n start(text: string) {\n s = ora(text);\n s.spinner = {\n interval: 150,\n frames: [\"✶\", \"✸\", \"✹\", \"✺\", \"✹\", \"✷\"],\n };\n s.start();\n },\n fail(text: string) {\n if (!s) {\n s = ora();\n }\n s.fail(text);\n s = undefined;\n },\n success(text: string) {\n if (!s) {\n s = ora();\n }\n s.succeed(text);\n s = undefined;\n },\n stop() {\n if (s) {\n s.stop();\n s = undefined;\n }\n },\n };\n}\n\nexport async function ensureApiKey() {\n const storage = await load();\n if (!storage.apiKey) {\n console.log(\n `Please run ${pc.cyan(pc.bold(\"`noto config`\"))} to set your API key.`\n );\n process.exit(1);\n }\n}\n\nexport async function ensureGitRepository() {\n if (await isGitRepository()) return;\n console.log(pc.red(\"Oops! No Git repository found in the current directory.\"));\n console.log(\n pc.dim(`You can initialize one by running ${pc.cyan(pc.bold(\"`git init`\"))}`)\n );\n process.exit(1);\n}\n\nexport async function ensureStagedChanges() {\n const diff = await getStagedDiff();\n if (diff) return diff;\n console.log(pc.red(\"Oops! No staged changes found to commit.\"));\n console.log(\n pc.dim(\n `Stage changes with ${pc.cyan(pc.bold(\"`git add <file>`\"))} or ${pc.cyan(\n pc.bold(\"`git add .`\")\n )} for stage all files.`\n )\n );\n process.exit(1);\n}\n","import simpleGit from \"simple-git\";\n\nexport async function isGitRepository() {\n try {\n return await simpleGit().checkIsRepo();\n } catch {\n return false;\n }\n}\n\nexport async function getStagedDiff(): Promise<string | null> {\n try {\n const stagedFiles = await simpleGit().diff([\"--cached\", \"--name-only\"]);\n\n const files = stagedFiles.split(\"\\n\").filter(Boolean);\n\n const excludedPatterns = [\n \"*.lock\",\n \"*.lockb\",\n \"*.yaml.lock\",\n \"*.hcl.lock\",\n \"*.resolved\",\n ];\n const filteredFiles = files.filter(\n (file) =>\n !excludedPatterns.some((pattern) => {\n const regex = new RegExp(pattern.replace(\"*\", \".*\"));\n return regex.test(file);\n })\n );\n\n if (filteredFiles.length === 0) return null;\n\n return await simpleGit().diff([\"--cached\", \"--\", ...filteredFiles]);\n } catch {\n return null;\n }\n}\n\nexport async function commit(message: string): Promise<boolean> {\n try {\n const result = await simpleGit().commit(message);\n return result.summary.changes > 0;\n } catch {\n return false;\n }\n}\n\nexport async function getCommitCount() {\n try {\n const count = await simpleGit().raw([\"rev-list\", \"--count\", \"HEAD\"]);\n return Number(count.trim());\n } catch (error) {\n if (\n /(ambiguous argument.*HEAD|unknown revision or path.*HEAD)/i.test(\n (error as Error).message\n )\n ) {\n return 0;\n }\n return null;\n }\n}\n\nexport async function isFirstCommit() {\n try {\n const count = await getCommitCount();\n return count === 0;\n } catch {\n return false;\n }\n}\n","import pc from \"picocolors\";\nimport clipboardy from \"clipboardy\";\n\nimport { load, dump } from \"@/storage\";\nimport { commit } from \"@/git\";\nimport { generateCommitMessage } from \"@/ai\";\nimport {\n ensureApiKey,\n ensureGitRepository,\n ensureStagedChanges,\n spinner,\n} from \"@/utils\";\n\nimport type { ArgumentsCamelCase } from \"yargs\";\n\nexport async function generate(args: ArgumentsCamelCase) {\n const storage = await load();\n\n await ensureApiKey();\n await ensureGitRepository();\n\n const diff = await ensureStagedChanges();\n\n const spin = spinner();\n\n try {\n spin.start(\"Generating commit message...\");\n\n const message = await generateCommitMessage(diff);\n\n storage.lastGeneratedMessage = message;\n await dump();\n\n spin.success(`Commit Message: ${pc.dim(pc.bold(message))}`);\n\n if (args.copy) {\n clipboardy.writeSync(message);\n spin.success(\"Message copied to clipboard!\");\n }\n\n if (args.apply) {\n if (!(await commit(message))) {\n spin.fail(\"Failed to commit staged changes.\");\n process.exit(1);\n }\n spin.success(\"Staged changes committed!\");\n }\n } catch (_) {\n spin.fail(\"Failed to generate commit message.\");\n process.exit(1);\n }\n}\n","import { generateObject } from \"ai\";\nimport { createGoogleGenerativeAI } from \"@ai-sdk/google\";\n\nimport dedent from \"dedent\";\n\nimport { z } from \"zod\";\n\nimport { load } from \"@/storage\";\nimport { isFirstCommit } from \"@/git\";\n\nexport async function generateCommitMessage(diff: string): Promise<string> {\n if (await isFirstCommit()) return \"chore: init repo\";\n\n const storage = await load();\n\n const google = createGoogleGenerativeAI({\n apiKey: storage.apiKey,\n });\n\n const { object } = await generateObject({\n model: google(\"gemini-2.0-pro-exp-02-05\", {\n structuredOutputs: false,\n }),\n schema: z.object({\n message: z.string(),\n }),\n messages: [\n {\n role: \"system\",\n content: dedent`\n You are a state-of-the-art AI model tasked with generating a precise Git commit message based on staged changes.\n Adhere strictly to the following instructions, ranked by priority:\n \n 1. Write the commit message in present tense, starting with a present-tense verb such as add, fix, update, remove, improve, or implement. This applies to all repositories, including Java.\n 2. Summarize the key changes only, crafting a concise and clear commit message in the format \"<type>: <description>\".\n 3. Use one of the following standardized types: feat, fix, refactor, docs, test, or chore.\n 4. Ensure the commit message is a single line, fully lowercase, with no scope or body, and omit punctuation such as full stops at the end.\n 5. Limit the length of the commit message to 72 characters.\n 6. Avoid mentioning file names unless a file was renamed or is critical for understanding the changes.\n 7. Prioritize clarity and focus on the most impactful changes for the commit.\n \n You are expected to generate structured outputs that align with the provided guidelines and produce a message optimized for readability and accuracy. Strictly follow all constraints to ensure high-quality results.`,\n },\n {\n role: \"user\",\n content: dedent`generate a commit message for the following staged changes:\\n${diff}`,\n },\n ],\n });\n\n return object.message.trim();\n}\n","import pc from \"picocolors\";\nimport clipboardy from \"clipboardy\";\n\nimport { load } from \"@/storage\";\nimport { commit } from \"@/git\";\n\nimport {\n ensureApiKey,\n ensureGitRepository,\n ensureStagedChanges,\n spinner,\n} from \"@/utils\";\n\nimport type { ArgumentsCamelCase } from \"yargs\";\n\nexport async function prev(args: ArgumentsCamelCase) {\n const storage = await load();\n\n if (!storage.lastGeneratedMessage) {\n console.log(pc.red(\"No previous commit message found.\"));\n console.log(\n pc.dim(`Generate a new message with ${pc.cyan(pc.bold(\"`noto`\"))} command.`)\n );\n process.exit(1);\n }\n const spin = spinner();\n\n const message = storage.lastGeneratedMessage;\n\n spin.success(`Previous Commit Message: ${pc.dim(pc.bold(message))}`);\n\n if (args.copy) {\n clipboardy.writeSync(message);\n spin.success(\"Message copied to clipboard!\");\n }\n\n if (args.apply) {\n await ensureGitRepository();\n\n await ensureStagedChanges();\n\n if (!(await commit(message))) {\n spin.fail(\"Failed to commit staged changes.\");\n process.exit(1);\n }\n\n spin.success(\"Staged changes committed!\");\n }\n}\n","{\n \"name\": \"@snelusha/noto\",\n \"type\": \"module\",\n \"version\": \"0.4.1\",\n \"description\": \"generate clean commit messages in a snap! ✨\",\n \"license\": \"MIT\",\n \"author\": {\n \"name\": \"Sithija Nelusha Silva\",\n \"email\": \"hello@snelusha.dev\",\n \"url\": \"https://snelusha.dev\"\n },\n \"homepage\": \"https://github.com/snelusha/noto\",\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git+https://github.com/snelusha/noto.git\"\n },\n \"bugs\": {\n \"url\": \"https://github.com/snelusha/noto/issues\"\n },\n \"main\": \"dist/cli.js\",\n \"module\": \"dist/cli.js\",\n \"types\": \"dist/cli.d.ts\",\n \"bin\": {\n \"noto\": \"bin/noto.mjs\"\n },\n \"files\": [\n \"dist\"\n ],\n \"scripts\": {\n \"build\": \"tsup\",\n \"publish\": \"npm publish --public\"\n },\n \"devDependencies\": {\n \"@types/bun\": \"latest\",\n \"@types/which\": \"^3.0.4\",\n \"@types/yargs\": \"^17.0.33\",\n \"esbuild-plugin-alias\": \"^0.2.1\",\n \"tsup\": \"^8.3.6\"\n },\n \"peerDependencies\": {\n \"typescript\": \"^5.0.0\"\n },\n \"dependencies\": {\n \"@ai-sdk/google\": \"^1.1.10\",\n \"@posva/prompts\": \"^2.4.4\",\n \"ai\": \"^4.1.21\",\n \"clipboardy\": \"^4.0.0\",\n \"dedent\": \"^1.5.3\",\n \"ora\": \"^8.2.0\",\n \"picocolors\": \"^1.1.1\",\n \"simple-git\": \"^3.27.0\",\n \"which\": \"^5.0.0\",\n \"yargs\": \"^17.7.2\",\n \"zod\": \"^3.24.1\"\n }\n}\n"],"mappings":"AAAA,OAAOA,MAAW,QAElB,OAAS,WAAAC,MAAe,gBCFxB,OAAOC,MAAa,iBACpB,OAAOC,MAAQ,aCDf,OAAS,WAAAC,MAAe,YACxB,OAAS,cAAAC,EAAY,YAAYC,MAAU,UCD3C,OAAOC,MAAQ,UACf,OAAOC,MAAa,eAEpB,OAAOC,OAAW,QAClB,OAAOC,MAAS,MAChB,OAAOC,MAAQ,aAEf,OAAS,WAAAC,EAAS,QAAAC,MAAY,YAC9B,OAAS,cAAAC,EAAY,YAAYC,MAAU,UCR3C,OAAOC,MAAe,aAEtB,eAAsBC,GAAkB,CACtC,GAAI,CACF,OAAO,MAAMD,EAAU,EAAE,YAAY,CACvC,MAAQ,CACN,MAAO,EACT,CACF,CAEA,eAAsBE,GAAwC,CAC5D,GAAI,CAGF,IAAMC,GAFc,MAAMH,EAAU,EAAE,KAAK,CAAC,WAAY,aAAa,CAAC,GAE5C,MAAM;AAAA,CAAI,EAAE,OAAO,OAAO,EAE9CI,EAAmB,CACvB,SACA,UACA,cACA,aACA,YACF,EACMC,EAAgBF,EAAM,OACzBG,GACC,CAACF,EAAiB,KAAMG,GACR,IAAI,OAAOA,EAAQ,QAAQ,IAAK,IAAI,CAAC,EACtC,KAAKD,CAAI,CACvB,CACL,EAEA,OAAID,EAAc,SAAW,EAAU,KAEhC,MAAML,EAAU,EAAE,KAAK,CAAC,WAAY,KAAM,GAAGK,CAAa,CAAC,CACpE,MAAQ,CACN,OAAO,IACT,CACF,CAEA,eAAsBG,EAAOC,EAAmC,CAC9D,GAAI,CAEF,OADe,MAAMT,EAAU,EAAE,OAAOS,CAAO,GACjC,QAAQ,QAAU,CAClC,MAAQ,CACN,MAAO,EACT,CACF,CAEA,eAAsBC,GAAiB,CACrC,GAAI,CACF,IAAMC,EAAQ,MAAMX,EAAU,EAAE,IAAI,CAAC,WAAY,UAAW,MAAM,CAAC,EACnE,OAAO,OAAOW,EAAM,KAAK,CAAC,CAC5B,OAASC,EAAO,CACd,MACE,6DAA6D,KAC1DA,EAAgB,OACnB,EAEO,EAEF,IACT,CACF,CAEA,eAAsBC,GAAgB,CACpC,GAAI,CAEF,OADc,MAAMH,EAAe,IAClB,CACnB,MAAQ,CACN,MAAO,EACT,CACF,CDtDO,IAAMI,EAAUC,EAAKC,EAAG,QAAQ,EAAG,eAAe,EAsBzD,IAAIC,EAAU,EAEd,eAAeC,GAA0C,CAClDC,EAAWC,CAAO,GAAG,MAAMC,EAAG,MAAMD,EAAS,CAAE,UAAW,EAAK,CAAC,EAErE,IAAME,EAAkBC,EAAKH,EAAS,IAAII,EAAQ,GAAG,IAAIP,CAAO,EAAE,EAClE,OAAAA,GAAW,EAEJI,EACJ,KAAKC,EAAiB,IAAI,EAC1B,KAAMG,IAAQ,CACb,GAAAA,EACA,KAAMH,EACN,SAAU,CACRG,EAAG,MAAM,EAAE,KAAK,IAAM,CAChBN,EAAWG,CAAe,GAAGD,EAAG,OAAOC,CAAe,CAC5D,CAAC,CACH,CACF,EAAE,EACD,MAAOI,GAAe,CACrB,GAAIA,GAASA,EAAM,OAAS,SAAU,OAAOR,EAAS,CAExD,CAAC,CACL,CAEA,eAAsBS,EACpBC,EACAC,EAAwB,GACN,CAClB,IAAMC,EAAO,MAAMZ,EAAS,EAE5B,GAAIY,EACF,GAAI,CAEF,MAAMT,EAAG,UAAUS,EAAK,KAAMD,CAAI,EAClC,IAAME,EAAYC,EAAQJ,CAAI,EAC9B,OAAKT,EAAWY,CAAS,GACvB,MAAMV,EAAG,MAAMU,EAAW,CAAE,UAAW,EAAK,CAAC,EAC/C,MAAMV,EAAG,OAAOS,EAAK,KAAMF,CAAI,EACxB,EACT,MAAQ,CACN,MAAO,EACT,QAAE,CACAE,EAAK,QAAQ,CACf,CAGF,MAAO,EACT,CAEO,SAASG,GAAU,CACxB,IAAIC,EAEJ,MAAO,CACL,MAAMC,EAAc,CAClBD,EAAIE,EAAID,CAAI,EACZD,EAAE,QAAU,CACV,SAAU,IACV,OAAQ,CAAC,SAAK,SAAK,SAAK,SAAK,SAAK,QAAG,CACvC,EACAA,EAAE,MAAM,CACV,EACA,KAAKC,EAAc,CACZD,IACHA,EAAIE,EAAI,GAEVF,EAAE,KAAKC,CAAI,EACXD,EAAI,MACN,EACA,QAAQC,EAAc,CACfD,IACHA,EAAIE,EAAI,GAEVF,EAAE,QAAQC,CAAI,EACdD,EAAI,MACN,EACA,MAAO,CACDA,IACFA,EAAE,KAAK,EACPA,EAAI,OAER,CACF,CACF,CAEA,eAAsBG,GAAe,EACnB,MAAMC,EAAK,GACd,SACX,QAAQ,IACN,cAAcC,EAAG,KAAKA,EAAG,KAAK,eAAe,CAAC,CAAC,uBACjD,EACAf,EAAQ,KAAK,CAAC,EAElB,CAEA,eAAsBgB,GAAsB,CACtC,MAAMC,EAAgB,IAC1B,QAAQ,IAAIF,EAAG,IAAI,yDAAyD,CAAC,EAC7E,QAAQ,IACNA,EAAG,IAAI,qCAAqCA,EAAG,KAAKA,EAAG,KAAK,YAAY,CAAC,CAAC,EAAE,CAC9E,EACAf,EAAQ,KAAK,CAAC,EAChB,CAEA,eAAsBkB,GAAsB,CAC1C,IAAMC,EAAO,MAAMC,EAAc,EACjC,GAAID,EAAM,OAAOA,EACjB,QAAQ,IAAIJ,EAAG,IAAI,0CAA0C,CAAC,EAC9D,QAAQ,IACNA,EAAG,IACD,sBAAsBA,EAAG,KAAKA,EAAG,KAAK,kBAAkB,CAAC,CAAC,OAAOA,EAAG,KAClEA,EAAG,KAAK,aAAa,CACvB,CAAC,uBACH,CACF,EACAf,EAAQ,KAAK,CAAC,CAChB,CDjJA,IAAIqB,EAAmB,CAAC,EAElBC,EAAcC,EAAQC,EAAS,cAAc,EAEnD,eAAsBC,EACpBC,EACkB,CAClB,GAAI,CACG,OAAO,KAAKL,CAAO,EAAE,SACxBA,EAAUM,EAAWL,CAAW,EAC5B,KAAK,MAAO,MAAMM,EAAG,SAASN,EAAa,OAAO,GAAM,IAAI,GAAK,CAAC,EAClE,CAAC,GAEHI,GAAO,MAAMA,EAAGL,CAAO,GACzB,MAAMQ,EAAK,CAEf,OAASC,EAAO,CACd,QAAQ,MAAM,yBAA0BA,CAAK,EAC7CT,EAAU,CAAC,CACb,CAEA,OAAOA,CACT,CAEA,eAAsBQ,GAAsB,CAC1C,GAAI,CACER,GACF,MAAMU,EAAcT,EAAa,KAAK,UAAUD,EAAS,KAAM,CAAC,CAAC,CAErE,OAASS,EAAO,CACd,QAAQ,MAAM,wBAAyBA,CAAK,CAC9C,CACF,CDrCA,eAAsBE,GAAS,CAC7B,IAAMC,EAAU,MAAMC,EAAK,EAEvBD,EAAQ,UACO,MAAME,EACrB,CACE,KAAM,UACN,KAAM,QACN,QAAS,oCACX,EACA,CACE,SAAU,IAAM,QAAQ,KAAK,CAAC,CAChC,CACF,GAEc,QACZ,QAAQ,IACN,OAAOC,EAAG,YACRA,EAAG,KAAK,QAAQ,CAClB,CAAC,mCACH,EACA,QAAQ,KAAK,CAAC,IAIlB,IAAMC,EAAW,MAAMF,EAAQ,CAC7B,KAAM,WACN,KAAM,SACN,QAAS,6BACT,SAAWG,GAAWA,EAAQ,GAAO,sBACvC,CAAC,EACGD,EAAS,SACXJ,EAAQ,OAASI,EAAS,OAC1B,MAAME,EAAK,EACX,QAAQ,IAAI,kCAAkC,EAC9C,QAAQ,IACN,OAAOH,EAAG,YAAYA,EAAG,KAAK,QAAQ,CAAC,CAAC,mCAC1C,EAEJ,CI5CA,OAAOI,MAAQ,aACf,OAAOC,MAAgB,aCDvB,OAAS,kBAAAC,MAAsB,KAC/B,OAAS,4BAAAC,MAAgC,iBAEzC,OAAOC,MAAY,SAEnB,OAAS,KAAAC,MAAS,MAKlB,eAAsBC,EAAsBC,EAA+B,CACzE,GAAI,MAAMC,EAAc,EAAG,MAAO,mBAElC,IAAMC,EAAU,MAAMC,EAAK,EAErBC,EAASC,EAAyB,CACtC,OAAQH,EAAQ,MAClB,CAAC,EAEK,CAAE,OAAAI,CAAO,EAAI,MAAMC,EAAe,CACtC,MAAOH,EAAO,2BAA4B,CACxC,kBAAmB,EACrB,CAAC,EACD,OAAQI,EAAE,OAAO,CACf,QAASA,EAAE,OAAO,CACpB,CAAC,EACD,SAAU,CACR,CACE,KAAM,SACN,QAASC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,8NAaX,EACA,CACE,KAAM,OACN,QAASA,iEAAsET,CAAI,EACrF,CACF,CACF,CAAC,EAED,OAAOM,EAAO,QAAQ,KAAK,CAC7B,CDpCA,eAAsBI,EAASC,EAA0B,CACvD,IAAMC,EAAU,MAAMC,EAAK,EAE3B,MAAMC,EAAa,EACnB,MAAMC,EAAoB,EAE1B,IAAMC,EAAO,MAAMC,EAAoB,EAEjCC,EAAOC,EAAQ,EAErB,GAAI,CACFD,EAAK,MAAM,8BAA8B,EAEzC,IAAME,EAAU,MAAMC,EAAsBL,CAAI,EAEhDJ,EAAQ,qBAAuBQ,EAC/B,MAAME,EAAK,EAEXJ,EAAK,QAAQ,mBAAmBK,EAAG,IAAIA,EAAG,KAAKH,CAAO,CAAC,CAAC,EAAE,EAEtDT,EAAK,OACPa,EAAW,UAAUJ,CAAO,EAC5BF,EAAK,QAAQ,8BAA8B,GAGzCP,EAAK,QACD,MAAMc,EAAOL,CAAO,IACxBF,EAAK,KAAK,kCAAkC,EAC5C,QAAQ,KAAK,CAAC,GAEhBA,EAAK,QAAQ,2BAA2B,EAE5C,MAAY,CACVA,EAAK,KAAK,oCAAoC,EAC9C,QAAQ,KAAK,CAAC,CAChB,CACF,CEnDA,OAAOQ,MAAQ,aACf,OAAOC,MAAgB,aAcvB,eAAsBC,EAAKC,EAA0B,CACnD,IAAMC,EAAU,MAAMC,EAAK,EAEtBD,EAAQ,uBACX,QAAQ,IAAIE,EAAG,IAAI,mCAAmC,CAAC,EACvD,QAAQ,IACNA,EAAG,IAAI,+BAA+BA,EAAG,KAAKA,EAAG,KAAK,QAAQ,CAAC,CAAC,WAAW,CAC7E,EACA,QAAQ,KAAK,CAAC,GAEhB,IAAMC,EAAOC,EAAQ,EAEfC,EAAUL,EAAQ,qBAExBG,EAAK,QAAQ,4BAA4BD,EAAG,IAAIA,EAAG,KAAKG,CAAO,CAAC,CAAC,EAAE,EAE/DN,EAAK,OACPO,EAAW,UAAUD,CAAO,EAC5BF,EAAK,QAAQ,8BAA8B,GAGzCJ,EAAK,QACP,MAAMQ,EAAoB,EAE1B,MAAMC,EAAoB,EAEpB,MAAMC,EAAOJ,CAAO,IACxBF,EAAK,KAAK,kCAAkC,EAC5C,QAAQ,KAAK,CAAC,GAGhBA,EAAK,QAAQ,2BAA2B,EAE5C,CC7CE,IAAAO,EAAW,QRObC,EAAMC,EAAQ,QAAQ,IAAI,CAAC,EACxB,WAAW,MAAM,EACjB,MAAM,WAAW,EACjB,QAAQ,SAAU,oCAAqC,IAAM,CAAC,EAAGC,CAAM,EACvE,QACC,OACA,iCACCC,GAAS,CACRA,EAAK,OAAO,OAAQ,CAClB,MAAO,IACP,KAAM,UACN,YAAa,oDACf,CAAC,EACDA,EAAK,OAAO,QAAS,CACnB,MAAO,IACP,KAAM,UACN,YAAa,sDACf,CAAC,CACH,EACAC,CACF,EACC,QACC,IACA,0BACCD,GAAS,CACRA,EAAK,OAAO,OAAQ,CAClB,MAAO,IACP,KAAM,UACN,YAAa,qDACf,CAAC,EACDA,EAAK,OAAO,QAAS,CACnB,MAAO,IACP,KAAM,UACN,YAAa,uDACf,CAAC,CACH,EACAE,CACF,EACC,QAAQ,UAAWC,CAAO,EAC1B,MAAM,KAAM,WAAW,EACvB,MAAM,KAAM,QAAQ,EAAE","names":["yargs","hideBin","prompts","pc","resolve","existsSync","fs","os","process","which","ora","pc","dirname","join","existsSync","fs","simpleGit","isGitRepository","getStagedDiff","files","excludedPatterns","filteredFiles","file","pattern","commit","message","getCommitCount","count","error","isFirstCommit","APP_DIR","join","os","counter","openTemp","existsSync","APP_DIR","fs","competitivePath","join","process","fd","error","writeFileSafe","path","data","temp","directory","dirname","spinner","s","text","ora","ensureApiKey","load","pc","ensureGitRepository","isGitRepository","ensureStagedChanges","diff","getStagedDiff","storage","storagePath","resolve","APP_DIR","load","fn","existsSync","fs","dump","error","writeFileSafe","config","storage","load","prompts","pc","response","value","dump","pc","clipboardy","generateObject","createGoogleGenerativeAI","dedent","z","generateCommitMessage","diff","isFirstCommit","storage","load","google","createGoogleGenerativeAI","object","generateObject","z","dedent","generate","args","storage","load","ensureApiKey","ensureGitRepository","diff","ensureStagedChanges","spin","spinner","message","generateCommitMessage","dump","pc","clipboardy","commit","pc","clipboardy","prev","args","storage","load","pc","spin","spinner","message","clipboardy","ensureGitRepository","ensureStagedChanges","commit","version","yargs","hideBin","config","args","prev","generate","version"]}
File without changes