@snelusha/noto 1.3.2 → 1.3.3
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/dist/index.js +25 -942
- package/package.json +4 -4
- package/bin/noto.mjs +0 -4
package/dist/index.js
CHANGED
|
@@ -1,626 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
import
|
|
8
|
-
import { initTRPC } from "@trpc/server";
|
|
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";import qe from"node:fs/promises";import{initTRPC as Ve}from"@trpc/server";import*as $ from"@clack/prompts";import b from"picocolors";import H from"dedent";import Oe from"simple-git";var S=Oe(),oe=async()=>{return S.checkIsRepo()},D=async()=>{try{return await S.revparse(["--show-toplevel"])}catch{return null}};var re=async(t=20,e=!1)=>{try{let i=e?{maxCount:t,"--no-merges":null}:{maxCount:t};return(await S.log(i)).all.map((o)=>o.message)}catch{return null}};var ae=async()=>{try{return S.diff(["--cached","--",":!*.lock"])}catch{return null}},E=async(t,e)=>{try{let i=e?{"--amend":null}:void 0,{summary:{changes:a}}=await S.commit(t,void 0,i);return Boolean(a)}catch{return!1}},ie=async()=>{try{let t=await S.push();return t.update||t.pushed&&t.pushed.length>0}catch{return!1}},se=async()=>{try{return(await S.branch()).current}catch{return null}},ne=async(t)=>{try{let e=await S.branch();return t?e.all:Object.keys(e.branches).filter((i)=>!i.startsWith("remotes/"))}catch{return null}},_=async(t)=>{try{return await S.checkout(t,{}),!0}catch{return!1}},W=async(t)=>{try{return await S.checkoutLocalBranch(t),!0}catch{return!1}};import ze from"os";import{join as De,resolve as Ue}from"path";import{z as O}from"zod";import{promises as U}from"fs";import{dirname as Fe}from"path";function L(t){let{schema:e,path:i}=t;return class{static storagePath=i;static storage={};static async load(){try{await U.access(this.storagePath);let o=await U.readFile(this.storagePath,"utf-8"),n=o?JSON.parse(o):{},s=e.safeParse(n);this.storage=s.success?s.data:{}}catch{this.storage={}}return this.storage}static async save(){try{let o=Fe(this.storagePath);await U.mkdir(o,{recursive:!0});let n=JSON.stringify(this.storage,null,2);await U.writeFile(this.storagePath,n,"utf-8")}catch{}}static async update(o){try{await this.load();let n=await o(this.storage),s=e.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(o){this.storagePath=o}static get raw(){return this.storage}}}import{z as Ne}from"zod";var ce=Ne.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 Le=O.object({llm:O.object({apiKey:O.string().optional(),model:ce.optional().or(O.string())}).optional(),lastGeneratedMessage:O.string().optional()}),p=L({path:Ue(De(ze.homedir(),".config","noto"),".notorc"),schema:Le});import Be from"node:fs/promises";import T from"node:path";import{fileURLToPath as je}from"node:url";var me=(t)=>t instanceof URL?je(t):t;async function le(t,e={}){let i=T.resolve(me(e.cwd??"")),{root:a}=T.parse(i);e.stopAt=T.resolve(me(e.stopAt??a));let o=T.isAbsolute(t);while(i){let n=o?t:T.join(i,t);try{let s=await Be.stat(n);if(e.type===void 0||e.type==="file"&&s.isFile()||e.type==="directory"&&s.isDirectory())return n}catch{}if(i===e.stopAt||i===a)break;i=T.dirname(i)}}var B=async()=>{let t=await D();return await le(".noto/commit-prompt.md",{stopAt:t||process.cwd(),type:"file"})};var r=async(t)=>{await new Promise((e)=>setTimeout(e,1)),console.log(),process.exit(t)};var A=Ve.meta().create({defaultMeta:{intro:!0,authRequired:!0,repoRequired:!0,diffRequired:!1,promptRequired:!1}}),pe=A.middleware(async(t)=>{let{meta:e,next:i}=t,a=await p.get(),o=process.env.NOTO_API_KEY||a.llm?.apiKey;if(e?.authRequired&&!o)return $.log.error(H`${b.red("noto api key is missing.")}
|
|
3
|
+
${b.dim(`run ${b.cyan("`noto config key`")} to set it up.`)}`),await r(1);return i()}),de=A.middleware(async(t)=>{let{meta:e,next:i}=t,a=await oe();if(e?.repoRequired&&!a)return $.log.error(H`${b.red("no git repository found in cwd.")}
|
|
4
|
+
${b.dim(`run ${b.cyan("`git init`")} to initialize a new repository.`)}`),await r(1);let o=a&&await ae();if(e?.diffRequired&&!o)return $.log.error(H`${b.red("no staged changes found.")}
|
|
5
|
+
${b.dim(`run ${b.cyan("`git add <file>`")} or ${b.cyan("`git add .`")} to stage changes.`)}`),await r(1);let n=null;if(e?.promptRequired){let s=await B();if(s)try{n=await qe.readFile(s,"utf-8")}catch{}}return i({ctx:{noto:{prompt:n},git:{isRepository:a,diff:o}}})}),k=A.procedure.use((t)=>{let{meta:e,next:i}=t;if(e?.intro)console.log(),$.intro(`${b.bgCyan(b.black(" @snelusha/noto "))}`);return i()}),Bt=k.use(pe),j=k.use(de),q=k.use(pe).use(de);import{z as G}from"zod";import*as c from"@clack/prompts";import u from"picocolors";import Ke from"clipboardy";var ue=j.meta({description:"checkout a branch"}).input(G.object({copy:G.boolean().meta({description:"copy the selected branch to clipboard",alias:"c"}),create:G.union([G.boolean(),G.string()]).optional().meta({description:"create a new branch",alias:"b"}),branch:G.string().optional().meta({positional:!0})})).mutation(async(t)=>{let{input:e}=t,i=await ne();if(!i)return c.log.error("failed to fetch branches"),await r(1);let a=await se(),o=typeof e.create==="string"?e.create:e.branch;if((e.create===!0||typeof e.create==="string")&&o){if(i.includes(o))return c.log.error(`branch ${u.red(o)} already exists in the repository`),await r(1);if(!await W(o))return c.log.error(`failed to create and checkout ${u.bold(o)}`),await r(1);return c.log.success(`created and checked out ${u.green(o)}`),await r(0)}if(o){if(!i.includes(o)){c.log.error(`branch ${u.red(o)} does not exist in the repository`);let I=await c.confirm({message:`do you want to create branch ${u.green(o)}?`});if(c.isCancel(I))return c.log.error("aborted"),await r(1);if(I){if(!await W(o))return c.log.error(`failed to create and checkout ${u.bold(o)}`),await r(1);return c.log.success(`created and checked out ${u.green(o)}`),await r(0)}return await r(1)}if(o===a)return c.log.error(`${u.red("already on branch")} ${u.green(o)}`),await r(1);if(!await _(o))return c.log.error(`failed to checkout ${u.bold(o)}`),await r(1);return c.log.success(`checked out ${u.green(o)}`),await r(0)}if(i.length===0)return c.log.error("no branches found in the repository"),await r(1);let s=await c.select({message:"select a branch to checkout",options:i.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 r(1);if(!s)return c.log.error("no branch selected"),await r(1);if(e.copy)return Ke.writeSync(s),c.log.success(`copied ${u.green(s)} to clipboard`),await r(0);if(s===a)return c.log.error(`${u.red("already on branch")}`),await r(1);if(!await _(s))return c.log.error(`failed to checkout ${u.bold(s)}`),await r(1);c.log.success(`checked out ${u.green(s)}`),await r(0)});import{z as Ye}from"zod";import*as x from"@clack/prompts";import J from"picocolors";var ge=k.meta({description:"configure noto api key"}).input(Ye.string().optional().describe("apiKey")).mutation(async(t)=>{let{input:e}=t,i=e;if((await p.get()).llm?.apiKey){let a=await x.confirm({message:"noto api key already configured, do you want to update it?"});if(x.isCancel(a)||!a)return x.log.error(J.red("nothing changed!")),await r(1)}if(!i){let a=await x.text({message:"enter your noto api key"});if(x.isCancel(a))return x.log.error(J.red("nothing changed!")),await r(1);i=a}await p.update((a)=>({...a,llm:{...a.llm,apiKey:i}})),x.log.success(J.green("noto api key configured!")),await r(0)});import*as C from"@clack/prompts";import X from"picocolors";import{createGoogleGenerativeAI as _e}from"@ai-sdk/google";var v=_e({apiKey:process.env.NOTO_API_KEY||(await p.get()).llm?.apiKey||"api-key"}),fe="gemini-2.0-flash",V={"gemini-1.5-flash":v("gemini-1.5-flash"),"gemini-1.5-flash-latest":v("gemini-1.5-flash-latest"),"gemini-1.5-flash-8b":v("gemini-1.5-flash-8b"),"gemini-1.5-flash-8b-latest":v("gemini-1.5-flash-8b-latest"),"gemini-1.5-pro":v("gemini-1.5-pro"),"gemini-1.5-pro-latest":v("gemini-1.5-pro-latest"),"gemini-2.0-flash-001":v("gemini-2.0-flash-001"),"gemini-2.0-flash":v("gemini-2.0-flash"),"gemini-2.0-flash-lite-preview-02-05":v("gemini-2.0-flash-lite-preview-02-05"),"gemini-2.5-flash-preview-04-17":v("gemini-2.5-flash-preview-04-17"),"gemini-2.5-pro-preview-05-06":v("gemini-2.5-pro-preview-05-06")},We=Object.keys(V),Q=async()=>{let t=(await p.get()).llm?.model;if(!t||!We.includes(t))t=fe,await p.update((e)=>({...e,llm:{...e.llm,model:fe}}));return V[t]};var he=k.meta({description:"configure model"}).mutation(async()=>{let t=await C.select({message:"select a model",initialValue:(await p.get()).llm?.model,options:Object.keys(V).map((e)=>({label:e,value:e}))});if(C.isCancel(t))return C.log.error(X.red("nothing changed!")),await r(1);if(t==="gemini-2.5-pro-preview-05-06"){let e=await C.confirm({message:"this model does not have free quota tier, do you want to continue?"});if(C.isCancel(e)||!e)return C.log.error(X.red("nothing changed!")),await r(1)}await p.update((e)=>({...e,llm:{...e.llm,model:t}})),C.log.success(X.green("model configured!")),await r(0)});import*as P from"@clack/prompts";import ye from"picocolors";var we=k.meta({description:"reset the configuration"}).mutation(async()=>{let t=await P.confirm({message:"are you sure you want to reset the configuration?"});if(P.isCancel(t)||!t)return P.log.error(ye.red("nothing changed!")),await r(1);await p.clear(),P.log.success(ye.green("configuration reset!")),await r(0)});var be=A.router({key:ge,model:he,reset:we});import{z as F}from"zod";import*as f from"@clack/prompts";import g from"picocolors";import xe from"dedent";import He from"clipboardy";var ve=j.meta({description:"access the last generated commit",repoRequired:!1}).input(F.object({copy:F.boolean().meta({description:"copy the last commit to clipboard",alias:"c"}),apply:F.boolean().meta({description:"commit the last generated message",alias:"a"}),edit:F.boolean().meta({description:"edit the last generated commit message",alias:"e"}),amend:F.boolean().meta({description:"amend the last commit with the last message"})})).mutation(async(t)=>{let{input:e,ctx:i}=t,a=(await p.get()).lastGeneratedMessage;if(!a)return f.log.error(g.red("no previous commit message found")),await r(1);let{edit:o,amend:n}=e;if(n&&!o)return f.log.error(g.red("the --amend option requires the --edit option")),await r(1);if(f.log.step(o?g.white(a):g.green(a)),o){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 r(1);a=s,await p.update((w)=>({...w,lastGeneratedMessage:s})),f.log.step(g.green(a))}if(e.copy)He.writeSync(a),f.log.step(g.dim("copied last generated commit message to clipboard"));if(e.apply||n){if(!i.git.isRepository)return f.log.error(xe`${g.red("no git repository found in cwd.")}
|
|
6
|
+
${g.dim(`run ${g.cyan("`git init`")} to initialize a new repository.`)}`),await r(1);if(!i.git.diff&&!n)return f.log.error(xe`${g.red("no staged changes found.")}
|
|
7
|
+
${g.dim(`run ${g.cyan("`git add <file>`")} or ${g.cyan("`git add .`")} to stage changes.`)}`),await r(1);if(await E(a,n))f.log.step(g.dim("commit successful"));else f.log.error(g.red("failed to commit changes"))}return await r(0)});import Ae from"node:fs/promises";import{z as ee}from"zod";import*as d from"@clack/prompts";import M from"picocolors";import z from"dedent";import{generateObject as Me,wrapLanguageModel as tt}from"ai";import Y from"zod";import N from"dedent";import Se from"superjson";import{createHash as Je}from"crypto";function Ce(t){let e=Buffer.from(t,"utf-8"),i=Buffer.from(`blob ${e.length}\x00`,"utf-8");return Je("sha1").update(i).update(e).digest("hex")}import Qe from"os";import{join as Xe,resolve as Ze}from"path";import{z as K}from"zod";var et=K.object({commitGenerationCache:K.record(K.string(),K.string()).default({})}),Z=L({path:Ze(Xe(Qe.homedir(),".cache","noto"),"cache"),schema:et});var ot=N`
|
|
624
8
|
# System Instruction for Noto
|
|
625
9
|
|
|
626
10
|
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 +75,7 @@ Or whatever format matches the user's guidelines. **Must be single-line only.**
|
|
|
691
75
|
- Don't use generic or vague descriptions
|
|
692
76
|
|
|
693
77
|
**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`
|
|
78
|
+
`,rt=N`
|
|
696
79
|
# Commit Message Guidelines
|
|
697
80
|
|
|
698
81
|
## Format
|
|
@@ -764,8 +147,7 @@ For breaking changes, add \`!\` after the type/scope:
|
|
|
764
147
|
## Additional Notes
|
|
765
148
|
- If a commit addresses a specific issue, you can reference it in the description (e.g., \`fix: resolve memory leak (fixes #123)\`)
|
|
766
149
|
- 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`
|
|
150
|
+
- Write commits as if completing the sentence: "If applied, this commit will..."`,at=N`
|
|
769
151
|
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
152
|
|
|
771
153
|
## Task
|
|
@@ -921,326 +303,27 @@ Start with action verb (add, implement, resolve, update, simplify). Be specific
|
|
|
921
303
|
- The output will be stored as \`.noto/commit-prompt.md\` and used by an AI to generate commits
|
|
922
304
|
|
|
923
305
|
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`
|
|
306
|
+
`,it={wrapGenerate:async({doGenerate:t,params:e})=>{let i=Ce(JSON.stringify(e)),a=(await Z.get()).commitGenerationCache;if(a&&i in a)return Se.parse(a[i]);let o=await t();return await Z.update((n)=>({...n,commitGenerationCache:{[i]:Se.stringify(o)}})),o}},ke=async(t,e,i,a=!1)=>{let o=await Q(),{object:n}=await Me({model:!a?tt({model:o,middleware:it}):o,schema:Y.object({message:Y.string()}),messages:[{role:"system",content:ot},{role:"user",content:N`
|
|
963
307
|
USER GUIDELINES:
|
|
964
|
-
${
|
|
965
|
-
${
|
|
308
|
+
${e??rt}
|
|
309
|
+
${i?`
|
|
966
310
|
USER CONTEXT:
|
|
967
|
-
${
|
|
311
|
+
${i}`:""}
|
|
968
312
|
|
|
969
313
|
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`
|
|
314
|
+
${t}
|
|
315
|
+
`}]});return n.message.trim()},Re=async(t)=>{let e=await Q(),{object:i}=await Me({model:e,schema:Y.object({prompt:Y.string()}),messages:[{role:"system",content:at},{role:"user",content:N`
|
|
992
316
|
COMMIT HISTORY:
|
|
993
|
-
${
|
|
994
|
-
`)}`
|
|
995
|
-
}
|
|
996
|
-
]
|
|
997
|
-
});
|
|
998
|
-
return object.prompt.trim();
|
|
999
|
-
};
|
|
1000
|
-
|
|
1001
|
-
// src/commands/init.ts
|
|
1002
|
-
var EMPTY_TEMPLATE = dedent4`
|
|
317
|
+
${t.join(`
|
|
318
|
+
`)}`}]});return i.prompt.trim()};var st=z`
|
|
1003
319
|
# Commit Message Guidelines
|
|
1004
320
|
|
|
1005
321
|
# 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();
|
|
322
|
+
# When no guidelines are present, noto will use conventional commits format by default.`,Pe=q.meta({description:"initialize noto in the repository"}).input(ee.object({root:ee.boolean().meta({description:"create the prompt file in the git root"}),generate:ee.boolean().meta({description:"generate a prompt file based on existing commits"})})).mutation(async(t)=>{let{input:e}=t,i=await D(),a=i,o=process.cwd(),n=await B(),s=null;if(n)if(!n.startsWith(o)){d.log.warn(z`${M.yellow("a prompt file already exists!")}
|
|
323
|
+
${M.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 r(1);a=o}else return d.log.error(z`${M.red("a prompt file already exists.")}
|
|
324
|
+
${M.gray(n)}`),await r(1);if(i!==o&&!e.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 r(1);if(!h)a=o}let w=await re(20,!0),m=e.generate;if(m){if(!w||w.length<5)return d.log.error(z`${M.red("not enough commits to generate a prompt file.")}
|
|
325
|
+
${M.gray("at least 5 commits are required.")}`),await r(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 r(1);m=h}let I=d.spinner();if(w&&m)I.start("generating commit message guidelines"),s=await Re(w),I.stop(M.green("generated commit message guidelines!"));else s=st;try{let h=`${a}/.noto`;await Ae.mkdir(h,{recursive:!0});let te=`${h}/commit-prompt.md`;return await Ae.writeFile(te,s,"utf-8"),d.log.success(z`${M.green("prompt file created!")}
|
|
326
|
+
${M.gray(te)}`),await r(0)}catch{d.log.error(M.red("failed to create the prompt file!"))}});import{z as R}from"zod";import*as l from"@clack/prompts";import y from"picocolors";import nt from"clipboardy";import{APICallError as ct,RetryError as mt}from"ai";var Ie=q.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(t)=>{let{input:e,ctx:i}=t,a=l.spinner();try{if(e.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 r(1);if(l.log.step(y.green(m)),await p.update((h)=>({...h,lastGeneratedMessage:m})),await E(m))l.log.step(y.dim("commit successful"));else l.log.error(y.red("failed to commit changes"));return await r(0)}let n=e.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 r(1);n=m}a.start("generating commit message");let s=null;s=await ke(i.git.diff,i.noto.prompt,typeof n==="string"?n:void 0,e.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 r(1);if(s=w,l.log.step(y.green(s)),await p.update((m)=>({...m,lastGeneratedMessage:s})),e.copy)nt.writeSync(s),l.log.step(y.dim("copied commit message to clipboard"));if(e.apply)if(await E(s))l.log.step(y.dim("commit successful"));else l.log.error(y.red("failed to commit changes"));if(e.push)if(await ie())l.log.step(y.dim("push successful"));else l.log.error(y.red("failed to push changes"));return await r(0)}catch(o){let n;if(mt.isInstance(o)&&ct.isInstance(o.lastError))n=lt(o.lastError.responseBody);let s=n?`
|
|
327
|
+
${n}`:"";a.stop(y.red(`failed to generate commit message${s}`),1),await r(1)}});function lt(t){if(typeof t!=="string")return;try{let e=JSON.parse(t);return e?.error?.message??e?.message}catch{return}}var Te={checkout:ue,config:be,prev:ve,init:Pe,noto:Ie};var $e=A.router(Te);var Ge=process.argv.slice(2),Ee="1.3.3";if(Ge.includes("--version")||Ge.includes("-v"))console.log(Ee),process.exit(0);pt({name:"noto",router:$e,version:Ee}).run();
|
|
328
|
+
|
|
329
|
+
// 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.3",
|
|
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,7 @@
|
|
|
42
42
|
"cli"
|
|
43
43
|
],
|
|
44
44
|
"devDependencies": {
|
|
45
|
-
"@types/node": "^24.10.
|
|
45
|
+
"@types/node": "^24.10.1",
|
|
46
46
|
"bunup": "^0.15.7",
|
|
47
47
|
"typescript": "^5.9.3",
|
|
48
48
|
"vitest": "^4.0.3"
|
package/bin/noto.mjs
DELETED