@snelusha/noto 1.3.2 → 1.3.4-beta.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.
- package/README.md +8 -0
- package/dist/index.js +26 -942
- package/package.json +7 -4
- package/bin/noto.mjs +0 -4
package/README.md
CHANGED
|
@@ -118,6 +118,14 @@ Create and switch to a new branch:
|
|
|
118
118
|
noto checkout -b new-branch-name
|
|
119
119
|
```
|
|
120
120
|
|
|
121
|
+
Update noto to the latest version:
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
noto upgrade
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
> noto will automatically detect your installation method and update itself accordingly.
|
|
128
|
+
|
|
121
129
|
## Pro Tips
|
|
122
130
|
|
|
123
131
|
- 🚀 Get fast commits on the fly with `noto -a` to streamline your workflow!
|
package/dist/index.js
CHANGED
|
@@ -1,626 +1,11 @@
|
|
|
1
|
-
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
import * as p from "@clack/prompts";
|
|
10
|
-
import color from "picocolors";
|
|
11
|
-
import dedent from "dedent";
|
|
12
|
-
|
|
13
|
-
// src/utils/git.ts
|
|
14
|
-
import simpleGit from "simple-git";
|
|
15
|
-
var git = simpleGit();
|
|
16
|
-
var isGitRepository = async () => {
|
|
17
|
-
return git.checkIsRepo();
|
|
18
|
-
};
|
|
19
|
-
var getGitRoot = async () => {
|
|
20
|
-
try {
|
|
21
|
-
return await git.revparse(["--show-toplevel"]);
|
|
22
|
-
} catch {
|
|
23
|
-
return null;
|
|
24
|
-
}
|
|
25
|
-
};
|
|
26
|
-
var getCommits = async (limit = 20, excludeMerges = false) => {
|
|
27
|
-
try {
|
|
28
|
-
const options = excludeMerges ? { maxCount: limit, "--no-merges": null } : { maxCount: limit };
|
|
29
|
-
const log = await git.log(options);
|
|
30
|
-
return log.all.map((c) => c.message);
|
|
31
|
-
} catch {
|
|
32
|
-
return null;
|
|
33
|
-
}
|
|
34
|
-
};
|
|
35
|
-
var getStagedDiff = async () => {
|
|
36
|
-
try {
|
|
37
|
-
return git.diff(["--cached", "--", ":!*.lock"]);
|
|
38
|
-
} catch {
|
|
39
|
-
return null;
|
|
40
|
-
}
|
|
41
|
-
};
|
|
42
|
-
var commit = async (message, amend) => {
|
|
43
|
-
try {
|
|
44
|
-
const options = amend ? { "--amend": null } : undefined;
|
|
45
|
-
const {
|
|
46
|
-
summary: { changes }
|
|
47
|
-
} = await git.commit(message, undefined, options);
|
|
48
|
-
return Boolean(changes);
|
|
49
|
-
} catch {
|
|
50
|
-
return false;
|
|
51
|
-
}
|
|
52
|
-
};
|
|
53
|
-
var push = async () => {
|
|
54
|
-
try {
|
|
55
|
-
const result = await git.push();
|
|
56
|
-
return result.update || result.pushed && result.pushed.length > 0;
|
|
57
|
-
} catch {
|
|
58
|
-
return false;
|
|
59
|
-
}
|
|
60
|
-
};
|
|
61
|
-
var getCurrentBranch = async () => {
|
|
62
|
-
try {
|
|
63
|
-
const branch = await git.branch();
|
|
64
|
-
return branch.current;
|
|
65
|
-
} catch {
|
|
66
|
-
return null;
|
|
67
|
-
}
|
|
68
|
-
};
|
|
69
|
-
var getBranches = async (remote) => {
|
|
70
|
-
try {
|
|
71
|
-
const branches = await git.branch();
|
|
72
|
-
return remote ? branches.all : Object.keys(branches.branches).filter((b) => !b.startsWith("remotes/"));
|
|
73
|
-
} catch {
|
|
74
|
-
return null;
|
|
75
|
-
}
|
|
76
|
-
};
|
|
77
|
-
var checkout = async (branch) => {
|
|
78
|
-
try {
|
|
79
|
-
await git.checkout(branch, {});
|
|
80
|
-
return true;
|
|
81
|
-
} catch {
|
|
82
|
-
return false;
|
|
83
|
-
}
|
|
84
|
-
};
|
|
85
|
-
var checkoutLocalBranch = async (branch) => {
|
|
86
|
-
try {
|
|
87
|
-
await git.checkoutLocalBranch(branch);
|
|
88
|
-
return true;
|
|
89
|
-
} catch {
|
|
90
|
-
return false;
|
|
91
|
-
}
|
|
92
|
-
};
|
|
93
|
-
|
|
94
|
-
// src/utils/storage.ts
|
|
95
|
-
import os from "os";
|
|
96
|
-
import { dirname, join, resolve } from "path";
|
|
97
|
-
import { promises as fs } from "fs";
|
|
98
|
-
import { z as z2 } from "zod";
|
|
99
|
-
|
|
100
|
-
// src/ai/types.ts
|
|
101
|
-
import { z } from "zod";
|
|
102
|
-
var AvailableModelsSchema = z.enum([
|
|
103
|
-
"gemini-1.5-flash",
|
|
104
|
-
"gemini-1.5-flash-latest",
|
|
105
|
-
"gemini-1.5-flash-8b",
|
|
106
|
-
"gemini-1.5-flash-8b-latest",
|
|
107
|
-
"gemini-1.5-pro",
|
|
108
|
-
"gemini-1.5-pro-latest",
|
|
109
|
-
"gemini-2.0-flash-001",
|
|
110
|
-
"gemini-2.0-flash",
|
|
111
|
-
"gemini-2.0-flash-lite-preview-02-05",
|
|
112
|
-
"gemini-2.5-flash-preview-04-17",
|
|
113
|
-
"gemini-2.5-pro-preview-05-06"
|
|
114
|
-
]);
|
|
115
|
-
|
|
116
|
-
// src/utils/storage.ts
|
|
117
|
-
var StorageSchema = z2.object({
|
|
118
|
-
llm: z2.object({
|
|
119
|
-
apiKey: z2.string().optional(),
|
|
120
|
-
model: AvailableModelsSchema.optional().or(z2.string())
|
|
121
|
-
}).optional(),
|
|
122
|
-
lastGeneratedMessage: z2.string().optional(),
|
|
123
|
-
cache: z2.record(z2.string(), z2.string()).optional()
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
class StorageManager {
|
|
127
|
-
static storagePath = resolve(join(os.homedir(), ".config", "noto"), ".notorc");
|
|
128
|
-
static storage = {};
|
|
129
|
-
static async load() {
|
|
130
|
-
try {
|
|
131
|
-
await fs.access(this.storagePath);
|
|
132
|
-
const data = await fs.readFile(this.storagePath, "utf-8");
|
|
133
|
-
const json = data ? JSON.parse(data) : {};
|
|
134
|
-
const result = StorageSchema.safeParse(json);
|
|
135
|
-
if (!result.success)
|
|
136
|
-
this.storage = {};
|
|
137
|
-
else
|
|
138
|
-
this.storage = result.data;
|
|
139
|
-
} catch {
|
|
140
|
-
this.storage = {};
|
|
141
|
-
}
|
|
142
|
-
return this.storage;
|
|
143
|
-
}
|
|
144
|
-
static async save() {
|
|
145
|
-
try {
|
|
146
|
-
const directory = dirname(this.storagePath);
|
|
147
|
-
await fs.mkdir(directory, { recursive: true });
|
|
148
|
-
const data = JSON.stringify(this.storage, null, 2);
|
|
149
|
-
await fs.writeFile(this.storagePath, data, "utf-8");
|
|
150
|
-
} catch {}
|
|
151
|
-
}
|
|
152
|
-
static async update(updater) {
|
|
153
|
-
try {
|
|
154
|
-
const updatedStorage = await updater(this.storage);
|
|
155
|
-
const result = StorageSchema.safeParse(updatedStorage);
|
|
156
|
-
if (!result.success)
|
|
157
|
-
return this.storage;
|
|
158
|
-
this.storage = result.data;
|
|
159
|
-
await this.save();
|
|
160
|
-
} catch {}
|
|
161
|
-
return this.storage;
|
|
162
|
-
}
|
|
163
|
-
static async get() {
|
|
164
|
-
await this.load();
|
|
165
|
-
return JSON.parse(JSON.stringify(this.storage));
|
|
166
|
-
}
|
|
167
|
-
static async clear() {
|
|
168
|
-
this.storage = {};
|
|
169
|
-
await this.save();
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
// src/utils/fs.ts
|
|
174
|
-
import fs2 from "node:fs/promises";
|
|
175
|
-
import path from "node:path";
|
|
176
|
-
import { fileURLToPath } from "node:url";
|
|
177
|
-
var toPath = (urlOrPath) => urlOrPath instanceof URL ? fileURLToPath(urlOrPath) : urlOrPath;
|
|
178
|
-
async function findUp(name, options = {}) {
|
|
179
|
-
let directory = path.resolve(toPath(options.cwd ?? ""));
|
|
180
|
-
const { root } = path.parse(directory);
|
|
181
|
-
options.stopAt = path.resolve(toPath(options.stopAt ?? root));
|
|
182
|
-
const isAbsoluteName = path.isAbsolute(name);
|
|
183
|
-
while (directory) {
|
|
184
|
-
const filePath = isAbsoluteName ? name : path.join(directory, name);
|
|
185
|
-
try {
|
|
186
|
-
const stats = await fs2.stat(filePath);
|
|
187
|
-
if (options.type === undefined || options.type === "file" && stats.isFile() || options.type === "directory" && stats.isDirectory())
|
|
188
|
-
return filePath;
|
|
189
|
-
} catch {}
|
|
190
|
-
if (directory === options.stopAt || directory === root)
|
|
191
|
-
break;
|
|
192
|
-
directory = path.dirname(directory);
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
// src/utils/prompt.ts
|
|
197
|
-
var getPromptFile = async () => {
|
|
198
|
-
const root = await getGitRoot();
|
|
199
|
-
return await findUp(".noto/commit-prompt.md", {
|
|
200
|
-
stopAt: root || process.cwd(),
|
|
201
|
-
type: "file"
|
|
202
|
-
});
|
|
203
|
-
};
|
|
204
|
-
|
|
205
|
-
// src/utils/process.ts
|
|
206
|
-
var exit = async (code) => {
|
|
207
|
-
await new Promise((resolve2) => setTimeout(resolve2, 1));
|
|
208
|
-
console.log();
|
|
209
|
-
process.exit(code);
|
|
210
|
-
};
|
|
211
|
-
|
|
212
|
-
// src/trpc.ts
|
|
213
|
-
var t = initTRPC.meta().create({
|
|
214
|
-
defaultMeta: {
|
|
215
|
-
intro: true,
|
|
216
|
-
authRequired: true,
|
|
217
|
-
repoRequired: true,
|
|
218
|
-
diffRequired: false,
|
|
219
|
-
promptRequired: false
|
|
220
|
-
}
|
|
221
|
-
});
|
|
222
|
-
var authMiddleware = t.middleware(async (opts) => {
|
|
223
|
-
const { meta, next } = opts;
|
|
224
|
-
const storage = await StorageManager.get();
|
|
225
|
-
const apiKey = process.env.NOTO_API_KEY || storage.llm?.apiKey;
|
|
226
|
-
if (meta?.authRequired && !apiKey) {
|
|
227
|
-
p.log.error(dedent`${color.red("noto api key is missing.")}
|
|
228
|
-
${color.dim(`run ${color.cyan("`noto config key`")} to set it up.`)}`);
|
|
229
|
-
return await exit(1);
|
|
230
|
-
}
|
|
231
|
-
return next();
|
|
232
|
-
});
|
|
233
|
-
var gitMiddleware = t.middleware(async (opts) => {
|
|
234
|
-
const { meta, next } = opts;
|
|
235
|
-
const isRepository = await isGitRepository();
|
|
236
|
-
if (meta?.repoRequired && !isRepository) {
|
|
237
|
-
p.log.error(dedent`${color.red("no git repository found in cwd.")}
|
|
238
|
-
${color.dim(`run ${color.cyan("`git init`")} to initialize a new repository.`)}`);
|
|
239
|
-
return await exit(1);
|
|
240
|
-
}
|
|
241
|
-
const diff = isRepository && await getStagedDiff();
|
|
242
|
-
if (meta?.diffRequired && !diff) {
|
|
243
|
-
p.log.error(dedent`${color.red("no staged changes found.")}
|
|
244
|
-
${color.dim(`run ${color.cyan("`git add <file>`")} or ${color.cyan("`git add .`")} to stage changes.`)}`);
|
|
245
|
-
return await exit(1);
|
|
246
|
-
}
|
|
247
|
-
let prompt = null;
|
|
248
|
-
if (meta?.promptRequired) {
|
|
249
|
-
const promptPath = await getPromptFile();
|
|
250
|
-
if (promptPath) {
|
|
251
|
-
try {
|
|
252
|
-
prompt = await fs3.readFile(promptPath, "utf-8");
|
|
253
|
-
} catch {}
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
return next({
|
|
257
|
-
ctx: {
|
|
258
|
-
noto: {
|
|
259
|
-
prompt
|
|
260
|
-
},
|
|
261
|
-
git: {
|
|
262
|
-
isRepository,
|
|
263
|
-
diff
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
});
|
|
267
|
-
});
|
|
268
|
-
var baseProcedure = t.procedure.use((opts) => {
|
|
269
|
-
const { meta, next } = opts;
|
|
270
|
-
if (meta?.intro) {
|
|
271
|
-
console.log();
|
|
272
|
-
p.intro(`${color.bgCyan(color.black(" @snelusha/noto "))}`);
|
|
273
|
-
}
|
|
274
|
-
return next();
|
|
275
|
-
});
|
|
276
|
-
var authProcedure = baseProcedure.use(authMiddleware);
|
|
277
|
-
var gitProcedure = baseProcedure.use(gitMiddleware);
|
|
278
|
-
var authedGitProcedure = baseProcedure.use(authMiddleware).use(gitMiddleware);
|
|
279
|
-
|
|
280
|
-
// src/commands/checkout.ts
|
|
281
|
-
import { z as z3 } from "zod";
|
|
282
|
-
import * as p2 from "@clack/prompts";
|
|
283
|
-
import color2 from "picocolors";
|
|
284
|
-
import clipboard from "clipboardy";
|
|
285
|
-
var checkout2 = gitProcedure.meta({
|
|
286
|
-
description: "checkout a branch"
|
|
287
|
-
}).input(z3.object({
|
|
288
|
-
copy: z3.boolean().meta({
|
|
289
|
-
description: "copy the selected branch to clipboard",
|
|
290
|
-
alias: "c"
|
|
291
|
-
}),
|
|
292
|
-
create: z3.union([z3.boolean(), z3.string()]).optional().meta({ description: "create a new branch", alias: "b" }),
|
|
293
|
-
branch: z3.string().optional().meta({ positional: true })
|
|
294
|
-
})).mutation(async (opts) => {
|
|
295
|
-
const { input } = opts;
|
|
296
|
-
const branches = await getBranches();
|
|
297
|
-
if (!branches) {
|
|
298
|
-
p2.log.error("failed to fetch branches");
|
|
299
|
-
return await exit(1);
|
|
300
|
-
}
|
|
301
|
-
const currentBranch = await getCurrentBranch();
|
|
302
|
-
const targetBranch = typeof input.create === "string" ? input.create : input.branch;
|
|
303
|
-
const createFlag = input.create === true || typeof input.create === "string";
|
|
304
|
-
if (createFlag && targetBranch) {
|
|
305
|
-
if (branches.includes(targetBranch)) {
|
|
306
|
-
p2.log.error(`branch ${color2.red(targetBranch)} already exists in the repository`);
|
|
307
|
-
return await exit(1);
|
|
308
|
-
}
|
|
309
|
-
const result2 = await checkoutLocalBranch(targetBranch);
|
|
310
|
-
if (!result2) {
|
|
311
|
-
p2.log.error(`failed to create and checkout ${color2.bold(targetBranch)}`);
|
|
312
|
-
return await exit(1);
|
|
313
|
-
}
|
|
314
|
-
p2.log.success(`created and checked out ${color2.green(targetBranch)}`);
|
|
315
|
-
return await exit(0);
|
|
316
|
-
}
|
|
317
|
-
if (targetBranch) {
|
|
318
|
-
if (!branches.includes(targetBranch)) {
|
|
319
|
-
p2.log.error(`branch ${color2.red(targetBranch)} does not exist in the repository`);
|
|
320
|
-
const createBranch = await p2.confirm({
|
|
321
|
-
message: `do you want to create branch ${color2.green(targetBranch)}?`
|
|
322
|
-
});
|
|
323
|
-
if (p2.isCancel(createBranch)) {
|
|
324
|
-
p2.log.error("aborted");
|
|
325
|
-
return await exit(1);
|
|
326
|
-
}
|
|
327
|
-
if (createBranch) {
|
|
328
|
-
const result3 = await checkoutLocalBranch(targetBranch);
|
|
329
|
-
if (!result3) {
|
|
330
|
-
p2.log.error(`failed to create and checkout ${color2.bold(targetBranch)}`);
|
|
331
|
-
return await exit(1);
|
|
332
|
-
}
|
|
333
|
-
p2.log.success(`created and checked out ${color2.green(targetBranch)}`);
|
|
334
|
-
return await exit(0);
|
|
335
|
-
}
|
|
336
|
-
return await exit(1);
|
|
337
|
-
}
|
|
338
|
-
if (targetBranch === currentBranch) {
|
|
339
|
-
p2.log.error(`${color2.red("already on branch")} ${color2.green(targetBranch)}`);
|
|
340
|
-
return await exit(1);
|
|
341
|
-
}
|
|
342
|
-
const result2 = await checkout(targetBranch);
|
|
343
|
-
if (!result2) {
|
|
344
|
-
p2.log.error(`failed to checkout ${color2.bold(targetBranch)}`);
|
|
345
|
-
return await exit(1);
|
|
346
|
-
}
|
|
347
|
-
p2.log.success(`checked out ${color2.green(targetBranch)}`);
|
|
348
|
-
return await exit(0);
|
|
349
|
-
}
|
|
350
|
-
if (branches.length === 0) {
|
|
351
|
-
p2.log.error("no branches found in the repository");
|
|
352
|
-
return await exit(1);
|
|
353
|
-
}
|
|
354
|
-
const branch = await p2.select({
|
|
355
|
-
message: "select a branch to checkout",
|
|
356
|
-
options: branches.map((branch2) => ({
|
|
357
|
-
value: branch2,
|
|
358
|
-
label: color2.bold(branch2 === currentBranch ? color2.green(branch2) : branch2),
|
|
359
|
-
hint: branch2 === currentBranch ? "current branch" : undefined
|
|
360
|
-
})),
|
|
361
|
-
initialValue: currentBranch
|
|
362
|
-
});
|
|
363
|
-
if (p2.isCancel(branch)) {
|
|
364
|
-
p2.log.error("nothing selected!");
|
|
365
|
-
return await exit(1);
|
|
366
|
-
}
|
|
367
|
-
if (!branch) {
|
|
368
|
-
p2.log.error("no branch selected");
|
|
369
|
-
return await exit(1);
|
|
370
|
-
}
|
|
371
|
-
if (input.copy) {
|
|
372
|
-
clipboard.writeSync(branch);
|
|
373
|
-
p2.log.success(`copied ${color2.green(branch)} to clipboard`);
|
|
374
|
-
return await exit(0);
|
|
375
|
-
}
|
|
376
|
-
if (branch === currentBranch) {
|
|
377
|
-
p2.log.error(`${color2.red("already on branch")}`);
|
|
378
|
-
return await exit(1);
|
|
379
|
-
}
|
|
380
|
-
const result = await checkout(branch);
|
|
381
|
-
if (!result) {
|
|
382
|
-
p2.log.error(`failed to checkout ${color2.bold(branch)}`);
|
|
383
|
-
return await exit(1);
|
|
384
|
-
}
|
|
385
|
-
p2.log.success(`checked out ${color2.green(branch)}`);
|
|
386
|
-
await exit(0);
|
|
387
|
-
});
|
|
388
|
-
|
|
389
|
-
// src/commands/config/key.ts
|
|
390
|
-
import { z as z4 } from "zod";
|
|
391
|
-
import * as p3 from "@clack/prompts";
|
|
392
|
-
import color3 from "picocolors";
|
|
393
|
-
var key = baseProcedure.meta({ description: "configure noto api key" }).input(z4.string().optional().describe("apiKey")).mutation(async (opts) => {
|
|
394
|
-
const { input } = opts;
|
|
395
|
-
let apiKey = input;
|
|
396
|
-
if ((await StorageManager.get()).llm?.apiKey) {
|
|
397
|
-
const confirm3 = await p3.confirm({
|
|
398
|
-
message: "noto api key already configured, do you want to update it?"
|
|
399
|
-
});
|
|
400
|
-
if (p3.isCancel(confirm3) || !confirm3) {
|
|
401
|
-
p3.log.error(color3.red("nothing changed!"));
|
|
402
|
-
return await exit(1);
|
|
403
|
-
}
|
|
404
|
-
}
|
|
405
|
-
if (!apiKey) {
|
|
406
|
-
const result = await p3.text({
|
|
407
|
-
message: "enter your noto api key"
|
|
408
|
-
});
|
|
409
|
-
if (p3.isCancel(result)) {
|
|
410
|
-
p3.log.error(color3.red("nothing changed!"));
|
|
411
|
-
return await exit(1);
|
|
412
|
-
}
|
|
413
|
-
apiKey = result;
|
|
414
|
-
}
|
|
415
|
-
await StorageManager.update((current) => ({
|
|
416
|
-
...current,
|
|
417
|
-
llm: {
|
|
418
|
-
...current.llm,
|
|
419
|
-
apiKey
|
|
420
|
-
}
|
|
421
|
-
}));
|
|
422
|
-
p3.log.success(color3.green("noto api key configured!"));
|
|
423
|
-
await exit(0);
|
|
424
|
-
});
|
|
425
|
-
|
|
426
|
-
// src/commands/config/model.ts
|
|
427
|
-
import * as p4 from "@clack/prompts";
|
|
428
|
-
import color4 from "picocolors";
|
|
429
|
-
|
|
430
|
-
// src/ai/models.ts
|
|
431
|
-
import { createGoogleGenerativeAI } from "@ai-sdk/google";
|
|
432
|
-
var google = createGoogleGenerativeAI({
|
|
433
|
-
apiKey: process.env.NOTO_API_KEY || (await StorageManager.get()).llm?.apiKey || "api-key"
|
|
434
|
-
});
|
|
435
|
-
var DEFAULT_MODEL = "gemini-2.0-flash";
|
|
436
|
-
var models = {
|
|
437
|
-
"gemini-1.5-flash": google("gemini-1.5-flash"),
|
|
438
|
-
"gemini-1.5-flash-latest": google("gemini-1.5-flash-latest"),
|
|
439
|
-
"gemini-1.5-flash-8b": google("gemini-1.5-flash-8b"),
|
|
440
|
-
"gemini-1.5-flash-8b-latest": google("gemini-1.5-flash-8b-latest"),
|
|
441
|
-
"gemini-1.5-pro": google("gemini-1.5-pro"),
|
|
442
|
-
"gemini-1.5-pro-latest": google("gemini-1.5-pro-latest"),
|
|
443
|
-
"gemini-2.0-flash-001": google("gemini-2.0-flash-001"),
|
|
444
|
-
"gemini-2.0-flash": google("gemini-2.0-flash"),
|
|
445
|
-
"gemini-2.0-flash-lite-preview-02-05": google("gemini-2.0-flash-lite-preview-02-05"),
|
|
446
|
-
"gemini-2.5-flash-preview-04-17": google("gemini-2.5-flash-preview-04-17"),
|
|
447
|
-
"gemini-2.5-pro-preview-05-06": google("gemini-2.5-pro-preview-05-06")
|
|
448
|
-
};
|
|
449
|
-
var availableModels = Object.keys(models);
|
|
450
|
-
var getModel = async () => {
|
|
451
|
-
let model = (await StorageManager.get()).llm?.model;
|
|
452
|
-
if (!model || !availableModels.includes(model)) {
|
|
453
|
-
model = DEFAULT_MODEL;
|
|
454
|
-
await StorageManager.update((current) => ({
|
|
455
|
-
...current,
|
|
456
|
-
llm: {
|
|
457
|
-
...current.llm,
|
|
458
|
-
model: DEFAULT_MODEL
|
|
459
|
-
}
|
|
460
|
-
}));
|
|
461
|
-
}
|
|
462
|
-
return models[model];
|
|
463
|
-
};
|
|
464
|
-
|
|
465
|
-
// src/commands/config/model.ts
|
|
466
|
-
var model = baseProcedure.meta({
|
|
467
|
-
description: "configure model"
|
|
468
|
-
}).mutation(async () => {
|
|
469
|
-
const model2 = await p4.select({
|
|
470
|
-
message: "select a model",
|
|
471
|
-
initialValue: (await StorageManager.get()).llm?.model,
|
|
472
|
-
options: Object.keys(models).map((model3) => ({
|
|
473
|
-
label: model3,
|
|
474
|
-
value: model3
|
|
475
|
-
}))
|
|
476
|
-
});
|
|
477
|
-
if (p4.isCancel(model2)) {
|
|
478
|
-
p4.log.error(color4.red("nothing changed!"));
|
|
479
|
-
return await exit(1);
|
|
480
|
-
}
|
|
481
|
-
if (model2 === "gemini-2.5-pro-preview-05-06") {
|
|
482
|
-
const confirm4 = await p4.confirm({
|
|
483
|
-
message: "this model does not have free quota tier, do you want to continue?"
|
|
484
|
-
});
|
|
485
|
-
if (p4.isCancel(confirm4) || !confirm4) {
|
|
486
|
-
p4.log.error(color4.red("nothing changed!"));
|
|
487
|
-
return await exit(1);
|
|
488
|
-
}
|
|
489
|
-
}
|
|
490
|
-
await StorageManager.update((current) => ({
|
|
491
|
-
...current,
|
|
492
|
-
llm: {
|
|
493
|
-
...current.llm,
|
|
494
|
-
model: model2
|
|
495
|
-
}
|
|
496
|
-
}));
|
|
497
|
-
p4.log.success(color4.green("model configured!"));
|
|
498
|
-
await exit(0);
|
|
499
|
-
});
|
|
500
|
-
|
|
501
|
-
// src/commands/config/reset.ts
|
|
502
|
-
import * as p5 from "@clack/prompts";
|
|
503
|
-
import color5 from "picocolors";
|
|
504
|
-
var reset = baseProcedure.meta({
|
|
505
|
-
description: "reset the configuration"
|
|
506
|
-
}).mutation(async () => {
|
|
507
|
-
const confirm5 = await p5.confirm({
|
|
508
|
-
message: "are you sure you want to reset the configuration?"
|
|
509
|
-
});
|
|
510
|
-
if (p5.isCancel(confirm5) || !confirm5) {
|
|
511
|
-
p5.log.error(color5.red("nothing changed!"));
|
|
512
|
-
return await exit(1);
|
|
513
|
-
}
|
|
514
|
-
await StorageManager.clear();
|
|
515
|
-
p5.log.success(color5.green("configuration reset!"));
|
|
516
|
-
await exit(0);
|
|
517
|
-
});
|
|
518
|
-
|
|
519
|
-
// src/commands/config/index.ts
|
|
520
|
-
var config = t.router({
|
|
521
|
-
key,
|
|
522
|
-
model,
|
|
523
|
-
reset
|
|
524
|
-
});
|
|
525
|
-
|
|
526
|
-
// src/commands/prev.ts
|
|
527
|
-
import { z as z5 } from "zod";
|
|
528
|
-
import * as p6 from "@clack/prompts";
|
|
529
|
-
import color6 from "picocolors";
|
|
530
|
-
import dedent2 from "dedent";
|
|
531
|
-
import clipboard2 from "clipboardy";
|
|
532
|
-
var prev = gitProcedure.meta({
|
|
533
|
-
description: "access the last generated commit",
|
|
534
|
-
repoRequired: false
|
|
535
|
-
}).input(z5.object({
|
|
536
|
-
copy: z5.boolean().meta({ description: "copy the last commit to clipboard", alias: "c" }),
|
|
537
|
-
apply: z5.boolean().meta({ description: "commit the last generated message", alias: "a" }),
|
|
538
|
-
edit: z5.boolean().meta({
|
|
539
|
-
description: "edit the last generated commit message",
|
|
540
|
-
alias: "e"
|
|
541
|
-
}),
|
|
542
|
-
amend: z5.boolean().meta({
|
|
543
|
-
description: "amend the last commit with the last message"
|
|
544
|
-
})
|
|
545
|
-
})).mutation(async (opts) => {
|
|
546
|
-
const { input, ctx } = opts;
|
|
547
|
-
let lastGeneratedMessage = (await StorageManager.get()).lastGeneratedMessage;
|
|
548
|
-
if (!lastGeneratedMessage) {
|
|
549
|
-
p6.log.error(color6.red("no previous commit message found"));
|
|
550
|
-
return await exit(1);
|
|
551
|
-
}
|
|
552
|
-
const isEditMode = input.edit;
|
|
553
|
-
const isAmend = input.amend;
|
|
554
|
-
if (isAmend && !isEditMode) {
|
|
555
|
-
p6.log.error(color6.red("the --amend option requires the --edit option"));
|
|
556
|
-
return await exit(1);
|
|
557
|
-
}
|
|
558
|
-
p6.log.step(isEditMode ? color6.white(lastGeneratedMessage) : color6.green(lastGeneratedMessage));
|
|
559
|
-
if (isEditMode) {
|
|
560
|
-
const editedMessage = await p6.text({
|
|
561
|
-
message: "edit the last generated commit message",
|
|
562
|
-
initialValue: lastGeneratedMessage,
|
|
563
|
-
placeholder: lastGeneratedMessage
|
|
564
|
-
});
|
|
565
|
-
if (p6.isCancel(editedMessage)) {
|
|
566
|
-
p6.log.error(color6.red("nothing changed!"));
|
|
567
|
-
return await exit(1);
|
|
568
|
-
}
|
|
569
|
-
lastGeneratedMessage = editedMessage;
|
|
570
|
-
await StorageManager.update((current) => ({
|
|
571
|
-
...current,
|
|
572
|
-
lastGeneratedMessage: editedMessage
|
|
573
|
-
}));
|
|
574
|
-
p6.log.step(color6.green(lastGeneratedMessage));
|
|
575
|
-
}
|
|
576
|
-
if (input.copy) {
|
|
577
|
-
clipboard2.writeSync(lastGeneratedMessage);
|
|
578
|
-
p6.log.step(color6.dim("copied last generated commit message to clipboard"));
|
|
579
|
-
}
|
|
580
|
-
if (input.apply || isAmend) {
|
|
581
|
-
if (!ctx.git.isRepository) {
|
|
582
|
-
p6.log.error(dedent2`${color6.red("no git repository found in cwd.")}
|
|
583
|
-
${color6.dim(`run ${color6.cyan("`git init`")} to initialize a new repository.`)}`);
|
|
584
|
-
return await exit(1);
|
|
585
|
-
}
|
|
586
|
-
if (!ctx.git.diff && !isAmend) {
|
|
587
|
-
p6.log.error(dedent2`${color6.red("no staged changes found.")}
|
|
588
|
-
${color6.dim(`run ${color6.cyan("`git add <file>`")} or ${color6.cyan("`git add .`")} to stage changes.`)}`);
|
|
589
|
-
return await exit(1);
|
|
590
|
-
}
|
|
591
|
-
const success = await commit(lastGeneratedMessage, isAmend);
|
|
592
|
-
if (success) {
|
|
593
|
-
p6.log.step(color6.dim("commit successful"));
|
|
594
|
-
} else {
|
|
595
|
-
p6.log.error(color6.red("failed to commit changes"));
|
|
596
|
-
}
|
|
597
|
-
}
|
|
598
|
-
return await exit(0);
|
|
599
|
-
});
|
|
600
|
-
|
|
601
|
-
// src/commands/init.ts
|
|
602
|
-
import fs4 from "node:fs/promises";
|
|
603
|
-
import { z as z7 } from "zod";
|
|
604
|
-
import * as p7 from "@clack/prompts";
|
|
605
|
-
import color7 from "picocolors";
|
|
606
|
-
import dedent4 from "dedent";
|
|
607
|
-
|
|
608
|
-
// src/ai/index.ts
|
|
609
|
-
import { generateObject, wrapLanguageModel } from "ai";
|
|
610
|
-
import z6 from "zod";
|
|
611
|
-
import dedent3 from "dedent";
|
|
612
|
-
import superjson from "superjson";
|
|
613
|
-
|
|
614
|
-
// src/utils/hash.ts
|
|
615
|
-
import { createHash } from "crypto";
|
|
616
|
-
function hashString(content) {
|
|
617
|
-
const body = Buffer.from(content, "utf-8");
|
|
618
|
-
const header = Buffer.from(`blob ${body.length}\x00`, "utf-8");
|
|
619
|
-
return createHash("sha1").update(header).update(body).digest("hex");
|
|
620
|
-
}
|
|
621
|
-
|
|
622
|
-
// src/ai/index.ts
|
|
623
|
-
var COMMIT_GENERATOR_PROMPT = dedent3`
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import{createCli as Pt}from"trpc-cli";var ge="@snelusha/noto",U="1.3.4-beta.0";import ft from"node:fs/promises";import{initTRPC as ht}from"@trpc/server";import*as D from"@clack/prompts";import b from"picocolors";import ne from"dedent";import Qe from"simple-git";var A=Qe(),Y=async()=>{return A.checkIsRepo()},K=async()=>{try{return await A.revparse(["--show-toplevel"])}catch{return null}};var fe=async(e=20,t=!1)=>{try{let o=t?{maxCount:e,"--no-merges":null}:{maxCount:e};return(await A.log(o)).all.map((r)=>r.message)}catch{return null}};var he=async()=>{try{return A.diff(["--cached","--",":!*.lock"])}catch{return null}},L=async(e,t)=>{try{let o=t?{"--amend":null}:void 0,{summary:{changes:a}}=await A.commit(e,void 0,o);return Boolean(a)}catch{return!1}},ye=async()=>{try{let e=await A.push();return e.update||e.pushed&&e.pushed.length>0}catch{return!1}},we=async()=>{try{return(await A.branch()).current}catch{return null}},be=async(e)=>{try{let t=await A.branch();return e?t.all:Object.keys(t.branches).filter((o)=>!o.startsWith("remotes/"))}catch{return null}},re=async(e)=>{try{return await A.checkout(e,{}),!0}catch{return!1}},ae=async(e)=>{try{return await A.checkoutLocalBranch(e),!0}catch{return!1}};import et from"os";import{join as tt,resolve as ot}from"path";import{z as j}from"zod";import{promises as _}from"fs";import{dirname as Xe}from"path";function H(e){let{schema:t,path:o}=e;return class{static storagePath=o;static storage={};static async load(){try{await _.access(this.storagePath);let r=await _.readFile(this.storagePath,"utf-8"),n=r?JSON.parse(r):{},s=t.safeParse(n);this.storage=s.success?s.data:{}}catch{this.storage={}}return this.storage}static async save(){try{let r=Xe(this.storagePath);await _.mkdir(r,{recursive:!0});let n=JSON.stringify(this.storage,null,2);await _.writeFile(this.storagePath,n,"utf-8")}catch{}}static async update(r){try{await this.load();let n=await r(this.storage),s=t.safeParse(n);if(s.success)this.storage=s.data,await this.save()}catch{}return this.storage}static async get(){return await this.load(),JSON.parse(JSON.stringify(this.storage))}static async clear(){this.storage={},await this.save()}static get path(){return this.storagePath}static set path(r){this.storagePath=r}static get raw(){return this.storage}}}import{z as Ze}from"zod";var ve=Ze.enum(["gemini-1.5-flash","gemini-1.5-flash-latest","gemini-1.5-flash-8b","gemini-1.5-flash-8b-latest","gemini-1.5-pro","gemini-1.5-pro-latest","gemini-2.0-flash-001","gemini-2.0-flash","gemini-2.0-flash-lite-preview-02-05","gemini-2.5-flash-preview-04-17","gemini-2.5-pro-preview-05-06"]);var rt=j.object({llm:j.object({apiKey:j.string().optional(),model:ve.optional().or(j.string())}).optional(),lastGeneratedMessage:j.string().optional()}),p=H({path:ot(tt(et.homedir(),".config","noto"),".notorc"),schema:rt});async function xe(){try{let e=await p.get();await p.update(()=>e)}catch{}}import at from"node:fs/promises";import P from"node:path";import{fileURLToPath as it}from"node:url";var Ce=(e)=>e instanceof URL?it(e):e;async function Se(e,t={}){let o=P.resolve(Ce(t.cwd??"")),{root:a}=P.parse(o);t.stopAt=P.resolve(Ce(t.stopAt??a));let r=P.isAbsolute(e);while(o){let n=r?e:P.join(o,e);try{let s=await at.stat(n);if(t.type===void 0||t.type==="file"&&s.isFile()||t.type==="directory"&&s.isDirectory())return n}catch{}if(o===t.stopAt||o===a)break;o=P.dirname(o)}}var W=async()=>{let e=await K();return await Se(".noto/commit-prompt.md",{stopAt:e||process.cwd(),type:"file"})};import*as ke from"@clack/prompts";import Ae from"picocolors";import ut from"dedent";import v from"semver";import lt from"latest-version";import st from"os";import{join as nt,resolve as ct}from"path";import{z as I}from"zod";var mt=I.object({commitGenerationCache:I.record(I.string(),I.string()).optional(),update:I.object({timestamp:I.number(),current:I.string(),latest:I.string()}).optional()}),O=H({path:ct(nt(st.homedir(),".cache","noto"),"cache"),schema:mt});var pt=43200000;async function ie(e=!1,t=!1){let o=(await O.get()).update;if(!t&&o){if(v.valid(o.current)&&v.valid(o.latest)){let r=v.gte(o.current,o.latest),n=Date.now()-o.timestamp<pt;if(r||n)return{latest:o.latest,current:o.current,timestamp:o.timestamp}}}try{let r={latest:await lt(ge),current:U,timestamp:Date.now()};if(e)await dt(r);return r}catch{if(o)return{latest:o.latest,current:o.current,timestamp:o.timestamp};return{latest:U,current:U,timestamp:Date.now()}}}async function dt(e){if(!e)return;await O.update((t)=>({...t,update:e&&{timestamp:e.timestamp,latest:e.latest,current:e.current}}))}async function J(e=!1,t=!1){let o=await ie(e,t);if(v.valid(o.current)&&v.valid(o.latest)){if(!v.gte(o.current,o.latest))return o}return null}import*as T from"node:fs";import*as z from"node:path";import*as Me from"node:child_process";import se from"node:process";async function Q(){let e=se.argv[1];if(!e)return{packageManager:"unknown",isGlobal:!1};let t=se.cwd();try{let o=T.realpathSync(e).replace(/\\/g,"/"),a=t.replace(/\\/g,"/");if(await Y()&&a&&o.startsWith(a)&&!o.includes("/node_modules/"))return{packageManager:"unknown",isGlobal:!1,updateMessage:'Running from a local git clone. Please update with "git pull".'};if(o.includes("/.npm/_npx")||o.includes("/npm/_npx"))return{packageManager:"npx",isGlobal:!1,updateMessage:"Running via npx, update not applicable."};if(o.includes("/.pnpm/_pnpx"))return{packageManager:"pnpx",isGlobal:!1,updateMessage:"Running via pnpx, update not applicable."};if(se.platform==="darwin")try{return Me.execSync('brew list -1 | grep -q "^noto$"',{stdio:"ignore"}),{packageManager:"homebrew",isGlobal:!0,updateMessage:'Installed via Homebrew. Please update with "brew upgrade".'}}catch{}if(o.includes("/.pnpm/global"))return{packageManager:"pnpm",isGlobal:!0,updateCommand:"pnpm add -g @snelusha/noto@latest",updateMessage:"Please run pnpm add -g @snelusha/noto@latest to update"};if(o.includes("/.yarn/global"))return{packageManager:"yarn",isGlobal:!0,updateCommand:"yarn global add @snelusha/noto@latest",updateMessage:"Please run yarn global add @snelusha/noto@latest to update"};if(o.includes("/.bun/install/cache"))return{packageManager:"bunx",isGlobal:!1,updateMessage:"Running via bunx, update not applicable."};if(o.includes("/.bun/install/global"))return{packageManager:"bun",isGlobal:!0,updateCommand:"bun add -g @snelusha/noto@latest",updateMessage:"Please run bun add -g @snelusha/noto@latest to update"};if(a&&o.startsWith(`${a}/node_modules`)){let s="npm";if(T.existsSync(z.join(t,"yarn.lock")))s="yarn";else if(T.existsSync(z.join(t,"pnpm-lock.yaml")))s="pnpm";else if(T.existsSync(z.join(t,"bun.lockb"))||T.existsSync(z.join(t,"bun.lock")))s="bun";return{packageManager:s,isGlobal:!1,updateMessage:"Locally installed. Please update via your project's package.json."}}let n="npm install -g @snelusha/noto@latest";return{packageManager:"npm",isGlobal:!0,updateCommand:n,updateMessage:`Please run ${n} to update`}}catch{return{packageManager:"unknown",isGlobal:!1}}}async function gt(){let e=await J();if(e){let o=(await Q()).updateMessage;ke.log.warn(ut`A new version of noto is available: ${Ae.dim(e.current)} → ${Ae.green(e.latest)}
|
|
3
|
+
${o??""}`.trim())}}var i=async(e,t=!0)=>{if(t)await gt();await new Promise((o)=>setTimeout(o,1)),console.log(),process.exit(e)};var $=ht.meta().create({defaultMeta:{intro:!0,authRequired:!0,repoRequired:!0,diffRequired:!1,promptRequired:!1}}),Ie=$.middleware(async(e)=>{let{meta:t,next:o}=e,a=await p.get(),r=process.env.NOTO_API_KEY||a.llm?.apiKey;if(t?.authRequired&&!r)return D.log.error(ne`${b.red("noto api key is missing.")}
|
|
4
|
+
${b.dim(`run ${b.cyan("`noto config key`")} to set it up.`)}`),await i(1);return o()}),Re=$.middleware(async(e)=>{let{meta:t,next:o}=e,a=await Y();if(t?.repoRequired&&!a)return D.log.error(ne`${b.red("no git repository found in cwd.")}
|
|
5
|
+
${b.dim(`run ${b.cyan("`git init`")} to initialize a new repository.`)}`),await i(1);let r=a&&await he();if(t?.diffRequired&&!r)return D.log.error(ne`${b.red("no staged changes found.")}
|
|
6
|
+
${b.dim(`run ${b.cyan("`git add <file>`")} or ${b.cyan("`git add .`")} to stage changes.`)}`),await i(1);let n=null;if(t?.promptRequired){let s=await W();if(s)try{n=await ft.readFile(s,"utf-8")}catch{}}return o({ctx:{noto:{prompt:n},git:{isRepository:a,diff:r}}})}),x=$.procedure.use((e)=>{let{meta:t,next:o}=e;if(t?.intro)console.log(),D.intro(`${b.bgCyan(b.black(" @snelusha/noto "))}`);return o()}),To=x.use(Ie),X=x.use(Re),Z=x.use(Ie).use(Re);import{z as F}from"zod";import*as c from"@clack/prompts";import u from"picocolors";import yt from"clipboardy";var Te=X.meta({description:"checkout a branch"}).input(F.object({copy:F.boolean().meta({description:"copy the selected branch to clipboard",alias:"c"}),create:F.union([F.boolean(),F.string()]).optional().meta({description:"create a new branch",alias:"b"}),branch:F.string().optional().meta({positional:!0})})).mutation(async(e)=>{let{input:t}=e,o=await be();if(!o)return c.log.error("failed to fetch branches"),await i(1);let a=await we(),r=typeof t.create==="string"?t.create:t.branch;if((t.create===!0||typeof t.create==="string")&&r){if(o.includes(r))return c.log.error(`branch ${u.red(r)} already exists in the repository`),await i(1);if(!await ae(r))return c.log.error(`failed to create and checkout ${u.bold(r)}`),await i(1);return c.log.success(`created and checked out ${u.green(r)}`),await i(0)}if(r){if(!o.includes(r)){c.log.error(`branch ${u.red(r)} does not exist in the repository`);let G=await c.confirm({message:`do you want to create branch ${u.green(r)}?`});if(c.isCancel(G))return c.log.error("aborted"),await i(1);if(G){if(!await ae(r))return c.log.error(`failed to create and checkout ${u.bold(r)}`),await i(1);return c.log.success(`created and checked out ${u.green(r)}`),await i(0)}return await i(1)}if(r===a)return c.log.error(`${u.red("already on branch")} ${u.green(r)}`),await i(1);if(!await re(r))return c.log.error(`failed to checkout ${u.bold(r)}`),await i(1);return c.log.success(`checked out ${u.green(r)}`),await i(0)}if(o.length===0)return c.log.error("no branches found in the repository"),await i(1);let s=await c.select({message:"select a branch to checkout",options:o.map((m)=>({value:m,label:u.bold(m===a?u.green(m):m),hint:m===a?"current branch":void 0})),initialValue:a});if(c.isCancel(s))return c.log.error("nothing selected!"),await i(1);if(!s)return c.log.error("no branch selected"),await i(1);if(t.copy)return yt.writeSync(s),c.log.success(`copied ${u.green(s)} to clipboard`),await i(0);if(s===a)return c.log.error(`${u.red("already on branch")}`),await i(1);if(!await re(s))return c.log.error(`failed to checkout ${u.bold(s)}`),await i(1);c.log.success(`checked out ${u.green(s)}`),await i(0)});import{z as wt}from"zod";import*as C from"@clack/prompts";import ce from"picocolors";var $e=x.meta({description:"configure noto api key"}).input(wt.string().optional().describe("apiKey")).mutation(async(e)=>{let{input:t}=e,o=t;if((await p.get()).llm?.apiKey){let a=await C.confirm({message:"noto api key already configured, do you want to update it?"});if(C.isCancel(a)||!a)return C.log.error(ce.red("nothing changed!")),await i(1)}if(!o){let a=await C.text({message:"enter your noto api key"});if(C.isCancel(a))return C.log.error(ce.red("nothing changed!")),await i(1);o=a}await p.update((a)=>({...a,llm:{...a.llm,apiKey:o}})),C.log.success(ce.green("noto api key configured!")),await i(0)});import*as M from"@clack/prompts";import le from"picocolors";import{createGoogleGenerativeAI as bt}from"@ai-sdk/google";var S=bt({apiKey:process.env.NOTO_API_KEY||(await p.get()).llm?.apiKey||"api-key"}),Ee="gemini-2.0-flash",ee={"gemini-1.5-flash":S("gemini-1.5-flash"),"gemini-1.5-flash-latest":S("gemini-1.5-flash-latest"),"gemini-1.5-flash-8b":S("gemini-1.5-flash-8b"),"gemini-1.5-flash-8b-latest":S("gemini-1.5-flash-8b-latest"),"gemini-1.5-pro":S("gemini-1.5-pro"),"gemini-1.5-pro-latest":S("gemini-1.5-pro-latest"),"gemini-2.0-flash-001":S("gemini-2.0-flash-001"),"gemini-2.0-flash":S("gemini-2.0-flash"),"gemini-2.0-flash-lite-preview-02-05":S("gemini-2.0-flash-lite-preview-02-05"),"gemini-2.5-flash-preview-04-17":S("gemini-2.5-flash-preview-04-17"),"gemini-2.5-pro-preview-05-06":S("gemini-2.5-pro-preview-05-06")},vt=Object.keys(ee),me=async()=>{let e=(await p.get()).llm?.model;if(!e||!vt.includes(e))e=Ee,await p.update((t)=>({...t,llm:{...t.llm,model:Ee}}));return ee[e]};var Ge=x.meta({description:"configure model"}).mutation(async()=>{let e=await M.select({message:"select a model",initialValue:(await p.get()).llm?.model,options:Object.keys(ee).map((t)=>({label:t,value:t}))});if(M.isCancel(e))return M.log.error(le.red("nothing changed!")),await i(1);if(e==="gemini-2.5-pro-preview-05-06"){let t=await M.confirm({message:"this model does not have free quota tier, do you want to continue?"});if(M.isCancel(t)||!t)return M.log.error(le.red("nothing changed!")),await i(1)}await p.update((t)=>({...t,llm:{...t.llm,model:e}})),M.log.success(le.green("model configured!")),await i(0)});import*as E from"@clack/prompts";import Ue from"picocolors";var Pe=x.meta({description:"reset the configuration"}).mutation(async()=>{let e=await E.confirm({message:"are you sure you want to reset the configuration?"});if(E.isCancel(e)||!e)return E.log.error(Ue.red("nothing changed!")),await i(1);await p.clear(),E.log.success(Ue.green("configuration reset!")),await i(0)});var Oe=$.router({key:$e,model:Ge,reset:Pe});import{z as B}from"zod";import*as f from"@clack/prompts";import g from"picocolors";import De from"dedent";import xt from"clipboardy";var Fe=X.meta({description:"access the last generated commit",repoRequired:!1}).input(B.object({copy:B.boolean().meta({description:"copy the last commit to clipboard",alias:"c"}),apply:B.boolean().meta({description:"commit the last generated message",alias:"a"}),edit:B.boolean().meta({description:"edit the last generated commit message",alias:"e"}),amend:B.boolean().meta({description:"amend the last commit with the last message"})})).mutation(async(e)=>{let{input:t,ctx:o}=e,a=(await p.get()).lastGeneratedMessage;if(!a)return f.log.error(g.red("no previous commit message found")),await i(1);let{edit:r,amend:n}=t;if(n&&!r)return f.log.error(g.red("the --amend option requires the --edit option")),await i(1);if(f.log.step(r?g.white(a):g.green(a)),r){let s=await f.text({message:"edit the last generated commit message",initialValue:a,placeholder:a});if(f.isCancel(s))return f.log.error(g.red("nothing changed!")),await i(1);a=s,await p.update((w)=>({...w,lastGeneratedMessage:s})),f.log.step(g.green(a))}if(t.copy)xt.writeSync(a),f.log.step(g.dim("copied last generated commit message to clipboard"));if(t.apply||n){if(!o.git.isRepository)return f.log.error(De`${g.red("no git repository found in cwd.")}
|
|
7
|
+
${g.dim(`run ${g.cyan("`git init`")} to initialize a new repository.`)}`),await i(1);if(!o.git.diff&&!n)return f.log.error(De`${g.red("no staged changes found.")}
|
|
8
|
+
${g.dim(`run ${g.cyan("`git add <file>`")} or ${g.cyan("`git add .`")} to stage changes.`)}`),await i(1);if(await L(a,n))f.log.step(g.dim("commit successful"));else f.log.error(g.red("failed to commit changes"))}return await i(0)});import qe from"node:fs/promises";import{z as pe}from"zod";import*as d from"@clack/prompts";import k from"picocolors";import V from"dedent";import{generateObject as je,wrapLanguageModel as St}from"ai";import te from"zod";import q from"dedent";import Le from"superjson";import{createHash as Ct}from"crypto";function Ne(e){let t=Buffer.from(e,"utf-8"),o=Buffer.from(`blob ${t.length}\x00`,"utf-8");return Ct("sha1").update(o).update(t).digest("hex")}var Mt=q`
|
|
624
9
|
# System Instruction for Noto
|
|
625
10
|
|
|
626
11
|
You are a Git commit message generator for the \`noto\` CLI tool. Your role is to analyze staged code changes and generate clear, single-line commit messages that follow the user's established style.
|
|
@@ -691,8 +76,7 @@ Or whatever format matches the user's guidelines. **Must be single-line only.**
|
|
|
691
76
|
- Don't use generic or vague descriptions
|
|
692
77
|
|
|
693
78
|
**Remember:** Your output becomes permanent git history. Generate commit messages that are clear, accurate, and consistent with the user's established patterns.
|
|
694
|
-
|
|
695
|
-
var DEFAULT_COMMIT_GUIDELINES = dedent3`
|
|
79
|
+
`,At=q`
|
|
696
80
|
# Commit Message Guidelines
|
|
697
81
|
|
|
698
82
|
## Format
|
|
@@ -764,8 +148,7 @@ For breaking changes, add \`!\` after the type/scope:
|
|
|
764
148
|
## Additional Notes
|
|
765
149
|
- If a commit addresses a specific issue, you can reference it in the description (e.g., \`fix: resolve memory leak (fixes #123)\`)
|
|
766
150
|
- Each commit should represent a single logical change
|
|
767
|
-
- Write commits as if completing the sentence: "If applied, this commit will..."
|
|
768
|
-
var GUIDELINES_GENERATOR_PROMPT = dedent3`
|
|
151
|
+
- Write commits as if completing the sentence: "If applied, this commit will..."`,kt=q`
|
|
769
152
|
You are a commit style analyzer. Analyze the provided commit history and generate a personalized style guide that will be used to generate future commit messages.
|
|
770
153
|
|
|
771
154
|
## Task
|
|
@@ -921,326 +304,27 @@ Start with action verb (add, implement, resolve, update, simplify). Be specific
|
|
|
921
304
|
- The output will be stored as \`.noto/commit-prompt.md\` and used by an AI to generate commits
|
|
922
305
|
|
|
923
306
|
Generate the markdown guidelines now based on the commit history provided.
|
|
924
|
-
|
|
925
|
-
var cacheMiddleware = {
|
|
926
|
-
wrapGenerate: async ({ doGenerate, params }) => {
|
|
927
|
-
const key2 = hashString(JSON.stringify(params));
|
|
928
|
-
const cache = (await StorageManager.get()).cache;
|
|
929
|
-
if (cache && key2 in cache) {
|
|
930
|
-
const cached = cache[key2];
|
|
931
|
-
return superjson.parse(cached);
|
|
932
|
-
}
|
|
933
|
-
const result = await doGenerate();
|
|
934
|
-
await StorageManager.update((current) => {
|
|
935
|
-
return {
|
|
936
|
-
...current,
|
|
937
|
-
cache: {
|
|
938
|
-
[key2]: superjson.stringify(result)
|
|
939
|
-
}
|
|
940
|
-
};
|
|
941
|
-
});
|
|
942
|
-
return result;
|
|
943
|
-
}
|
|
944
|
-
};
|
|
945
|
-
var generateCommitMessage = async (diff, prompt, context, forceCache = false) => {
|
|
946
|
-
const model2 = await getModel();
|
|
947
|
-
const { object } = await generateObject({
|
|
948
|
-
model: !forceCache ? wrapLanguageModel({
|
|
949
|
-
model: model2,
|
|
950
|
-
middleware: cacheMiddleware
|
|
951
|
-
}) : model2,
|
|
952
|
-
schema: z6.object({
|
|
953
|
-
message: z6.string()
|
|
954
|
-
}),
|
|
955
|
-
messages: [
|
|
956
|
-
{
|
|
957
|
-
role: "system",
|
|
958
|
-
content: COMMIT_GENERATOR_PROMPT
|
|
959
|
-
},
|
|
960
|
-
{
|
|
961
|
-
role: "user",
|
|
962
|
-
content: dedent3`
|
|
307
|
+
`,It={wrapGenerate:async({doGenerate:e,params:t})=>{let o=Ne(JSON.stringify(t)),a=(await O.get()).commitGenerationCache;if(a&&o in a)return Le.parse(a[o]);let r=await e();return await O.update((n)=>({...n,commitGenerationCache:{[o]:Le.stringify(r)}})),r}},ze=async(e,t,o,a=!1)=>{let r=await me(),{object:n}=await je({model:!a?St({model:r,middleware:It}):r,schema:te.object({message:te.string()}),messages:[{role:"system",content:Mt},{role:"user",content:q`
|
|
963
308
|
USER GUIDELINES:
|
|
964
|
-
${
|
|
965
|
-
${
|
|
309
|
+
${t??At}
|
|
310
|
+
${o?`
|
|
966
311
|
USER CONTEXT:
|
|
967
|
-
${
|
|
312
|
+
${o}`:""}
|
|
968
313
|
|
|
969
314
|
GIT DIFF:
|
|
970
|
-
${
|
|
971
|
-
`
|
|
972
|
-
}
|
|
973
|
-
]
|
|
974
|
-
});
|
|
975
|
-
return object.message.trim();
|
|
976
|
-
};
|
|
977
|
-
var generateCommitGuidelines = async (commits) => {
|
|
978
|
-
const model2 = await getModel();
|
|
979
|
-
const { object } = await generateObject({
|
|
980
|
-
model: model2,
|
|
981
|
-
schema: z6.object({
|
|
982
|
-
prompt: z6.string()
|
|
983
|
-
}),
|
|
984
|
-
messages: [
|
|
985
|
-
{
|
|
986
|
-
role: "system",
|
|
987
|
-
content: GUIDELINES_GENERATOR_PROMPT
|
|
988
|
-
},
|
|
989
|
-
{
|
|
990
|
-
role: "user",
|
|
991
|
-
content: dedent3`
|
|
315
|
+
${e}
|
|
316
|
+
`}]});return n.message.trim()},Be=async(e)=>{let t=await me(),{object:o}=await je({model:t,schema:te.object({prompt:te.string()}),messages:[{role:"system",content:kt},{role:"user",content:q`
|
|
992
317
|
COMMIT HISTORY:
|
|
993
|
-
${
|
|
994
|
-
`)}`
|
|
995
|
-
}
|
|
996
|
-
]
|
|
997
|
-
});
|
|
998
|
-
return object.prompt.trim();
|
|
999
|
-
};
|
|
1000
|
-
|
|
1001
|
-
// src/commands/init.ts
|
|
1002
|
-
var EMPTY_TEMPLATE = dedent4`
|
|
318
|
+
${e.join(`
|
|
319
|
+
`)}`}]});return o.prompt.trim()};var Rt=V`
|
|
1003
320
|
# Commit Message Guidelines
|
|
1004
321
|
|
|
1005
322
|
# Add your custom guidelines here.
|
|
1006
|
-
# When no guidelines are present, noto will use conventional commits format by default
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
}).
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
description: "generate a prompt file based on existing commits"
|
|
1015
|
-
})
|
|
1016
|
-
})).mutation(async (opts) => {
|
|
1017
|
-
const { input } = opts;
|
|
1018
|
-
const root = await getGitRoot();
|
|
1019
|
-
let promptFile = root;
|
|
1020
|
-
const cwd = process.cwd();
|
|
1021
|
-
const existingPromptFile = await getPromptFile();
|
|
1022
|
-
let prompt = null;
|
|
1023
|
-
if (existingPromptFile) {
|
|
1024
|
-
if (!existingPromptFile.startsWith(cwd)) {
|
|
1025
|
-
p7.log.warn(dedent4`${color7.yellow("a prompt file already exists!")}
|
|
1026
|
-
${color7.gray(existingPromptFile)}`);
|
|
1027
|
-
const shouldContinue = await p7.confirm({
|
|
1028
|
-
message: "do you want to create in the current directory instead?",
|
|
1029
|
-
initialValue: true
|
|
1030
|
-
});
|
|
1031
|
-
if (p7.isCancel(shouldContinue) || !shouldContinue) {
|
|
1032
|
-
p7.log.error("aborted");
|
|
1033
|
-
return await exit(1);
|
|
1034
|
-
}
|
|
1035
|
-
promptFile = cwd;
|
|
1036
|
-
} else {
|
|
1037
|
-
p7.log.error(dedent4`${color7.red("a prompt file already exists.")}
|
|
1038
|
-
${color7.gray(existingPromptFile)}`);
|
|
1039
|
-
return await exit(1);
|
|
1040
|
-
}
|
|
1041
|
-
}
|
|
1042
|
-
if (root !== cwd && !input.root) {
|
|
1043
|
-
const shouldUseRoot = await p7.confirm({
|
|
1044
|
-
message: "do you want to create the prompt file in the git root?",
|
|
1045
|
-
initialValue: true
|
|
1046
|
-
});
|
|
1047
|
-
if (p7.isCancel(shouldUseRoot)) {
|
|
1048
|
-
p7.log.error("aborted");
|
|
1049
|
-
return await exit(1);
|
|
1050
|
-
}
|
|
1051
|
-
if (!shouldUseRoot)
|
|
1052
|
-
promptFile = cwd;
|
|
1053
|
-
}
|
|
1054
|
-
const commits = await getCommits(20, true);
|
|
1055
|
-
let generate = input.generate;
|
|
1056
|
-
if (generate) {
|
|
1057
|
-
if (!commits || commits.length < 5) {
|
|
1058
|
-
p7.log.error(dedent4`${color7.red("not enough commits to generate a prompt file.")}
|
|
1059
|
-
${color7.gray("at least 5 commits are required.")}`);
|
|
1060
|
-
return await exit(1);
|
|
1061
|
-
}
|
|
1062
|
-
} else if (commits && commits.length >= 5) {
|
|
1063
|
-
const shouldGenerate = await p7.confirm({
|
|
1064
|
-
message: "do you want to generate a prompt file based on existing commits?",
|
|
1065
|
-
initialValue: true
|
|
1066
|
-
});
|
|
1067
|
-
if (p7.isCancel(shouldGenerate)) {
|
|
1068
|
-
p7.log.error("aborted");
|
|
1069
|
-
return await exit(1);
|
|
1070
|
-
}
|
|
1071
|
-
generate = shouldGenerate;
|
|
1072
|
-
}
|
|
1073
|
-
const spin = p7.spinner();
|
|
1074
|
-
if (commits && generate) {
|
|
1075
|
-
spin.start("generating commit message guidelines");
|
|
1076
|
-
prompt = await generateCommitGuidelines(commits);
|
|
1077
|
-
spin.stop(color7.green("generated commit message guidelines!"));
|
|
1078
|
-
} else {
|
|
1079
|
-
prompt = EMPTY_TEMPLATE;
|
|
1080
|
-
}
|
|
1081
|
-
try {
|
|
1082
|
-
const dir = `${promptFile}/.noto`;
|
|
1083
|
-
await fs4.mkdir(dir, { recursive: true });
|
|
1084
|
-
const filePath = `${dir}/commit-prompt.md`;
|
|
1085
|
-
await fs4.writeFile(filePath, prompt, "utf-8");
|
|
1086
|
-
p7.log.success(dedent4`${color7.green("prompt file created!")}
|
|
1087
|
-
${color7.gray(filePath)}`);
|
|
1088
|
-
return await exit(0);
|
|
1089
|
-
} catch {
|
|
1090
|
-
p7.log.error(color7.red("failed to create the prompt file!"));
|
|
1091
|
-
}
|
|
1092
|
-
});
|
|
1093
|
-
|
|
1094
|
-
// src/commands/noto.ts
|
|
1095
|
-
import { z as z8 } from "zod";
|
|
1096
|
-
import * as p8 from "@clack/prompts";
|
|
1097
|
-
import color8 from "picocolors";
|
|
1098
|
-
import clipboard3 from "clipboardy";
|
|
1099
|
-
import { APICallError, RetryError } from "ai";
|
|
1100
|
-
var noto = authedGitProcedure.meta({
|
|
1101
|
-
description: "generate a commit message",
|
|
1102
|
-
default: true,
|
|
1103
|
-
diffRequired: true,
|
|
1104
|
-
promptRequired: true
|
|
1105
|
-
}).input(z8.object({
|
|
1106
|
-
message: z8.string().or(z8.boolean()).meta({
|
|
1107
|
-
description: "provide context for commit message",
|
|
1108
|
-
alias: "m"
|
|
1109
|
-
}),
|
|
1110
|
-
copy: z8.boolean().meta({
|
|
1111
|
-
description: "copy the generated message to clipboard",
|
|
1112
|
-
alias: "c"
|
|
1113
|
-
}),
|
|
1114
|
-
apply: z8.boolean().meta({ description: "commit the generated message", alias: "a" }),
|
|
1115
|
-
push: z8.boolean().meta({ description: "commit and push the changes", alias: "p" }),
|
|
1116
|
-
force: z8.boolean().meta({
|
|
1117
|
-
description: "bypass cache and force regeneration of commit message",
|
|
1118
|
-
alias: "f"
|
|
1119
|
-
}),
|
|
1120
|
-
manual: z8.boolean().meta({ description: "custom commit message" })
|
|
1121
|
-
})).mutation(async (opts) => {
|
|
1122
|
-
const { input, ctx } = opts;
|
|
1123
|
-
const spin = p8.spinner();
|
|
1124
|
-
try {
|
|
1125
|
-
const manual = input.manual;
|
|
1126
|
-
if (manual) {
|
|
1127
|
-
const message2 = await p8.text({
|
|
1128
|
-
message: "edit the generated commit message",
|
|
1129
|
-
placeholder: "chore: init repo"
|
|
1130
|
-
});
|
|
1131
|
-
if (p8.isCancel(message2)) {
|
|
1132
|
-
p8.log.error(color8.red("nothing changed!"));
|
|
1133
|
-
return await exit(1);
|
|
1134
|
-
}
|
|
1135
|
-
p8.log.step(color8.green(message2));
|
|
1136
|
-
await StorageManager.update((current) => ({
|
|
1137
|
-
...current,
|
|
1138
|
-
lastGeneratedMessage: message2
|
|
1139
|
-
}));
|
|
1140
|
-
const success = await commit(message2);
|
|
1141
|
-
if (success) {
|
|
1142
|
-
p8.log.step(color8.dim("commit successful"));
|
|
1143
|
-
} else {
|
|
1144
|
-
p8.log.error(color8.red("failed to commit changes"));
|
|
1145
|
-
}
|
|
1146
|
-
return await exit(0);
|
|
1147
|
-
}
|
|
1148
|
-
let context = input.message;
|
|
1149
|
-
if (typeof context === "string") {
|
|
1150
|
-
context = context.trim();
|
|
1151
|
-
} else if (typeof context === "boolean" && context === true) {
|
|
1152
|
-
const enteredContext = await p8.text({
|
|
1153
|
-
message: "provide context for the commit message",
|
|
1154
|
-
placeholder: "describe the changes"
|
|
1155
|
-
});
|
|
1156
|
-
if (p8.isCancel(enteredContext)) {
|
|
1157
|
-
p8.log.error(color8.red("nothing changed!"));
|
|
1158
|
-
return await exit(1);
|
|
1159
|
-
}
|
|
1160
|
-
context = enteredContext;
|
|
1161
|
-
}
|
|
1162
|
-
spin.start("generating commit message");
|
|
1163
|
-
let message = null;
|
|
1164
|
-
message = await generateCommitMessage(ctx.git.diff, ctx.noto.prompt, typeof context === "string" ? context : undefined, input.force);
|
|
1165
|
-
spin.stop(color8.white(message));
|
|
1166
|
-
const editedMessage = await p8.text({
|
|
1167
|
-
message: "edit the generated commit message",
|
|
1168
|
-
initialValue: message,
|
|
1169
|
-
placeholder: message
|
|
1170
|
-
});
|
|
1171
|
-
if (p8.isCancel(editedMessage)) {
|
|
1172
|
-
p8.log.error(color8.red("nothing changed!"));
|
|
1173
|
-
return await exit(1);
|
|
1174
|
-
}
|
|
1175
|
-
message = editedMessage;
|
|
1176
|
-
p8.log.step(color8.green(message));
|
|
1177
|
-
await StorageManager.update((current) => ({
|
|
1178
|
-
...current,
|
|
1179
|
-
lastGeneratedMessage: message
|
|
1180
|
-
}));
|
|
1181
|
-
if (input.copy) {
|
|
1182
|
-
clipboard3.writeSync(message);
|
|
1183
|
-
p8.log.step(color8.dim("copied commit message to clipboard"));
|
|
1184
|
-
}
|
|
1185
|
-
if (input.apply) {
|
|
1186
|
-
const success = await commit(message);
|
|
1187
|
-
if (success) {
|
|
1188
|
-
p8.log.step(color8.dim("commit successful"));
|
|
1189
|
-
} else {
|
|
1190
|
-
p8.log.error(color8.red("failed to commit changes"));
|
|
1191
|
-
}
|
|
1192
|
-
}
|
|
1193
|
-
if (input.push) {
|
|
1194
|
-
const success = await push();
|
|
1195
|
-
if (success) {
|
|
1196
|
-
p8.log.step(color8.dim("push successful"));
|
|
1197
|
-
} else {
|
|
1198
|
-
p8.log.error(color8.red("failed to push changes"));
|
|
1199
|
-
}
|
|
1200
|
-
}
|
|
1201
|
-
return await exit(0);
|
|
1202
|
-
} catch (e) {
|
|
1203
|
-
let msg;
|
|
1204
|
-
if (RetryError.isInstance(e) && APICallError.isInstance(e.lastError)) {
|
|
1205
|
-
msg = safeParseErrorMessage(e.lastError.responseBody);
|
|
1206
|
-
}
|
|
1207
|
-
const suffix = msg ? `
|
|
1208
|
-
${msg}` : "";
|
|
1209
|
-
spin.stop(color8.red(`failed to generate commit message${suffix}`), 1);
|
|
1210
|
-
await exit(1);
|
|
1211
|
-
}
|
|
1212
|
-
});
|
|
1213
|
-
function safeParseErrorMessage(body) {
|
|
1214
|
-
if (typeof body !== "string")
|
|
1215
|
-
return;
|
|
1216
|
-
try {
|
|
1217
|
-
const parsed = JSON.parse(body);
|
|
1218
|
-
return parsed?.error?.message ?? parsed?.message;
|
|
1219
|
-
} catch {
|
|
1220
|
-
return;
|
|
1221
|
-
}
|
|
1222
|
-
}
|
|
1223
|
-
|
|
1224
|
-
// src/commands/index.ts
|
|
1225
|
-
var commands = {
|
|
1226
|
-
checkout: checkout2,
|
|
1227
|
-
config,
|
|
1228
|
-
prev,
|
|
1229
|
-
init,
|
|
1230
|
-
noto
|
|
1231
|
-
};
|
|
1232
|
-
|
|
1233
|
-
// src/router.ts
|
|
1234
|
-
var router = t.router(commands);
|
|
1235
|
-
|
|
1236
|
-
// src/index.ts
|
|
1237
|
-
var args = process.argv.slice(2);
|
|
1238
|
-
if (args.includes("--version") || args.includes("-v")) {
|
|
1239
|
-
console.log(version);
|
|
1240
|
-
process.exit(0);
|
|
1241
|
-
}
|
|
1242
|
-
createCli({
|
|
1243
|
-
name: "noto",
|
|
1244
|
-
router,
|
|
1245
|
-
version
|
|
1246
|
-
}).run();
|
|
323
|
+
# When no guidelines are present, noto will use conventional commits format by default.`,Ve=Z.meta({description:"initialize noto in the repository"}).input(pe.object({root:pe.boolean().meta({description:"create the prompt file in the git root"}),generate:pe.boolean().meta({description:"generate a prompt file based on existing commits"})})).mutation(async(e)=>{let{input:t}=e,o=await K(),a=o,r=process.cwd(),n=await W(),s=null;if(n)if(!n.startsWith(r)){d.log.warn(V`${k.yellow("a prompt file already exists!")}
|
|
324
|
+
${k.gray(n)}`);let h=await d.confirm({message:"do you want to create in the current directory instead?",initialValue:!0});if(d.isCancel(h)||!h)return d.log.error("aborted"),await i(1);a=r}else return d.log.error(V`${k.red("a prompt file already exists.")}
|
|
325
|
+
${k.gray(n)}`),await i(1);if(o!==r&&!t.root){let h=await d.confirm({message:"do you want to create the prompt file in the git root?",initialValue:!0});if(d.isCancel(h))return d.log.error("aborted"),await i(1);if(!h)a=r}let w=await fe(20,!0),m=t.generate;if(m){if(!w||w.length<5)return d.log.error(V`${k.red("not enough commits to generate a prompt file.")}
|
|
326
|
+
${k.gray("at least 5 commits are required.")}`),await i(1)}else if(w&&w.length>=5){let h=await d.confirm({message:"do you want to generate a prompt file based on existing commits?",initialValue:!0});if(d.isCancel(h))return d.log.error("aborted"),await i(1);m=h}let G=d.spinner();if(w&&m)G.start("generating commit message guidelines"),s=await Be(w),G.stop(k.green("generated commit message guidelines!"));else s=Rt;try{let h=`${a}/.noto`;await qe.mkdir(h,{recursive:!0});let de=`${h}/commit-prompt.md`;return await qe.writeFile(de,s,"utf-8"),d.log.success(V`${k.green("prompt file created!")}
|
|
327
|
+
${k.gray(de)}`),await i(0)}catch{d.log.error(k.red("failed to create the prompt file!"))}});import{z as R}from"zod";import*as l from"@clack/prompts";import y from"picocolors";import Tt from"clipboardy";import{APICallError as $t,RetryError as Et}from"ai";var Ye=Z.meta({description:"generate a commit message",default:!0,diffRequired:!0,promptRequired:!0}).input(R.object({message:R.string().or(R.boolean()).meta({description:"provide context for commit message",alias:"m"}),copy:R.boolean().meta({description:"copy the generated message to clipboard",alias:"c"}),apply:R.boolean().meta({description:"commit the generated message",alias:"a"}),push:R.boolean().meta({description:"commit and push the changes",alias:"p"}),force:R.boolean().meta({description:"bypass cache and force regeneration of commit message",alias:"f"}),manual:R.boolean().meta({description:"custom commit message"})})).mutation(async(e)=>{let{input:t,ctx:o}=e,a=l.spinner();try{if(t.manual){let m=await l.text({message:"edit the generated commit message",placeholder:"chore: init repo"});if(l.isCancel(m))return l.log.error(y.red("nothing changed!")),await i(1);if(l.log.step(y.green(m)),await p.update((h)=>({...h,lastGeneratedMessage:m})),await L(m))l.log.step(y.dim("commit successful"));else l.log.error(y.red("failed to commit changes"));return await i(0)}let n=t.message;if(typeof n==="string")n=n.trim();else if(typeof n==="boolean"&&n===!0){let m=await l.text({message:"provide context for the commit message",placeholder:"describe the changes"});if(l.isCancel(m))return l.log.error(y.red("nothing changed!")),await i(1);n=m}a.start("generating commit message");let s=null;s=await ze(o.git.diff,o.noto.prompt,typeof n==="string"?n:void 0,t.force),a.stop(y.white(s));let w=await l.text({message:"edit the generated commit message",initialValue:s,placeholder:s});if(l.isCancel(w))return l.log.error(y.red("nothing changed!")),await i(1);if(s=w,l.log.step(y.green(s)),await p.update((m)=>({...m,lastGeneratedMessage:s})),t.copy)Tt.writeSync(s),l.log.step(y.dim("copied commit message to clipboard"));if(t.apply)if(await L(s))l.log.step(y.dim("commit successful"));else l.log.error(y.red("failed to commit changes"));if(t.push)if(await ye())l.log.step(y.dim("push successful"));else l.log.error(y.red("failed to push changes"));return await i(0)}catch(r){let n;if(Et.isInstance(r)&&$t.isInstance(r.lastError))n=Gt(r.lastError.responseBody);let s=n?`
|
|
328
|
+
${n}`:"";a.stop(y.red(`failed to generate commit message${s}`),1),await i(1)}});function Gt(e){if(typeof e!=="string")return;try{let t=JSON.parse(e);return t?.error?.message??t?.message}catch{return}}import{spawn as Ut}from"node:child_process";import*as N from"@clack/prompts";import oe from"picocolors";var Ke=x.meta({description:"upgrade noto"}).mutation(async()=>{let e=N.spinner();e.start("fetching latest version");let t=await J(!0,!0);if(!t)return e.stop(`You're already on the latest version of noto (${oe.dim(`which is ${U}`)})`),await i(0,!1);e.stop(`noto ${oe.green(t.latest)} is out! You are on ${oe.dim(t.current)}.`);let o=await Q();if(!o.updateCommand){if(o.updateMessage)return N.log.warn(o.updateMessage),await i(0,!1);return N.log.error("unable to determine update command for your installation."),await i(1,!1)}let a=Ut(o.updateCommand,{stdio:"pipe",shell:!0});e.start("upgrading noto");try{await new Promise((r,n)=>{a.on("close",(s)=>{if(s===0)r();else n()})}),e.stop(oe.green("noto has been updated successfully!"))}catch{return N.log.error(`automatic update failed. please try updating manually by running: ${o.updateCommand}`),await i(1,!1)}return await i(0,!1)});var _e={checkout:Te,config:Oe,prev:Fe,init:Ve,noto:Ye,upgrade:Ke};var He=$.router(_e);var We=process.argv.slice(2),Je="1.3.4-beta.0";if(We.includes("--version")||We.includes("-v"))console.log(Je),process.exit(0);xe();ie(!0);Pt({name:"noto",router:He,version:Je}).run();
|
|
329
|
+
|
|
330
|
+
// Made by a human on earth!
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@snelusha/noto",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.4-beta.0",
|
|
4
4
|
"description": "Generate clean commit messages in a snap! ✨",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -23,11 +23,11 @@
|
|
|
23
23
|
"module": "dist/index.js",
|
|
24
24
|
"types": "dist/index.d.ts",
|
|
25
25
|
"bin": {
|
|
26
|
-
"noto": "
|
|
26
|
+
"noto": "dist/index.js"
|
|
27
27
|
},
|
|
28
28
|
"scripts": {
|
|
29
29
|
"build": "bunup --clean",
|
|
30
|
-
"test": "bunup --clean && vitest",
|
|
30
|
+
"test": "bunup --clean && vitest run",
|
|
31
31
|
"clean": "git clean -xdf .cache .turbo node_modules"
|
|
32
32
|
},
|
|
33
33
|
"files": [
|
|
@@ -42,7 +42,8 @@
|
|
|
42
42
|
"cli"
|
|
43
43
|
],
|
|
44
44
|
"devDependencies": {
|
|
45
|
-
"@types/node": "^24.10.
|
|
45
|
+
"@types/node": "^24.10.1",
|
|
46
|
+
"@types/semver": "^7.7.1",
|
|
46
47
|
"bunup": "^0.15.7",
|
|
47
48
|
"typescript": "^5.9.3",
|
|
48
49
|
"vitest": "^4.0.3"
|
|
@@ -55,7 +56,9 @@
|
|
|
55
56
|
"ai": "^5.0.80",
|
|
56
57
|
"clipboardy": "^5.0.0",
|
|
57
58
|
"dedent": "^1.7.0",
|
|
59
|
+
"latest-version": "^9.0.0",
|
|
58
60
|
"picocolors": "^1.1.1",
|
|
61
|
+
"semver": "^7.7.3",
|
|
59
62
|
"simple-git": "^3.28.0",
|
|
60
63
|
"superjson": "^2.2.3",
|
|
61
64
|
"tinyexec": "^1.0.1",
|
package/bin/noto.mjs
DELETED