@akanjs/cli 3.0.0-beta.13 → 3.0.0-beta.14
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/.build-stamp +1 -1
- package/{TunnelShare-2kwqb1j6.js → TunnelShare-r5tc2qk5.js} +2 -2
- package/{agent.command-7357vxd8.js → agent.command-bhwsc2aj.js} +1 -1
- package/{application.command-8s8ns95d.js → application.command-436y543n.js} +1 -11
- package/{cloud.command-p5dnhft9.js → cloud.command-3xn0xpz8.js} +4 -17
- package/{cloud.script-gxpbhras.js → cloud.script-0a76c2g8.js} +4 -5
- package/commandManifest.json +1 -1
- package/{context.command-wsjt98c1.js → context.command-hxh96cc3.js} +4 -6
- package/{devTui-myg3pqeg.js → devTui-4ted58a6.js} +1 -1
- package/{devTuiView-gz2fbf4v.js → devTuiView-1dj2yq7t.js} +1 -1
- package/guideline.command-cyw8c0m0.js +48 -0
- package/guidelines/agentRule/agentRule.instruction.md +24 -13
- package/guidelines/conventions/conventions.instruction.md +6 -3
- package/guidelines/moduleCodegen/moduleCodegen.instruction.md +1 -1
- package/guidelines/workspaceRecipes/workspaceRecipes.generate.json +1 -0
- package/guidelines/workspaceRecipes/workspaceRecipes.instruction.md +62 -0
- package/index-1mrmve4s.js +61 -0
- package/{index-s3fvrydw.js → index-31hmq4ys.js} +1 -1
- package/{index-t3c045et.js → index-8tcj5wk8.js} +6 -19
- package/index-9fbk3dbh.js +234 -0
- package/{index-j7rwsd23.js → index-fad0hhty.js} +3 -3
- package/{index-wmb05v4q.js → index-gm76f0qb.js} +1 -1
- package/{index-649svkyt.js → index-myj01w9r.js} +8 -15
- package/{index-h3g1152j.js → index-r73ha1rf.js} +1 -1
- package/{index-cnzm1wn5.js → index-sg1bgdhe.js} +65 -68
- package/{index-bz10w9t2.js → index-tt6kzwj5.js} +5 -15
- package/index.js +14 -14
- package/{localRegistry.command-w6w35j4r.js → localRegistry.command-4cbgz63s.js} +3 -4
- package/{module.command-8zkq0q9q.js → module.command-ew0484va.js} +4 -7
- package/package.json +2 -5
- package/{primitive.command-qm7tybpn.js → primitive.command-50ecfzg4.js} +2 -5
- package/{quality.command-5evdd263.js → quality.command-a2jytczs.js} +63 -3
- package/{scalar.command-a1ewkmab.js → scalar.command-bptz6aj6.js} +3 -6
- package/{subspace.command-3w5x13a1.js → subspace.command-kmemx1vm.js} +2 -2
- package/{tunnel.command-09n8h4s1.js → tunnel.command-67q8vsg5.js} +2 -2
- package/{workflow.command-y3fct1rq.js → workflow.command-2ze5y2xp.js} +4 -7
- package/{workspace.command-p6td4qad.js → workspace.command-2n385jkt.js} +9 -10
- package/abstractCompactor-g04vqcff.js +0 -102
- package/guideline.command-snqzfyt0.js +0 -360
- package/index-5tw8t1ts.js +0 -338
- package/index-j1tajctp.js +0 -229
- package/index-kpnp2e10.js +0 -841
- package/index-meh558jy.js +0 -65
|
@@ -1,360 +0,0 @@
|
|
|
1
|
-
// @bun
|
|
2
|
-
import {
|
|
3
|
-
Workspace,
|
|
4
|
-
runner,
|
|
5
|
-
script,
|
|
6
|
-
command
|
|
7
|
-
} from "./index-b3ae16mg.js";
|
|
8
|
-
import {
|
|
9
|
-
FileSys
|
|
10
|
-
} from "./index-q0y2jq9j.js";
|
|
11
|
-
import"./index-5nyznra1.js";
|
|
12
|
-
import {
|
|
13
|
-
getDirname2
|
|
14
|
-
} from "./index-6wwzs1bc.js";
|
|
15
|
-
import {
|
|
16
|
-
Prompter
|
|
17
|
-
} from "./index-h7yw2z9n.js";
|
|
18
|
-
import"./index-649svkyt.js";
|
|
19
|
-
import {
|
|
20
|
-
AiSession
|
|
21
|
-
} from "./index-5tw8t1ts.js";
|
|
22
|
-
|
|
23
|
-
// pkgs/@akanjs/cli/guideline/guideline.script.ts
|
|
24
|
-
import { Logger } from "akanjs/common";
|
|
25
|
-
|
|
26
|
-
// pkgs/@akanjs/cli/guideline/guideline.prompt.ts
|
|
27
|
-
import { randomPicks } from "akanjs/common";
|
|
28
|
-
|
|
29
|
-
class GuidelinePrompt extends Prompter {
|
|
30
|
-
workspace;
|
|
31
|
-
name;
|
|
32
|
-
constructor(workspace, name) {
|
|
33
|
-
super();
|
|
34
|
-
this.workspace = workspace;
|
|
35
|
-
this.name = name;
|
|
36
|
-
}
|
|
37
|
-
#resolveDocsPagePath(page) {
|
|
38
|
-
if (page.startsWith("apps/"))
|
|
39
|
-
return page;
|
|
40
|
-
if (page.endsWith(".tsx"))
|
|
41
|
-
return page;
|
|
42
|
-
return `apps/akan/page${page}`;
|
|
43
|
-
}
|
|
44
|
-
async#getScanFilePaths(matchPattern, { avoidDirs = ["node_modules", ".next"], filterText } = {}) {
|
|
45
|
-
const glob = new Bun.Glob(matchPattern);
|
|
46
|
-
const paths = [];
|
|
47
|
-
for await (const path of glob.scan({ cwd: this.workspace.workspaceRoot, absolute: true })) {
|
|
48
|
-
if (avoidDirs.some((dir) => path.includes(dir)))
|
|
49
|
-
continue;
|
|
50
|
-
const fileContent = await FileSys.readText(path);
|
|
51
|
-
const textFilter = filterText ? new RegExp(filterText) : null;
|
|
52
|
-
if (filterText && !textFilter?.test(fileContent))
|
|
53
|
-
continue;
|
|
54
|
-
paths.push(path);
|
|
55
|
-
}
|
|
56
|
-
return paths;
|
|
57
|
-
}
|
|
58
|
-
async requestCreateInstruction() {
|
|
59
|
-
const guideJson = await Prompter.getGuideJson(this.name);
|
|
60
|
-
const scanFiles = [];
|
|
61
|
-
for (const scan of guideJson.scans) {
|
|
62
|
-
const scanFilePaths = await this.#getScanFilePaths(scan.path, { filterText: scan.filterText });
|
|
63
|
-
const targetFilePaths = scan.sample ? randomPicks(scanFilePaths, scan.sample) : scanFilePaths;
|
|
64
|
-
const scanResult = await Promise.all(targetFilePaths.map(async (filePath) => ({ ...scan, ...await this.workspace.getLocalFile(filePath) })));
|
|
65
|
-
scanFiles.push(...scanResult);
|
|
66
|
-
}
|
|
67
|
-
const resultPath = `${getDirname2(import.meta.url)}/../guidelines/${this.name}/${guideJson.update.filePath}`;
|
|
68
|
-
const writePath = `${this.workspace.workspaceRoot}/pkgs/@akanjs/cli/guidelines/${this.name}/${guideJson.update.filePath}`;
|
|
69
|
-
const isResultExists = await this.workspace.exists(writePath);
|
|
70
|
-
const existingResult = isResultExists ? await this.workspace.readFile(resultPath) : null;
|
|
71
|
-
const request = `
|
|
72
|
-
I am a developer of akanjs framework, a full-stack framework for building web applications.
|
|
73
|
-
I want to generate a ${guideJson.update.filePath} file for ${guideJson.description}.
|
|
74
|
-
This file is a programming guideline for Akan.js framwork users.
|
|
75
|
-
|
|
76
|
-
# ${guideJson.title} Workflow
|
|
77
|
-
- 1. Explore target files in ${guideJson.scans.map((scan) => scan.path).join(", ")}.
|
|
78
|
-
- 2. Read already-written ${guideJson.update.filePath} file if exists. There can be some outdated information, so you need to update the file and add new information, make it more accurate and more detailed.
|
|
79
|
-
- 3. Write the updated file content of the ${guideJson.update.filePath}. It should include the contents of ${guideJson.update.contents.join(",")}, and any other helpful information and contents for guiding. Also, you can rearrange the contents if you think it's more helpful.
|
|
80
|
-
|
|
81
|
-
## 1. Explore target files in ${guideJson.scans.map((scan) => scan.path).join(", ")}.
|
|
82
|
-
${scanFiles.map(({ type, description, filePath, content, sample }) => `
|
|
83
|
-
\`\`\`${filePath.endsWith(".tsx") ? "tsx" : "typescript"}
|
|
84
|
-
// File: ${filePath}
|
|
85
|
-
// Type: ${type}${sample ? " (sample)" : ""}
|
|
86
|
-
// Desc: ${description}
|
|
87
|
-
${content}
|
|
88
|
-
\`\`\`
|
|
89
|
-
`).join(`
|
|
90
|
-
`)}
|
|
91
|
-
|
|
92
|
-
## 2. Read already-written ${guideJson.update.filePath} file if exists. There can be some outdated information, so you need to update the file and add new information, make it more accurate and more detailed.
|
|
93
|
-
${existingResult ? `\`\`\`markdown
|
|
94
|
-
${existingResult}
|
|
95
|
-
\`\`\`` : "- No existing guideline file."}
|
|
96
|
-
|
|
97
|
-
## 3. Write the updated file content of the ${guideJson.update.filePath}. It should include the contents of ${guideJson.update.contents.join(",")}, and any other helpful information and contents for guiding. Also, you can rearrange the contents if you think it's more helpful.
|
|
98
|
-
|
|
99
|
-
Here's the last rules for writing the file content:
|
|
100
|
-
${guideJson.update.rules.map((rule) => `- ${rule}`).join(`
|
|
101
|
-
`)}
|
|
102
|
-
|
|
103
|
-
=> Now, you need to write the file content here. Let's go.
|
|
104
|
-
`;
|
|
105
|
-
return { guideJson, request, writePath };
|
|
106
|
-
}
|
|
107
|
-
async requestUpdateInstruction(updateRequest) {
|
|
108
|
-
const guideJson = await Prompter.getGuideJson(this.name);
|
|
109
|
-
const resultPath = `${getDirname2(import.meta.url)}/../guidelines/${this.name}/${guideJson.update.filePath}`;
|
|
110
|
-
const writePath = `${this.workspace.workspaceRoot}/pkgs/@akanjs/cli/guidelines/${this.name}/${guideJson.update.filePath}`;
|
|
111
|
-
const isResultExists = await this.workspace.exists(writePath);
|
|
112
|
-
if (!isResultExists)
|
|
113
|
-
throw new Error(`${guideJson.update.filePath} file does not exist. Please create it first.`);
|
|
114
|
-
const existingResult = await this.workspace.readFile(resultPath);
|
|
115
|
-
const request = `
|
|
116
|
-
I am a developer of akanjs framework, a full-stack framework for building web applications.
|
|
117
|
-
I want to update a ${guideJson.update.filePath} file for ${guideJson.description}.
|
|
118
|
-
This file is a programming guideline for Akan.js framwork users.
|
|
119
|
-
|
|
120
|
-
# ${guideJson.title} Workflow
|
|
121
|
-
- 1. Read already-written ${guideJson.update.filePath} file.
|
|
122
|
-
- 2. Write the updated file content of the ${guideJson.update.filePath} with the update request.
|
|
123
|
-
|
|
124
|
-
## 1. Read already-written ${guideJson.update.filePath} file.
|
|
125
|
-
\`\`\`markdown
|
|
126
|
-
${existingResult}
|
|
127
|
-
\`\`\`
|
|
128
|
-
|
|
129
|
-
## 2. Write the updated file content of the ${guideJson.update.filePath} with the update request.
|
|
130
|
-
Request: ${updateRequest}
|
|
131
|
-
|
|
132
|
-
=> Now, you need to write the file content here. Let's go.
|
|
133
|
-
`;
|
|
134
|
-
return { guideJson, request, writePath };
|
|
135
|
-
}
|
|
136
|
-
async requestCreateDocumentPage(page) {
|
|
137
|
-
const writePath = this.#resolveDocsPagePath(page);
|
|
138
|
-
if (!await this.workspace.exists(writePath))
|
|
139
|
-
await this.workspace.writeFile(writePath, `import { page } from "akanjs/client";
|
|
140
|
-
|
|
141
|
-
export default page().render(() => {
|
|
142
|
-
return <div>No Content</div>;
|
|
143
|
-
});
|
|
144
|
-
`);
|
|
145
|
-
const instruction = await Prompter.getInstruction(this.name);
|
|
146
|
-
const pageContent = await this.workspace.getLocalFile(writePath);
|
|
147
|
-
const request = `
|
|
148
|
-
I'm creating a documentation website for the Akan.js framework.
|
|
149
|
-
|
|
150
|
-
1. Overview of the Akan.js framework
|
|
151
|
-
${await this.getDocumentation("framework")}
|
|
152
|
-
|
|
153
|
-
2. Documentation page writing method
|
|
154
|
-
${await this.getDocumentation("docPageRule")}
|
|
155
|
-
|
|
156
|
-
3. CSS rule with TailwindCSS and akanjs/ui primitives (semantic tokens; no daisyui)
|
|
157
|
-
${await this.getDocumentation("cssRule")}
|
|
158
|
-
|
|
159
|
-
I want to update the Next.js server-side page located at ${writePath}.
|
|
160
|
-
Below is the content of the currently written page.
|
|
161
|
-
\`\`\`tsx
|
|
162
|
-
// File: ${writePath}
|
|
163
|
-
${pageContent.content}
|
|
164
|
-
\`\`\`
|
|
165
|
-
|
|
166
|
-
Please update this page with the latest content below. A great design application is needed.
|
|
167
|
-
${instruction}
|
|
168
|
-
|
|
169
|
-
Please follow these CSS rules when writing:
|
|
170
|
-
- Use tailwindcss with the semantic design tokens (bg-background, text-foreground, bg-primary, text-muted-foreground, border-border, \u2026)
|
|
171
|
-
- Use akanjs/ui primitives (Button, Badge, Input, Field, \u2026) instead of raw daisyui classes; do not use daisyui
|
|
172
|
-
|
|
173
|
-
Please return only the file result in the following format for easy parsing.
|
|
174
|
-
\`\`\`tsx
|
|
175
|
-
// File: ${writePath}
|
|
176
|
-
...pageContent
|
|
177
|
-
\`\`\`
|
|
178
|
-
`;
|
|
179
|
-
return { request, writePath };
|
|
180
|
-
}
|
|
181
|
-
async requestUpdateDocumentPage(page, updateRequest) {
|
|
182
|
-
const writePath = this.#resolveDocsPagePath(page);
|
|
183
|
-
if (!await this.workspace.exists(writePath))
|
|
184
|
-
await this.workspace.writeFile(writePath, `import { page } from "akanjs/client";
|
|
185
|
-
|
|
186
|
-
export default page().render(() => {
|
|
187
|
-
return <div>No Content</div>;
|
|
188
|
-
});
|
|
189
|
-
`);
|
|
190
|
-
const instruction = await Prompter.getInstruction(this.name);
|
|
191
|
-
const pageContent = await this.workspace.getLocalFile(writePath);
|
|
192
|
-
const request = `
|
|
193
|
-
I'm updating a documentation website for the Akan.js framework.
|
|
194
|
-
|
|
195
|
-
I want to update the Next.js server-side page located at ${writePath}.
|
|
196
|
-
Below is the content of the currently written page. You should update stale infos and preserve the existing content.
|
|
197
|
-
\`\`\`tsx
|
|
198
|
-
// File: ${writePath}
|
|
199
|
-
${pageContent.content}
|
|
200
|
-
\`\`\`
|
|
201
|
-
|
|
202
|
-
The existing instruction is below.
|
|
203
|
-
\`\`\`markdown
|
|
204
|
-
${instruction}
|
|
205
|
-
\`\`\`
|
|
206
|
-
|
|
207
|
-
Please update this page with the request below.
|
|
208
|
-
${updateRequest}
|
|
209
|
-
|
|
210
|
-
Please return only the file result in the following format for easy parsing.
|
|
211
|
-
\`\`\`tsx
|
|
212
|
-
// File: ${writePath}
|
|
213
|
-
...pageContent
|
|
214
|
-
\`\`\`
|
|
215
|
-
`;
|
|
216
|
-
return { request, writePath };
|
|
217
|
-
}
|
|
218
|
-
async requestReapplyInstruction(filePath) {
|
|
219
|
-
const guideJson = await Prompter.getGuideJson(this.name);
|
|
220
|
-
const writePath = `${this.workspace.workspaceRoot}/pkgs/@akanjs/cli/guidelines/${this.name}/${guideJson.update.filePath}`;
|
|
221
|
-
if (!guideJson.page)
|
|
222
|
-
throw new Error(`${this.name} does not have a page.`);
|
|
223
|
-
const pagePath = this.#resolveDocsPagePath(guideJson.page);
|
|
224
|
-
const pageFile = await this.workspace.getLocalFile(pagePath);
|
|
225
|
-
const request = `
|
|
226
|
-
I want to apply information in the Next.js page to markdown instruction file.
|
|
227
|
-
|
|
228
|
-
Here's the newest information in the Next.js page.
|
|
229
|
-
\`\`\`tsx
|
|
230
|
-
// File: ${pagePath}
|
|
231
|
-
${pageFile.content}
|
|
232
|
-
\`\`\`
|
|
233
|
-
|
|
234
|
-
Here's the existing instruction file.
|
|
235
|
-
\`\`\`markdown
|
|
236
|
-
${await Prompter.getInstruction(this.name)}
|
|
237
|
-
\`\`\`
|
|
238
|
-
|
|
239
|
-
Please update the instruction file with the information in the Next.js page.
|
|
240
|
-
`;
|
|
241
|
-
return { request, writePath };
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
// pkgs/@akanjs/cli/guideline/guideline.runner.ts
|
|
246
|
-
class GuidelineRunner extends runner("guideline") {
|
|
247
|
-
async generateInstruction(workspace, guideName) {
|
|
248
|
-
const session = new AiSession("generateInstruction", { workspace, cacheKey: guideName });
|
|
249
|
-
const prompt = new GuidelinePrompt(workspace, guideName);
|
|
250
|
-
const { guideJson, request, writePath } = await prompt.requestCreateInstruction();
|
|
251
|
-
const guidelineContent = await session.editMarkdown(request);
|
|
252
|
-
await workspace.writeFile(writePath, guidelineContent);
|
|
253
|
-
return { guideJson, session };
|
|
254
|
-
}
|
|
255
|
-
async updateInstruction(workspace, guideName, { updateRequest }) {
|
|
256
|
-
const session = new AiSession("updateInstruction", { workspace, cacheKey: guideName });
|
|
257
|
-
const prompt = new GuidelinePrompt(workspace, guideName);
|
|
258
|
-
const { guideJson, request, writePath } = await prompt.requestUpdateInstruction(updateRequest);
|
|
259
|
-
const guidelineContent = await session.editMarkdown(request);
|
|
260
|
-
await workspace.writeFile(writePath, guidelineContent);
|
|
261
|
-
return { guideJson, session };
|
|
262
|
-
}
|
|
263
|
-
async generateDocument(workspace, guideName) {
|
|
264
|
-
const session = new AiSession("deployDocPages", { workspace, cacheKey: guideName });
|
|
265
|
-
const guideJson = await Prompter.getGuideJson(guideName);
|
|
266
|
-
const prompt = new GuidelinePrompt(workspace, guideName);
|
|
267
|
-
if (!guideJson.page)
|
|
268
|
-
return Promise.resolve({});
|
|
269
|
-
const { request } = await prompt.requestCreateDocumentPage(guideJson.page);
|
|
270
|
-
await session.writeTypescripts(request, workspace);
|
|
271
|
-
}
|
|
272
|
-
async updateDocument(workspace, guideName, { updateRequest, session }) {
|
|
273
|
-
const guideJson = await Prompter.getGuideJson(guideName);
|
|
274
|
-
if (!guideJson.page)
|
|
275
|
-
throw new Error(`${guideName} does not have a page.`);
|
|
276
|
-
const prompt = new GuidelinePrompt(workspace, guideName);
|
|
277
|
-
const { request, writePath } = await prompt.requestUpdateDocumentPage(guideJson.page, updateRequest);
|
|
278
|
-
const guidelineContent = await session.editMarkdown(request);
|
|
279
|
-
await workspace.writeFile(writePath, guidelineContent);
|
|
280
|
-
}
|
|
281
|
-
async reapplyInstruction(workspace, guideName) {
|
|
282
|
-
const session = new AiSession("reapplyInstruction", { workspace, cacheKey: guideName });
|
|
283
|
-
const guideJson = await Prompter.getGuideJson(guideName);
|
|
284
|
-
const prompt = new GuidelinePrompt(workspace, guideName);
|
|
285
|
-
const { request, writePath } = await prompt.requestReapplyInstruction(guideJson.update.filePath);
|
|
286
|
-
const guidelineContent = await session.editMarkdown(request);
|
|
287
|
-
await workspace.writeFile(writePath, guidelineContent);
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
// pkgs/@akanjs/cli/guideline/guideline.script.ts
|
|
292
|
-
class GuidelineScript extends script("guideline", [GuidelineRunner]) {
|
|
293
|
-
async guideline(action, name = null, format = "markdown") {
|
|
294
|
-
if (action === "list") {
|
|
295
|
-
const guidelines = await Prompter.listGuidelines();
|
|
296
|
-
Logger.rawLog(format === "json" ? JSON.stringify({ guidelines }, null, 2) : guidelines.join(`
|
|
297
|
-
`));
|
|
298
|
-
return;
|
|
299
|
-
}
|
|
300
|
-
if (action === "show") {
|
|
301
|
-
if (!name)
|
|
302
|
-
throw new Error("Guideline name is required. Example: akan guideline show framework");
|
|
303
|
-
const [instruction, guideJson] = await Promise.all([Prompter.getInstruction(name), Prompter.getGuideJson(name)]);
|
|
304
|
-
Logger.rawLog(format === "json" ? JSON.stringify({ name, instruction, guideJson }, null, 2) : instruction);
|
|
305
|
-
return;
|
|
306
|
-
}
|
|
307
|
-
throw new Error(`Unknown guideline action: ${action}. Use "list" or "show".`);
|
|
308
|
-
}
|
|
309
|
-
async generateInstruction(workspace, name = null) {
|
|
310
|
-
const guideName = name ?? await Prompter.selectGuideline();
|
|
311
|
-
await this.guidelineRunner.generateInstruction(workspace, guideName);
|
|
312
|
-
}
|
|
313
|
-
async updateInstruction(workspace, name = null, updateRequest) {
|
|
314
|
-
const guideName = name ?? await Prompter.selectGuideline();
|
|
315
|
-
const { guideJson, session } = await this.guidelineRunner.updateInstruction(workspace, guideName, {
|
|
316
|
-
updateRequest
|
|
317
|
-
});
|
|
318
|
-
if (guideJson.page)
|
|
319
|
-
await this.updateDocument(workspace, guideName, { updateRequest, session });
|
|
320
|
-
}
|
|
321
|
-
async generateDocument(workspace, name = null) {
|
|
322
|
-
const guideName = name ?? await Prompter.selectGuideline();
|
|
323
|
-
await this.guidelineRunner.generateDocument(workspace, guideName);
|
|
324
|
-
}
|
|
325
|
-
async updateDocument(workspace, name = null, { updateRequest, session }) {
|
|
326
|
-
const guideName = name ?? await Prompter.selectGuideline();
|
|
327
|
-
await this.guidelineRunner.updateDocument(workspace, guideName, { updateRequest, session });
|
|
328
|
-
}
|
|
329
|
-
async reapplyInstruction(workspace, name = null) {
|
|
330
|
-
const guideName = name ?? await Prompter.selectGuideline();
|
|
331
|
-
await this.guidelineRunner.reapplyInstruction(workspace, guideName);
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
// pkgs/@akanjs/cli/guideline/guideline.command.ts
|
|
336
|
-
class GuidelineCommand extends command("guideline", [GuidelineScript], ({ public: target }) => ({
|
|
337
|
-
guideline: target({ desc: "List or show Akan AI guideline instructions" }).arg("action", String, { desc: "list or show" }).arg("name", String, { desc: "guideline name for show", nullable: true }).option("format", String, {
|
|
338
|
-
desc: "output format",
|
|
339
|
-
default: "markdown",
|
|
340
|
-
enum: ["markdown", "json"]
|
|
341
|
-
}).exec(async function(action, name, format) {
|
|
342
|
-
await this.guidelineScript.guideline(action, name, format);
|
|
343
|
-
}),
|
|
344
|
-
generateInstruction: target({ devOnly: true, desc: "Generate AI development guideline/instruction for your project" }).arg("name", String, { ask: "name of the instruction", nullable: true }).with(Workspace).exec(async function(name, workspace) {
|
|
345
|
-
await this.guidelineScript.generateInstruction(workspace, name);
|
|
346
|
-
}),
|
|
347
|
-
updateInstruction: target({ devOnly: true, desc: "Update existing AI guideline/instruction" }).arg("name", String, { ask: "name of the instruction", nullable: true }).option("request", String, { ask: "What do you want to update?" }).with(Workspace).exec(async function(name, request, workspace) {
|
|
348
|
-
await this.guidelineScript.updateInstruction(workspace, name, request);
|
|
349
|
-
}),
|
|
350
|
-
generateDocument: target({ devOnly: true, desc: "Generate documentation from guideline/instruction" }).arg("name", String, { ask: "name of the instruction", nullable: true }).with(Workspace).exec(async function(name, workspace) {
|
|
351
|
-
await this.guidelineScript.generateDocument(workspace, name);
|
|
352
|
-
}),
|
|
353
|
-
reapplyInstruction: target({ devOnly: true, desc: "Re-apply guideline/instruction to codebase" }).arg("name", String, { ask: "name of the instruction", nullable: true }).with(Workspace).exec(async function(name, workspace) {
|
|
354
|
-
await this.guidelineScript.reapplyInstruction(workspace, name);
|
|
355
|
-
})
|
|
356
|
-
})) {
|
|
357
|
-
}
|
|
358
|
-
export {
|
|
359
|
-
GuidelineCommand
|
|
360
|
-
};
|
package/index-5tw8t1ts.js
DELETED
|
@@ -1,338 +0,0 @@
|
|
|
1
|
-
// @bun
|
|
2
|
-
import {
|
|
3
|
-
GlobalConfig
|
|
4
|
-
} from "./index-649svkyt.js";
|
|
5
|
-
import {
|
|
6
|
-
Spinner
|
|
7
|
-
} from "./index-5nyznra1.js";
|
|
8
|
-
|
|
9
|
-
// pkgs/@akanjs/devkit/aiEditor.ts
|
|
10
|
-
import { input, select } from "@inquirer/prompts";
|
|
11
|
-
import {
|
|
12
|
-
AIMessage,
|
|
13
|
-
HumanMessage,
|
|
14
|
-
mapChatMessagesToStoredMessages,
|
|
15
|
-
mapStoredMessagesToChatMessages
|
|
16
|
-
} from "@langchain/core/messages";
|
|
17
|
-
import { ChatDeepSeek } from "@langchain/deepseek";
|
|
18
|
-
import { ChatOpenAI } from "@langchain/openai";
|
|
19
|
-
import { Logger } from "akanjs/common";
|
|
20
|
-
import chalk from "chalk";
|
|
21
|
-
var MAX_ASK_TRY = 300;
|
|
22
|
-
var deepSeekLlmModels = ["deepseek-v4-flash", "deepseek-v4-pro"];
|
|
23
|
-
var openAiLlmModels = ["gpt-5.5"];
|
|
24
|
-
var supportedLlmModels = [...deepSeekLlmModels, ...openAiLlmModels];
|
|
25
|
-
var isOpenAiLlmModel = (model) => openAiLlmModels.includes(model);
|
|
26
|
-
var parseTypescriptFileBlocks = (text) => {
|
|
27
|
-
const fileBlocks = [];
|
|
28
|
-
const codeBlockRegex = /```(?:typescript|ts|tsx)\s*\n([\s\S]*?)```/gi;
|
|
29
|
-
const filePathRegex = /^\s*\/\/\s*File:\s*(.+?)\s*$/im;
|
|
30
|
-
for (const codeBlock of text.matchAll(codeBlockRegex)) {
|
|
31
|
-
const content = codeBlock[1]?.trim();
|
|
32
|
-
if (!content)
|
|
33
|
-
continue;
|
|
34
|
-
const filePath = filePathRegex.exec(content)?.[1]?.trim();
|
|
35
|
-
if (!filePath)
|
|
36
|
-
continue;
|
|
37
|
-
fileBlocks.push({
|
|
38
|
-
filePath,
|
|
39
|
-
content: content.replace(filePathRegex, "").trim()
|
|
40
|
-
});
|
|
41
|
-
}
|
|
42
|
-
return fileBlocks;
|
|
43
|
-
};
|
|
44
|
-
var preserveTypescriptResponseContent = (previousContent, nextContent) => {
|
|
45
|
-
const previousWrites = parseTypescriptFileBlocks(previousContent);
|
|
46
|
-
const nextWrites = parseTypescriptFileBlocks(nextContent);
|
|
47
|
-
if (previousWrites.length > 0 && nextWrites.length === 0)
|
|
48
|
-
return previousContent;
|
|
49
|
-
return nextContent;
|
|
50
|
-
};
|
|
51
|
-
|
|
52
|
-
class AiSession {
|
|
53
|
-
static #cacheDir = "node_modules/.cache/akan/aiSession";
|
|
54
|
-
static #chat = null;
|
|
55
|
-
static async init({ temperature = 0, useExisting = true } = {}) {
|
|
56
|
-
if (useExisting) {
|
|
57
|
-
const llmConfig = await AiSession.getLlmConfig();
|
|
58
|
-
if (llmConfig) {
|
|
59
|
-
AiSession.#setChatModel(llmConfig.model, llmConfig.apiKey);
|
|
60
|
-
Logger.rawLog(chalk.dim(`\uD83E\uDD16akan editor uses existing LLM config (${llmConfig.model})`));
|
|
61
|
-
return AiSession;
|
|
62
|
-
}
|
|
63
|
-
} else
|
|
64
|
-
Logger.rawLog(chalk.yellow("\uD83E\uDD16akan-editor is not initialized. LLM configuration should be set first."));
|
|
65
|
-
const llmConfig = await AiSession.#requestLlmConfig();
|
|
66
|
-
const { model, apiKey } = llmConfig;
|
|
67
|
-
await AiSession.#validateApiKey(model, apiKey);
|
|
68
|
-
const session = AiSession.#setChatModel(model, apiKey, { temperature });
|
|
69
|
-
await session.setLlmConfig({ model, apiKey });
|
|
70
|
-
return session;
|
|
71
|
-
}
|
|
72
|
-
static #setChatModel(model, apiKey, { temperature = 0 } = {}) {
|
|
73
|
-
AiSession.#chat = AiSession.#createChatModel(model, apiKey, {
|
|
74
|
-
temperature,
|
|
75
|
-
streaming: true
|
|
76
|
-
});
|
|
77
|
-
return AiSession;
|
|
78
|
-
}
|
|
79
|
-
static #createChatModel(model, apiKey, { temperature = 0, streaming = false } = {}) {
|
|
80
|
-
if (isOpenAiLlmModel(model))
|
|
81
|
-
return new ChatOpenAI({
|
|
82
|
-
modelName: model,
|
|
83
|
-
temperature,
|
|
84
|
-
streaming,
|
|
85
|
-
openAIApiKey: apiKey
|
|
86
|
-
});
|
|
87
|
-
return new ChatDeepSeek({
|
|
88
|
-
modelName: model,
|
|
89
|
-
temperature,
|
|
90
|
-
streaming,
|
|
91
|
-
apiKey
|
|
92
|
-
});
|
|
93
|
-
}
|
|
94
|
-
static async getLlmConfig() {
|
|
95
|
-
return await GlobalConfig.getLlmConfig();
|
|
96
|
-
}
|
|
97
|
-
static async setLlmConfig(llmConfig) {
|
|
98
|
-
await GlobalConfig.setLlmConfig(llmConfig);
|
|
99
|
-
return AiSession;
|
|
100
|
-
}
|
|
101
|
-
static async#requestLlmConfig() {
|
|
102
|
-
const model = await select({
|
|
103
|
-
message: "Select a LLM model",
|
|
104
|
-
choices: supportedLlmModels
|
|
105
|
-
});
|
|
106
|
-
const apiKey = await input({ message: "Enter your API key" });
|
|
107
|
-
return { model, apiKey };
|
|
108
|
-
}
|
|
109
|
-
static async#validateApiKey(modelName, apiKey) {
|
|
110
|
-
const spinner = new Spinner("Validating LLM API key...", {
|
|
111
|
-
prefix: `\uD83E\uDD16akan-editor`
|
|
112
|
-
}).start();
|
|
113
|
-
const chat = AiSession.#createChatModel(modelName, apiKey);
|
|
114
|
-
try {
|
|
115
|
-
await chat.invoke("Hi, and just say 'ok'");
|
|
116
|
-
spinner.succeed("LLM API key is valid");
|
|
117
|
-
return true;
|
|
118
|
-
} catch (error) {
|
|
119
|
-
spinner.fail(chalk.red(`LLM API key is invalid. Please check your API key and try again. You can set it again by running "akan set-llm" or reset by running "akan reset-llm"`));
|
|
120
|
-
throw error;
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
static async clearCache(workspaceRoot) {
|
|
124
|
-
const cacheDir = `${workspaceRoot}/${AiSession.#cacheDir}`;
|
|
125
|
-
await Bun.$`rm -rf ${cacheDir}`;
|
|
126
|
-
}
|
|
127
|
-
messageHistory = [];
|
|
128
|
-
sessionKey;
|
|
129
|
-
isCacheLoaded = false;
|
|
130
|
-
workspace;
|
|
131
|
-
constructor(type, {
|
|
132
|
-
workspace,
|
|
133
|
-
cacheKey,
|
|
134
|
-
isContinued
|
|
135
|
-
}) {
|
|
136
|
-
this.workspace = workspace;
|
|
137
|
-
this.sessionKey = `${type}${cacheKey ? `-${cacheKey}` : ""}`;
|
|
138
|
-
if (isContinued)
|
|
139
|
-
this.#cacheLoadPromise = this.#loadCache();
|
|
140
|
-
}
|
|
141
|
-
#cacheLoadPromise = null;
|
|
142
|
-
async#loadCache() {
|
|
143
|
-
const cacheFile = `${AiSession.#cacheDir}/${this.sessionKey}.json`;
|
|
144
|
-
const isCacheExists = await this.workspace.exists(cacheFile);
|
|
145
|
-
if (isCacheExists)
|
|
146
|
-
this.messageHistory = mapStoredMessagesToChatMessages(await this.workspace.readJson(cacheFile));
|
|
147
|
-
else
|
|
148
|
-
this.messageHistory = [];
|
|
149
|
-
this.isCacheLoaded = isCacheExists;
|
|
150
|
-
}
|
|
151
|
-
async#saveCache() {
|
|
152
|
-
const cacheFilePath = `${AiSession.#cacheDir}/${this.sessionKey}.json`;
|
|
153
|
-
await this.workspace.writeJson(cacheFilePath, mapChatMessagesToStoredMessages(this.messageHistory));
|
|
154
|
-
}
|
|
155
|
-
async ask(question, {
|
|
156
|
-
onReasoning = (reasoning) => {
|
|
157
|
-
Logger.raw(chalk.dim(reasoning));
|
|
158
|
-
},
|
|
159
|
-
onChunk = (chunk) => {
|
|
160
|
-
Logger.raw(chunk);
|
|
161
|
-
}
|
|
162
|
-
} = {}) {
|
|
163
|
-
if (!AiSession.#chat)
|
|
164
|
-
await AiSession.init();
|
|
165
|
-
if (this.#cacheLoadPromise)
|
|
166
|
-
await this.#cacheLoadPromise;
|
|
167
|
-
if (!AiSession.#chat)
|
|
168
|
-
throw new Error("Failed to initialize the AI session");
|
|
169
|
-
const loader = new Spinner(`${AiSession.#chat.model} is thinking...`, {
|
|
170
|
-
prefix: `\uD83E\uDD16akan-editor`
|
|
171
|
-
}).start();
|
|
172
|
-
try {
|
|
173
|
-
const humanMessage = new HumanMessage(question);
|
|
174
|
-
this.messageHistory.push(humanMessage);
|
|
175
|
-
const stream = await AiSession.#chat.stream(this.messageHistory);
|
|
176
|
-
let reasoningResponse = "", fullResponse = "";
|
|
177
|
-
for await (const chunk of stream) {
|
|
178
|
-
if (loader.isSpinning())
|
|
179
|
-
loader.succeed(`${AiSession.#chat.model} responded`);
|
|
180
|
-
if (!fullResponse.length) {
|
|
181
|
-
const reasoningContent = chunk.additional_kwargs.reasoning_content ?? "";
|
|
182
|
-
if (reasoningContent.length) {
|
|
183
|
-
reasoningResponse += reasoningContent;
|
|
184
|
-
onReasoning(reasoningContent);
|
|
185
|
-
continue;
|
|
186
|
-
} else if (chunk.content.length) {
|
|
187
|
-
reasoningResponse += `
|
|
188
|
-
`;
|
|
189
|
-
onReasoning(reasoningResponse);
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
const content = chunk.content;
|
|
193
|
-
if (typeof content === "string") {
|
|
194
|
-
fullResponse += content;
|
|
195
|
-
onChunk(content);
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
fullResponse += `
|
|
199
|
-
`;
|
|
200
|
-
onChunk(`
|
|
201
|
-
`);
|
|
202
|
-
this.messageHistory.push(new AIMessage(fullResponse));
|
|
203
|
-
return { content: fullResponse, messageHistory: this.messageHistory };
|
|
204
|
-
} catch {
|
|
205
|
-
loader.fail(`${AiSession.#chat.model} failed to respond`);
|
|
206
|
-
throw new Error("Failed to stream response");
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
async edit(question, { onChunk, onReasoning, maxTry = MAX_ASK_TRY, validate, approve, fallbackToPreviousTypescript } = {}) {
|
|
210
|
-
for (let tryCount = 0;tryCount < maxTry; tryCount++) {
|
|
211
|
-
let response = await this.ask(question, { onChunk, onReasoning });
|
|
212
|
-
if (validate?.length && tryCount === 0) {
|
|
213
|
-
const validateQuestion = `Double check if the response meets the requirements and conditions, and follow the instructions. If not, rewrite it.
|
|
214
|
-
${validate.map((v) => `- ${v}`).join(`
|
|
215
|
-
`)}`;
|
|
216
|
-
const validateResponse = await this.ask(validateQuestion, {
|
|
217
|
-
onChunk,
|
|
218
|
-
onReasoning
|
|
219
|
-
});
|
|
220
|
-
response = {
|
|
221
|
-
...validateResponse,
|
|
222
|
-
content: fallbackToPreviousTypescript ? preserveTypescriptResponseContent(response.content, validateResponse.content) : validateResponse.content
|
|
223
|
-
};
|
|
224
|
-
}
|
|
225
|
-
const isConfirmed = approve ? true : await select({
|
|
226
|
-
message: "Do you want to edit the response?",
|
|
227
|
-
choices: [
|
|
228
|
-
{ name: "\u2705 Yes, confirm and apply this result", value: true },
|
|
229
|
-
{ name: "\uD83D\uDD04 No, I want to edit it more", value: false }
|
|
230
|
-
]
|
|
231
|
-
});
|
|
232
|
-
if (isConfirmed) {
|
|
233
|
-
await this.#saveCache();
|
|
234
|
-
return response.content;
|
|
235
|
-
}
|
|
236
|
-
question = await input({ message: "What do you want to change?" });
|
|
237
|
-
tryCount++;
|
|
238
|
-
}
|
|
239
|
-
throw new Error("Failed to edit");
|
|
240
|
-
}
|
|
241
|
-
async editTypescript(question, options = {}) {
|
|
242
|
-
const content = await this.edit(question, options);
|
|
243
|
-
return this.#getTypescriptCode(content);
|
|
244
|
-
}
|
|
245
|
-
#getTypescriptCode(content) {
|
|
246
|
-
//! will be deprecated
|
|
247
|
-
const code = /```(typescript|tsx)([\s\S]*?)```/.exec(content);
|
|
248
|
-
return code?.[2] ?? content;
|
|
249
|
-
}
|
|
250
|
-
addToolMessgaes(messages) {
|
|
251
|
-
const toolMessages = messages.map((message) => new HumanMessage(message.content));
|
|
252
|
-
this.messageHistory.push(...toolMessages);
|
|
253
|
-
return this;
|
|
254
|
-
}
|
|
255
|
-
async writeTypescripts(question, executor, options = {}) {
|
|
256
|
-
const content = await this.edit(question, {
|
|
257
|
-
...options,
|
|
258
|
-
fallbackToPreviousTypescript: true
|
|
259
|
-
});
|
|
260
|
-
const writes = this.#getTypescriptCodes(content);
|
|
261
|
-
if (!writes.length)
|
|
262
|
-
throw new Error("No parseable TypeScript file blocks were found in the AI response. Include `// File: <path>` in each code block.");
|
|
263
|
-
for (const write of writes)
|
|
264
|
-
await executor.writeFile(write.filePath, write.content);
|
|
265
|
-
return await this.#tryFixTypescripts(writes, executor, options);
|
|
266
|
-
}
|
|
267
|
-
async#editTypescripts(question, options = {}, fallbackWrites) {
|
|
268
|
-
const content = await this.edit(question, {
|
|
269
|
-
...options,
|
|
270
|
-
fallbackToPreviousTypescript: true
|
|
271
|
-
});
|
|
272
|
-
const writes = this.#getTypescriptCodes(content);
|
|
273
|
-
if (!writes.length && fallbackWrites?.length)
|
|
274
|
-
return fallbackWrites;
|
|
275
|
-
if (!writes.length)
|
|
276
|
-
throw new Error("No parseable TypeScript file blocks were found in the AI response. Include `// File: <path>` in each code block.");
|
|
277
|
-
return writes;
|
|
278
|
-
}
|
|
279
|
-
async#tryFixTypescripts(writes, executor, options = {}) {
|
|
280
|
-
const MAX_EDIT_TRY = 5;
|
|
281
|
-
for (let tryCount = 0;tryCount < MAX_EDIT_TRY; tryCount++) {
|
|
282
|
-
const loader = new Spinner(`Type checking and linting...`, {
|
|
283
|
-
prefix: `\uD83E\uDD16akan-editor`
|
|
284
|
-
}).start();
|
|
285
|
-
const fileChecks = await Promise.all(writes.map(async ({ filePath }) => {
|
|
286
|
-
const lintResult = await executor.lint(filePath, { fix: true });
|
|
287
|
-
const typeCheckResult = await executor.typeCheckAsync(filePath);
|
|
288
|
-
const hasTypeErrors = typeCheckResult.fileErrors.length > 0;
|
|
289
|
-
const hasLintErrors = lintResult.errors.length > 0;
|
|
290
|
-
const needFix = hasTypeErrors || hasLintErrors;
|
|
291
|
-
return { filePath, typeCheckResult, lintResult, needFix };
|
|
292
|
-
}));
|
|
293
|
-
const hasAnyFix = fileChecks.some((fileCheck) => fileCheck.needFix);
|
|
294
|
-
if (hasAnyFix) {
|
|
295
|
-
loader.fail("Type checking and linting has some errors, try to fix them");
|
|
296
|
-
fileChecks.forEach((fileCheck) => {
|
|
297
|
-
Logger.rawLog(`TypeCheck Result
|
|
298
|
-
${fileCheck.typeCheckResult.message}
|
|
299
|
-
Lint Result
|
|
300
|
-
${fileCheck.lintResult.message}`);
|
|
301
|
-
this.addToolMessgaes([
|
|
302
|
-
{ type: "typescript", content: fileCheck.typeCheckResult.message },
|
|
303
|
-
{ type: "eslint", content: fileCheck.lintResult.message }
|
|
304
|
-
]);
|
|
305
|
-
});
|
|
306
|
-
writes = await this.#editTypescripts("Fix the typescript and eslint errors", {
|
|
307
|
-
...options,
|
|
308
|
-
validate: undefined,
|
|
309
|
-
approve: true
|
|
310
|
-
}, writes);
|
|
311
|
-
for (const write of writes)
|
|
312
|
-
await executor.writeFile(write.filePath, write.content);
|
|
313
|
-
} else {
|
|
314
|
-
loader.succeed("Type checking and linting has no errors");
|
|
315
|
-
return writes;
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
throw new Error("Failed to create scalar");
|
|
319
|
-
}
|
|
320
|
-
#getTypescriptCodes(text) {
|
|
321
|
-
return parseTypescriptFileBlocks(text);
|
|
322
|
-
}
|
|
323
|
-
async editMarkdown(request, options = {}) {
|
|
324
|
-
const content = await this.edit(request, options);
|
|
325
|
-
return this.#getMarkdownContent(content);
|
|
326
|
-
}
|
|
327
|
-
#getMarkdownContent(text) {
|
|
328
|
-
const searchText = "```markdown";
|
|
329
|
-
const firstIndex = text.indexOf("```markdown");
|
|
330
|
-
const lastIndex = text.lastIndexOf("```");
|
|
331
|
-
if (firstIndex === -1)
|
|
332
|
-
return text;
|
|
333
|
-
else
|
|
334
|
-
return text.slice(firstIndex + searchText.length, lastIndex).trim();
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
export { AiSession };
|