@snelusha/noto 1.2.9 → 1.3.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -42
- package/dist/index.js +1046 -934
- package/package.json +16 -12
- package/dist/index.d.ts +0 -0
package/dist/index.js
CHANGED
|
@@ -1,50 +1,95 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
|
-
import
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
// src/utils/parser.ts
|
|
6
|
-
import arg from "arg";
|
|
7
|
-
var parse = (schema, raw) => {
|
|
8
|
-
const args = arg(schema, { argv: raw, permissive: true });
|
|
9
|
-
return {
|
|
10
|
-
command: args._[0],
|
|
11
|
-
options: args
|
|
12
|
-
};
|
|
13
|
-
};
|
|
14
|
-
var safeParse = (schema, raw) => {
|
|
15
|
-
let current = { ...schema };
|
|
16
|
-
let iterations = 0;
|
|
17
|
-
const maxIterations = Object.keys(current).filter((key) => current[key] === String).length;
|
|
18
|
-
while (iterations++ < maxIterations) {
|
|
19
|
-
try {
|
|
20
|
-
return parse(current, raw);
|
|
21
|
-
} catch (error) {
|
|
22
|
-
if (error.code === "ARG_MISSING_REQUIRED_LONGARG") {
|
|
23
|
-
const match = error.message.match(/(--\w[\w-]*)/);
|
|
24
|
-
if (match) {
|
|
25
|
-
const missingFlag = match[0];
|
|
26
|
-
if (current[missingFlag] === String) {
|
|
27
|
-
current[missingFlag] = Boolean;
|
|
28
|
-
continue;
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
throw error;
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
return parse(current, raw);
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
// src/commands/noto.ts
|
|
39
|
-
import * as p3 from "@clack/prompts";
|
|
40
|
-
import color3 from "picocolors";
|
|
41
|
-
import clipboard from "clipboardy";
|
|
2
|
+
import { createCli } from "trpc-cli";
|
|
3
|
+
// package.json
|
|
4
|
+
var version = "1.3.0-beta.1";
|
|
42
5
|
|
|
43
|
-
// src/
|
|
6
|
+
// src/trpc.ts
|
|
7
|
+
import fs3 from "node:fs/promises";
|
|
8
|
+
import { initTRPC } from "@trpc/server";
|
|
44
9
|
import * as p from "@clack/prompts";
|
|
45
10
|
import color from "picocolors";
|
|
46
11
|
import dedent from "dedent";
|
|
47
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 = 10) => {
|
|
27
|
+
try {
|
|
28
|
+
const log = await git.log({ maxCount: limit });
|
|
29
|
+
return log.all.map((c) => c.message);
|
|
30
|
+
} catch {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
var getStagedDiff = async () => {
|
|
35
|
+
try {
|
|
36
|
+
return git.diff(["--cached", "--", ":!*.lock"]);
|
|
37
|
+
} catch {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
var commit = async (message, amend) => {
|
|
42
|
+
try {
|
|
43
|
+
const options = amend ? { "--amend": null } : undefined;
|
|
44
|
+
const {
|
|
45
|
+
summary: { changes }
|
|
46
|
+
} = await git.commit(message, undefined, options);
|
|
47
|
+
return Boolean(changes);
|
|
48
|
+
} catch {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
var push = async () => {
|
|
53
|
+
try {
|
|
54
|
+
const result = await git.push();
|
|
55
|
+
return result.update || result.pushed && result.pushed.length > 0;
|
|
56
|
+
} catch {
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
var getCurrentBranch = async () => {
|
|
61
|
+
try {
|
|
62
|
+
const branch = await git.branch();
|
|
63
|
+
return branch.current;
|
|
64
|
+
} catch {
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
var getBranches = async (remote) => {
|
|
69
|
+
try {
|
|
70
|
+
const branches = await git.branch();
|
|
71
|
+
return remote ? branches.all : Object.keys(branches.branches).filter((b) => !b.startsWith("remotes/"));
|
|
72
|
+
} catch {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
var checkout = async (branch) => {
|
|
77
|
+
try {
|
|
78
|
+
await git.checkout(branch, {});
|
|
79
|
+
return true;
|
|
80
|
+
} catch {
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
var checkoutLocalBranch = async (branch) => {
|
|
85
|
+
try {
|
|
86
|
+
await git.checkoutLocalBranch(branch);
|
|
87
|
+
return true;
|
|
88
|
+
} catch {
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
|
|
48
93
|
// src/utils/storage.ts
|
|
49
94
|
import os from "os";
|
|
50
95
|
import { dirname, join, resolve } from "path";
|
|
@@ -73,11 +118,12 @@ var StorageSchema = z2.object({
|
|
|
73
118
|
apiKey: z2.string().optional(),
|
|
74
119
|
model: AvailableModelsSchema.optional().or(z2.string())
|
|
75
120
|
}).optional(),
|
|
76
|
-
lastGeneratedMessage: z2.string().optional()
|
|
121
|
+
lastGeneratedMessage: z2.string().optional(),
|
|
122
|
+
cache: z2.record(z2.string(), z2.string()).optional()
|
|
77
123
|
});
|
|
78
124
|
|
|
79
125
|
class StorageManager {
|
|
80
|
-
static storagePath = resolve(join(os.homedir(), ".noto"), "
|
|
126
|
+
static storagePath = resolve(join(os.homedir(), ".config", "noto"), ".notorc");
|
|
81
127
|
static storage = {};
|
|
82
128
|
static async load() {
|
|
83
129
|
try {
|
|
@@ -123,6 +169,38 @@ class StorageManager {
|
|
|
123
169
|
}
|
|
124
170
|
}
|
|
125
171
|
|
|
172
|
+
// src/utils/fs.ts
|
|
173
|
+
import fs2 from "node:fs/promises";
|
|
174
|
+
import path from "node:path";
|
|
175
|
+
import { fileURLToPath } from "node:url";
|
|
176
|
+
var toPath = (urlOrPath) => urlOrPath instanceof URL ? fileURLToPath(urlOrPath) : urlOrPath;
|
|
177
|
+
async function findUp(name, options = {}) {
|
|
178
|
+
let directory = path.resolve(toPath(options.cwd ?? ""));
|
|
179
|
+
const { root } = path.parse(directory);
|
|
180
|
+
options.stopAt = path.resolve(toPath(options.stopAt ?? root));
|
|
181
|
+
const isAbsoluteName = path.isAbsolute(name);
|
|
182
|
+
while (directory) {
|
|
183
|
+
const filePath = isAbsoluteName ? name : path.join(directory, name);
|
|
184
|
+
try {
|
|
185
|
+
const stats = await fs2.stat(filePath);
|
|
186
|
+
if (options.type === undefined || options.type === "file" && stats.isFile() || options.type === "directory" && stats.isDirectory())
|
|
187
|
+
return filePath;
|
|
188
|
+
} catch {}
|
|
189
|
+
if (directory === options.stopAt || directory === root)
|
|
190
|
+
break;
|
|
191
|
+
directory = path.dirname(directory);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// src/utils/prompt.ts
|
|
196
|
+
var getPromptFile = async () => {
|
|
197
|
+
const root = await getGitRoot();
|
|
198
|
+
return await findUp(".noto/commit-prompt.md", {
|
|
199
|
+
stopAt: root || process.cwd(),
|
|
200
|
+
type: "file"
|
|
201
|
+
});
|
|
202
|
+
};
|
|
203
|
+
|
|
126
204
|
// src/utils/process.ts
|
|
127
205
|
var exit = async (code) => {
|
|
128
206
|
await new Promise((resolve2) => setTimeout(resolve2, 1));
|
|
@@ -130,149 +208,230 @@ var exit = async (code) => {
|
|
|
130
208
|
process.exit(code);
|
|
131
209
|
};
|
|
132
210
|
|
|
133
|
-
// src/
|
|
134
|
-
var
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
211
|
+
// src/trpc.ts
|
|
212
|
+
var t = initTRPC.meta().create({
|
|
213
|
+
defaultMeta: {
|
|
214
|
+
intro: true,
|
|
215
|
+
authRequired: true,
|
|
216
|
+
repoRequired: true,
|
|
217
|
+
diffRequired: false,
|
|
218
|
+
promptRequired: false
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
var authMiddleware = t.middleware(async (opts) => {
|
|
222
|
+
const { meta, next } = opts;
|
|
223
|
+
const storage = await StorageManager.get();
|
|
224
|
+
const apiKey = process.env.NOTO_API_KEY || storage.llm?.apiKey;
|
|
225
|
+
if (meta?.authRequired && !apiKey) {
|
|
226
|
+
p.log.error(dedent`${color.red("noto api key is missing.")}
|
|
140
227
|
${color.dim(`run ${color.cyan("`noto config key`")} to set it up.`)}`);
|
|
141
|
-
|
|
228
|
+
return await exit(1);
|
|
229
|
+
}
|
|
230
|
+
return next();
|
|
231
|
+
});
|
|
232
|
+
var gitMiddleware = t.middleware(async (opts) => {
|
|
233
|
+
const { meta, next } = opts;
|
|
234
|
+
const isRepository = await isGitRepository();
|
|
235
|
+
if (meta?.repoRequired && !isRepository) {
|
|
236
|
+
p.log.error(dedent`${color.red("no git repository found in cwd.")}
|
|
237
|
+
${color.dim(`run ${color.cyan("`git init`")} to initialize a new repository.`)}`);
|
|
238
|
+
return await exit(1);
|
|
239
|
+
}
|
|
240
|
+
const diff = isRepository && await getStagedDiff();
|
|
241
|
+
if (meta?.diffRequired && !diff) {
|
|
242
|
+
p.log.error(dedent`${color.red("no staged changes found.")}
|
|
243
|
+
${color.dim(`run ${color.cyan("`git add <file>`")} or ${color.cyan("`git add .`")} to stage changes.`)}`);
|
|
244
|
+
return await exit(1);
|
|
245
|
+
}
|
|
246
|
+
let prompt = null;
|
|
247
|
+
if (meta?.promptRequired) {
|
|
248
|
+
const promptPath = await getPromptFile();
|
|
249
|
+
if (promptPath) {
|
|
250
|
+
try {
|
|
251
|
+
prompt = await fs3.readFile(promptPath, "utf-8");
|
|
252
|
+
} catch {}
|
|
142
253
|
}
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
254
|
+
}
|
|
255
|
+
return next({
|
|
256
|
+
ctx: {
|
|
257
|
+
noto: {
|
|
258
|
+
prompt
|
|
259
|
+
},
|
|
260
|
+
git: {
|
|
261
|
+
isRepository,
|
|
262
|
+
diff
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
});
|
|
266
|
+
});
|
|
267
|
+
var baseProcedure = t.procedure.use((opts) => {
|
|
268
|
+
const { meta, next } = opts;
|
|
269
|
+
if (meta?.intro) {
|
|
270
|
+
console.log();
|
|
271
|
+
p.intro(`${color.bgCyan(color.black(" @snelusha/noto "))}`);
|
|
272
|
+
}
|
|
273
|
+
return next();
|
|
274
|
+
});
|
|
275
|
+
var authProcedure = baseProcedure.use(authMiddleware);
|
|
276
|
+
var gitProcedure = baseProcedure.use(gitMiddleware);
|
|
277
|
+
var authedGitProcedure = baseProcedure.use(authMiddleware).use(gitMiddleware);
|
|
146
278
|
|
|
147
|
-
// src/
|
|
279
|
+
// src/commands/checkout.ts
|
|
280
|
+
import { z as z3 } from "zod";
|
|
148
281
|
import * as p2 from "@clack/prompts";
|
|
149
282
|
import color2 from "picocolors";
|
|
150
|
-
import
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
if (regex.test(message))
|
|
167
|
-
return 0;
|
|
168
|
-
throw error;
|
|
283
|
+
import clipboard from "clipboardy";
|
|
284
|
+
var checkout2 = gitProcedure.meta({
|
|
285
|
+
description: "checkout a branch"
|
|
286
|
+
}).input(z3.object({
|
|
287
|
+
copy: z3.boolean().meta({
|
|
288
|
+
description: "copy the selected branch to clipboard",
|
|
289
|
+
alias: "c"
|
|
290
|
+
}),
|
|
291
|
+
create: z3.union([z3.boolean(), z3.string()]).optional().meta({ description: "create a new branch", alias: "b" }),
|
|
292
|
+
branch: z3.string().optional().meta({ positional: true })
|
|
293
|
+
})).mutation(async (opts) => {
|
|
294
|
+
const { input } = opts;
|
|
295
|
+
const branches = await getBranches();
|
|
296
|
+
if (!branches) {
|
|
297
|
+
p2.log.error("failed to fetch branches");
|
|
298
|
+
return await exit(1);
|
|
169
299
|
}
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
const
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
300
|
+
const currentBranch = await getCurrentBranch();
|
|
301
|
+
const targetBranch = typeof input.create === "string" ? input.create : input.branch;
|
|
302
|
+
const createFlag = input.create === true || typeof input.create === "string";
|
|
303
|
+
if (createFlag && targetBranch) {
|
|
304
|
+
if (branches.includes(targetBranch)) {
|
|
305
|
+
p2.log.error(`branch ${color2.red(targetBranch)} already exists in the repository`);
|
|
306
|
+
return await exit(1);
|
|
307
|
+
}
|
|
308
|
+
const result2 = await checkoutLocalBranch(targetBranch);
|
|
309
|
+
if (!result2) {
|
|
310
|
+
p2.log.error(`failed to create and checkout ${color2.bold(targetBranch)}`);
|
|
311
|
+
return await exit(1);
|
|
312
|
+
}
|
|
313
|
+
p2.log.success(`created and checked out ${color2.green(targetBranch)}`);
|
|
314
|
+
return await exit(0);
|
|
180
315
|
}
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
316
|
+
if (targetBranch) {
|
|
317
|
+
if (!branches.includes(targetBranch)) {
|
|
318
|
+
p2.log.error(`branch ${color2.red(targetBranch)} does not exist in the repository`);
|
|
319
|
+
const createBranch = await p2.confirm({
|
|
320
|
+
message: `do you want to create branch ${color2.green(targetBranch)}?`
|
|
321
|
+
});
|
|
322
|
+
if (p2.isCancel(createBranch)) {
|
|
323
|
+
p2.log.error("aborted");
|
|
324
|
+
return await exit(1);
|
|
325
|
+
}
|
|
326
|
+
if (createBranch) {
|
|
327
|
+
const result3 = await checkoutLocalBranch(targetBranch);
|
|
328
|
+
if (!result3) {
|
|
329
|
+
p2.log.error(`failed to create and checkout ${color2.bold(targetBranch)}`);
|
|
330
|
+
return await exit(1);
|
|
331
|
+
}
|
|
332
|
+
p2.log.success(`created and checked out ${color2.green(targetBranch)}`);
|
|
333
|
+
return await exit(0);
|
|
334
|
+
}
|
|
335
|
+
return await exit(1);
|
|
336
|
+
}
|
|
337
|
+
if (targetBranch === currentBranch) {
|
|
338
|
+
p2.log.error(`${color2.red("already on branch")} ${color2.green(targetBranch)}`);
|
|
339
|
+
return await exit(1);
|
|
340
|
+
}
|
|
341
|
+
const result2 = await checkout(targetBranch);
|
|
342
|
+
if (!result2) {
|
|
343
|
+
p2.log.error(`failed to checkout ${color2.bold(targetBranch)}`);
|
|
344
|
+
return await exit(1);
|
|
345
|
+
}
|
|
346
|
+
p2.log.success(`checked out ${color2.green(targetBranch)}`);
|
|
347
|
+
return await exit(0);
|
|
191
348
|
}
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
const result = await git.push();
|
|
196
|
-
return result.update || result.pushed && result.pushed.length > 0;
|
|
197
|
-
} catch {
|
|
198
|
-
return false;
|
|
349
|
+
if (branches.length === 0) {
|
|
350
|
+
p2.log.error("no branches found in the repository");
|
|
351
|
+
return await exit(1);
|
|
199
352
|
}
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
353
|
+
const branch = await p2.select({
|
|
354
|
+
message: "select a branch to checkout",
|
|
355
|
+
options: branches.map((branch2) => ({
|
|
356
|
+
value: branch2,
|
|
357
|
+
label: color2.bold(branch2 === currentBranch ? color2.green(branch2) : branch2),
|
|
358
|
+
hint: branch2 === currentBranch ? "current branch" : undefined
|
|
359
|
+
})),
|
|
360
|
+
initialValue: currentBranch
|
|
361
|
+
});
|
|
362
|
+
if (p2.isCancel(branch)) {
|
|
363
|
+
p2.log.error("nothing selected!");
|
|
364
|
+
return await exit(1);
|
|
207
365
|
}
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
const branches = await git.branch();
|
|
212
|
-
return remote ? branches.all : Object.keys(branches.branches).filter((b) => !b.startsWith("remotes/"));
|
|
213
|
-
} catch {
|
|
214
|
-
return null;
|
|
366
|
+
if (!branch) {
|
|
367
|
+
p2.log.error("no branch selected");
|
|
368
|
+
return await exit(1);
|
|
215
369
|
}
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
await
|
|
220
|
-
return true;
|
|
221
|
-
} catch {
|
|
222
|
-
return false;
|
|
370
|
+
if (input.copy) {
|
|
371
|
+
clipboard.writeSync(branch);
|
|
372
|
+
p2.log.success(`copied ${color2.green(branch)} to clipboard`);
|
|
373
|
+
return await exit(0);
|
|
223
374
|
}
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
await git.checkoutLocalBranch(branch);
|
|
228
|
-
return true;
|
|
229
|
-
} catch {
|
|
230
|
-
return false;
|
|
375
|
+
if (branch === currentBranch) {
|
|
376
|
+
p2.log.error(`${color2.red("already on branch")}`);
|
|
377
|
+
return await exit(1);
|
|
231
378
|
}
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
return result.success;
|
|
237
|
-
} catch {
|
|
238
|
-
return false;
|
|
379
|
+
const result = await checkout(branch);
|
|
380
|
+
if (!result) {
|
|
381
|
+
p2.log.error(`failed to checkout ${color2.bold(branch)}`);
|
|
382
|
+
return await exit(1);
|
|
239
383
|
}
|
|
240
|
-
};
|
|
384
|
+
p2.log.success(`checked out ${color2.green(branch)}`);
|
|
385
|
+
await exit(0);
|
|
386
|
+
});
|
|
241
387
|
|
|
242
|
-
// src/
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
388
|
+
// src/commands/config/key.ts
|
|
389
|
+
import { z as z4 } from "zod";
|
|
390
|
+
import * as p3 from "@clack/prompts";
|
|
391
|
+
import color3 from "picocolors";
|
|
392
|
+
var key = baseProcedure.meta({ description: "configure noto api key" }).input(z4.string().optional().describe("apiKey")).mutation(async (opts) => {
|
|
393
|
+
const { input } = opts;
|
|
394
|
+
let apiKey = input;
|
|
395
|
+
if ((await StorageManager.get()).llm?.apiKey) {
|
|
396
|
+
const confirm3 = await p3.confirm({
|
|
397
|
+
message: "noto api key already configured, do you want to update it?"
|
|
398
|
+
});
|
|
399
|
+
if (p3.isCancel(confirm3) || !confirm3) {
|
|
400
|
+
p3.log.error(color3.red("nothing changed!"));
|
|
249
401
|
return await exit(1);
|
|
250
402
|
}
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
opts.diff = diff;
|
|
403
|
+
}
|
|
404
|
+
if (!apiKey) {
|
|
405
|
+
const result = await p3.text({
|
|
406
|
+
message: "enter your noto api key"
|
|
407
|
+
});
|
|
408
|
+
if (p3.isCancel(result)) {
|
|
409
|
+
p3.log.error(color3.red("nothing changed!"));
|
|
410
|
+
return await exit(1);
|
|
260
411
|
}
|
|
261
|
-
|
|
262
|
-
}
|
|
263
|
-
|
|
412
|
+
apiKey = result;
|
|
413
|
+
}
|
|
414
|
+
await StorageManager.update((current) => ({
|
|
415
|
+
...current,
|
|
416
|
+
llm: {
|
|
417
|
+
...current.llm,
|
|
418
|
+
apiKey
|
|
419
|
+
}
|
|
420
|
+
}));
|
|
421
|
+
p3.log.success(color3.green("noto api key configured!"));
|
|
422
|
+
await exit(0);
|
|
423
|
+
});
|
|
264
424
|
|
|
265
|
-
// src/
|
|
266
|
-
import
|
|
267
|
-
import
|
|
268
|
-
import dedent3 from "dedent";
|
|
425
|
+
// src/commands/config/model.ts
|
|
426
|
+
import * as p4 from "@clack/prompts";
|
|
427
|
+
import color4 from "picocolors";
|
|
269
428
|
|
|
270
429
|
// src/ai/models.ts
|
|
271
430
|
import { createGoogleGenerativeAI } from "@ai-sdk/google";
|
|
272
431
|
var google = createGoogleGenerativeAI({
|
|
273
432
|
apiKey: (await StorageManager.get()).llm?.apiKey ?? "api-key"
|
|
274
433
|
});
|
|
275
|
-
var
|
|
434
|
+
var DEFAULT_MODEL = "gemini-2.0-flash";
|
|
276
435
|
var models = {
|
|
277
436
|
"gemini-1.5-flash": google("gemini-1.5-flash"),
|
|
278
437
|
"gemini-1.5-flash-latest": google("gemini-1.5-flash-latest"),
|
|
@@ -290,839 +449,792 @@ var availableModels = Object.keys(models);
|
|
|
290
449
|
var getModel = async () => {
|
|
291
450
|
let model = (await StorageManager.get()).llm?.model;
|
|
292
451
|
if (!model || !availableModels.includes(model)) {
|
|
293
|
-
model =
|
|
452
|
+
model = DEFAULT_MODEL;
|
|
294
453
|
await StorageManager.update((current) => ({
|
|
295
454
|
...current,
|
|
296
455
|
llm: {
|
|
297
456
|
...current.llm,
|
|
298
|
-
model:
|
|
457
|
+
model: DEFAULT_MODEL
|
|
299
458
|
}
|
|
300
459
|
}));
|
|
301
460
|
}
|
|
302
461
|
return models[model];
|
|
303
462
|
};
|
|
304
463
|
|
|
305
|
-
// src/
|
|
306
|
-
var
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
1. **Commit Message Format:**
|
|
341
|
-
* The output MUST strictly follow the single-line format: \`<type>: <description>\`
|
|
342
|
-
* A single colon (\`:\`) followed by a single space MUST separate the \`<type>\` and \`<description>\`.
|
|
343
|
-
* Scopes within the type (e.g., \`feat(api):\`) are explicitly disallowed.
|
|
344
|
-
* Message bodies and footers are explicitly disallowed. The output MUST be exactly one line.
|
|
345
|
-
|
|
346
|
-
2. **Type (\`<type>\`) - Determination Logic:**
|
|
347
|
-
* **Priority 1: User-Specified Type:** Check the value provided on the \`USER_SPECIFIED_TYPE:\` line. If this value is exactly one of \`chore\`, \`feat\`, \`fix\`, \`docs\`, \`refactor\`, \`perf\`, \`test\`, then you **MUST** use this user-specified type.
|
|
348
|
-
* **Priority 2: Diff & Context Analysis (Default Behavior):** If the value on the \`USER_SPECIFIED_TYPE:\` line is \`[none]\` or invalid, you **MUST** select the \`<type>\` exclusively from the predefined vocabulary (\`chore\`, \`feat\`, \`fix\`, \`docs\`, \`refactor\`, \`perf\`, \`test\`). Base your selection on your analysis of the diff, **informed by the \`USER_PROVIDED_CONTEXT\`** if available, to accurately reflect the primary semantic purpose of the changes.
|
|
349
|
-
|
|
350
|
-
3. **Description (\`<description>\`):**
|
|
351
|
-
* **Tense:** MUST employ the imperative, present tense (e.g.,\`add\`, \`fix\`, \`update\`, \`implement\`, \`refactor\`, \`remove\`).
|
|
352
|
-
* **Content:** Must succinctly convey the core semantic change introduced by the diff.
|
|
353
|
-
* **Leverage Context:** If \`USER_PROVIDED_CONTEXT\` is available and not \`[none]\`, use it to **refine the description**, ensuring it reflects the *intent* and *purpose* behind the changes, while still accurately summarizing *what* was changed in the diff.
|
|
354
|
-
* **Focus:** Prioritize the most significant aspects of the change.
|
|
355
|
-
* **File Name Reference:** Inclusion of file names should be exceptional, reserved only for renaming operations or when essential for disambiguating the change's primary focus.
|
|
356
|
-
* **Case Sensitivity:** The entire output string, encompassing both \`<type>\` and \`<description>\`, MUST be rendered in lowercase.
|
|
357
|
-
* **Punctuation:** The description MUST NOT conclude with any terminal punctuation (e.g., no period/full stop).
|
|
358
|
-
* **Length Constraint:** The total character count of the generated commit message line MUST NOT exceed 72 characters. **The description must be concise enough to fit within this limit alongside the chosen type.**`
|
|
359
|
-
},
|
|
360
|
-
{
|
|
361
|
-
role: "user",
|
|
362
|
-
content: dedent3`
|
|
363
|
-
\`\`\`text
|
|
364
|
-
USER_SPECIFIED_TYPE: ${type ?? "[none]"}
|
|
365
|
-
|
|
366
|
-
USER_PROVIDED_CONTEXT:
|
|
367
|
-
${context ?? "[none]"}
|
|
368
|
-
\`\`\`
|
|
464
|
+
// src/commands/config/model.ts
|
|
465
|
+
var model = baseProcedure.meta({
|
|
466
|
+
description: "configure model"
|
|
467
|
+
}).mutation(async () => {
|
|
468
|
+
const model2 = await p4.select({
|
|
469
|
+
message: "select a model",
|
|
470
|
+
initialValue: (await StorageManager.get()).llm?.model,
|
|
471
|
+
options: Object.keys(models).map((model3) => ({
|
|
472
|
+
label: model3,
|
|
473
|
+
value: model3
|
|
474
|
+
}))
|
|
475
|
+
});
|
|
476
|
+
if (p4.isCancel(model2)) {
|
|
477
|
+
p4.log.error(color4.red("nothing changed!"));
|
|
478
|
+
return await exit(1);
|
|
479
|
+
}
|
|
480
|
+
if (model2 === "gemini-2.5-pro-preview-05-06") {
|
|
481
|
+
const confirm4 = await p4.confirm({
|
|
482
|
+
message: "this model does not have free quota tier, do you want to continue?"
|
|
483
|
+
});
|
|
484
|
+
if (p4.isCancel(confirm4) || !confirm4) {
|
|
485
|
+
p4.log.error(color4.red("nothing changed!"));
|
|
486
|
+
return await exit(1);
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
await StorageManager.update((current) => ({
|
|
490
|
+
...current,
|
|
491
|
+
llm: {
|
|
492
|
+
...current.llm,
|
|
493
|
+
model: model2
|
|
494
|
+
}
|
|
495
|
+
}));
|
|
496
|
+
p4.log.success(color4.green("model configured!"));
|
|
497
|
+
await exit(0);
|
|
498
|
+
});
|
|
369
499
|
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
500
|
+
// src/commands/config/reset.ts
|
|
501
|
+
import * as p5 from "@clack/prompts";
|
|
502
|
+
import color5 from "picocolors";
|
|
503
|
+
var reset = baseProcedure.meta({
|
|
504
|
+
description: "reset the configuration"
|
|
505
|
+
}).mutation(async () => {
|
|
506
|
+
const confirm5 = await p5.confirm({
|
|
507
|
+
message: "are you sure you want to reset the configuration?"
|
|
375
508
|
});
|
|
376
|
-
|
|
377
|
-
|
|
509
|
+
if (p5.isCancel(confirm5) || !confirm5) {
|
|
510
|
+
p5.log.error(color5.red("nothing changed!"));
|
|
511
|
+
return await exit(1);
|
|
512
|
+
}
|
|
513
|
+
await StorageManager.clear();
|
|
514
|
+
p5.log.success(color5.green("configuration reset!"));
|
|
515
|
+
await exit(0);
|
|
516
|
+
});
|
|
378
517
|
|
|
379
|
-
// src/commands/
|
|
380
|
-
var
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
"refactor",
|
|
386
|
-
"perf",
|
|
387
|
-
"test"
|
|
388
|
-
];
|
|
389
|
-
var commitTypeOptions = availableTypes.map((type) => ({
|
|
390
|
-
label: type,
|
|
391
|
-
value: type
|
|
392
|
-
}));
|
|
393
|
-
var command = {
|
|
394
|
-
name: "noto",
|
|
395
|
-
description: "generate commit message",
|
|
396
|
-
usage: "noto [options]",
|
|
397
|
-
options: [
|
|
398
|
-
{
|
|
399
|
-
type: String,
|
|
400
|
-
flag: "--type",
|
|
401
|
-
alias: "-t",
|
|
402
|
-
description: "generate commit message based on type"
|
|
403
|
-
},
|
|
404
|
-
{
|
|
405
|
-
type: String,
|
|
406
|
-
flag: "--message",
|
|
407
|
-
alias: "-m",
|
|
408
|
-
description: "provide context for the commit message"
|
|
409
|
-
},
|
|
410
|
-
{
|
|
411
|
-
type: Boolean,
|
|
412
|
-
flag: "--copy",
|
|
413
|
-
alias: "-c",
|
|
414
|
-
description: "copy the generated commit message to clipboard"
|
|
415
|
-
},
|
|
416
|
-
{
|
|
417
|
-
type: Boolean,
|
|
418
|
-
flag: "--apply",
|
|
419
|
-
alias: "-a",
|
|
420
|
-
description: "commit the generated message directly"
|
|
421
|
-
},
|
|
422
|
-
{
|
|
423
|
-
type: Boolean,
|
|
424
|
-
flag: "--push",
|
|
425
|
-
alias: "-p",
|
|
426
|
-
description: "commit and push the changes"
|
|
427
|
-
},
|
|
428
|
-
{
|
|
429
|
-
type: Boolean,
|
|
430
|
-
flag: "--manual",
|
|
431
|
-
description: "commit and push the changes"
|
|
432
|
-
}
|
|
433
|
-
],
|
|
434
|
-
execute: withAuth(withRepository(async (options) => {
|
|
435
|
-
const spin = p3.spinner();
|
|
436
|
-
try {
|
|
437
|
-
const { diff } = options;
|
|
438
|
-
const manual = options["--manual"];
|
|
439
|
-
if (manual) {
|
|
440
|
-
const message2 = await p3.text({
|
|
441
|
-
message: "edit the generated commit message",
|
|
442
|
-
placeholder: "chore: init repo"
|
|
443
|
-
});
|
|
444
|
-
if (p3.isCancel(message2)) {
|
|
445
|
-
p3.log.error(color3.red("nothing changed!"));
|
|
446
|
-
return await exit(1);
|
|
447
|
-
}
|
|
448
|
-
p3.log.step(color3.green(message2));
|
|
449
|
-
await StorageManager.update((current) => ({
|
|
450
|
-
...current,
|
|
451
|
-
lastGeneratedMessage: message2
|
|
452
|
-
}));
|
|
453
|
-
if (options["--apply"]) {
|
|
454
|
-
const success = await commit(message2);
|
|
455
|
-
if (success) {
|
|
456
|
-
p3.log.step(color3.dim("commit successful"));
|
|
457
|
-
} else {
|
|
458
|
-
p3.log.error(color3.red("failed to commit changes"));
|
|
459
|
-
}
|
|
460
|
-
}
|
|
461
|
-
return await exit(0);
|
|
462
|
-
}
|
|
463
|
-
const type = options["--type"];
|
|
464
|
-
if (typeof type === "string" && !availableTypes.includes(type) || typeof type === "boolean") {
|
|
465
|
-
const type2 = await p3.select({
|
|
466
|
-
message: "select the type of commit message",
|
|
467
|
-
options: commitTypeOptions
|
|
468
|
-
});
|
|
469
|
-
if (p3.isCancel(type2)) {
|
|
470
|
-
p3.log.error(color3.red("nothing selected!"));
|
|
471
|
-
return await exit(1);
|
|
472
|
-
}
|
|
473
|
-
options.type = type2;
|
|
474
|
-
} else if (typeof type === "string") {
|
|
475
|
-
options.type = type;
|
|
476
|
-
}
|
|
477
|
-
const context = options["--message"];
|
|
478
|
-
if (typeof context === "string") {
|
|
479
|
-
options.context = context;
|
|
480
|
-
} else if (typeof context === "boolean") {
|
|
481
|
-
const context2 = await p3.text({
|
|
482
|
-
message: "provide context for the commit message",
|
|
483
|
-
placeholder: "describe the changes"
|
|
484
|
-
});
|
|
485
|
-
if (p3.isCancel(context2)) {
|
|
486
|
-
p3.log.error(color3.red("nothing changed!"));
|
|
487
|
-
return await exit(1);
|
|
488
|
-
}
|
|
489
|
-
options.context = context2;
|
|
490
|
-
}
|
|
491
|
-
spin.start("generating commit message");
|
|
492
|
-
let message = null;
|
|
493
|
-
if (!await isFirstCommit()) {
|
|
494
|
-
message = await generateCommitMessage(diff, options.type, options.context);
|
|
495
|
-
} else {
|
|
496
|
-
message = INIT_COMMIT_MESSAGE;
|
|
497
|
-
}
|
|
498
|
-
spin.stop(color3.white(message));
|
|
499
|
-
const editedMessage = await p3.text({
|
|
500
|
-
message: "edit the generated commit message",
|
|
501
|
-
initialValue: message,
|
|
502
|
-
placeholder: message
|
|
503
|
-
});
|
|
504
|
-
if (p3.isCancel(editedMessage)) {
|
|
505
|
-
p3.log.error(color3.red("nothing changed!"));
|
|
506
|
-
return await exit(1);
|
|
507
|
-
}
|
|
508
|
-
message = editedMessage;
|
|
509
|
-
p3.log.step(color3.green(message));
|
|
510
|
-
await StorageManager.update((current) => ({
|
|
511
|
-
...current,
|
|
512
|
-
lastGeneratedMessage: message
|
|
513
|
-
}));
|
|
514
|
-
if (options["--copy"]) {
|
|
515
|
-
clipboard.writeSync(message);
|
|
516
|
-
p3.log.step(color3.dim("copied commit message to clipboard"));
|
|
517
|
-
}
|
|
518
|
-
if (options["--apply"]) {
|
|
519
|
-
const success = await commit(message);
|
|
520
|
-
if (success) {
|
|
521
|
-
p3.log.step(color3.dim("commit successful"));
|
|
522
|
-
} else {
|
|
523
|
-
p3.log.error(color3.red("failed to commit changes"));
|
|
524
|
-
}
|
|
525
|
-
}
|
|
526
|
-
if (options["--push"]) {
|
|
527
|
-
const success = await push();
|
|
528
|
-
if (success) {
|
|
529
|
-
p3.log.step(color3.dim("push successful"));
|
|
530
|
-
} else {
|
|
531
|
-
p3.log.error(color3.red("failed to push changes"));
|
|
532
|
-
}
|
|
533
|
-
}
|
|
534
|
-
return await exit(0);
|
|
535
|
-
} catch {
|
|
536
|
-
spin.stop(color3.red("failed to generate commit message"), 1);
|
|
537
|
-
return await exit(1);
|
|
538
|
-
}
|
|
539
|
-
}))
|
|
540
|
-
};
|
|
541
|
-
var noto_default = command;
|
|
518
|
+
// src/commands/config/index.ts
|
|
519
|
+
var config = t.router({
|
|
520
|
+
key,
|
|
521
|
+
model,
|
|
522
|
+
reset
|
|
523
|
+
});
|
|
542
524
|
|
|
543
525
|
// src/commands/prev.ts
|
|
544
|
-
import
|
|
545
|
-
import
|
|
546
|
-
import
|
|
526
|
+
import { z as z5 } from "zod";
|
|
527
|
+
import * as p6 from "@clack/prompts";
|
|
528
|
+
import color6 from "picocolors";
|
|
529
|
+
import dedent2 from "dedent";
|
|
547
530
|
import clipboard2 from "clipboardy";
|
|
548
|
-
var
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
p4.log.error(color4.red("no previous commit message found"));
|
|
581
|
-
return await exit(1);
|
|
582
|
-
}
|
|
583
|
-
const isEditMode = options["--edit"];
|
|
584
|
-
const isAmend = options["--amend"];
|
|
585
|
-
if (isAmend && !isEditMode) {
|
|
586
|
-
p4.log.error(color4.red("the --amend option requires the --edit option"));
|
|
587
|
-
return await exit(1);
|
|
588
|
-
}
|
|
589
|
-
p4.log.step(isEditMode ? color4.white(lastGeneratedMessage) : color4.green(lastGeneratedMessage));
|
|
590
|
-
if (options["--edit"]) {
|
|
591
|
-
const editedMessage = await p4.text({
|
|
592
|
-
message: "edit the last generated commit message",
|
|
593
|
-
initialValue: lastGeneratedMessage,
|
|
594
|
-
placeholder: lastGeneratedMessage
|
|
595
|
-
});
|
|
596
|
-
if (p4.isCancel(editedMessage)) {
|
|
597
|
-
p4.log.error(color4.red("nothing changed!"));
|
|
598
|
-
return await exit(1);
|
|
599
|
-
}
|
|
600
|
-
lastGeneratedMessage = editedMessage;
|
|
601
|
-
await StorageManager.update((current) => ({
|
|
602
|
-
...current,
|
|
603
|
-
lastGeneratedMessage: editedMessage
|
|
604
|
-
}));
|
|
605
|
-
p4.log.step(color4.green(lastGeneratedMessage));
|
|
606
|
-
}
|
|
607
|
-
if (options["--copy"]) {
|
|
608
|
-
clipboard2.writeSync(lastGeneratedMessage);
|
|
609
|
-
p4.log.step(color4.dim("copied last generated commit message to clipboard"));
|
|
610
|
-
}
|
|
611
|
-
if (options["--apply"] || isAmend) {
|
|
612
|
-
if (!options.isRepo) {
|
|
613
|
-
p4.log.error(dedent4`${color4.red("no git repository found in cwd.")}
|
|
614
|
-
${color4.dim(`run ${color4.cyan("`git init`")} to initialize a new repository.`)}`);
|
|
615
|
-
return await exit(1);
|
|
616
|
-
}
|
|
617
|
-
if (!options.diff && !isAmend) {
|
|
618
|
-
p4.log.error(dedent4`${color4.red("no staged changes found.")}
|
|
619
|
-
${color4.dim(`run ${color4.cyan("`git add <file>`")} or ${color4.cyan("`git add .`")} to stage changes.`)}`);
|
|
620
|
-
return await exit(1);
|
|
621
|
-
}
|
|
622
|
-
const success = await commit(lastGeneratedMessage, isAmend);
|
|
623
|
-
if (success) {
|
|
624
|
-
p4.log.step(color4.dim("commit successful"));
|
|
625
|
-
} else {
|
|
626
|
-
p4.log.error(color4.red("failed to commit changes"));
|
|
627
|
-
}
|
|
628
|
-
}
|
|
629
|
-
return await exit(0);
|
|
630
|
-
}, { enabled: false }))
|
|
631
|
-
};
|
|
632
|
-
var prev_default = command2;
|
|
633
|
-
|
|
634
|
-
// src/commands/branch.ts
|
|
635
|
-
import * as p5 from "@clack/prompts";
|
|
636
|
-
import color5 from "picocolors";
|
|
637
|
-
import clipboard3 from "clipboardy";
|
|
638
|
-
import dedent5 from "dedent";
|
|
639
|
-
var current = {
|
|
640
|
-
name: "current",
|
|
641
|
-
description: "get current branch",
|
|
642
|
-
usage: "branch current",
|
|
643
|
-
options: [
|
|
644
|
-
{
|
|
645
|
-
type: Boolean,
|
|
646
|
-
flag: "--copy",
|
|
647
|
-
alias: "-c",
|
|
648
|
-
description: "copy the selected branch to clipboard"
|
|
649
|
-
}
|
|
650
|
-
],
|
|
651
|
-
execute: withRepository(async (options) => {
|
|
652
|
-
if (!options.isRepo) {
|
|
653
|
-
p5.log.error(dedent5`${color5.red("no git repository found in cwd.")}
|
|
654
|
-
${color5.dim(`run ${color5.cyan("`git init`")} to initialize a new repository.`)}`);
|
|
655
|
-
return await exit(1);
|
|
656
|
-
}
|
|
657
|
-
const branch = await getCurrentBranch();
|
|
658
|
-
if (!branch) {
|
|
659
|
-
p5.log.error("failed to fetch current branch");
|
|
660
|
-
return await exit(1);
|
|
661
|
-
}
|
|
662
|
-
p5.log.success(`current branch: ${color5.bold(branch)}`);
|
|
663
|
-
if (options["--copy"]) {
|
|
664
|
-
clipboard3.writeSync(branch);
|
|
665
|
-
p5.log.success(`${color5.green("copied to clipboard!")}`);
|
|
666
|
-
}
|
|
667
|
-
await exit(0);
|
|
668
|
-
}, { enabled: false })
|
|
669
|
-
};
|
|
670
|
-
var del = {
|
|
671
|
-
name: "delete",
|
|
672
|
-
description: "delete a branch",
|
|
673
|
-
usage: "branch delete <branch>",
|
|
674
|
-
options: [
|
|
675
|
-
{
|
|
676
|
-
type: Boolean,
|
|
677
|
-
flag: "--force",
|
|
678
|
-
alias: "-f",
|
|
679
|
-
description: "force delete a branch"
|
|
680
|
-
},
|
|
681
|
-
{
|
|
682
|
-
type: Boolean,
|
|
683
|
-
flag: "--all",
|
|
684
|
-
alias: "-a",
|
|
685
|
-
description: "select all branches except the current one"
|
|
686
|
-
}
|
|
687
|
-
],
|
|
688
|
-
execute: withRepository(async (options) => {
|
|
689
|
-
if (!options.isRepo) {
|
|
690
|
-
p5.log.error(dedent5`${color5.red("no git repository found in cwd.")}
|
|
691
|
-
${color5.dim(`run ${color5.cyan("`git init`")} to initialize a new repository.`)}`);
|
|
692
|
-
return await exit(1);
|
|
693
|
-
}
|
|
694
|
-
const currentBranch = await getCurrentBranch();
|
|
695
|
-
const branches = await getBranches();
|
|
696
|
-
if (!currentBranch || !branches) {
|
|
697
|
-
p5.log.error("failed to fetch branches");
|
|
698
|
-
return await exit(1);
|
|
699
|
-
}
|
|
700
|
-
const selectedBranches = await p5.multiselect({
|
|
701
|
-
message: "select branches to delete",
|
|
702
|
-
initialValues: options["--all"] ? branches.filter((b) => b !== currentBranch) : [],
|
|
703
|
-
options: branches.map((branch) => ({
|
|
704
|
-
value: branch,
|
|
705
|
-
label: color5.bold(branch),
|
|
706
|
-
hint: branch === options["--current"] ? "current branch" : undefined
|
|
707
|
-
}))
|
|
531
|
+
var prev = gitProcedure.meta({
|
|
532
|
+
description: "access the last generated commit",
|
|
533
|
+
repoRequired: false
|
|
534
|
+
}).input(z5.object({
|
|
535
|
+
copy: z5.boolean().meta({ description: "copy the last commit to clipboard", alias: "c" }),
|
|
536
|
+
apply: z5.boolean().meta({ description: "commit the last generated message", alias: "a" }),
|
|
537
|
+
edit: z5.boolean().meta({
|
|
538
|
+
description: "edit the last generated commit message",
|
|
539
|
+
alias: "e"
|
|
540
|
+
}),
|
|
541
|
+
amend: z5.boolean().meta({
|
|
542
|
+
description: "amend the last commit with the last message"
|
|
543
|
+
})
|
|
544
|
+
})).mutation(async (opts) => {
|
|
545
|
+
const { input, ctx } = opts;
|
|
546
|
+
let lastGeneratedMessage = (await StorageManager.get()).lastGeneratedMessage;
|
|
547
|
+
if (!lastGeneratedMessage) {
|
|
548
|
+
p6.log.error(color6.red("no previous commit message found"));
|
|
549
|
+
return await exit(1);
|
|
550
|
+
}
|
|
551
|
+
const isEditMode = input.edit;
|
|
552
|
+
const isAmend = input.amend;
|
|
553
|
+
if (isAmend && !isEditMode) {
|
|
554
|
+
p6.log.error(color6.red("the --amend option requires the --edit option"));
|
|
555
|
+
return await exit(1);
|
|
556
|
+
}
|
|
557
|
+
p6.log.step(isEditMode ? color6.white(lastGeneratedMessage) : color6.green(lastGeneratedMessage));
|
|
558
|
+
if (isEditMode) {
|
|
559
|
+
const editedMessage = await p6.text({
|
|
560
|
+
message: "edit the last generated commit message",
|
|
561
|
+
initialValue: lastGeneratedMessage,
|
|
562
|
+
placeholder: lastGeneratedMessage
|
|
708
563
|
});
|
|
709
|
-
if (
|
|
710
|
-
|
|
564
|
+
if (p6.isCancel(editedMessage)) {
|
|
565
|
+
p6.log.error(color6.red("nothing changed!"));
|
|
711
566
|
return await exit(1);
|
|
712
567
|
}
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
568
|
+
lastGeneratedMessage = editedMessage;
|
|
569
|
+
await StorageManager.update((current) => ({
|
|
570
|
+
...current,
|
|
571
|
+
lastGeneratedMessage: editedMessage
|
|
572
|
+
}));
|
|
573
|
+
p6.log.step(color6.green(lastGeneratedMessage));
|
|
574
|
+
}
|
|
575
|
+
if (input.copy) {
|
|
576
|
+
clipboard2.writeSync(lastGeneratedMessage);
|
|
577
|
+
p6.log.step(color6.dim("copied last generated commit message to clipboard"));
|
|
578
|
+
}
|
|
579
|
+
if (input.apply || isAmend) {
|
|
580
|
+
if (!ctx.git.isRepository) {
|
|
581
|
+
p6.log.error(dedent2`${color6.red("no git repository found in cwd.")}
|
|
582
|
+
${color6.dim(`run ${color6.cyan("`git init`")} to initialize a new repository.`)}`);
|
|
720
583
|
return await exit(1);
|
|
721
584
|
}
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
585
|
+
if (!ctx.git.diff && !isAmend) {
|
|
586
|
+
p6.log.error(dedent2`${color6.red("no staged changes found.")}
|
|
587
|
+
${color6.dim(`run ${color6.cyan("`git add <file>`")} or ${color6.cyan("`git add .`")} to stage changes.`)}`);
|
|
725
588
|
return await exit(1);
|
|
726
589
|
}
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
}
|
|
731
|
-
|
|
732
|
-
name: "branch",
|
|
733
|
-
description: "list branches",
|
|
734
|
-
usage: "branch [options]",
|
|
735
|
-
options: [
|
|
736
|
-
{
|
|
737
|
-
type: Boolean,
|
|
738
|
-
flag: "--remote",
|
|
739
|
-
alias: "-r",
|
|
740
|
-
description: "list branches including remotes"
|
|
741
|
-
},
|
|
742
|
-
{
|
|
743
|
-
type: Boolean,
|
|
744
|
-
flag: "--delete",
|
|
745
|
-
alias: "-d",
|
|
746
|
-
description: "delete a branch"
|
|
747
|
-
},
|
|
748
|
-
{
|
|
749
|
-
type: Boolean,
|
|
750
|
-
flag: "--force",
|
|
751
|
-
alias: "-f",
|
|
752
|
-
description: "force delete a branch"
|
|
753
|
-
},
|
|
754
|
-
{
|
|
755
|
-
type: Boolean,
|
|
756
|
-
flag: "--all",
|
|
757
|
-
alias: "-a",
|
|
758
|
-
description: "select all branches except the current one"
|
|
759
|
-
}
|
|
760
|
-
],
|
|
761
|
-
execute: withRepository(async (options) => {
|
|
762
|
-
if (!options.isRepo) {
|
|
763
|
-
p5.log.error(dedent5`${color5.red("no git repository found in cwd.")}
|
|
764
|
-
${color5.dim(`run ${color5.cyan("`git init`")} to initialize a new repository.`)}`);
|
|
765
|
-
return await exit(1);
|
|
590
|
+
const success = await commit(lastGeneratedMessage, isAmend);
|
|
591
|
+
if (success) {
|
|
592
|
+
p6.log.step(color6.dim("commit successful"));
|
|
593
|
+
} else {
|
|
594
|
+
p6.log.error(color6.red("failed to commit changes"));
|
|
766
595
|
}
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
596
|
+
}
|
|
597
|
+
return await exit(0);
|
|
598
|
+
});
|
|
599
|
+
|
|
600
|
+
// src/commands/init.ts
|
|
601
|
+
import fs4 from "node:fs/promises";
|
|
602
|
+
import { z as z7 } from "zod";
|
|
603
|
+
import * as p7 from "@clack/prompts";
|
|
604
|
+
import color7 from "picocolors";
|
|
605
|
+
import dedent4 from "dedent";
|
|
606
|
+
|
|
607
|
+
// src/ai/index.ts
|
|
608
|
+
import { generateObject, wrapLanguageModel } from "ai";
|
|
609
|
+
import z6 from "zod";
|
|
610
|
+
import dedent3 from "dedent";
|
|
611
|
+
import superjson from "superjson";
|
|
612
|
+
|
|
613
|
+
// src/utils/hash.ts
|
|
614
|
+
import { createHash } from "crypto";
|
|
615
|
+
function hashString(content) {
|
|
616
|
+
const body = Buffer.from(content, "utf-8");
|
|
617
|
+
const header = Buffer.from(`blob ${body.length}\x00`, "utf-8");
|
|
618
|
+
return createHash("sha1").update(header).update(body).digest("hex");
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
// src/ai/index.ts
|
|
622
|
+
var COMMIT_GENERATOR_PROMPT = dedent3`
|
|
623
|
+
# System Instruction for Noto
|
|
624
|
+
|
|
625
|
+
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.
|
|
626
|
+
|
|
627
|
+
## Your Purpose
|
|
628
|
+
|
|
629
|
+
Generate accurate, concise commit messages by analyzing git diffs and following user-provided style guidelines.
|
|
630
|
+
|
|
631
|
+
## Core Behavior
|
|
632
|
+
|
|
633
|
+
**Output Format:** Return ONLY a single-line commit message. No explanations, no markdown, no body, no footer.
|
|
634
|
+
|
|
635
|
+
**Response Style:**
|
|
636
|
+
- Be precise and factual based on the diff
|
|
637
|
+
- Follow the user's custom guidelines exactly
|
|
638
|
+
- Use clear, specific language
|
|
639
|
+
- Stay within 50-72 characters when possible
|
|
640
|
+
|
|
641
|
+
**Analysis Process:**
|
|
642
|
+
1. Read any user-provided context to understand the intent behind the changes
|
|
643
|
+
2. Examine the git diff to understand what changed technically
|
|
644
|
+
3. Combine user context and diff analysis to get complete understanding
|
|
645
|
+
4. Apply the user's style guidelines from \`.noto/commit-prompt.md\`
|
|
646
|
+
5. Classify the change type (feat, fix, refactor, docs, style, test, chore, etc.)
|
|
647
|
+
6. Generate a commit message that accurately describes both what changed and why
|
|
648
|
+
|
|
649
|
+
## Input Structure
|
|
650
|
+
|
|
651
|
+
You will receive:
|
|
652
|
+
\`\`\`
|
|
653
|
+
USER GUIDELINES:
|
|
654
|
+
[Custom style from .noto/commit-prompt.md]
|
|
655
|
+
|
|
656
|
+
USER CONTEXT (optional):
|
|
657
|
+
[Additional context or description provided by the user about the changes]
|
|
658
|
+
|
|
659
|
+
GIT DIFF:
|
|
660
|
+
[Staged changes]
|
|
661
|
+
\`\`\`
|
|
662
|
+
|
|
663
|
+
If user context is provided, use it to better understand the intent and purpose of the changes. Combine this context with your diff analysis to generate a more accurate and meaningful commit message.
|
|
664
|
+
|
|
665
|
+
## Output Requirements
|
|
666
|
+
|
|
667
|
+
Return only the commit message text:
|
|
668
|
+
\`\`\`
|
|
669
|
+
type(scope): description
|
|
670
|
+
\`\`\`
|
|
671
|
+
|
|
672
|
+
Or whatever format matches the user's guidelines. **Must be single-line only.**
|
|
673
|
+
|
|
674
|
+
## Quality Standards
|
|
675
|
+
|
|
676
|
+
- Accurately reflect the changes in the diff
|
|
677
|
+
- Incorporate user-provided context when available to capture the "why"
|
|
678
|
+
- Follow user's format, tense, and capitalization preferences
|
|
679
|
+
- Be specific (avoid vague terms like "update" or "change")
|
|
680
|
+
- Use proper grammar
|
|
681
|
+
- Make the git history useful and searchable
|
|
682
|
+
- Balance technical accuracy with user intent
|
|
683
|
+
|
|
684
|
+
## What NOT to Do
|
|
685
|
+
|
|
686
|
+
- Don't add explanations or commentary
|
|
687
|
+
- Don't generate multi-line messages
|
|
688
|
+
- Don't make assumptions beyond the visible diff
|
|
689
|
+
- Don't ignore the user's style guidelines
|
|
690
|
+
- Don't use generic or vague descriptions
|
|
691
|
+
|
|
692
|
+
**Remember:** Your output becomes permanent git history. Generate commit messages that are clear, accurate, and consistent with the user's established patterns.
|
|
693
|
+
`;
|
|
694
|
+
var DEFAULT_COMMIT_GUIDELINES = dedent3`
|
|
695
|
+
# Commit Message Guidelines
|
|
696
|
+
|
|
697
|
+
## Format
|
|
698
|
+
Use conventional commits: \`type(scope): description\`
|
|
699
|
+
|
|
700
|
+
The scope is optional but recommended when changes affect a specific component or area.
|
|
701
|
+
|
|
702
|
+
## Style Rules
|
|
703
|
+
- **Tense**: Imperative present tense (e.g., "add" not "added" or "adds")
|
|
704
|
+
- **Capitalization**: Lowercase for the first letter of description
|
|
705
|
+
- **Length**: Keep the entire message under 72 characters, ideally around 50
|
|
706
|
+
- **Tone**: Clear, concise, and professional
|
|
707
|
+
|
|
708
|
+
## Commit Types
|
|
709
|
+
- \`feat\`: New feature or functionality for the user
|
|
710
|
+
- \`fix\`: Bug fix that resolves an issue
|
|
711
|
+
- \`docs\`: Documentation changes only
|
|
712
|
+
- \`style\`: Code style changes (formatting, missing semicolons, etc.) with no logic changes
|
|
713
|
+
- \`refactor\`: Code changes that neither fix bugs nor add features
|
|
714
|
+
- \`perf\`: Performance improvements
|
|
715
|
+
- \`test\`: Adding or updating tests
|
|
716
|
+
- \`build\`: Changes to build system or dependencies (npm, webpack, etc.)
|
|
717
|
+
- \`ci\`: Changes to CI/CD configuration files and scripts
|
|
718
|
+
- \`chore\`: Routine tasks, maintenance, or tooling changes
|
|
719
|
+
- \`revert\`: Revert a previous commit
|
|
720
|
+
|
|
721
|
+
## Scope Usage
|
|
722
|
+
**Prefer omitting scopes whenever possible.** Only include a scope when it significantly clarifies which part of the codebase is affected.
|
|
723
|
+
|
|
724
|
+
Use scopes sparingly and only when the change is isolated to a specific area:
|
|
725
|
+
- Component or module names (e.g., \`auth\`, \`api\`, \`ui\`, \`parser\`)
|
|
726
|
+
- Feature areas (e.g., \`login\`, \`checkout\`, \`dashboard\`)
|
|
727
|
+
- File or directory names when appropriate
|
|
728
|
+
|
|
729
|
+
Omit scope for:
|
|
730
|
+
- Changes that affect the entire project
|
|
731
|
+
- Changes that don't fit a specific area
|
|
732
|
+
- Most commits where the type and description are clear enough
|
|
733
|
+
- When in doubt, leave it out
|
|
734
|
+
|
|
735
|
+
## Description Patterns
|
|
736
|
+
- Start with a verb in imperative mood (add, update, remove, fix, implement, etc.)
|
|
737
|
+
- Be specific about what changed, not how it changed
|
|
738
|
+
- Focus on the "what" and "why", not the "how"
|
|
739
|
+
- Avoid ending with a period
|
|
740
|
+
- Keep it clear enough that someone can understand the change without reading the code
|
|
741
|
+
|
|
742
|
+
## Examples
|
|
743
|
+
- \`feat: add OAuth2 authentication support\`
|
|
744
|
+
- \`fix: resolve timeout issue in user endpoint\`
|
|
745
|
+
- \`docs: update installation instructions in README\`
|
|
746
|
+
- \`style: fix indentation and spacing\`
|
|
747
|
+
- \`refactor: simplify data parsing logic\`
|
|
748
|
+
- \`perf: optimize database query performance\`
|
|
749
|
+
- \`test: add unit tests for login validation\`
|
|
750
|
+
- \`build: upgrade webpack to version 5\`
|
|
751
|
+
- \`ci: add automated deployment workflow\`
|
|
752
|
+
- \`chore: update dependencies to latest versions\`
|
|
753
|
+
|
|
754
|
+
Examples with scope (use only when necessary):
|
|
755
|
+
- \`feat(auth): add biometric authentication\`
|
|
756
|
+
- \`fix(api): handle null response in user service\`
|
|
757
|
+
|
|
758
|
+
## Breaking Changes
|
|
759
|
+
For breaking changes, add \`!\` after the type/scope:
|
|
760
|
+
- \`feat!: remove deprecated API endpoints\`
|
|
761
|
+
- \`refactor(api)!: change response format to JSON:API spec\`
|
|
762
|
+
|
|
763
|
+
## Additional Notes
|
|
764
|
+
- If a commit addresses a specific issue, you can reference it in the description (e.g., \`fix: resolve memory leak (fixes #123)\`)
|
|
765
|
+
- Each commit should represent a single logical change
|
|
766
|
+
- Write commits as if completing the sentence: "If applied, this commit will..."`;
|
|
767
|
+
var GUIDELINES_GENERATOR_PROMPT = dedent3`
|
|
768
|
+
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.
|
|
769
|
+
|
|
770
|
+
## Task
|
|
771
|
+
|
|
772
|
+
Analyze the commit messages below and create clear guidelines that capture the user's commit message style and patterns.
|
|
773
|
+
|
|
774
|
+
## Input Format
|
|
775
|
+
|
|
776
|
+
You will receive a list of commit messages from the user's git history:
|
|
777
|
+
|
|
778
|
+
\`\`\`
|
|
779
|
+
COMMIT HISTORY:
|
|
780
|
+
[List of previous commit messages]
|
|
781
|
+
\`\`\`
|
|
782
|
+
|
|
783
|
+
## Output Format
|
|
784
|
+
|
|
785
|
+
Generate a markdown document with clear, actionable guidelines. Use this structure:
|
|
786
|
+
|
|
787
|
+
\`\`\`markdown
|
|
788
|
+
# Commit Message Guidelines
|
|
789
|
+
|
|
790
|
+
## Format
|
|
791
|
+
[Describe the exact format: conventional commits, custom format, etc.]
|
|
792
|
+
|
|
793
|
+
## Style Rules
|
|
794
|
+
- **Tense**: [present/past/imperative]
|
|
795
|
+
- **Capitalization**: [first letter uppercase/lowercase/varies]
|
|
796
|
+
- **Length**: [typical character count or "concise"/"detailed"]
|
|
797
|
+
- **Tone**: [technical/casual/formal]
|
|
798
|
+
|
|
799
|
+
## Commit Types
|
|
800
|
+
[List the types they use with brief descriptions]
|
|
801
|
+
- \`type\`: When to use this type
|
|
802
|
+
|
|
803
|
+
## Scope Usage
|
|
804
|
+
[If they use scopes, describe the pattern. If not, say "No scopes used"]
|
|
805
|
+
|
|
806
|
+
## Description Patterns
|
|
807
|
+
[How they write descriptions - specific patterns, keywords, style]
|
|
808
|
+
|
|
809
|
+
## Examples from History
|
|
810
|
+
[Include 3-5 actual examples from their commits]
|
|
811
|
+
\`\`\`
|
|
812
|
+
|
|
813
|
+
## Analysis Guidelines
|
|
814
|
+
|
|
815
|
+
**IMPORTANT**: Ignore merge commits when analyzing style. Skip any commits that:
|
|
816
|
+
- Start with "Merge pull request"
|
|
817
|
+
- Start with "Merge branch"
|
|
818
|
+
- Start with "Merge remote-tracking branch"
|
|
819
|
+
- Contain "Merge" as the primary action
|
|
820
|
+
|
|
821
|
+
Focus only on regular commits (features, fixes, refactors, etc.) for style analysis.
|
|
822
|
+
|
|
823
|
+
When analyzing commits, look for:
|
|
824
|
+
|
|
825
|
+
1. **Structure Patterns**:
|
|
826
|
+
- Do they follow conventional commits? (type: description or type(scope): description)
|
|
827
|
+
- Is there a consistent format?
|
|
828
|
+
- Any special characters or prefixes?
|
|
829
|
+
|
|
830
|
+
2. **Commit Types**:
|
|
831
|
+
- What types do they use? (feat, fix, docs, refactor, style, test, chore, etc.)
|
|
832
|
+
- Are types consistent or mixed?
|
|
833
|
+
- Any custom types?
|
|
834
|
+
|
|
835
|
+
3. **Scope Patterns**:
|
|
836
|
+
- Do they use scopes in parentheses?
|
|
837
|
+
- What scopes appear frequently?
|
|
838
|
+
- Are scopes specific (file/component names) or general (area names)?
|
|
839
|
+
|
|
840
|
+
4. **Writing Style**:
|
|
841
|
+
- Present tense ("add feature") vs past tense ("added feature") vs imperative ("add feature")
|
|
842
|
+
- First letter capitalized or lowercase?
|
|
843
|
+
- Typical length - short and concise or longer and detailed?
|
|
844
|
+
- Technical terminology or simple language?
|
|
845
|
+
|
|
846
|
+
5. **Common Patterns**:
|
|
847
|
+
- Repeated keywords or phrases
|
|
848
|
+
- How they describe features vs fixes vs refactors
|
|
849
|
+
- Any emoji usage?
|
|
850
|
+
- Any ticket/issue references?
|
|
851
|
+
|
|
852
|
+
## Quality Requirements
|
|
853
|
+
|
|
854
|
+
The generated guidelines must be:
|
|
855
|
+
- ✓ **Clear and specific** - no vague statements
|
|
856
|
+
- ✓ **Actionable** - easy to follow when generating new commits
|
|
857
|
+
- ✓ **Accurate** - truly reflect the user's style
|
|
858
|
+
- ✓ **Concise** - keep it under 300 words
|
|
859
|
+
- ✓ **Consistent** - don't contradict yourself
|
|
860
|
+
|
|
861
|
+
## Special Cases
|
|
862
|
+
|
|
863
|
+
**If commits are inconsistent**: Choose the most frequent pattern and note: "Style varies, but most commonly uses [pattern]"
|
|
864
|
+
|
|
865
|
+
**If very few commits after filtering merges**: Note: "Limited commit history available. Using conventional commits as base with observed patterns."
|
|
866
|
+
|
|
867
|
+
**If only merge commits exist**: Generate standard conventional commits guidelines and note: "No regular commits found in history. Using conventional commits format."
|
|
868
|
+
|
|
869
|
+
**If commits are very simple**: That's fine! Note: "Prefers simple, straightforward commit messages"
|
|
870
|
+
|
|
871
|
+
**If no clear pattern**: Generate sensible conventional commit guidelines with a note: "No strong pattern detected. Recommending conventional commits format."
|
|
872
|
+
|
|
873
|
+
## Example Analysis
|
|
874
|
+
|
|
875
|
+
Given these commits:
|
|
876
|
+
\`\`\`
|
|
877
|
+
feat(auth): add OAuth2 login support
|
|
878
|
+
fix(api): resolve timeout issue in user endpoint
|
|
879
|
+
refactor: simplify database connection logic
|
|
880
|
+
docs: update README with setup instructions
|
|
881
|
+
feat(ui): implement dark mode toggle
|
|
882
|
+
\`\`\`
|
|
883
|
+
|
|
884
|
+
Generate guidelines like:
|
|
885
|
+
\`\`\`markdown
|
|
886
|
+
# Commit Message Guidelines
|
|
887
|
+
|
|
888
|
+
## Format
|
|
889
|
+
Use conventional commits: \`type(scope): description\`
|
|
890
|
+
|
|
891
|
+
## Style Rules
|
|
892
|
+
- **Tense**: Imperative/present ("add", "fix", "implement")
|
|
893
|
+
- **Capitalization**: Lowercase first letter
|
|
894
|
+
- **Length**: Concise, 40-60 characters
|
|
895
|
+
- **Tone**: Technical and specific
|
|
896
|
+
|
|
897
|
+
## Commit Types
|
|
898
|
+
- \`feat\`: New features or capabilities
|
|
899
|
+
- \`fix\`: Bug fixes and issue resolutions
|
|
900
|
+
- \`refactor\`: Code improvements without new features
|
|
901
|
+
- \`docs\`: Documentation updates
|
|
902
|
+
|
|
903
|
+
## Scope Usage
|
|
904
|
+
Use specific component/area names in parentheses (auth, api, ui). Omit scope for general changes like refactoring.
|
|
905
|
+
|
|
906
|
+
## Description Patterns
|
|
907
|
+
Start with action verb (add, implement, resolve, update, simplify). Be specific about what changed. Reference the component or feature affected.
|
|
908
|
+
|
|
909
|
+
## Examples from History
|
|
910
|
+
- feat(auth): add OAuth2 login support
|
|
911
|
+
- fix(api): resolve timeout issue in user endpoint
|
|
912
|
+
- refactor: simplify database connection logic
|
|
913
|
+
\`\`\`
|
|
914
|
+
|
|
915
|
+
## Important Notes
|
|
916
|
+
|
|
917
|
+
- Focus on patterns, not on individual commit content
|
|
918
|
+
- Generate guidelines that will work for future commits, not just explain past ones
|
|
919
|
+
- Keep it practical and easy to follow
|
|
920
|
+
- The output will be stored as \`.noto/commit-prompt.md\` and used by an AI to generate commits
|
|
921
|
+
|
|
922
|
+
Generate the markdown guidelines now based on the commit history provided.
|
|
923
|
+
`;
|
|
924
|
+
var cacheMiddleware = {
|
|
925
|
+
wrapGenerate: async ({ doGenerate, params }) => {
|
|
926
|
+
const key2 = hashString(JSON.stringify(params));
|
|
927
|
+
const cache = (await StorageManager.get()).cache;
|
|
928
|
+
if (cache && key2 in cache) {
|
|
929
|
+
const cached = cache[key2];
|
|
930
|
+
return superjson.parse(cached);
|
|
774
931
|
}
|
|
775
|
-
const
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
}
|
|
783
|
-
initialValue: currentBranch
|
|
932
|
+
const result = await doGenerate();
|
|
933
|
+
await StorageManager.update((current) => {
|
|
934
|
+
return {
|
|
935
|
+
...current,
|
|
936
|
+
cache: {
|
|
937
|
+
[key2]: superjson.stringify(result)
|
|
938
|
+
}
|
|
939
|
+
};
|
|
784
940
|
});
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
return await exit(1);
|
|
788
|
-
}
|
|
789
|
-
if (!branch) {
|
|
790
|
-
p5.log.error("no branch selected");
|
|
791
|
-
return await exit(1);
|
|
792
|
-
}
|
|
793
|
-
clipboard3.writeSync(branch);
|
|
794
|
-
p5.log.success(`${color5.green("copied to clipboard!")}`);
|
|
795
|
-
await exit(0);
|
|
796
|
-
}, { enabled: false }),
|
|
797
|
-
subCommands: [current, del]
|
|
941
|
+
return result;
|
|
942
|
+
}
|
|
798
943
|
};
|
|
799
|
-
var
|
|
944
|
+
var generateCommitMessage = async (diff, prompt, context, forceCache = false) => {
|
|
945
|
+
const model2 = await getModel();
|
|
946
|
+
const { object } = await generateObject({
|
|
947
|
+
model: !forceCache ? wrapLanguageModel({
|
|
948
|
+
model: model2,
|
|
949
|
+
middleware: cacheMiddleware
|
|
950
|
+
}) : model2,
|
|
951
|
+
schema: z6.object({
|
|
952
|
+
message: z6.string()
|
|
953
|
+
}),
|
|
954
|
+
messages: [
|
|
955
|
+
{
|
|
956
|
+
role: "system",
|
|
957
|
+
content: COMMIT_GENERATOR_PROMPT
|
|
958
|
+
},
|
|
959
|
+
{
|
|
960
|
+
role: "user",
|
|
961
|
+
content: dedent3`
|
|
962
|
+
USER GUIDELINES:
|
|
963
|
+
${prompt ?? DEFAULT_COMMIT_GUIDELINES}
|
|
964
|
+
${context ? `
|
|
965
|
+
USER CONTEXT:
|
|
966
|
+
${context}` : ""}
|
|
800
967
|
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
import clipboard4 from "clipboardy";
|
|
805
|
-
import dedent6 from "dedent";
|
|
806
|
-
var command4 = {
|
|
807
|
-
name: "checkout",
|
|
808
|
-
description: "checkout a branch",
|
|
809
|
-
usage: "checkout [options]",
|
|
810
|
-
options: [
|
|
811
|
-
{
|
|
812
|
-
type: Boolean,
|
|
813
|
-
flag: "--copy",
|
|
814
|
-
alias: "-c",
|
|
815
|
-
description: "copy the selected branch to clipboard"
|
|
816
|
-
},
|
|
817
|
-
{
|
|
818
|
-
type: Boolean,
|
|
819
|
-
flag: "--create",
|
|
820
|
-
alias: "-b",
|
|
821
|
-
description: "create a new branch"
|
|
822
|
-
}
|
|
823
|
-
],
|
|
824
|
-
execute: withRepository(async (options) => {
|
|
825
|
-
const args = options._.slice(1);
|
|
826
|
-
if (!options.isRepo) {
|
|
827
|
-
p6.log.error(dedent6`${color6.red("no git repository found in cwd.")}
|
|
828
|
-
${color6.dim(`run ${color6.cyan("`git init`")} to initialize a new repository.`)}`);
|
|
829
|
-
return await exit(1);
|
|
830
|
-
}
|
|
831
|
-
const branches = await getBranches();
|
|
832
|
-
if (!branches) {
|
|
833
|
-
p6.log.error("failed to fetch branches");
|
|
834
|
-
return await exit(1);
|
|
835
|
-
}
|
|
836
|
-
const currentBranch = await getCurrentBranch();
|
|
837
|
-
const branchName = args[0];
|
|
838
|
-
if (options["--create"]) {
|
|
839
|
-
if (branches.includes(branchName)) {
|
|
840
|
-
p6.log.error(`branch ${color6.red(branchName)} already exists in the repository`);
|
|
841
|
-
return await exit(1);
|
|
842
|
-
}
|
|
843
|
-
const result2 = await checkoutLocalBranch(branchName);
|
|
844
|
-
if (!result2) {
|
|
845
|
-
p6.log.error(`failed to create and checkout ${color6.bold(branchName)}`);
|
|
846
|
-
return await exit(1);
|
|
847
|
-
}
|
|
848
|
-
p6.log.success(`created and checked out ${color6.green(branchName)}`);
|
|
849
|
-
return await exit(0);
|
|
850
|
-
}
|
|
851
|
-
if (branchName) {
|
|
852
|
-
if (!branches.includes(branchName)) {
|
|
853
|
-
p6.log.error(`branch ${color6.red(branchName)} does not exist in the repository`);
|
|
854
|
-
const createBranch = await p6.confirm({
|
|
855
|
-
message: `do you want to create branch ${color6.green(branchName)}?`
|
|
856
|
-
});
|
|
857
|
-
if (p6.isCancel(createBranch)) {
|
|
858
|
-
p6.log.error("aborted");
|
|
859
|
-
return await exit(1);
|
|
860
|
-
}
|
|
861
|
-
if (createBranch) {
|
|
862
|
-
const result3 = await checkoutLocalBranch(branchName);
|
|
863
|
-
if (!result3) {
|
|
864
|
-
p6.log.error(`failed to create and checkout ${color6.bold(branchName)}`);
|
|
865
|
-
return await exit(1);
|
|
866
|
-
}
|
|
867
|
-
p6.log.success(`created and checked out ${color6.green(branchName)}`);
|
|
868
|
-
return await exit(0);
|
|
869
|
-
}
|
|
870
|
-
return await exit(1);
|
|
968
|
+
GIT DIFF:
|
|
969
|
+
${diff}
|
|
970
|
+
`
|
|
871
971
|
}
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
972
|
+
]
|
|
973
|
+
});
|
|
974
|
+
return object.message.trim();
|
|
975
|
+
};
|
|
976
|
+
var generateCommitGuidelines = async (commits) => {
|
|
977
|
+
const model2 = await getModel();
|
|
978
|
+
const { object } = await generateObject({
|
|
979
|
+
model: model2,
|
|
980
|
+
schema: z6.object({
|
|
981
|
+
prompt: z6.string()
|
|
982
|
+
}),
|
|
983
|
+
messages: [
|
|
984
|
+
{
|
|
985
|
+
role: "system",
|
|
986
|
+
content: GUIDELINES_GENERATOR_PROMPT
|
|
987
|
+
},
|
|
988
|
+
{
|
|
989
|
+
role: "user",
|
|
990
|
+
content: dedent3`
|
|
991
|
+
COMMIT HISTORY:
|
|
992
|
+
${commits.join(`
|
|
993
|
+
`)}`
|
|
875
994
|
}
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
995
|
+
]
|
|
996
|
+
});
|
|
997
|
+
return object.prompt.trim();
|
|
998
|
+
};
|
|
999
|
+
|
|
1000
|
+
// src/commands/init.ts
|
|
1001
|
+
var EMPTY_TEMPLATE = dedent4`
|
|
1002
|
+
# Commit Message Guidelines
|
|
1003
|
+
|
|
1004
|
+
# Add your custom guidelines here.
|
|
1005
|
+
# When no guidelines are present, noto will use conventional commits format by default.`;
|
|
1006
|
+
var init = authedGitProcedure.meta({
|
|
1007
|
+
description: "initialize noto in the repository"
|
|
1008
|
+
}).input(z7.object({
|
|
1009
|
+
root: z7.boolean().meta({
|
|
1010
|
+
description: "create the prompt file in the git root"
|
|
1011
|
+
}),
|
|
1012
|
+
generate: z7.boolean().meta({
|
|
1013
|
+
description: "generate a prompt file based on existing commits"
|
|
1014
|
+
})
|
|
1015
|
+
})).mutation(async (opts) => {
|
|
1016
|
+
const { input } = opts;
|
|
1017
|
+
const root = await getGitRoot();
|
|
1018
|
+
let promptFile = root;
|
|
1019
|
+
const cwd = process.cwd();
|
|
1020
|
+
const existingPromptFile = await getPromptFile();
|
|
1021
|
+
let prompt = null;
|
|
1022
|
+
if (existingPromptFile) {
|
|
1023
|
+
if (!existingPromptFile.startsWith(cwd)) {
|
|
1024
|
+
p7.log.warn(dedent4`${color7.yellow("a prompt file already exists!")}
|
|
1025
|
+
${color7.gray(existingPromptFile)}`);
|
|
1026
|
+
const shouldContinue = await p7.confirm({
|
|
1027
|
+
message: "do you want to create in the current directory instead?",
|
|
1028
|
+
initialValue: true
|
|
1029
|
+
});
|
|
1030
|
+
if (p7.isCancel(shouldContinue) || !shouldContinue) {
|
|
1031
|
+
p7.log.error("aborted");
|
|
879
1032
|
return await exit(1);
|
|
880
1033
|
}
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
message: "select a branch to checkout",
|
|
886
|
-
options: branches.map((branch2) => ({
|
|
887
|
-
value: branch2,
|
|
888
|
-
label: color6.bold(branch2 === currentBranch ? color6.green(branch2) : branch2),
|
|
889
|
-
hint: branch2 === currentBranch ? "current branch" : undefined
|
|
890
|
-
})),
|
|
891
|
-
initialValue: currentBranch
|
|
892
|
-
});
|
|
893
|
-
if (p6.isCancel(branch)) {
|
|
894
|
-
p6.log.error("nothing selected!");
|
|
1034
|
+
promptFile = cwd;
|
|
1035
|
+
} else {
|
|
1036
|
+
p7.log.error(dedent4`${color7.red("a prompt file already exists.")}
|
|
1037
|
+
${color7.gray(existingPromptFile)}`);
|
|
895
1038
|
return await exit(1);
|
|
896
1039
|
}
|
|
897
|
-
|
|
898
|
-
|
|
1040
|
+
}
|
|
1041
|
+
if (root !== cwd && !input.root) {
|
|
1042
|
+
const shouldUseRoot = await p7.confirm({
|
|
1043
|
+
message: "do you want to create the prompt file in the git root?",
|
|
1044
|
+
initialValue: true
|
|
1045
|
+
});
|
|
1046
|
+
if (p7.isCancel(shouldUseRoot)) {
|
|
1047
|
+
p7.log.error("aborted");
|
|
899
1048
|
return await exit(1);
|
|
900
1049
|
}
|
|
901
|
-
if (
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
1050
|
+
if (!shouldUseRoot)
|
|
1051
|
+
promptFile = cwd;
|
|
1052
|
+
}
|
|
1053
|
+
const commits = await getCommits();
|
|
1054
|
+
let generate = input.generate;
|
|
1055
|
+
if (generate) {
|
|
1056
|
+
if (!commits || commits.length < 5) {
|
|
1057
|
+
p7.log.error(dedent4`${color7.red("not enough commits to generate a prompt file.")}
|
|
1058
|
+
${color7.gray("at least 5 commits are required.")}`);
|
|
908
1059
|
return await exit(1);
|
|
909
1060
|
}
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
1061
|
+
} else if (commits && commits.length >= 5) {
|
|
1062
|
+
const shouldGenerate = await p7.confirm({
|
|
1063
|
+
message: "do you want to generate a prompt file based on existing commits?",
|
|
1064
|
+
initialValue: true
|
|
1065
|
+
});
|
|
1066
|
+
if (p7.isCancel(shouldGenerate)) {
|
|
1067
|
+
p7.log.error("aborted");
|
|
913
1068
|
return await exit(1);
|
|
914
1069
|
}
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
1070
|
+
generate = shouldGenerate;
|
|
1071
|
+
}
|
|
1072
|
+
const spin = p7.spinner();
|
|
1073
|
+
if (commits && generate) {
|
|
1074
|
+
spin.start("generating commit message guidelines");
|
|
1075
|
+
prompt = await generateCommitGuidelines(commits);
|
|
1076
|
+
spin.stop(color7.green("generated commit message guidelines!"));
|
|
1077
|
+
} else {
|
|
1078
|
+
prompt = EMPTY_TEMPLATE;
|
|
1079
|
+
}
|
|
1080
|
+
try {
|
|
1081
|
+
const dir = `${promptFile}/.noto`;
|
|
1082
|
+
await fs4.mkdir(dir, { recursive: true });
|
|
1083
|
+
const filePath = `${dir}/commit-prompt.md`;
|
|
1084
|
+
await fs4.writeFile(filePath, prompt, "utf-8");
|
|
1085
|
+
p7.log.success(dedent4`${color7.green("prompt file created!")}
|
|
1086
|
+
${color7.gray(filePath)}`);
|
|
1087
|
+
return await exit(0);
|
|
1088
|
+
} catch {
|
|
1089
|
+
p7.log.error(color7.red("failed to create the prompt file!"));
|
|
1090
|
+
}
|
|
1091
|
+
});
|
|
920
1092
|
|
|
921
|
-
// src/commands/
|
|
922
|
-
import
|
|
923
|
-
import
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
1093
|
+
// src/commands/noto.ts
|
|
1094
|
+
import { z as z8 } from "zod";
|
|
1095
|
+
import * as p8 from "@clack/prompts";
|
|
1096
|
+
import color8 from "picocolors";
|
|
1097
|
+
import clipboard3 from "clipboardy";
|
|
1098
|
+
import { APICallError, RetryError } from "ai";
|
|
1099
|
+
var noto = authedGitProcedure.meta({
|
|
1100
|
+
description: "generate a commit message",
|
|
1101
|
+
default: true,
|
|
1102
|
+
diffRequired: true,
|
|
1103
|
+
promptRequired: true
|
|
1104
|
+
}).input(z8.object({
|
|
1105
|
+
message: z8.string().or(z8.boolean()).meta({
|
|
1106
|
+
description: "provide context for commit message",
|
|
1107
|
+
alias: "m"
|
|
1108
|
+
}),
|
|
1109
|
+
copy: z8.boolean().meta({
|
|
1110
|
+
description: "copy the generated message to clipboard",
|
|
1111
|
+
alias: "c"
|
|
1112
|
+
}),
|
|
1113
|
+
apply: z8.boolean().meta({ description: "commit the generated message", alias: "a" }),
|
|
1114
|
+
push: z8.boolean().meta({ description: "commit and push the changes", alias: "p" }),
|
|
1115
|
+
force: z8.boolean().meta({
|
|
1116
|
+
description: "bypass cache and force regeneration of commit message",
|
|
1117
|
+
alias: "f"
|
|
1118
|
+
}),
|
|
1119
|
+
manual: z8.boolean().meta({ description: "custom commit message" })
|
|
1120
|
+
})).mutation(async (opts) => {
|
|
1121
|
+
const { input, ctx } = opts;
|
|
1122
|
+
const spin = p8.spinner();
|
|
1123
|
+
try {
|
|
1124
|
+
const manual = input.manual;
|
|
1125
|
+
if (manual) {
|
|
1126
|
+
const message2 = await p8.text({
|
|
1127
|
+
message: "edit the generated commit message",
|
|
1128
|
+
placeholder: "chore: init repo"
|
|
932
1129
|
});
|
|
933
|
-
if (
|
|
934
|
-
|
|
1130
|
+
if (p8.isCancel(message2)) {
|
|
1131
|
+
p8.log.error(color8.red("nothing changed!"));
|
|
935
1132
|
return await exit(1);
|
|
936
1133
|
}
|
|
1134
|
+
p8.log.step(color8.green(message2));
|
|
1135
|
+
await StorageManager.update((current) => ({
|
|
1136
|
+
...current,
|
|
1137
|
+
lastGeneratedMessage: message2
|
|
1138
|
+
}));
|
|
1139
|
+
const success = await commit(message2);
|
|
1140
|
+
if (success) {
|
|
1141
|
+
p8.log.step(color8.dim("commit successful"));
|
|
1142
|
+
} else {
|
|
1143
|
+
p8.log.error(color8.red("failed to commit changes"));
|
|
1144
|
+
}
|
|
1145
|
+
return await exit(0);
|
|
937
1146
|
}
|
|
938
|
-
let
|
|
939
|
-
if (
|
|
940
|
-
|
|
941
|
-
|
|
1147
|
+
let context = input.message;
|
|
1148
|
+
if (typeof context === "string") {
|
|
1149
|
+
context = context.trim();
|
|
1150
|
+
} else if (typeof context === "boolean" && context === true) {
|
|
1151
|
+
const enteredContext = await p8.text({
|
|
1152
|
+
message: "provide context for the commit message",
|
|
1153
|
+
placeholder: "describe the changes"
|
|
942
1154
|
});
|
|
943
|
-
if (
|
|
944
|
-
|
|
1155
|
+
if (p8.isCancel(enteredContext)) {
|
|
1156
|
+
p8.log.error(color8.red("nothing changed!"));
|
|
945
1157
|
return await exit(1);
|
|
946
1158
|
}
|
|
947
|
-
|
|
1159
|
+
context = enteredContext;
|
|
948
1160
|
}
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
await exit(0);
|
|
958
|
-
}
|
|
959
|
-
};
|
|
960
|
-
var model = {
|
|
961
|
-
name: "model",
|
|
962
|
-
description: "configure model",
|
|
963
|
-
usage: "noto config model [options]",
|
|
964
|
-
execute: async () => {
|
|
965
|
-
const model2 = await p7.select({
|
|
966
|
-
message: "select a model",
|
|
967
|
-
initialValue: (await StorageManager.get()).llm?.model,
|
|
968
|
-
options: Object.keys(models).map((model3) => ({
|
|
969
|
-
label: model3,
|
|
970
|
-
value: model3
|
|
971
|
-
}))
|
|
1161
|
+
spin.start("generating commit message");
|
|
1162
|
+
let message = null;
|
|
1163
|
+
message = await generateCommitMessage(ctx.git.diff, ctx.noto.prompt, typeof context === "string" ? context : undefined, input.force);
|
|
1164
|
+
spin.stop(color8.white(message));
|
|
1165
|
+
const editedMessage = await p8.text({
|
|
1166
|
+
message: "edit the generated commit message",
|
|
1167
|
+
initialValue: message,
|
|
1168
|
+
placeholder: message
|
|
972
1169
|
});
|
|
973
|
-
if (
|
|
974
|
-
|
|
1170
|
+
if (p8.isCancel(editedMessage)) {
|
|
1171
|
+
p8.log.error(color8.red("nothing changed!"));
|
|
975
1172
|
return await exit(1);
|
|
976
1173
|
}
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
p7.log.error(color7.red("nothing changed!"));
|
|
983
|
-
return await exit(1);
|
|
984
|
-
}
|
|
985
|
-
}
|
|
986
|
-
await StorageManager.update((current2) => ({
|
|
987
|
-
...current2,
|
|
988
|
-
llm: {
|
|
989
|
-
...current2.llm,
|
|
990
|
-
model: model2
|
|
991
|
-
}
|
|
1174
|
+
message = editedMessage;
|
|
1175
|
+
p8.log.step(color8.green(message));
|
|
1176
|
+
await StorageManager.update((current) => ({
|
|
1177
|
+
...current,
|
|
1178
|
+
lastGeneratedMessage: message
|
|
992
1179
|
}));
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
};
|
|
997
|
-
var reset = {
|
|
998
|
-
name: "reset",
|
|
999
|
-
description: "reset configuration",
|
|
1000
|
-
usage: "noto config reset",
|
|
1001
|
-
execute: async () => {
|
|
1002
|
-
const confirm3 = await p7.confirm({
|
|
1003
|
-
message: "are you sure you want to reset the configuration?"
|
|
1004
|
-
});
|
|
1005
|
-
if (p7.isCancel(confirm3) || !confirm3) {
|
|
1006
|
-
p7.log.error(color7.red("nothing changed!"));
|
|
1007
|
-
return await exit(1);
|
|
1180
|
+
if (input.copy) {
|
|
1181
|
+
clipboard3.writeSync(message);
|
|
1182
|
+
p8.log.step(color8.dim("copied commit message to clipboard"));
|
|
1008
1183
|
}
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
}
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
name: "config",
|
|
1017
|
-
description: "configure noto",
|
|
1018
|
-
usage: "noto config [subcommand]",
|
|
1019
|
-
execute: async (options) => {
|
|
1020
|
-
const command6 = await p7.select({
|
|
1021
|
-
message: "select a subcommand",
|
|
1022
|
-
options: subCommands.map((cmd2) => ({
|
|
1023
|
-
label: cmd2.description,
|
|
1024
|
-
value: cmd2.name
|
|
1025
|
-
}))
|
|
1026
|
-
});
|
|
1027
|
-
if (p7.isCancel(command6)) {
|
|
1028
|
-
return await exit(1);
|
|
1184
|
+
if (input.apply) {
|
|
1185
|
+
const success = await commit(message);
|
|
1186
|
+
if (success) {
|
|
1187
|
+
p8.log.step(color8.dim("commit successful"));
|
|
1188
|
+
} else {
|
|
1189
|
+
p8.log.error(color8.red("failed to commit changes"));
|
|
1190
|
+
}
|
|
1029
1191
|
}
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1192
|
+
if (input.push) {
|
|
1193
|
+
const success = await push();
|
|
1194
|
+
if (success) {
|
|
1195
|
+
p8.log.step(color8.dim("push successful"));
|
|
1196
|
+
} else {
|
|
1197
|
+
p8.log.error(color8.red("failed to push changes"));
|
|
1198
|
+
}
|
|
1034
1199
|
}
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
var config_default = command5;
|
|
1041
|
-
|
|
1042
|
-
// src/commands/help.ts
|
|
1043
|
-
import color8 from "picocolors";
|
|
1044
|
-
var help = {
|
|
1045
|
-
name: "help",
|
|
1046
|
-
description: "show help",
|
|
1047
|
-
usage: "noto help [command]",
|
|
1048
|
-
execute: async (options) => {
|
|
1049
|
-
const command6 = getCommand(options._[0]);
|
|
1050
|
-
if (command6 && command6.name !== "help") {
|
|
1051
|
-
console.log();
|
|
1052
|
-
console.log(color8.bold("usage"));
|
|
1053
|
-
console.log(` ${command6.usage}`);
|
|
1054
|
-
console.log();
|
|
1055
|
-
console.log(color8.bold("description"));
|
|
1056
|
-
console.log(` ${command6.description}`);
|
|
1057
|
-
console.log();
|
|
1058
|
-
} else {
|
|
1059
|
-
const commands = listCommand();
|
|
1060
|
-
console.log();
|
|
1061
|
-
console.log(color8.bold("usage"));
|
|
1062
|
-
console.log(` noto [command] [options]`);
|
|
1063
|
-
console.log();
|
|
1064
|
-
console.log(color8.bold("commands"));
|
|
1065
|
-
commands.forEach((command7) => {
|
|
1066
|
-
console.log(` ${color8.bold(command7.name)} ${color8.dim(command7.description)}`);
|
|
1067
|
-
});
|
|
1068
|
-
await exit(0);
|
|
1200
|
+
return await exit(0);
|
|
1201
|
+
} catch (e) {
|
|
1202
|
+
let msg;
|
|
1203
|
+
if (RetryError.isInstance(e) && APICallError.isInstance(e.lastError)) {
|
|
1204
|
+
msg = safeParseErrorMessage(e.lastError.responseBody);
|
|
1069
1205
|
}
|
|
1206
|
+
const suffix = msg ? `
|
|
1207
|
+
${msg}` : "";
|
|
1208
|
+
spin.stop(color8.red(`failed to generate commit message${suffix}`), 1);
|
|
1209
|
+
await exit(1);
|
|
1070
1210
|
}
|
|
1071
|
-
};
|
|
1072
|
-
|
|
1211
|
+
});
|
|
1212
|
+
function safeParseErrorMessage(body) {
|
|
1213
|
+
if (typeof body !== "string")
|
|
1214
|
+
return;
|
|
1215
|
+
try {
|
|
1216
|
+
const parsed = JSON.parse(body);
|
|
1217
|
+
return parsed?.error?.message ?? parsed?.message;
|
|
1218
|
+
} catch {
|
|
1219
|
+
return;
|
|
1220
|
+
}
|
|
1221
|
+
}
|
|
1073
1222
|
|
|
1074
1223
|
// src/commands/index.ts
|
|
1075
|
-
var commands =
|
|
1076
|
-
|
|
1077
|
-
|
|
1224
|
+
var commands = {
|
|
1225
|
+
checkout: checkout2,
|
|
1226
|
+
config,
|
|
1227
|
+
prev,
|
|
1228
|
+
init,
|
|
1229
|
+
noto
|
|
1078
1230
|
};
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
// package.json
|
|
1083
|
-
var version = "1.2.9";
|
|
1231
|
+
|
|
1232
|
+
// src/router.ts
|
|
1233
|
+
var router = t.router(commands);
|
|
1084
1234
|
|
|
1085
1235
|
// src/index.ts
|
|
1086
|
-
|
|
1087
|
-
"
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
};
|
|
1092
|
-
function main() {
|
|
1093
|
-
const args = process.argv.slice(2);
|
|
1094
|
-
const { command: command6, options: globalOptions } = parse(globalSpec, args);
|
|
1095
|
-
console.log();
|
|
1096
|
-
p8.intro(`${color9.bgCyan(color9.black(" @snelusha/noto "))}`);
|
|
1097
|
-
if (globalOptions["--version"])
|
|
1098
|
-
return p8.outro(version);
|
|
1099
|
-
if (globalOptions["--help"]) {
|
|
1100
|
-
getCommand("help")?.execute(globalOptions);
|
|
1101
|
-
return;
|
|
1102
|
-
}
|
|
1103
|
-
const cmd = getCommand(command6) ?? getCommand("noto");
|
|
1104
|
-
if (!cmd)
|
|
1105
|
-
return getCommand("noto")?.execute(globalOptions);
|
|
1106
|
-
let commandArgs = args;
|
|
1107
|
-
let selectedCommand = cmd;
|
|
1108
|
-
if (cmd.subCommands && commandArgs.length) {
|
|
1109
|
-
const possibleCommand = commandArgs[1];
|
|
1110
|
-
const subCommand = cmd.subCommands.find((cmd2) => cmd2.name === possibleCommand || cmd2.aliases && cmd2.aliases.includes(possibleCommand));
|
|
1111
|
-
if (subCommand) {
|
|
1112
|
-
selectedCommand = subCommand;
|
|
1113
|
-
commandArgs = commandArgs.slice(2);
|
|
1114
|
-
}
|
|
1115
|
-
}
|
|
1116
|
-
const commandSpec = (selectedCommand.options ?? []).reduce((acc, opt) => {
|
|
1117
|
-
acc[opt.flag] = opt.type ?? Boolean;
|
|
1118
|
-
if (Array.isArray(opt.alias))
|
|
1119
|
-
opt.alias.forEach((alias) => acc[alias] = opt.flag);
|
|
1120
|
-
else if (opt.alias)
|
|
1121
|
-
acc[opt.alias] = opt.flag;
|
|
1122
|
-
return acc;
|
|
1123
|
-
}, {});
|
|
1124
|
-
const { options: commandOptions } = safeParse(commandSpec, commandArgs);
|
|
1125
|
-
const options = { ...globalOptions, ...commandOptions };
|
|
1126
|
-
selectedCommand.execute(options);
|
|
1127
|
-
}
|
|
1128
|
-
main();
|
|
1236
|
+
createCli({
|
|
1237
|
+
name: "noto",
|
|
1238
|
+
router,
|
|
1239
|
+
version
|
|
1240
|
+
}).run();
|