@snelusha/noto 1.0.0-beta.2 → 1.0.0-beta.4

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 (2) hide show
  1. package/dist/index.js +222 -66
  2. package/package.json +2 -1
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  // src/index.ts
2
- import * as p5 from "@clack/prompts";
3
- import color5 from "picocolors";
2
+ import * as p6 from "@clack/prompts";
3
+ import color6 from "picocolors";
4
4
 
5
5
  // src/utils/parser.ts
6
6
  import arg from "arg";
@@ -15,6 +15,7 @@ var parse = (schema, raw) => {
15
15
  // src/commands/noto.ts
16
16
  import * as p3 from "@clack/prompts";
17
17
  import color3 from "picocolors";
18
+ import clipboard from "clipboardy";
18
19
 
19
20
  // src/middleware/auth.ts
20
21
  import * as p from "@clack/prompts";
@@ -46,7 +47,8 @@ var StorageSchema = z2.object({
46
47
  llm: z2.object({
47
48
  apiKey: z2.string().optional(),
48
49
  model: AvailableModelsSchema.optional()
49
- }).optional()
50
+ }).optional(),
51
+ lastGeneratedMessage: z2.string().optional()
50
52
  });
51
53
  var StorageManager = class {
52
54
  static storagePath = resolve(
@@ -93,19 +95,25 @@ var StorageManager = class {
93
95
  }
94
96
  };
95
97
 
98
+ // src/utils/process.ts
99
+ var exit = async (code) => {
100
+ await new Promise((resolve2) => setTimeout(resolve2, 1));
101
+ console.log();
102
+ process.exit(code);
103
+ };
104
+
96
105
  // src/middleware/auth.ts
97
- var withAuth = (fn) => {
98
- return async (options) => {
106
+ var withAuth = (fn, options = {}) => {
107
+ return async (opts) => {
99
108
  const storage = await StorageManager.get();
100
- if (!storage.llm?.apiKey) {
109
+ if (!storage.llm?.apiKey && !options.silent) {
101
110
  p.log.error(
102
111
  dedent`${color.red("noto api key is missing.")}
103
112
  ${color.dim(`run ${color.cyan("`noto config key`")} to set it up.`)}`
104
113
  );
105
- console.log();
106
- process.exit(1);
114
+ return await exit(1);
107
115
  }
108
- return fn(options);
116
+ return fn(opts);
109
117
  };
110
118
  };
111
119
 
@@ -127,33 +135,50 @@ var getStagedDiff = async () => {
127
135
  return null;
128
136
  }
129
137
  };
138
+ var commit = async (message) => {
139
+ try {
140
+ const {
141
+ summary: { changes }
142
+ } = await git.commit(message);
143
+ return Boolean(changes);
144
+ } catch {
145
+ return false;
146
+ }
147
+ };
130
148
 
131
149
  // src/middleware/git.ts
132
- var withRepository = (fn) => {
133
- return async (options) => {
150
+ var withRepository = (fn, options = {}) => {
151
+ return async (opts) => {
134
152
  const isRepo = await isGitRepository();
135
- if (!isRepo) {
153
+ if (!isRepo && !options.silent) {
136
154
  p2.log.error(
137
155
  dedent2`${color2.red("no git repository found in cwd.")}
138
156
  ${color2.dim(`run ${color2.cyan("`git init`")} to initialize a new repository.`)}`
139
157
  );
140
- console.log();
141
- process.exit(1);
158
+ return await exit(1);
142
159
  }
160
+ opts.isRepo = isRepo;
143
161
  const diff = await getStagedDiff();
144
- if (!diff) {
162
+ if (!diff && !options.silent) {
145
163
  p2.log.error(
146
164
  dedent2`${color2.red("no staged changes found.")}
147
165
  ${color2.dim(`run ${color2.cyan("`git add <file>`")} or ${color2.cyan("`git add .`")} to stage changes.`)}`
148
166
  );
149
- console.log();
150
- process.exit(1);
167
+ return await exit(1);
151
168
  }
152
- options.diff = diff;
153
- return fn(options);
169
+ opts.diff = diff;
170
+ return fn(opts);
154
171
  };
155
172
  };
156
173
 
174
+ // src/ai/index.ts
175
+ import { generateObject } from "ai";
176
+ import z3 from "zod";
177
+ import dedent3 from "dedent";
178
+
179
+ // src/ai/models.ts
180
+ import { createGoogleGenerativeAI } from "@ai-sdk/google";
181
+
157
182
  // src/errors.ts
158
183
  var NotoError = class _NotoError extends Error {
159
184
  code;
@@ -164,13 +189,7 @@ var NotoError = class _NotoError extends Error {
164
189
  }
165
190
  };
166
191
 
167
- // src/ai/index.ts
168
- import { generateObject } from "ai";
169
- import z3 from "zod";
170
- import dedent3 from "dedent";
171
-
172
192
  // src/ai/models.ts
173
- import { createGoogleGenerativeAI } from "@ai-sdk/google";
174
193
  var google = createGoogleGenerativeAI({
175
194
  apiKey: (await StorageManager.get()).llm?.apiKey ?? "api-key"
176
195
  });
@@ -240,53 +259,193 @@ var command = {
240
259
  name: "noto",
241
260
  description: "generate commit message",
242
261
  usage: "noto [options]",
243
- options: [],
262
+ options: [
263
+ {
264
+ type: Boolean,
265
+ flag: "--copy",
266
+ alias: "-c",
267
+ description: "copy the generated commit message to clipboard"
268
+ },
269
+ {
270
+ type: Boolean,
271
+ flag: "--apply",
272
+ alias: "-a",
273
+ description: "commit the generated message directly"
274
+ },
275
+ {
276
+ type: Boolean,
277
+ flag: "--edit",
278
+ alias: "-e",
279
+ description: "edit the generated commit message"
280
+ }
281
+ ],
244
282
  execute: withAuth(
245
283
  withRepository(async (options) => {
246
284
  const spin = p3.spinner();
247
285
  try {
248
286
  const { diff } = options;
287
+ const isEditMode = options["--edit"];
249
288
  spin.start("generating commit message");
250
- const message = await generateCommitMessage(diff);
251
- spin.stop(color3.green(message));
289
+ let message = await generateCommitMessage(diff);
290
+ spin.stop(isEditMode ? color3.white(message) : color3.green(message));
291
+ if (isEditMode) {
292
+ const editedMessage = await p3.text({
293
+ message: "edit the generated commit message",
294
+ initialValue: message,
295
+ placeholder: message
296
+ });
297
+ if (p3.isCancel(editedMessage)) {
298
+ p3.log.error(color3.red("nothing changed!"));
299
+ return await exit(1);
300
+ }
301
+ message = editedMessage;
302
+ p3.log.step(color3.green(message));
303
+ }
304
+ await StorageManager.update((current) => ({
305
+ ...current,
306
+ lastGeneratedMessage: message
307
+ }));
308
+ if (options["--copy"]) {
309
+ clipboard.writeSync(message);
310
+ p3.log.step(color3.dim("copied commit message to clipboard"));
311
+ }
312
+ if (options["--apply"]) {
313
+ const success = await commit(message);
314
+ if (success) {
315
+ p3.log.step(color3.dim("commit successful"));
316
+ } else {
317
+ p3.log.error(color3.red("failed to commit changes"));
318
+ }
319
+ }
320
+ process.stdout.write("\n");
252
321
  } catch {
253
322
  spin.stop(color3.red("failed to generate commit message"), 1);
254
- console.log();
255
- process.exit(1);
323
+ return await exit(1);
256
324
  }
257
- console.log();
258
325
  })
259
326
  )
260
327
  };
261
328
  var noto_default = command;
262
329
 
263
- // src/commands/config.ts
330
+ // src/commands/prev.ts
264
331
  import * as p4 from "@clack/prompts";
265
332
  import color4 from "picocolors";
333
+ import dedent4 from "dedent";
334
+ import clipboard2 from "clipboardy";
335
+ var command2 = {
336
+ name: "prev",
337
+ description: "access the last generated commit message",
338
+ usage: "noto prev [options]",
339
+ options: [
340
+ {
341
+ type: Boolean,
342
+ flag: "--copy",
343
+ alias: "-c",
344
+ description: "copy the last generated commit message to clipboard"
345
+ },
346
+ {
347
+ type: Boolean,
348
+ flag: "--apply",
349
+ alias: "-a",
350
+ description: "commit the last generated message directly"
351
+ },
352
+ {
353
+ type: Boolean,
354
+ flag: "--edit",
355
+ alias: "-e",
356
+ description: "edit the last generated commit message"
357
+ }
358
+ ],
359
+ execute: withAuth(
360
+ withRepository(
361
+ async (options) => {
362
+ let lastGeneratedMessage = (await StorageManager.get()).lastGeneratedMessage;
363
+ if (!lastGeneratedMessage) {
364
+ p4.log.error(color4.red("no previous commit message found"));
365
+ return await exit(1);
366
+ }
367
+ const isEditMode = options["--edit"];
368
+ p4.log.step(
369
+ isEditMode ? color4.white(lastGeneratedMessage) : color4.green(lastGeneratedMessage)
370
+ );
371
+ if (options["--edit"]) {
372
+ const editedMessage = await p4.text({
373
+ message: "edit the last generated commit message",
374
+ initialValue: lastGeneratedMessage,
375
+ placeholder: lastGeneratedMessage
376
+ });
377
+ if (p4.isCancel(editedMessage)) {
378
+ p4.log.error(color4.red("nothing changed!"));
379
+ return await exit(1);
380
+ }
381
+ lastGeneratedMessage = editedMessage;
382
+ await StorageManager.update((current) => ({
383
+ ...current,
384
+ lastGeneratedMessage: editedMessage
385
+ }));
386
+ p4.log.step(color4.green(lastGeneratedMessage));
387
+ }
388
+ if (options["--copy"]) {
389
+ clipboard2.writeSync(lastGeneratedMessage);
390
+ p4.log.step(
391
+ color4.dim("copied last generated commit message to clipboard")
392
+ );
393
+ }
394
+ if (options["--apply"]) {
395
+ if (!options.isRepo) {
396
+ p4.log.error(
397
+ dedent4`${color4.red("no git repository found in cwd.")}
398
+ ${color4.dim(`run ${color4.cyan("`git init`")} to initialize a new repository.`)}`
399
+ );
400
+ return await exit(1);
401
+ }
402
+ if (!options.diff) {
403
+ p4.log.error(
404
+ dedent4`${color4.red("no staged changes found.")}
405
+ ${color4.dim(`run ${color4.cyan("`git add <file>`")} or ${color4.cyan("`git add .`")} to stage changes.`)}`
406
+ );
407
+ return await exit(1);
408
+ }
409
+ const success = await commit(lastGeneratedMessage);
410
+ if (success) {
411
+ p4.log.step(color4.dim("commit successful"));
412
+ } else {
413
+ p4.log.error(color4.red("failed to commit changes"));
414
+ }
415
+ }
416
+ console.log();
417
+ },
418
+ { silent: true }
419
+ )
420
+ )
421
+ };
422
+ var prev_default = command2;
423
+
424
+ // src/commands/config.ts
425
+ import * as p5 from "@clack/prompts";
426
+ import color5 from "picocolors";
266
427
  var key = {
267
428
  name: "key",
268
429
  description: "configure api key",
269
430
  usage: "noto config key [options]",
270
431
  execute: async (options) => {
271
432
  if ((await StorageManager.get()).llm?.apiKey) {
272
- const confirm2 = await p4.confirm({
433
+ const confirm2 = await p5.confirm({
273
434
  message: "noto api key already configured, do you want to update it?"
274
435
  });
275
- if (p4.isCancel(confirm2) || !confirm2) {
276
- p4.log.error(color4.red("nothing changed!"));
277
- console.log();
278
- process.exit(1);
436
+ if (p5.isCancel(confirm2) || !confirm2) {
437
+ p5.log.error(color5.red("nothing changed!"));
438
+ return await exit(1);
279
439
  }
280
440
  }
281
441
  let apiKey = options._[0];
282
442
  if (!apiKey) {
283
- const result = await p4.text({
443
+ const result = await p5.text({
284
444
  message: "enter your noto api key"
285
445
  });
286
- if (p4.isCancel(result)) {
287
- p4.log.error(color4.red("nothing changed!"));
288
- console.log();
289
- process.exit(1);
446
+ if (p5.isCancel(result)) {
447
+ p5.log.error(color5.red("nothing changed!"));
448
+ return await exit(1);
290
449
  }
291
450
  apiKey = result;
292
451
  }
@@ -297,7 +456,7 @@ var key = {
297
456
  apiKey
298
457
  }
299
458
  }));
300
- p4.log.success(color4.green("noto api key configured!"));
459
+ p5.log.success(color5.green("noto api key configured!"));
301
460
  console.log();
302
461
  }
303
462
  };
@@ -306,7 +465,7 @@ var model = {
306
465
  description: "configure model",
307
466
  usage: "noto config model [options]",
308
467
  execute: async (options) => {
309
- const model2 = await p4.select({
468
+ const model2 = await p5.select({
310
469
  message: "select a model",
311
470
  initialValue: (await StorageManager.get()).llm?.model,
312
471
  options: Object.keys(models).map((model3) => ({
@@ -314,10 +473,9 @@ var model = {
314
473
  value: model3
315
474
  }))
316
475
  });
317
- if (p4.isCancel(model2)) {
318
- p4.log.error(color4.red("nothing changed!"));
319
- console.log();
320
- process.exit();
476
+ if (p5.isCancel(model2)) {
477
+ p5.log.error(color5.red("nothing changed!"));
478
+ return await exit(1);
321
479
  }
322
480
  await StorageManager.update((current) => ({
323
481
  ...current,
@@ -326,48 +484,46 @@ var model = {
326
484
  model: model2
327
485
  }
328
486
  }));
329
- p4.log.success(color4.green("model configured!"));
487
+ p5.log.success(color5.green("model configured!"));
330
488
  console.log();
331
489
  }
332
490
  };
333
491
  var subCommands = [key, model];
334
- var command2 = {
492
+ var command3 = {
335
493
  name: "config",
336
494
  description: "configure noto",
337
495
  usage: "noto config [subcommand]",
338
496
  execute: async (options) => {
339
- const command3 = await p4.select({
497
+ const command4 = await p5.select({
340
498
  message: "Select a subcommand",
341
499
  options: subCommands.map((cmd2) => ({
342
500
  label: cmd2.description,
343
501
  value: cmd2.name
344
502
  }))
345
503
  });
346
- if (p4.isCancel(command3)) {
347
- console.log();
348
- process.exit(1);
504
+ if (p5.isCancel(command4)) {
505
+ return await exit(1);
349
506
  }
350
- const cmd = getCommand(command3, subCommands);
507
+ const cmd = getCommand(command4, subCommands);
351
508
  if (!cmd) {
352
- p4.log.error(color4.red("unknown config command"));
353
- console.log();
354
- process.exit(1);
509
+ p5.log.error(color5.red("unknown config command"));
510
+ return await exit(1);
355
511
  }
356
512
  options._ = options._.slice(1);
357
513
  cmd.execute(options);
358
514
  },
359
515
  subCommands
360
516
  };
361
- var config_default = command2;
517
+ var config_default = command3;
362
518
 
363
519
  // src/commands/index.ts
364
- var commands = [noto_default, config_default];
520
+ var commands = [noto_default, prev_default, config_default];
365
521
  var getCommand = (name, cmds = commands) => {
366
522
  return cmds.find((cmd) => cmd.name === name);
367
523
  };
368
524
 
369
525
  // package.json
370
- var version = "1.0.0-beta.2";
526
+ var version = "1.0.0-beta.4";
371
527
 
372
528
  // src/index.ts
373
529
  var globalSpec = {
@@ -378,13 +534,13 @@ var globalSpec = {
378
534
  };
379
535
  function main() {
380
536
  const args = process.argv.slice(2);
381
- const { command: command3, options: globalOptions } = parse(globalSpec, args);
537
+ const { command: command4, options: globalOptions } = parse(globalSpec, args);
382
538
  console.log();
383
- p5.intro(`${color5.bgCyan(color5.black(" @snelusha/noto "))}`);
384
- if (globalOptions["--version"]) return p5.outro(version);
385
- const cmd = getCommand(command3) ?? getCommand("noto");
539
+ p6.intro(`${color6.bgCyan(color6.black(" @snelusha/noto "))}`);
540
+ if (globalOptions["--version"]) return p6.outro(version);
541
+ const cmd = getCommand(command4) ?? getCommand("noto");
386
542
  if (!cmd) return getCommand("noto")?.execute(globalOptions);
387
- let commandArgs = command3 ? args.slice(1) : args;
543
+ let commandArgs = args;
388
544
  let selectedCommand = cmd;
389
545
  if (cmd.subCommands && commandArgs.length) {
390
546
  const possibleCommand = commandArgs[0];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@snelusha/noto",
3
- "version": "1.0.0-beta.2",
3
+ "version": "1.0.0-beta.4",
4
4
  "description": "Generate clean commit messages in a snap! ✨",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -43,6 +43,7 @@
43
43
  "@clack/prompts": "^0.10.0",
44
44
  "ai": "^4.1.40",
45
45
  "arg": "^5.0.2",
46
+ "clipboardy": "^4.0.0",
46
47
  "dedent": "^1.5.3",
47
48
  "picocolors": "^1.1.1",
48
49
  "simple-git": "^3.27.0",