@akanjs/devkit 2.2.1 → 2.2.2
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/aiEditor.ts +47 -90
- package/cloud/cloudApi.ts +19 -11
- package/cloud/constants.ts +4 -3
- package/cloud/globalConfig.ts +11 -6
- package/package.json +2 -2
package/aiEditor.ts
CHANGED
|
@@ -19,11 +19,16 @@ import type { FileContent } from "./types";
|
|
|
19
19
|
|
|
20
20
|
const MAX_ASK_TRY = 300;
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
22
|
+
const deepSeekLlmModels = ["deepseek-chat", "deepseek-reasoner"] as const;
|
|
23
|
+
|
|
24
|
+
const openAiLlmModels = ["gpt-5.5"] as const;
|
|
25
|
+
|
|
26
|
+
export const supportedLlmModels = [...deepSeekLlmModels, ...openAiLlmModels] as const;
|
|
26
27
|
export type SupportedLlmModel = (typeof supportedLlmModels)[number];
|
|
28
|
+
type OpenAiLlmModel = (typeof openAiLlmModels)[number];
|
|
29
|
+
|
|
30
|
+
const isOpenAiLlmModel = (model: SupportedLlmModel): model is OpenAiLlmModel =>
|
|
31
|
+
openAiLlmModels.includes(model as OpenAiLlmModel);
|
|
27
32
|
|
|
28
33
|
interface EditOptions {
|
|
29
34
|
onReasoning?: (reasoning: string) => void;
|
|
@@ -55,44 +60,25 @@ export const parseTypescriptFileBlocks = (text: string): FileContent[] => {
|
|
|
55
60
|
return fileBlocks;
|
|
56
61
|
};
|
|
57
62
|
|
|
58
|
-
export const preserveTypescriptResponseContent = (
|
|
59
|
-
previousContent: string,
|
|
60
|
-
nextContent: string,
|
|
61
|
-
) => {
|
|
63
|
+
export const preserveTypescriptResponseContent = (previousContent: string, nextContent: string) => {
|
|
62
64
|
const previousWrites = parseTypescriptFileBlocks(previousContent);
|
|
63
65
|
const nextWrites = parseTypescriptFileBlocks(nextContent);
|
|
64
|
-
if (previousWrites.length > 0 && nextWrites.length === 0)
|
|
65
|
-
return previousContent;
|
|
66
|
+
if (previousWrites.length > 0 && nextWrites.length === 0) return previousContent;
|
|
66
67
|
return nextContent;
|
|
67
68
|
};
|
|
68
69
|
|
|
69
70
|
export class AiSession {
|
|
70
71
|
static #cacheDir = "node_modules/.cache/akan/aiSession";
|
|
71
72
|
static #chat: ChatDeepSeek | ChatOpenAI | null = null;
|
|
72
|
-
static async init({
|
|
73
|
-
temperature = 0,
|
|
74
|
-
useExisting = true,
|
|
75
|
-
}: {
|
|
76
|
-
temperature?: number;
|
|
77
|
-
useExisting?: boolean;
|
|
78
|
-
} = {}) {
|
|
73
|
+
static async init({ temperature = 0, useExisting = true }: { temperature?: number; useExisting?: boolean } = {}) {
|
|
79
74
|
if (useExisting) {
|
|
80
75
|
const llmConfig = await AiSession.getLlmConfig();
|
|
81
76
|
if (llmConfig) {
|
|
82
77
|
AiSession.#setChatModel(llmConfig.model, llmConfig.apiKey);
|
|
83
|
-
Logger.rawLog(
|
|
84
|
-
chalk.dim(
|
|
85
|
-
`🤖akan editor uses existing LLM config (${llmConfig.model})`,
|
|
86
|
-
),
|
|
87
|
-
);
|
|
78
|
+
Logger.rawLog(chalk.dim(`🤖akan editor uses existing LLM config (${llmConfig.model})`));
|
|
88
79
|
return AiSession;
|
|
89
80
|
}
|
|
90
|
-
} else
|
|
91
|
-
Logger.rawLog(
|
|
92
|
-
chalk.yellow(
|
|
93
|
-
"🤖akan-editor is not initialized. LLM configuration should be set first.",
|
|
94
|
-
),
|
|
95
|
-
);
|
|
81
|
+
} else Logger.rawLog(chalk.yellow("🤖akan-editor is not initialized. LLM configuration should be set first."));
|
|
96
82
|
|
|
97
83
|
const llmConfig = await AiSession.#requestLlmConfig();
|
|
98
84
|
const { model, apiKey } = llmConfig;
|
|
@@ -102,26 +88,36 @@ export class AiSession {
|
|
|
102
88
|
await session.setLlmConfig({ model, apiKey });
|
|
103
89
|
return session;
|
|
104
90
|
}
|
|
105
|
-
static #setChatModel(
|
|
91
|
+
static #setChatModel(model: SupportedLlmModel, apiKey: string, { temperature = 0 }: { temperature?: number } = {}) {
|
|
92
|
+
AiSession.#chat = AiSession.#createChatModel(model, apiKey, {
|
|
93
|
+
temperature,
|
|
94
|
+
streaming: true,
|
|
95
|
+
});
|
|
96
|
+
return AiSession;
|
|
97
|
+
}
|
|
98
|
+
static #createChatModel(
|
|
106
99
|
model: SupportedLlmModel,
|
|
107
100
|
apiKey: string,
|
|
108
|
-
{ temperature = 0 }: { temperature?: number } = {},
|
|
101
|
+
{ temperature = 0, streaming = false }: { temperature?: number; streaming?: boolean } = {},
|
|
109
102
|
) {
|
|
110
|
-
|
|
103
|
+
if (isOpenAiLlmModel(model))
|
|
104
|
+
return new ChatOpenAI({
|
|
105
|
+
modelName: model,
|
|
106
|
+
temperature,
|
|
107
|
+
streaming,
|
|
108
|
+
openAIApiKey: apiKey,
|
|
109
|
+
});
|
|
110
|
+
return new ChatDeepSeek({
|
|
111
111
|
modelName: model,
|
|
112
112
|
temperature,
|
|
113
|
-
streaming
|
|
113
|
+
streaming,
|
|
114
114
|
apiKey,
|
|
115
|
-
// configuration: { baseURL: "https://api.deepseek.com/v1", apiKey },
|
|
116
115
|
});
|
|
117
|
-
return AiSession;
|
|
118
116
|
}
|
|
119
117
|
static async getLlmConfig() {
|
|
120
118
|
return await GlobalConfig.getLlmConfig();
|
|
121
119
|
}
|
|
122
|
-
static async setLlmConfig(
|
|
123
|
-
llmConfig: { model: SupportedLlmModel; apiKey: string } | null,
|
|
124
|
-
) {
|
|
120
|
+
static async setLlmConfig(llmConfig: { model: SupportedLlmModel; apiKey: string } | null) {
|
|
125
121
|
await GlobalConfig.setLlmConfig(llmConfig);
|
|
126
122
|
return AiSession;
|
|
127
123
|
}
|
|
@@ -137,11 +133,7 @@ export class AiSession {
|
|
|
137
133
|
const spinner = new Spinner("Validating LLM API key...", {
|
|
138
134
|
prefix: `🤖akan-editor`,
|
|
139
135
|
}).start();
|
|
140
|
-
const chat =
|
|
141
|
-
modelName,
|
|
142
|
-
temperature: 0,
|
|
143
|
-
configuration: { baseURL: "https://api.deepseek.com/v1", apiKey },
|
|
144
|
-
});
|
|
136
|
+
const chat = AiSession.#createChatModel(modelName, apiKey);
|
|
145
137
|
try {
|
|
146
138
|
await chat.invoke("Hi, and just say 'ok'");
|
|
147
139
|
spinner.succeed("LLM API key is valid");
|
|
@@ -193,10 +185,7 @@ export class AiSession {
|
|
|
193
185
|
}
|
|
194
186
|
async #saveCache() {
|
|
195
187
|
const cacheFilePath = `${AiSession.#cacheDir}/${this.sessionKey}.json`;
|
|
196
|
-
await this.workspace.writeJson(
|
|
197
|
-
cacheFilePath,
|
|
198
|
-
mapChatMessagesToStoredMessages(this.messageHistory),
|
|
199
|
-
);
|
|
188
|
+
await this.workspace.writeJson(cacheFilePath, mapChatMessagesToStoredMessages(this.messageHistory));
|
|
200
189
|
}
|
|
201
190
|
async ask(
|
|
202
191
|
question: string,
|
|
@@ -212,8 +201,7 @@ export class AiSession {
|
|
|
212
201
|
if (!AiSession.#chat) await AiSession.init();
|
|
213
202
|
if (this.#cacheLoadPromise) await this.#cacheLoadPromise;
|
|
214
203
|
|
|
215
|
-
if (!AiSession.#chat)
|
|
216
|
-
throw new Error("Failed to initialize the AI session");
|
|
204
|
+
if (!AiSession.#chat) throw new Error("Failed to initialize the AI session");
|
|
217
205
|
const loader = new Spinner(`${AiSession.#chat.model} is thinking...`, {
|
|
218
206
|
prefix: `🤖akan-editor`,
|
|
219
207
|
}).start();
|
|
@@ -224,13 +212,10 @@ export class AiSession {
|
|
|
224
212
|
let reasoningResponse = "",
|
|
225
213
|
fullResponse = "";
|
|
226
214
|
for await (const chunk of stream) {
|
|
227
|
-
if (loader.isSpinning())
|
|
228
|
-
loader.succeed(`${AiSession.#chat.model} responded`);
|
|
215
|
+
if (loader.isSpinning()) loader.succeed(`${AiSession.#chat.model} responded`);
|
|
229
216
|
|
|
230
217
|
if (!fullResponse.length) {
|
|
231
|
-
const reasoningContent =
|
|
232
|
-
(chunk.additional_kwargs as { reasoning_content?: string })
|
|
233
|
-
.reasoning_content ?? "";
|
|
218
|
+
const reasoningContent = (chunk.additional_kwargs as { reasoning_content?: string }).reasoning_content ?? "";
|
|
234
219
|
if (reasoningContent.length) {
|
|
235
220
|
reasoningResponse += reasoningContent;
|
|
236
221
|
onReasoning(reasoningContent);
|
|
@@ -258,14 +243,7 @@ export class AiSession {
|
|
|
258
243
|
}
|
|
259
244
|
async edit(
|
|
260
245
|
question: string,
|
|
261
|
-
{
|
|
262
|
-
onChunk,
|
|
263
|
-
onReasoning,
|
|
264
|
-
maxTry = MAX_ASK_TRY,
|
|
265
|
-
validate,
|
|
266
|
-
approve,
|
|
267
|
-
fallbackToPreviousTypescript,
|
|
268
|
-
}: EditOptions = {},
|
|
246
|
+
{ onChunk, onReasoning, maxTry = MAX_ASK_TRY, validate, approve, fallbackToPreviousTypescript }: EditOptions = {},
|
|
269
247
|
) {
|
|
270
248
|
for (let tryCount = 0; tryCount < maxTry; tryCount++) {
|
|
271
249
|
let response = await this.ask(question, { onChunk, onReasoning });
|
|
@@ -279,10 +257,7 @@ ${validate.map((v) => `- ${v}`).join("\n")}`;
|
|
|
279
257
|
response = {
|
|
280
258
|
...validateResponse,
|
|
281
259
|
content: fallbackToPreviousTypescript
|
|
282
|
-
? preserveTypescriptResponseContent(
|
|
283
|
-
response.content,
|
|
284
|
-
validateResponse.content,
|
|
285
|
-
)
|
|
260
|
+
? preserveTypescriptResponseContent(response.content, validateResponse.content)
|
|
286
261
|
: validateResponse.content,
|
|
287
262
|
};
|
|
288
263
|
}
|
|
@@ -320,17 +295,11 @@ ${validate.map((v) => `- ${v}`).join("\n")}`;
|
|
|
320
295
|
// const toolMessages = messages.map(
|
|
321
296
|
// (message) => new ToolMessage({ content: message.content, tool_call_id: message.type })
|
|
322
297
|
// );
|
|
323
|
-
const toolMessages = messages.map(
|
|
324
|
-
(message) => new HumanMessage(message.content),
|
|
325
|
-
);
|
|
298
|
+
const toolMessages = messages.map((message) => new HumanMessage(message.content));
|
|
326
299
|
this.messageHistory.push(...toolMessages);
|
|
327
300
|
return this;
|
|
328
301
|
}
|
|
329
|
-
async writeTypescripts(
|
|
330
|
-
question: string,
|
|
331
|
-
executor: Executor,
|
|
332
|
-
options: EditOptions = {},
|
|
333
|
-
) {
|
|
302
|
+
async writeTypescripts(question: string, executor: Executor, options: EditOptions = {}) {
|
|
334
303
|
const content = await this.edit(question, {
|
|
335
304
|
...options,
|
|
336
305
|
fallbackToPreviousTypescript: true,
|
|
@@ -340,15 +309,10 @@ ${validate.map((v) => `- ${v}`).join("\n")}`;
|
|
|
340
309
|
throw new Error(
|
|
341
310
|
"No parseable TypeScript file blocks were found in the AI response. Include `// File: <path>` in each code block.",
|
|
342
311
|
);
|
|
343
|
-
for (const write of writes)
|
|
344
|
-
await executor.writeFile(write.filePath, write.content);
|
|
312
|
+
for (const write of writes) await executor.writeFile(write.filePath, write.content);
|
|
345
313
|
return await this.#tryFixTypescripts(writes, executor, options);
|
|
346
314
|
}
|
|
347
|
-
async #editTypescripts(
|
|
348
|
-
question: string,
|
|
349
|
-
options: EditOptions = {},
|
|
350
|
-
fallbackWrites?: FileContent[],
|
|
351
|
-
) {
|
|
315
|
+
async #editTypescripts(question: string, options: EditOptions = {}, fallbackWrites?: FileContent[]) {
|
|
352
316
|
const content = await this.edit(question, {
|
|
353
317
|
...options,
|
|
354
318
|
fallbackToPreviousTypescript: true,
|
|
@@ -361,11 +325,7 @@ ${validate.map((v) => `- ${v}`).join("\n")}`;
|
|
|
361
325
|
);
|
|
362
326
|
return writes;
|
|
363
327
|
}
|
|
364
|
-
async #tryFixTypescripts(
|
|
365
|
-
writes: FileContent[],
|
|
366
|
-
executor: Executor,
|
|
367
|
-
options: EditOptions = {},
|
|
368
|
-
) {
|
|
328
|
+
async #tryFixTypescripts(writes: FileContent[], executor: Executor, options: EditOptions = {}) {
|
|
369
329
|
const MAX_EDIT_TRY = 5;
|
|
370
330
|
for (let tryCount = 0; tryCount < MAX_EDIT_TRY; tryCount++) {
|
|
371
331
|
const loader = new Spinner(`Type checking and linting...`, {
|
|
@@ -383,9 +343,7 @@ ${validate.map((v) => `- ${v}`).join("\n")}`;
|
|
|
383
343
|
);
|
|
384
344
|
const hasAnyFix = fileChecks.some((fileCheck) => fileCheck.needFix);
|
|
385
345
|
if (hasAnyFix) {
|
|
386
|
-
loader.fail(
|
|
387
|
-
"Type checking and linting has some errors, try to fix them",
|
|
388
|
-
);
|
|
346
|
+
loader.fail("Type checking and linting has some errors, try to fix them");
|
|
389
347
|
fileChecks.forEach((fileCheck) => {
|
|
390
348
|
Logger.rawLog(
|
|
391
349
|
`TypeCheck Result \n${fileCheck.typeCheckResult.message}\nLint Result \n${fileCheck.lintResult.message}`,
|
|
@@ -404,8 +362,7 @@ ${validate.map((v) => `- ${v}`).join("\n")}`;
|
|
|
404
362
|
},
|
|
405
363
|
writes,
|
|
406
364
|
);
|
|
407
|
-
for (const write of writes)
|
|
408
|
-
await executor.writeFile(write.filePath, write.content);
|
|
365
|
+
for (const write of writes) await executor.writeFile(write.filePath, write.content);
|
|
409
366
|
} else {
|
|
410
367
|
loader.succeed("Type checking and linting has no errors");
|
|
411
368
|
return writes;
|
package/cloud/cloudApi.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Workspace } from "../commandDecorators";
|
|
2
|
-
import {
|
|
2
|
+
import type { AccessToken, AccessTokenDto, HostConfig } from "./constants";
|
|
3
3
|
import { GlobalConfig } from "./globalConfig";
|
|
4
4
|
|
|
5
5
|
class HttpClient {
|
|
@@ -17,7 +17,7 @@ class HttpClient {
|
|
|
17
17
|
...headers,
|
|
18
18
|
},
|
|
19
19
|
});
|
|
20
|
-
return response.json();
|
|
20
|
+
return await response.json();
|
|
21
21
|
}
|
|
22
22
|
async getFile(url: string, localPath: string, headers?: Record<string, string>): Promise<void> {
|
|
23
23
|
const response = await fetch(`${this.baseUrl}${url}`, {
|
|
@@ -35,7 +35,7 @@ class HttpClient {
|
|
|
35
35
|
? { ...this.headers, ...headers }
|
|
36
36
|
: { "Content-Type": "application/json", ...this.headers, ...headers },
|
|
37
37
|
});
|
|
38
|
-
return response.json();
|
|
38
|
+
return await response.json();
|
|
39
39
|
}
|
|
40
40
|
setHeaders(headers: Record<string, string>) {
|
|
41
41
|
Object.assign(this.headers, headers);
|
|
@@ -47,6 +47,8 @@ export class CloudApi {
|
|
|
47
47
|
readonly #api: HttpClient;
|
|
48
48
|
#accessToken: AccessToken | null = null;
|
|
49
49
|
#workspace: Workspace;
|
|
50
|
+
host: string;
|
|
51
|
+
url: string;
|
|
50
52
|
|
|
51
53
|
static async fromHost(workspace: Workspace, host?: string) {
|
|
52
54
|
const hostConfig = await GlobalConfig.getHostConfig(host);
|
|
@@ -54,9 +56,10 @@ export class CloudApi {
|
|
|
54
56
|
}
|
|
55
57
|
constructor(workspace: Workspace, hostConfig: HostConfig) {
|
|
56
58
|
this.#workspace = workspace;
|
|
57
|
-
const host = akanCloudHost;
|
|
58
|
-
this.#api = new HttpClient(`${host}/api`);
|
|
59
59
|
this.#accessToken = hostConfig.auth?.accessToken ?? null;
|
|
60
|
+
this.host = hostConfig.host;
|
|
61
|
+
this.url = `${this.host}/api`;
|
|
62
|
+
this.#api = new HttpClient(this.url);
|
|
60
63
|
if (this.#accessToken && !GlobalConfig.needRefreshToken(this.#accessToken))
|
|
61
64
|
this.#api.setHeaders({
|
|
62
65
|
Authorization: `Bearer ${this.#accessToken.jwt}`,
|
|
@@ -64,6 +67,7 @@ export class CloudApi {
|
|
|
64
67
|
}
|
|
65
68
|
|
|
66
69
|
async uploadEnv(devProjectId: string, file: File): Promise<boolean> {
|
|
70
|
+
await this.#ensureAccessTokenLive();
|
|
67
71
|
const formData = new FormData();
|
|
68
72
|
formData.append("devProjectId", devProjectId);
|
|
69
73
|
formData.append("file", file);
|
|
@@ -71,16 +75,13 @@ export class CloudApi {
|
|
|
71
75
|
return data;
|
|
72
76
|
}
|
|
73
77
|
async downloadEnv(devProjectId: string): Promise<unknown> {
|
|
78
|
+
await this.#ensureAccessTokenLive();
|
|
74
79
|
const localPath = `${this.#workspace.workspaceRoot}/local/env.tar`;
|
|
75
80
|
await this.#api.getFile(`/downloadEnv/${devProjectId}`, localPath);
|
|
76
81
|
return localPath;
|
|
77
82
|
}
|
|
78
83
|
async getRemoteAuthToken(remoteId: string): Promise<AccessToken | null> {
|
|
79
84
|
try {
|
|
80
|
-
if (this.#accessToken) {
|
|
81
|
-
if (GlobalConfig.needRefreshToken(this.#accessToken)) return await this.#refreshAuthToken();
|
|
82
|
-
else return await this.#refreshAuthToken();
|
|
83
|
-
}
|
|
84
85
|
const accessToken = await this.#api.get<AccessTokenDto>(`/getRemoteAuthToken/${remoteId}`);
|
|
85
86
|
this.#accessToken = GlobalConfig.toAccessToken(accessToken);
|
|
86
87
|
this.#api.setHeaders({
|
|
@@ -91,13 +92,20 @@ export class CloudApi {
|
|
|
91
92
|
return null;
|
|
92
93
|
}
|
|
93
94
|
}
|
|
94
|
-
async #
|
|
95
|
+
async #ensureAccessTokenLive({
|
|
96
|
+
allowUnauthorized = false,
|
|
97
|
+
}: {
|
|
98
|
+
allowUnauthorized?: boolean;
|
|
99
|
+
} = {}): Promise<AccessToken> {
|
|
100
|
+
if (!this.#accessToken) throw new Error("No access token");
|
|
101
|
+
const needRefresh = GlobalConfig.needRefreshToken(this.#accessToken);
|
|
102
|
+
if (!needRefresh) return this.#accessToken;
|
|
95
103
|
const refreshToken = this.#accessToken?.refreshToken;
|
|
96
104
|
if (!refreshToken) throw new Error("No refresh token");
|
|
97
105
|
return await this.refreshAuthToken(refreshToken);
|
|
98
106
|
}
|
|
99
107
|
async refreshAuthToken(refreshToken: string): Promise<AccessToken> {
|
|
100
|
-
const response = await this.#api.post<AccessTokenDto>(`/
|
|
108
|
+
const response = await this.#api.post<AccessTokenDto>(`/refreshAuthToken`, { refreshToken });
|
|
101
109
|
this.#accessToken = GlobalConfig.toAccessToken(response);
|
|
102
110
|
this.#api.setHeaders({ Authorization: `Bearer ${this.#accessToken.jwt}` });
|
|
103
111
|
return this.#accessToken;
|
package/cloud/constants.ts
CHANGED
|
@@ -1,24 +1,25 @@
|
|
|
1
1
|
import type { Dayjs } from "dayjs";
|
|
2
|
+
import { GlobalConfig } from "..";
|
|
2
3
|
import type { SupportedLlmModel } from "../aiEditor";
|
|
3
4
|
|
|
4
5
|
export const basePath = `${Bun.env.HOME ?? Bun.env.USERPROFILE}/.akan`;
|
|
5
6
|
export const configPath = `${basePath}/config.json`;
|
|
6
|
-
export const akanCloudHost = process.env.USE_AKANJS_PKGS === "true" ? "http://localhost" : "https://cloud.akanjs.com";
|
|
7
|
-
export const akanCloudUrl = `${akanCloudHost}${process.env.USE_AKANJS_PKGS === "true" ? ":8282" : ""}/api`;
|
|
8
7
|
|
|
9
8
|
export interface HostConfig {
|
|
9
|
+
host: string;
|
|
10
10
|
auth?: {
|
|
11
11
|
accessToken?: AccessToken;
|
|
12
12
|
self?: { id: string; nickname: string };
|
|
13
13
|
};
|
|
14
14
|
}
|
|
15
15
|
export interface HostConfigDto {
|
|
16
|
+
host: string;
|
|
16
17
|
auth?: {
|
|
17
18
|
accessToken?: AccessTokenDto;
|
|
18
19
|
self?: { id: string; nickname: string };
|
|
19
20
|
};
|
|
20
21
|
}
|
|
21
|
-
export const
|
|
22
|
+
export const getDefaultHostConfig = (host = GlobalConfig.akanCloudHost): HostConfig => ({ host });
|
|
22
23
|
export interface RemoteEnvServerConfig {
|
|
23
24
|
host: string;
|
|
24
25
|
username?: string;
|
package/cloud/globalConfig.ts
CHANGED
|
@@ -5,17 +5,20 @@ import {
|
|
|
5
5
|
type AccessToken,
|
|
6
6
|
type AccessTokenDto,
|
|
7
7
|
type AkanGlobalConfig,
|
|
8
|
-
akanCloudHost,
|
|
9
8
|
basePath,
|
|
10
9
|
configPath,
|
|
11
10
|
defaultAkanGlobalConfig,
|
|
12
|
-
|
|
11
|
+
getDefaultHostConfig,
|
|
13
12
|
type HostConfig,
|
|
14
13
|
type HostConfigDto,
|
|
15
14
|
type RemoteEnvServerConfig,
|
|
16
15
|
} from "./constants";
|
|
17
16
|
|
|
18
17
|
export class GlobalConfig {
|
|
18
|
+
static akanCloudHost =
|
|
19
|
+
process.env.USE_AKANJS_PKGS === "true"
|
|
20
|
+
? `http://localhost:${process.env.CLOUD_HOST_PORT ?? 8283}`
|
|
21
|
+
: "https://cloud.akanjs.com";
|
|
19
22
|
static async #getAkanGlobalConfig(): Promise<AkanGlobalConfig> {
|
|
20
23
|
const exists = await FileSys.fileExists(configPath);
|
|
21
24
|
const akanConfig = exists ? await FileSys.readJson<Partial<AkanGlobalConfig>>(configPath) : {};
|
|
@@ -30,13 +33,13 @@ export class GlobalConfig {
|
|
|
30
33
|
await mkdir(basePath, { recursive: true });
|
|
31
34
|
await Bun.write(configPath, JSON.stringify(akanConfig, null, 2));
|
|
32
35
|
}
|
|
33
|
-
static async getHostConfig(host = akanCloudHost): Promise<HostConfig> {
|
|
36
|
+
static async getHostConfig(host = GlobalConfig.akanCloudHost): Promise<HostConfig> {
|
|
34
37
|
const akanConfig = await GlobalConfig.#getAkanGlobalConfig();
|
|
35
|
-
return GlobalConfig.toHostConfig(akanConfig.cloudHost[host] ??
|
|
38
|
+
return GlobalConfig.toHostConfig(akanConfig.cloudHost[host] ?? getDefaultHostConfig(host));
|
|
36
39
|
}
|
|
37
|
-
static async setHostConfig(
|
|
40
|
+
static async setHostConfig(config: HostConfig = getDefaultHostConfig()) {
|
|
38
41
|
const akanConfig = await GlobalConfig.#getAkanGlobalConfig();
|
|
39
|
-
akanConfig.cloudHost[host] = GlobalConfig.toHostConfigDto(config);
|
|
42
|
+
akanConfig.cloudHost[config.host] = GlobalConfig.toHostConfigDto(config);
|
|
40
43
|
await GlobalConfig.#setAkanGlobalConfig(akanConfig);
|
|
41
44
|
}
|
|
42
45
|
static async getLlmConfig(): Promise<AkanGlobalConfig["llm"]> {
|
|
@@ -88,6 +91,7 @@ export class GlobalConfig {
|
|
|
88
91
|
}
|
|
89
92
|
static toHostConfigDto(hostConfig: HostConfig): HostConfigDto {
|
|
90
93
|
return {
|
|
94
|
+
host: hostConfig.host,
|
|
91
95
|
auth: {
|
|
92
96
|
accessToken: hostConfig.auth?.accessToken
|
|
93
97
|
? GlobalConfig.toAccessTokenDto(hostConfig.auth.accessToken)
|
|
@@ -98,6 +102,7 @@ export class GlobalConfig {
|
|
|
98
102
|
}
|
|
99
103
|
static toHostConfig(hostConfigDto: HostConfigDto): HostConfig {
|
|
100
104
|
return {
|
|
105
|
+
host: hostConfigDto.host,
|
|
101
106
|
auth: {
|
|
102
107
|
accessToken: hostConfigDto.auth?.accessToken
|
|
103
108
|
? GlobalConfig.toAccessToken(hostConfigDto.auth.accessToken)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@akanjs/devkit",
|
|
3
|
-
"version": "2.2.
|
|
3
|
+
"version": "2.2.2",
|
|
4
4
|
"sourceType": "module",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"publishConfig": {
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"@langchain/openai": "^1.4.6",
|
|
33
33
|
"@tailwindcss/node": "^4.3.0",
|
|
34
34
|
"@trapezedev/project": "^7.1.4",
|
|
35
|
-
"akanjs": "2.2.
|
|
35
|
+
"akanjs": "2.2.2",
|
|
36
36
|
"chalk": "^5.6.2",
|
|
37
37
|
"commander": "^14.0.3",
|
|
38
38
|
"daisyui": "^5.5.20",
|