@akanjs/devkit 2.1.1-rc.1 → 2.1.1-rc.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 +106 -34
- package/cloud/cloudApi.ts +83 -50
- package/cloud/constants.ts +48 -0
- package/cloud/globalConfig.ts +109 -0
- package/cloud/index.ts +2 -0
- package/executors.ts +125 -19
- package/index.ts +1 -2
- package/package.json +2 -2
- package/auth.ts +0 -41
- package/constants.ts +0 -32
package/aiEditor.ts
CHANGED
|
@@ -12,14 +12,17 @@ import { ChatOpenAI } from "@langchain/openai";
|
|
|
12
12
|
import { Logger } from "akanjs/common";
|
|
13
13
|
import chalk from "chalk";
|
|
14
14
|
|
|
15
|
-
import {
|
|
15
|
+
import { GlobalConfig } from "./cloud";
|
|
16
16
|
import type { Executor, WorkspaceExecutor } from "./executors";
|
|
17
17
|
import { Spinner } from "./spinner";
|
|
18
18
|
import type { FileContent } from "./types";
|
|
19
19
|
|
|
20
20
|
const MAX_ASK_TRY = 300;
|
|
21
21
|
|
|
22
|
-
export const supportedLlmModels = [
|
|
22
|
+
export const supportedLlmModels = [
|
|
23
|
+
"deepseek-chat",
|
|
24
|
+
"deepseek-reasoner",
|
|
25
|
+
] as const;
|
|
23
26
|
export type SupportedLlmModel = (typeof supportedLlmModels)[number];
|
|
24
27
|
|
|
25
28
|
interface EditOptions {
|
|
@@ -33,15 +36,30 @@ interface EditOptions {
|
|
|
33
36
|
export class AiSession {
|
|
34
37
|
static #cacheDir = "node_modules/.cache/akan/aiSession";
|
|
35
38
|
static #chat: ChatDeepSeek | ChatOpenAI | null = null;
|
|
36
|
-
static async init({
|
|
39
|
+
static async init({
|
|
40
|
+
temperature = 0,
|
|
41
|
+
useExisting = true,
|
|
42
|
+
}: {
|
|
43
|
+
temperature?: number;
|
|
44
|
+
useExisting?: boolean;
|
|
45
|
+
} = {}) {
|
|
37
46
|
if (useExisting) {
|
|
38
47
|
const llmConfig = await AiSession.getLlmConfig();
|
|
39
48
|
if (llmConfig) {
|
|
40
49
|
AiSession.#setChatModel(llmConfig.model, llmConfig.apiKey);
|
|
41
|
-
Logger.rawLog(
|
|
50
|
+
Logger.rawLog(
|
|
51
|
+
chalk.dim(
|
|
52
|
+
`🤖akan editor uses existing LLM config (${llmConfig.model})`,
|
|
53
|
+
),
|
|
54
|
+
);
|
|
42
55
|
return AiSession;
|
|
43
56
|
}
|
|
44
|
-
} else
|
|
57
|
+
} else
|
|
58
|
+
Logger.rawLog(
|
|
59
|
+
chalk.yellow(
|
|
60
|
+
"🤖akan-editor is not initialized. LLM configuration should be set first.",
|
|
61
|
+
),
|
|
62
|
+
);
|
|
45
63
|
|
|
46
64
|
const llmConfig = await AiSession.#requestLlmConfig();
|
|
47
65
|
const { model, apiKey } = llmConfig;
|
|
@@ -51,7 +69,11 @@ export class AiSession {
|
|
|
51
69
|
await session.setLlmConfig({ model, apiKey });
|
|
52
70
|
return session;
|
|
53
71
|
}
|
|
54
|
-
static #setChatModel(
|
|
72
|
+
static #setChatModel(
|
|
73
|
+
model: SupportedLlmModel,
|
|
74
|
+
apiKey: string,
|
|
75
|
+
{ temperature = 0 }: { temperature?: number } = {},
|
|
76
|
+
) {
|
|
55
77
|
AiSession.#chat = new ChatDeepSeek({
|
|
56
78
|
modelName: model,
|
|
57
79
|
temperature,
|
|
@@ -62,22 +84,26 @@ export class AiSession {
|
|
|
62
84
|
return AiSession;
|
|
63
85
|
}
|
|
64
86
|
static async getLlmConfig() {
|
|
65
|
-
|
|
66
|
-
return akanConfig.llm ?? null;
|
|
87
|
+
return await GlobalConfig.getLlmConfig();
|
|
67
88
|
}
|
|
68
|
-
static async setLlmConfig(
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
await
|
|
89
|
+
static async setLlmConfig(
|
|
90
|
+
llmConfig: { model: SupportedLlmModel; apiKey: string } | null,
|
|
91
|
+
) {
|
|
92
|
+
await GlobalConfig.setLlmConfig(llmConfig);
|
|
72
93
|
return AiSession;
|
|
73
94
|
}
|
|
74
95
|
static async #requestLlmConfig() {
|
|
75
|
-
const model = await select<SupportedLlmModel>({
|
|
96
|
+
const model = await select<SupportedLlmModel>({
|
|
97
|
+
message: "Select a LLM model",
|
|
98
|
+
choices: supportedLlmModels,
|
|
99
|
+
});
|
|
76
100
|
const apiKey = await input({ message: "Enter your API key" });
|
|
77
101
|
return { model, apiKey };
|
|
78
102
|
}
|
|
79
103
|
static async #validateApiKey(modelName: SupportedLlmModel, apiKey: string) {
|
|
80
|
-
const spinner = new Spinner("Validating LLM API key...", {
|
|
104
|
+
const spinner = new Spinner("Validating LLM API key...", {
|
|
105
|
+
prefix: `🤖akan-editor`,
|
|
106
|
+
}).start();
|
|
81
107
|
const chat = new ChatOpenAI({
|
|
82
108
|
modelName,
|
|
83
109
|
temperature: 0,
|
|
@@ -107,7 +133,15 @@ export class AiSession {
|
|
|
107
133
|
workspace: WorkspaceExecutor;
|
|
108
134
|
constructor(
|
|
109
135
|
type: string,
|
|
110
|
-
{
|
|
136
|
+
{
|
|
137
|
+
workspace,
|
|
138
|
+
cacheKey,
|
|
139
|
+
isContinued,
|
|
140
|
+
}: {
|
|
141
|
+
workspace: WorkspaceExecutor;
|
|
142
|
+
cacheKey?: string;
|
|
143
|
+
isContinued?: boolean;
|
|
144
|
+
},
|
|
111
145
|
) {
|
|
112
146
|
this.workspace = workspace;
|
|
113
147
|
this.sessionKey = `${type}${cacheKey ? `-${cacheKey}` : ""}`;
|
|
@@ -126,7 +160,10 @@ export class AiSession {
|
|
|
126
160
|
}
|
|
127
161
|
async #saveCache() {
|
|
128
162
|
const cacheFilePath = `${AiSession.#cacheDir}/${this.sessionKey}.json`;
|
|
129
|
-
await this.workspace.writeJson(
|
|
163
|
+
await this.workspace.writeJson(
|
|
164
|
+
cacheFilePath,
|
|
165
|
+
mapChatMessagesToStoredMessages(this.messageHistory),
|
|
166
|
+
);
|
|
130
167
|
}
|
|
131
168
|
async ask(
|
|
132
169
|
question: string,
|
|
@@ -142,7 +179,8 @@ export class AiSession {
|
|
|
142
179
|
if (!AiSession.#chat) await AiSession.init();
|
|
143
180
|
if (this.#cacheLoadPromise) await this.#cacheLoadPromise;
|
|
144
181
|
|
|
145
|
-
if (!AiSession.#chat)
|
|
182
|
+
if (!AiSession.#chat)
|
|
183
|
+
throw new Error("Failed to initialize the AI session");
|
|
146
184
|
const loader = new Spinner(`${AiSession.#chat.model} is thinking...`, {
|
|
147
185
|
prefix: `🤖akan-editor`,
|
|
148
186
|
}).start();
|
|
@@ -154,10 +192,13 @@ export class AiSession {
|
|
|
154
192
|
fullResponse = "",
|
|
155
193
|
tokenIdx = 0;
|
|
156
194
|
for await (const chunk of stream) {
|
|
157
|
-
if (loader.isSpinning())
|
|
195
|
+
if (loader.isSpinning())
|
|
196
|
+
loader.succeed(`${AiSession.#chat.model} responded`);
|
|
158
197
|
|
|
159
198
|
if (!fullResponse.length) {
|
|
160
|
-
const reasoningContent =
|
|
199
|
+
const reasoningContent =
|
|
200
|
+
(chunk.additional_kwargs as { reasoning_content?: string })
|
|
201
|
+
.reasoning_content ?? "";
|
|
161
202
|
if (reasoningContent.length) {
|
|
162
203
|
reasoningResponse += reasoningContent;
|
|
163
204
|
onReasoning(reasoningContent);
|
|
@@ -184,7 +225,16 @@ export class AiSession {
|
|
|
184
225
|
throw new Error("Failed to stream response");
|
|
185
226
|
}
|
|
186
227
|
}
|
|
187
|
-
async edit(
|
|
228
|
+
async edit(
|
|
229
|
+
question: string,
|
|
230
|
+
{
|
|
231
|
+
onChunk,
|
|
232
|
+
onReasoning,
|
|
233
|
+
maxTry = MAX_ASK_TRY,
|
|
234
|
+
validate,
|
|
235
|
+
approve,
|
|
236
|
+
}: EditOptions = {},
|
|
237
|
+
) {
|
|
188
238
|
for (let tryCount = 0; tryCount < maxTry; tryCount++) {
|
|
189
239
|
let response = await this.ask(question, { onChunk, onReasoning });
|
|
190
240
|
if (validate?.length && tryCount === 0) {
|
|
@@ -226,35 +276,51 @@ ${validate.map((v) => `- ${v}`).join("\n")}`;
|
|
|
226
276
|
// const toolMessages = messages.map(
|
|
227
277
|
// (message) => new ToolMessage({ content: message.content, tool_call_id: message.type })
|
|
228
278
|
// );
|
|
229
|
-
const toolMessages = messages.map(
|
|
279
|
+
const toolMessages = messages.map(
|
|
280
|
+
(message) => new HumanMessage(message.content),
|
|
281
|
+
);
|
|
230
282
|
this.messageHistory.push(...toolMessages);
|
|
231
283
|
return this;
|
|
232
284
|
}
|
|
233
|
-
async writeTypescripts(
|
|
285
|
+
async writeTypescripts(
|
|
286
|
+
question: string,
|
|
287
|
+
executor: Executor,
|
|
288
|
+
options: EditOptions = {},
|
|
289
|
+
) {
|
|
234
290
|
const content = await this.edit(question, options);
|
|
235
291
|
const writes = this.#getTypescriptCodes(content);
|
|
236
|
-
for (const write of writes)
|
|
292
|
+
for (const write of writes)
|
|
293
|
+
await executor.writeFile(write.filePath, write.content);
|
|
237
294
|
return await this.#tryFixTypescripts(writes, executor, options);
|
|
238
295
|
}
|
|
239
296
|
async #editTypescripts(question: string, options: EditOptions = {}) {
|
|
240
297
|
const content = await this.edit(question, options);
|
|
241
298
|
return this.#getTypescriptCodes(content);
|
|
242
299
|
}
|
|
243
|
-
async #tryFixTypescripts(
|
|
300
|
+
async #tryFixTypescripts(
|
|
301
|
+
writes: FileContent[],
|
|
302
|
+
executor: Executor,
|
|
303
|
+
options: EditOptions = {},
|
|
304
|
+
) {
|
|
244
305
|
const MAX_EDIT_TRY = 5;
|
|
245
306
|
for (let tryCount = 0; tryCount < MAX_EDIT_TRY; tryCount++) {
|
|
246
|
-
const loader = new Spinner(`Type checking and linting...`, {
|
|
307
|
+
const loader = new Spinner(`Type checking and linting...`, {
|
|
308
|
+
prefix: `🤖akan-editor`,
|
|
309
|
+
}).start();
|
|
247
310
|
const fileChecks = await Promise.all(
|
|
248
311
|
writes.map(async ({ filePath }) => {
|
|
249
312
|
const typeCheckResult = executor.typeCheck(filePath);
|
|
250
313
|
const lintResult = await executor.lint(filePath);
|
|
251
|
-
const needFix =
|
|
314
|
+
const needFix =
|
|
315
|
+
!!typeCheckResult.fileErrors.length || !!lintResult.errors.length;
|
|
252
316
|
return { filePath, typeCheckResult, lintResult, needFix };
|
|
253
317
|
}),
|
|
254
318
|
);
|
|
255
319
|
const needFix = fileChecks.some((fileCheck) => fileCheck.needFix);
|
|
256
320
|
if (needFix) {
|
|
257
|
-
loader.fail(
|
|
321
|
+
loader.fail(
|
|
322
|
+
"Type checking and linting has some errors, try to fix them",
|
|
323
|
+
);
|
|
258
324
|
fileChecks.forEach((fileCheck) => {
|
|
259
325
|
Logger.rawLog(
|
|
260
326
|
`TypeCheck Result \n${fileCheck.typeCheckResult.message}\nLint Result \n${fileCheck.lintResult.message}`,
|
|
@@ -264,12 +330,16 @@ ${validate.map((v) => `- ${v}`).join("\n")}`;
|
|
|
264
330
|
{ type: "eslint", content: fileCheck.lintResult.message },
|
|
265
331
|
]);
|
|
266
332
|
});
|
|
267
|
-
writes = await this.#editTypescripts(
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
333
|
+
writes = await this.#editTypescripts(
|
|
334
|
+
"Fix the typescript and eslint errors",
|
|
335
|
+
{
|
|
336
|
+
...options,
|
|
337
|
+
validate: undefined,
|
|
338
|
+
approve: true,
|
|
339
|
+
},
|
|
340
|
+
);
|
|
341
|
+
for (const write of writes)
|
|
342
|
+
await executor.writeFile(write.filePath, write.content);
|
|
273
343
|
} else {
|
|
274
344
|
loader.succeed("Type checking and linting has no errors");
|
|
275
345
|
return writes;
|
|
@@ -285,7 +355,9 @@ ${validate.map((v) => `- ${v}`).join("\n")}`;
|
|
|
285
355
|
if (!content) return null;
|
|
286
356
|
const filePath = /\/\/ File: (.*?)(?:\n|$)/.exec(content)?.[1]?.trim();
|
|
287
357
|
if (!filePath) return null;
|
|
288
|
-
const contentWithoutFilepath = content
|
|
358
|
+
const contentWithoutFilepath = content
|
|
359
|
+
.replace(`// File: ${filePath}\n`, "")
|
|
360
|
+
.trim();
|
|
289
361
|
return { filePath, content: contentWithoutFilepath };
|
|
290
362
|
});
|
|
291
363
|
return result.filter((code) => code !== null) as FileContent[];
|
package/cloud/cloudApi.ts
CHANGED
|
@@ -1,30 +1,45 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
}
|
|
8
|
-
interface AccessToken {
|
|
9
|
-
jwt: string;
|
|
10
|
-
refreshToken: string | null;
|
|
11
|
-
expiresAt: Dayjs | null;
|
|
12
|
-
}
|
|
1
|
+
import {
|
|
2
|
+
type AccessToken,
|
|
3
|
+
type AccessTokenDto,
|
|
4
|
+
akanCloudHost,
|
|
5
|
+
type HostConfig,
|
|
6
|
+
} from "./constants";
|
|
7
|
+
import { GlobalConfig } from "./globalConfig";
|
|
13
8
|
|
|
14
9
|
class HttpClient {
|
|
15
10
|
readonly baseUrl: string;
|
|
16
|
-
|
|
11
|
+
readonly headers: Record<string, string> = {};
|
|
12
|
+
constructor(baseUrl: string, headers: Record<string, string> = {}) {
|
|
17
13
|
this.baseUrl = baseUrl;
|
|
14
|
+
this.headers = headers;
|
|
18
15
|
}
|
|
19
16
|
async get<T>(
|
|
20
17
|
url: string,
|
|
21
18
|
{ headers }: { headers?: Record<string, string> } = {},
|
|
22
19
|
): Promise<T> {
|
|
23
20
|
const response = await fetch(`${this.baseUrl}${url}`, {
|
|
24
|
-
headers: {
|
|
21
|
+
headers: {
|
|
22
|
+
"Content-Type": "application/json",
|
|
23
|
+
...this.headers,
|
|
24
|
+
...headers,
|
|
25
|
+
},
|
|
25
26
|
});
|
|
26
27
|
return response.json();
|
|
27
28
|
}
|
|
29
|
+
async getFile(
|
|
30
|
+
url: string,
|
|
31
|
+
localPath: string,
|
|
32
|
+
headers?: Record<string, string>,
|
|
33
|
+
): Promise<void> {
|
|
34
|
+
const response = await fetch(`${this.baseUrl}${url}`, {
|
|
35
|
+
headers: { ...this.headers, ...headers },
|
|
36
|
+
});
|
|
37
|
+
if (!response.ok)
|
|
38
|
+
throw new Error(
|
|
39
|
+
`Failed to download file: ${response.status} ${response.statusText}`,
|
|
40
|
+
);
|
|
41
|
+
await Bun.write(localPath, response);
|
|
42
|
+
}
|
|
28
43
|
async post<T>(
|
|
29
44
|
url: string,
|
|
30
45
|
data: unknown,
|
|
@@ -35,68 +50,86 @@ class HttpClient {
|
|
|
35
50
|
method: "POST",
|
|
36
51
|
body: isFormData ? data : JSON.stringify(data),
|
|
37
52
|
headers: isFormData
|
|
38
|
-
? headers
|
|
39
|
-
: { "Content-Type": "application/json", ...headers },
|
|
53
|
+
? { ...this.headers, ...headers }
|
|
54
|
+
: { "Content-Type": "application/json", ...this.headers, ...headers },
|
|
40
55
|
});
|
|
41
56
|
return response.json();
|
|
42
57
|
}
|
|
58
|
+
setHeaders(headers: Record<string, string>) {
|
|
59
|
+
Object.assign(this.headers, headers);
|
|
60
|
+
return this;
|
|
61
|
+
}
|
|
43
62
|
}
|
|
44
63
|
|
|
45
64
|
export class CloudApi {
|
|
46
|
-
readonly api: HttpClient;
|
|
65
|
+
readonly #api: HttpClient;
|
|
47
66
|
#accessToken: AccessToken | null = null;
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
67
|
+
|
|
68
|
+
static async fromHost(host?: string) {
|
|
69
|
+
const hostConfig = await GlobalConfig.getHostConfig(host);
|
|
70
|
+
return new CloudApi(hostConfig);
|
|
71
|
+
}
|
|
72
|
+
constructor(hostConfig: HostConfig) {
|
|
73
|
+
const host = akanCloudHost;
|
|
74
|
+
this.#api = new HttpClient(`${host}/api`);
|
|
75
|
+
this.#accessToken = hostConfig.auth?.accessToken ?? null;
|
|
76
|
+
if (this.#accessToken && !GlobalConfig.needRefreshToken(this.#accessToken))
|
|
77
|
+
this.#api.setHeaders({
|
|
78
|
+
Authorization: `Bearer ${this.#accessToken.jwt}`,
|
|
79
|
+
});
|
|
54
80
|
}
|
|
55
81
|
|
|
56
82
|
async uploadEnv(devProjectId: string, file: File): Promise<boolean> {
|
|
57
83
|
const formData = new FormData();
|
|
58
84
|
formData.append("devProjectId", devProjectId);
|
|
59
85
|
formData.append("file", file);
|
|
60
|
-
const
|
|
86
|
+
const data = await this.#api.post<boolean>(
|
|
61
87
|
`/uploadEnv/${devProjectId}`,
|
|
62
88
|
formData,
|
|
63
89
|
);
|
|
64
|
-
return
|
|
90
|
+
return data;
|
|
65
91
|
}
|
|
66
|
-
async downloadEnv(devProjectId: string): Promise<
|
|
67
|
-
|
|
68
|
-
`/downloadEnv/${devProjectId}`,
|
|
69
|
-
);
|
|
70
|
-
return response.success;
|
|
92
|
+
async downloadEnv(devProjectId: string, localPath: string): Promise<void> {
|
|
93
|
+
await this.#api.getFile(`/downloadEnv/${devProjectId}`, localPath);
|
|
71
94
|
}
|
|
72
|
-
async getRemoteAuthToken(remoteId: string): Promise<AccessToken> {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
95
|
+
async getRemoteAuthToken(remoteId: string): Promise<AccessToken | null> {
|
|
96
|
+
try {
|
|
97
|
+
if (this.#accessToken) {
|
|
98
|
+
if (GlobalConfig.needRefreshToken(this.#accessToken))
|
|
99
|
+
return await this.refreshAuthToken();
|
|
100
|
+
else return await this.refreshAuthToken();
|
|
101
|
+
}
|
|
102
|
+
const accessToken = await this.#api.get<AccessTokenDto>(
|
|
103
|
+
`/getRemoteAuthToken/${remoteId}`,
|
|
104
|
+
);
|
|
105
|
+
this.#accessToken = GlobalConfig.toAccessToken(accessToken);
|
|
106
|
+
this.#api.setHeaders({
|
|
107
|
+
Authorization: `Bearer ${this.#accessToken.jwt}`,
|
|
108
|
+
});
|
|
109
|
+
return this.#accessToken;
|
|
110
|
+
} catch (_) {
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
84
113
|
}
|
|
85
114
|
async refreshAuthToken(): Promise<AccessToken> {
|
|
86
|
-
const response = await this
|
|
115
|
+
const response = await this.#api.post<AccessTokenDto>(
|
|
87
116
|
`/refreshRemoteAuthToken`,
|
|
88
117
|
{
|
|
89
118
|
refreshToken: this.#accessToken?.refreshToken,
|
|
90
119
|
},
|
|
91
120
|
);
|
|
92
|
-
this.#accessToken =
|
|
93
|
-
|
|
94
|
-
refreshToken: response.refreshToken,
|
|
95
|
-
expiresAt: response.expiresAt ? dayjs(response.expiresAt) : null,
|
|
96
|
-
};
|
|
121
|
+
this.#accessToken = GlobalConfig.toAccessToken(response);
|
|
122
|
+
this.#api.setHeaders({ Authorization: `Bearer ${this.#accessToken.jwt}` });
|
|
97
123
|
return this.#accessToken;
|
|
98
124
|
}
|
|
99
|
-
|
|
100
|
-
|
|
125
|
+
async getRemoteSelf(): Promise<{ id: string; nickname: string } | null> {
|
|
126
|
+
try {
|
|
127
|
+
const data = await this.#api.get<{ id: string; nickname: string }>(
|
|
128
|
+
`/getRemoteSelf`,
|
|
129
|
+
);
|
|
130
|
+
return data;
|
|
131
|
+
} catch {
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
101
134
|
}
|
|
102
135
|
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { Dayjs } from "dayjs";
|
|
2
|
+
import type { SupportedLlmModel } from "../aiEditor";
|
|
3
|
+
|
|
4
|
+
export const basePath = `${Bun.env.HOME ?? Bun.env.USERPROFILE}/.akan`;
|
|
5
|
+
export const configPath = `${basePath}/config.json`;
|
|
6
|
+
export const akanCloudHost =
|
|
7
|
+
process.env.AKAN_PUBLIC_OPERATION_MODE === "local" ? "http://localhost" : "https://cloud.akanjs.com";
|
|
8
|
+
export const akanCloudUrl = `${akanCloudHost}${process.env.AKAN_PUBLIC_OPERATION_MODE === "local" ? ":8282" : ""}/api`;
|
|
9
|
+
|
|
10
|
+
export interface HostConfig {
|
|
11
|
+
auth?: {
|
|
12
|
+
accessToken?: AccessToken;
|
|
13
|
+
self?: { id: string; nickname: string };
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
export interface HostConfigDto {
|
|
17
|
+
auth?: {
|
|
18
|
+
accessToken?: AccessTokenDto;
|
|
19
|
+
self?: { id: string; nickname: string };
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
export const defaultHostConfig: HostConfig = {};
|
|
23
|
+
export interface RemoteEnvServerConfig {
|
|
24
|
+
host: string;
|
|
25
|
+
username?: string;
|
|
26
|
+
port?: number;
|
|
27
|
+
}
|
|
28
|
+
export interface AkanGlobalConfig {
|
|
29
|
+
cloudHost: { [key: string]: HostConfigDto };
|
|
30
|
+
remoteEnvServers: Record<string, RemoteEnvServerConfig>;
|
|
31
|
+
llm: { model: SupportedLlmModel; apiKey: string } | null;
|
|
32
|
+
}
|
|
33
|
+
export const defaultAkanGlobalConfig: AkanGlobalConfig = {
|
|
34
|
+
cloudHost: {},
|
|
35
|
+
remoteEnvServers: {},
|
|
36
|
+
llm: null,
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export interface AccessTokenDto {
|
|
40
|
+
jwt: string;
|
|
41
|
+
refreshToken: string | null;
|
|
42
|
+
expiresAt: string | null;
|
|
43
|
+
}
|
|
44
|
+
export interface AccessToken {
|
|
45
|
+
jwt: string;
|
|
46
|
+
refreshToken: string | null;
|
|
47
|
+
expiresAt: Dayjs | null;
|
|
48
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { mkdir } from "node:fs/promises";
|
|
2
|
+
import dayjs from "dayjs";
|
|
3
|
+
import { FileSys } from "../fileSys";
|
|
4
|
+
import {
|
|
5
|
+
type AccessToken,
|
|
6
|
+
type AccessTokenDto,
|
|
7
|
+
type AkanGlobalConfig,
|
|
8
|
+
akanCloudHost,
|
|
9
|
+
basePath,
|
|
10
|
+
configPath,
|
|
11
|
+
defaultAkanGlobalConfig,
|
|
12
|
+
defaultHostConfig,
|
|
13
|
+
type HostConfig,
|
|
14
|
+
type HostConfigDto,
|
|
15
|
+
type RemoteEnvServerConfig,
|
|
16
|
+
} from "./constants";
|
|
17
|
+
|
|
18
|
+
export class GlobalConfig {
|
|
19
|
+
static async #getAkanGlobalConfig(): Promise<AkanGlobalConfig> {
|
|
20
|
+
const exists = await FileSys.fileExists(configPath);
|
|
21
|
+
const akanConfig = exists ? await FileSys.readJson<Partial<AkanGlobalConfig>>(configPath) : {};
|
|
22
|
+
return {
|
|
23
|
+
...defaultAkanGlobalConfig,
|
|
24
|
+
...akanConfig,
|
|
25
|
+
cloudHost: akanConfig.cloudHost ?? defaultAkanGlobalConfig.cloudHost,
|
|
26
|
+
remoteEnvServers: akanConfig.remoteEnvServers ?? defaultAkanGlobalConfig.remoteEnvServers,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
static async #setAkanGlobalConfig(akanConfig: AkanGlobalConfig) {
|
|
30
|
+
await mkdir(basePath, { recursive: true });
|
|
31
|
+
await Bun.write(configPath, JSON.stringify(akanConfig, null, 2));
|
|
32
|
+
}
|
|
33
|
+
static async getHostConfig(host = akanCloudHost): Promise<HostConfig> {
|
|
34
|
+
const akanConfig = await GlobalConfig.#getAkanGlobalConfig();
|
|
35
|
+
return GlobalConfig.toHostConfig(akanConfig.cloudHost[host] ?? defaultHostConfig);
|
|
36
|
+
}
|
|
37
|
+
static async setHostConfig(host = akanCloudHost, config: HostConfig = {}) {
|
|
38
|
+
const akanConfig = await GlobalConfig.#getAkanGlobalConfig();
|
|
39
|
+
akanConfig.cloudHost[host] = GlobalConfig.toHostConfigDto(config);
|
|
40
|
+
await GlobalConfig.#setAkanGlobalConfig(akanConfig);
|
|
41
|
+
}
|
|
42
|
+
static async getLlmConfig(): Promise<AkanGlobalConfig["llm"]> {
|
|
43
|
+
const akanConfig = await GlobalConfig.#getAkanGlobalConfig();
|
|
44
|
+
return akanConfig.llm ?? null;
|
|
45
|
+
}
|
|
46
|
+
static async setLlmConfig(llmConfig: AkanGlobalConfig["llm"]) {
|
|
47
|
+
const akanConfig = await GlobalConfig.#getAkanGlobalConfig();
|
|
48
|
+
await GlobalConfig.#setAkanGlobalConfig({ ...akanConfig, llm: llmConfig });
|
|
49
|
+
}
|
|
50
|
+
static async getRemoteEnvServers(): Promise<AkanGlobalConfig["remoteEnvServers"]> {
|
|
51
|
+
const akanConfig = await GlobalConfig.#getAkanGlobalConfig();
|
|
52
|
+
return akanConfig.remoteEnvServers;
|
|
53
|
+
}
|
|
54
|
+
static async setRemoteEnvServer(name: string, config: RemoteEnvServerConfig) {
|
|
55
|
+
const akanConfig = await GlobalConfig.#getAkanGlobalConfig();
|
|
56
|
+
await GlobalConfig.#setAkanGlobalConfig({
|
|
57
|
+
...akanConfig,
|
|
58
|
+
remoteEnvServers: {
|
|
59
|
+
...akanConfig.remoteEnvServers,
|
|
60
|
+
[name]: config,
|
|
61
|
+
},
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
static async removeRemoteEnvServer(name: string) {
|
|
65
|
+
const akanConfig = await GlobalConfig.#getAkanGlobalConfig();
|
|
66
|
+
const { [name]: _, ...remoteEnvServers } = akanConfig.remoteEnvServers;
|
|
67
|
+
await GlobalConfig.#setAkanGlobalConfig({
|
|
68
|
+
...akanConfig,
|
|
69
|
+
remoteEnvServers,
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
static needRefreshToken(accessToken: AccessToken): boolean {
|
|
73
|
+
return !!accessToken?.expiresAt?.isBefore(dayjs().add(1, "hour"));
|
|
74
|
+
}
|
|
75
|
+
static toAccessToken(accessToken: AccessTokenDto): AccessToken {
|
|
76
|
+
return {
|
|
77
|
+
jwt: accessToken.jwt,
|
|
78
|
+
refreshToken: accessToken.refreshToken ?? null,
|
|
79
|
+
expiresAt: accessToken.expiresAt ? dayjs(accessToken.expiresAt) : null,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
static toAccessTokenDto(accessToken: AccessToken): AccessTokenDto {
|
|
83
|
+
return {
|
|
84
|
+
jwt: accessToken.jwt,
|
|
85
|
+
refreshToken: accessToken.refreshToken ?? null,
|
|
86
|
+
expiresAt: accessToken.expiresAt?.toString() ?? null,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
static toHostConfigDto(hostConfig: HostConfig): HostConfigDto {
|
|
90
|
+
return {
|
|
91
|
+
auth: {
|
|
92
|
+
accessToken: hostConfig.auth?.accessToken
|
|
93
|
+
? GlobalConfig.toAccessTokenDto(hostConfig.auth.accessToken)
|
|
94
|
+
: undefined,
|
|
95
|
+
self: hostConfig.auth?.self,
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
static toHostConfig(hostConfigDto: HostConfigDto): HostConfig {
|
|
100
|
+
return {
|
|
101
|
+
auth: {
|
|
102
|
+
accessToken: hostConfigDto.auth?.accessToken
|
|
103
|
+
? GlobalConfig.toAccessToken(hostConfigDto.auth.accessToken)
|
|
104
|
+
: undefined,
|
|
105
|
+
self: hostConfigDto.auth?.self,
|
|
106
|
+
},
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
}
|
package/cloud/index.ts
CHANGED
package/executors.ts
CHANGED
|
@@ -261,10 +261,30 @@ export class Executor {
|
|
|
261
261
|
});
|
|
262
262
|
return new Promise((resolve, reject) => {
|
|
263
263
|
proc.on("error", (error) => {
|
|
264
|
-
reject(
|
|
264
|
+
reject(
|
|
265
|
+
new CommandExecutionError({
|
|
266
|
+
command,
|
|
267
|
+
cwd,
|
|
268
|
+
code: null,
|
|
269
|
+
signal: null,
|
|
270
|
+
stdout,
|
|
271
|
+
stderr,
|
|
272
|
+
cause: error,
|
|
273
|
+
}),
|
|
274
|
+
);
|
|
265
275
|
});
|
|
266
276
|
proc.on("exit", (code, signal) => {
|
|
267
|
-
if (!!code || signal)
|
|
277
|
+
if (!!code || signal)
|
|
278
|
+
reject(
|
|
279
|
+
new CommandExecutionError({
|
|
280
|
+
command,
|
|
281
|
+
cwd,
|
|
282
|
+
code,
|
|
283
|
+
signal,
|
|
284
|
+
stdout,
|
|
285
|
+
stderr,
|
|
286
|
+
}),
|
|
287
|
+
);
|
|
268
288
|
else resolve({ code, signal });
|
|
269
289
|
});
|
|
270
290
|
});
|
|
@@ -293,12 +313,31 @@ export class Executor {
|
|
|
293
313
|
return new Promise((resolve, reject) => {
|
|
294
314
|
proc.on("error", (error) => {
|
|
295
315
|
reject(
|
|
296
|
-
new CommandExecutionError({
|
|
316
|
+
new CommandExecutionError({
|
|
317
|
+
command,
|
|
318
|
+
args,
|
|
319
|
+
cwd,
|
|
320
|
+
code: null,
|
|
321
|
+
signal: null,
|
|
322
|
+
stdout,
|
|
323
|
+
stderr,
|
|
324
|
+
cause: error,
|
|
325
|
+
}),
|
|
297
326
|
);
|
|
298
327
|
});
|
|
299
328
|
proc.on("close", (code, signal) => {
|
|
300
329
|
if (code !== 0 || signal)
|
|
301
|
-
reject(
|
|
330
|
+
reject(
|
|
331
|
+
new CommandExecutionError({
|
|
332
|
+
command,
|
|
333
|
+
args,
|
|
334
|
+
cwd,
|
|
335
|
+
code,
|
|
336
|
+
signal,
|
|
337
|
+
stdout,
|
|
338
|
+
stderr,
|
|
339
|
+
}),
|
|
340
|
+
);
|
|
302
341
|
else resolve(stdout);
|
|
303
342
|
});
|
|
304
343
|
});
|
|
@@ -345,7 +384,17 @@ export class Executor {
|
|
|
345
384
|
});
|
|
346
385
|
proc.on("exit", (code, signal) => {
|
|
347
386
|
if (!!code || signal)
|
|
348
|
-
reject(
|
|
387
|
+
reject(
|
|
388
|
+
new CommandExecutionError({
|
|
389
|
+
command: modulePath,
|
|
390
|
+
args,
|
|
391
|
+
cwd,
|
|
392
|
+
code,
|
|
393
|
+
signal,
|
|
394
|
+
stdout,
|
|
395
|
+
stderr,
|
|
396
|
+
}),
|
|
397
|
+
);
|
|
349
398
|
else resolve({ code, signal });
|
|
350
399
|
});
|
|
351
400
|
});
|
|
@@ -490,7 +539,10 @@ export class Executor {
|
|
|
490
539
|
const result = {
|
|
491
540
|
...extendsTsconfig,
|
|
492
541
|
...tsconfig,
|
|
493
|
-
compilerOptions: {
|
|
542
|
+
compilerOptions: {
|
|
543
|
+
...extendsTsconfig.compilerOptions,
|
|
544
|
+
...tsconfig.compilerOptions,
|
|
545
|
+
},
|
|
494
546
|
} as TsConfigJson;
|
|
495
547
|
this.#tsconfig = result;
|
|
496
548
|
return result;
|
|
@@ -571,7 +623,9 @@ export class Executor {
|
|
|
571
623
|
content,
|
|
572
624
|
);
|
|
573
625
|
this.logger.verbose(`Apply template ${templatePath} to ${convertedTargetPath}`);
|
|
574
|
-
return this.writeFile(convertedTargetPath, convertedContent, {
|
|
626
|
+
return this.writeFile(convertedTargetPath, convertedContent, {
|
|
627
|
+
overwrite,
|
|
628
|
+
});
|
|
575
629
|
} else if (staticTemplateFileExtensions.has(path.extname(targetPath).toLowerCase())) {
|
|
576
630
|
const convertedTargetPath = Object.entries(dict).reduce(
|
|
577
631
|
(path, [key, value]) => path.replace(new RegExp(`__${key}__`, "g"), value),
|
|
@@ -606,7 +660,12 @@ export class Executor {
|
|
|
606
660
|
if ((await stat(prefixTemplatePath)).isFile()) {
|
|
607
661
|
const filename = path.basename(prefixTemplatePath);
|
|
608
662
|
const fileContent = await this.#applyTemplateFile(
|
|
609
|
-
{
|
|
663
|
+
{
|
|
664
|
+
templatePath: prefixTemplatePath,
|
|
665
|
+
targetPath: path.join(basePath, filename),
|
|
666
|
+
scanInfo,
|
|
667
|
+
overwrite,
|
|
668
|
+
},
|
|
610
669
|
dict,
|
|
611
670
|
options,
|
|
612
671
|
);
|
|
@@ -619,7 +678,12 @@ export class Executor {
|
|
|
619
678
|
const subpath = path.join(templatePath, subdir);
|
|
620
679
|
if ((await stat(subpath)).isFile()) {
|
|
621
680
|
const fileContent = await this.#applyTemplateFile(
|
|
622
|
-
{
|
|
681
|
+
{
|
|
682
|
+
templatePath: subpath,
|
|
683
|
+
targetPath: path.join(basePath, subdir),
|
|
684
|
+
scanInfo,
|
|
685
|
+
overwrite,
|
|
686
|
+
},
|
|
623
687
|
dict,
|
|
624
688
|
options,
|
|
625
689
|
);
|
|
@@ -690,7 +754,10 @@ export class Executor {
|
|
|
690
754
|
}> {
|
|
691
755
|
const path = this.getPath(filePath);
|
|
692
756
|
const linter = this.getLinter();
|
|
693
|
-
const { results, errors, warnings } = await linter.lint(path, {
|
|
757
|
+
const { results, errors, warnings } = await linter.lint(path, {
|
|
758
|
+
fix,
|
|
759
|
+
dryRun,
|
|
760
|
+
});
|
|
694
761
|
const message = linter.formatLintResults(results);
|
|
695
762
|
return { results, message, errors, warnings };
|
|
696
763
|
}
|
|
@@ -726,6 +793,7 @@ export class WorkspaceExecutor extends Executor {
|
|
|
726
793
|
|
|
727
794
|
const appName = sourceEnv.AKAN_PUBLIC_APP_NAME;
|
|
728
795
|
const workspaceRoot = sourceEnv.AKAN_WORKSPACE_ROOT;
|
|
796
|
+
const workspaceId = sourceEnv.AKAN_WORKSPACE_ID;
|
|
729
797
|
|
|
730
798
|
const repoName = sourceEnv.AKAN_PUBLIC_REPO_NAME;
|
|
731
799
|
if (!repoName) throw new Error("AKAN_PUBLIC_REPO_NAME is not set");
|
|
@@ -743,7 +811,16 @@ export class WorkspaceExecutor extends Executor {
|
|
|
743
811
|
| "local"
|
|
744
812
|
| undefined;
|
|
745
813
|
if (!env) throw new Error("AKAN_PUBLIC_ENV is not set");
|
|
746
|
-
return { ...(appName ? { appName } : {}), workspaceRoot, repoName, serveDomain, env, portOffset };
|
|
814
|
+
return { ...(appName ? { appName } : {}), workspaceRoot, repoName, serveDomain, env, portOffset, workspaceId };
|
|
815
|
+
}
|
|
816
|
+
getWorkspaceId<AllowEmpty extends boolean = false>({
|
|
817
|
+
allowEmpty,
|
|
818
|
+
}: {
|
|
819
|
+
allowEmpty?: AllowEmpty;
|
|
820
|
+
} = {}): AllowEmpty extends true ? string | undefined : string {
|
|
821
|
+
const { workspaceId } = WorkspaceExecutor.getBaseDevEnv();
|
|
822
|
+
if (!workspaceId && !allowEmpty) throw new Error("Workspace ID is not found");
|
|
823
|
+
return workspaceId as AllowEmpty extends true ? string | undefined : string;
|
|
747
824
|
}
|
|
748
825
|
async scan(): Promise<WorkspaceInfo> {
|
|
749
826
|
return await WorkspaceInfo.fromExecutor(this);
|
|
@@ -976,8 +1053,12 @@ export class SysExecutor extends Executor {
|
|
|
976
1053
|
if (this.#scanInfo && !refresh) return this.#scanInfo;
|
|
977
1054
|
const scanInfo =
|
|
978
1055
|
this.type === "app"
|
|
979
|
-
? await AppInfo.fromExecutor(this as unknown as AppExecutor, {
|
|
980
|
-
|
|
1056
|
+
? await AppInfo.fromExecutor(this as unknown as AppExecutor, {
|
|
1057
|
+
refresh,
|
|
1058
|
+
})
|
|
1059
|
+
: await LibInfo.fromExecutor(this as unknown as LibExecutor, {
|
|
1060
|
+
refresh,
|
|
1061
|
+
});
|
|
981
1062
|
if (write) {
|
|
982
1063
|
await Promise.all(this.#getScanTemplateTasks(scanInfo));
|
|
983
1064
|
await this.writeJson(`akan.${this.type}.json`, scanInfo.getScanResult());
|
|
@@ -1147,7 +1228,11 @@ export class SysExecutor extends Executor {
|
|
|
1147
1228
|
),
|
|
1148
1229
|
};
|
|
1149
1230
|
const scanInfo = await this.scan();
|
|
1150
|
-
const fileContents = await this._applyTemplate({
|
|
1231
|
+
const fileContents = await this._applyTemplate({
|
|
1232
|
+
...options,
|
|
1233
|
+
scanInfo,
|
|
1234
|
+
dict,
|
|
1235
|
+
});
|
|
1151
1236
|
await this.scan();
|
|
1152
1237
|
return fileContents;
|
|
1153
1238
|
}
|
|
@@ -1247,7 +1332,11 @@ export class AppExecutor extends SysExecutor {
|
|
|
1247
1332
|
this.#pageKeys = [];
|
|
1248
1333
|
return this.#pageKeys;
|
|
1249
1334
|
}
|
|
1250
|
-
for await (const rel of glob.scan({
|
|
1335
|
+
for await (const rel of glob.scan({
|
|
1336
|
+
cwd: pageDir,
|
|
1337
|
+
absolute: false,
|
|
1338
|
+
onlyFiles: true,
|
|
1339
|
+
})) {
|
|
1251
1340
|
const segments = rel.split(path.sep);
|
|
1252
1341
|
if (segments.some((s) => s === "node_modules")) continue;
|
|
1253
1342
|
const posix = segments.join("/");
|
|
@@ -1255,7 +1344,10 @@ export class AppExecutor extends SysExecutor {
|
|
|
1255
1344
|
validatePageSourceFile(posix, { filePath: absPath });
|
|
1256
1345
|
if (!isRouteSourceFile(posix)) continue;
|
|
1257
1346
|
const key = `./${posix}`;
|
|
1258
|
-
validateSubRoutePageKey(key, akanConfig.basePaths, {
|
|
1347
|
+
validateSubRoutePageKey(key, akanConfig.basePaths, {
|
|
1348
|
+
appName: this.name,
|
|
1349
|
+
filePath: absPath,
|
|
1350
|
+
});
|
|
1259
1351
|
const parsed = parseRouteModuleKey(key);
|
|
1260
1352
|
if (parsed.isInternalRootLayout) {
|
|
1261
1353
|
throw new Error(`[route-convention] __root_layout is reserved for Akan.js generated root layout: ${absPath}`);
|
|
@@ -1298,7 +1390,11 @@ export class AppExecutor extends SysExecutor {
|
|
|
1298
1390
|
]);
|
|
1299
1391
|
}
|
|
1300
1392
|
async scanSync({ refresh = false, write = true }: { refresh?: boolean; write?: boolean } = {}) {
|
|
1301
|
-
const scanInfo = (await this.scan({
|
|
1393
|
+
const scanInfo = (await this.scan({
|
|
1394
|
+
refresh,
|
|
1395
|
+
write,
|
|
1396
|
+
writeLib: write,
|
|
1397
|
+
})) as AppInfo;
|
|
1302
1398
|
if (write) await this.syncAssets(scanInfo.getScanResult().libDeps);
|
|
1303
1399
|
return scanInfo;
|
|
1304
1400
|
}
|
|
@@ -1365,7 +1461,10 @@ export class PkgExecutor extends Executor {
|
|
|
1365
1461
|
return scanInfo;
|
|
1366
1462
|
}
|
|
1367
1463
|
async #getDependencyVersion(rootPackageJson: PackageJson, dep: string): Promise<string | undefined> {
|
|
1368
|
-
const rootDeps = {
|
|
1464
|
+
const rootDeps = {
|
|
1465
|
+
...rootPackageJson.dependencies,
|
|
1466
|
+
...rootPackageJson.devDependencies,
|
|
1467
|
+
};
|
|
1369
1468
|
const rootVersion = rootDeps[dep];
|
|
1370
1469
|
if (rootVersion) return rootVersion;
|
|
1371
1470
|
|
|
@@ -1426,7 +1525,14 @@ export class PkgExecutor extends Executor {
|
|
|
1426
1525
|
const distPkgJson: PackageJson = {
|
|
1427
1526
|
...pkgJson,
|
|
1428
1527
|
type: "module",
|
|
1429
|
-
exports: {
|
|
1528
|
+
exports: {
|
|
1529
|
+
...pkgJson.exports,
|
|
1530
|
+
".": {
|
|
1531
|
+
import: "./index.ts",
|
|
1532
|
+
types: "./index.ts",
|
|
1533
|
+
default: "./index.ts",
|
|
1534
|
+
},
|
|
1535
|
+
},
|
|
1430
1536
|
engines: { bun: ">=1.3.13" },
|
|
1431
1537
|
...dependencyMaps,
|
|
1432
1538
|
};
|
package/index.ts
CHANGED
|
@@ -6,11 +6,10 @@ export * from "./applicationBuildRunner";
|
|
|
6
6
|
export * from "./applicationReleasePackager";
|
|
7
7
|
export * from "./applicationTestPreload";
|
|
8
8
|
export * from "./artifact";
|
|
9
|
-
export * from "./auth";
|
|
10
9
|
export * from "./builder";
|
|
11
10
|
export * from "./capacitorApp";
|
|
11
|
+
export * from "./cloud";
|
|
12
12
|
export * from "./commandDecorators";
|
|
13
|
-
export * from "./constants";
|
|
14
13
|
export * from "./createTunnel";
|
|
15
14
|
export * from "./dependencyScanner";
|
|
16
15
|
export * from "./executors";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@akanjs/devkit",
|
|
3
|
-
"version": "2.1.1-rc.
|
|
3
|
+
"version": "2.1.1-rc.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.1.1-rc.
|
|
35
|
+
"akanjs": "2.1.1-rc.2",
|
|
36
36
|
"chalk": "^5.6.2",
|
|
37
37
|
"commander": "^14.0.3",
|
|
38
38
|
"daisyui": "^5.5.20",
|
package/auth.ts
DELETED
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import { mkdir } from "node:fs/promises";
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
type AkanGlobalConfig,
|
|
5
|
-
akanCloudHost,
|
|
6
|
-
akanCloudUrl,
|
|
7
|
-
basePath,
|
|
8
|
-
configPath,
|
|
9
|
-
defaultAkanGlobalConfig,
|
|
10
|
-
defaultHostConfig,
|
|
11
|
-
type HostConfig,
|
|
12
|
-
} from "./constants";
|
|
13
|
-
import { FileSys } from "./fileSys";
|
|
14
|
-
|
|
15
|
-
export const getAkanGlobalConfig = async () => {
|
|
16
|
-
const exists = await FileSys.fileExists(configPath);
|
|
17
|
-
const akanConfig = exists ? await FileSys.readJson<AkanGlobalConfig>(configPath) : defaultAkanGlobalConfig;
|
|
18
|
-
return akanConfig;
|
|
19
|
-
};
|
|
20
|
-
export const setAkanGlobalConfig = async (akanConfig: AkanGlobalConfig) => {
|
|
21
|
-
await mkdir(basePath, { recursive: true });
|
|
22
|
-
await Bun.write(configPath, JSON.stringify(akanConfig, null, 2));
|
|
23
|
-
};
|
|
24
|
-
export const getHostConfig = async (host = akanCloudHost) => {
|
|
25
|
-
const akanConfig = await getAkanGlobalConfig();
|
|
26
|
-
return akanConfig.cloudHost[host] ?? defaultHostConfig;
|
|
27
|
-
};
|
|
28
|
-
export const setHostConfig = async (host = akanCloudHost, config: HostConfig = {}) => {
|
|
29
|
-
const akanConfig = await getAkanGlobalConfig();
|
|
30
|
-
akanConfig.cloudHost[host] = config;
|
|
31
|
-
await setAkanGlobalConfig(akanConfig);
|
|
32
|
-
};
|
|
33
|
-
export const getSelf = async (token: string) => {
|
|
34
|
-
try {
|
|
35
|
-
const res = await fetch(`${akanCloudUrl}/user/getSelf`, { headers: { Authorization: `Bearer ${token}` } });
|
|
36
|
-
const user = (await res.json()) as { id: string; nickname: string };
|
|
37
|
-
return user;
|
|
38
|
-
} catch (e) {
|
|
39
|
-
return null;
|
|
40
|
-
}
|
|
41
|
-
};
|
package/constants.ts
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import type { SupportedLlmModel } from "./aiEditor";
|
|
2
|
-
|
|
3
|
-
export const basePath = `${Bun.env.HOME ?? Bun.env.USERPROFILE}/.akan`;
|
|
4
|
-
export const configPath = `${basePath}/config.json`;
|
|
5
|
-
export const akanCloudHost =
|
|
6
|
-
process.env.AKAN_PUBLIC_OPERATION_MODE === "local"
|
|
7
|
-
? "http://localhost"
|
|
8
|
-
: "https://cloud.akanjs.com";
|
|
9
|
-
export const akanCloudUrl = `${akanCloudHost}${
|
|
10
|
-
process.env.AKAN_PUBLIC_OPERATION_MODE === "local" ? ":8282" : ""
|
|
11
|
-
}/api`;
|
|
12
|
-
|
|
13
|
-
export interface HostConfig {
|
|
14
|
-
auth?: {
|
|
15
|
-
token: string;
|
|
16
|
-
self: { id: string; nickname: string };
|
|
17
|
-
};
|
|
18
|
-
}
|
|
19
|
-
export const defaultHostConfig: HostConfig = {};
|
|
20
|
-
export interface AkanGlobalConfig {
|
|
21
|
-
cloudHost: {
|
|
22
|
-
[key: string]: HostConfig;
|
|
23
|
-
};
|
|
24
|
-
llm: {
|
|
25
|
-
model: SupportedLlmModel;
|
|
26
|
-
apiKey: string;
|
|
27
|
-
} | null;
|
|
28
|
-
}
|
|
29
|
-
export const defaultAkanGlobalConfig: AkanGlobalConfig = {
|
|
30
|
-
cloudHost: {},
|
|
31
|
-
llm: null,
|
|
32
|
-
};
|