@akanjs/cli 2.1.1-rc.0 → 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/index.js CHANGED
@@ -15,10 +15,7 @@ import { ChatOpenAI } from "@langchain/openai";
15
15
  import { Logger as Logger2 } from "akanjs/common";
16
16
  import chalk from "chalk";
17
17
 
18
- // pkgs/@akanjs/devkit/auth.ts
19
- import { mkdir } from "fs/promises";
20
-
21
- // pkgs/@akanjs/devkit/constants.ts
18
+ // pkgs/@akanjs/devkit/cloud/constants.ts
22
19
  var basePath = `${Bun.env.HOME ?? Bun.env.USERPROFILE}/.akan`;
23
20
  var configPath = `${basePath}/config.json`;
24
21
  var akanCloudHost = process.env.AKAN_PUBLIC_OPERATION_MODE === "local" ? "http://localhost" : "https://cloud.akanjs.com";
@@ -26,9 +23,14 @@ var akanCloudUrl = `${akanCloudHost}${process.env.AKAN_PUBLIC_OPERATION_MODE ===
26
23
  var defaultHostConfig = {};
27
24
  var defaultAkanGlobalConfig = {
28
25
  cloudHost: {},
26
+ remoteEnvServers: {},
29
27
  llm: null
30
28
  };
31
29
 
30
+ // pkgs/@akanjs/devkit/cloud/globalConfig.ts
31
+ import { mkdir } from "fs/promises";
32
+ import dayjs from "dayjs";
33
+
32
34
  // pkgs/@akanjs/devkit/fileSys.ts
33
35
  import { stat } from "fs/promises";
34
36
  import { Logger } from "akanjs/common";
@@ -67,35 +69,198 @@ class FileSys {
67
69
  }
68
70
  }
69
71
 
70
- // pkgs/@akanjs/devkit/auth.ts
71
- var getAkanGlobalConfig = async () => {
72
- const exists = await FileSys.fileExists(configPath);
73
- const akanConfig = exists ? await FileSys.readJson(configPath) : defaultAkanGlobalConfig;
74
- return akanConfig;
75
- };
76
- var setAkanGlobalConfig = async (akanConfig) => {
77
- await mkdir(basePath, { recursive: true });
78
- await Bun.write(configPath, JSON.stringify(akanConfig, null, 2));
79
- };
80
- var getHostConfig = async (host = akanCloudHost) => {
81
- const akanConfig = await getAkanGlobalConfig();
82
- return akanConfig.cloudHost[host] ?? defaultHostConfig;
83
- };
84
- var setHostConfig = async (host = akanCloudHost, config = {}) => {
85
- const akanConfig = await getAkanGlobalConfig();
86
- akanConfig.cloudHost[host] = config;
87
- await setAkanGlobalConfig(akanConfig);
88
- };
89
- var getSelf = async (token) => {
90
- try {
91
- const res = await fetch(`${akanCloudUrl}/user/getSelf`, { headers: { Authorization: `Bearer ${token}` } });
92
- const user = await res.json();
93
- return user;
94
- } catch (e) {
95
- return null;
72
+ // pkgs/@akanjs/devkit/cloud/globalConfig.ts
73
+ class GlobalConfig {
74
+ static async#getAkanGlobalConfig() {
75
+ const exists = await FileSys.fileExists(configPath);
76
+ const akanConfig = exists ? await FileSys.readJson(configPath) : {};
77
+ return {
78
+ ...defaultAkanGlobalConfig,
79
+ ...akanConfig,
80
+ cloudHost: akanConfig.cloudHost ?? defaultAkanGlobalConfig.cloudHost,
81
+ remoteEnvServers: akanConfig.remoteEnvServers ?? defaultAkanGlobalConfig.remoteEnvServers
82
+ };
96
83
  }
97
- };
84
+ static async#setAkanGlobalConfig(akanConfig) {
85
+ await mkdir(basePath, { recursive: true });
86
+ await Bun.write(configPath, JSON.stringify(akanConfig, null, 2));
87
+ }
88
+ static async getHostConfig(host = akanCloudHost) {
89
+ const akanConfig = await GlobalConfig.#getAkanGlobalConfig();
90
+ return GlobalConfig.toHostConfig(akanConfig.cloudHost[host] ?? defaultHostConfig);
91
+ }
92
+ static async setHostConfig(host = akanCloudHost, config = {}) {
93
+ const akanConfig = await GlobalConfig.#getAkanGlobalConfig();
94
+ akanConfig.cloudHost[host] = GlobalConfig.toHostConfigDto(config);
95
+ await GlobalConfig.#setAkanGlobalConfig(akanConfig);
96
+ }
97
+ static async getLlmConfig() {
98
+ const akanConfig = await GlobalConfig.#getAkanGlobalConfig();
99
+ return akanConfig.llm ?? null;
100
+ }
101
+ static async setLlmConfig(llmConfig) {
102
+ const akanConfig = await GlobalConfig.#getAkanGlobalConfig();
103
+ await GlobalConfig.#setAkanGlobalConfig({ ...akanConfig, llm: llmConfig });
104
+ }
105
+ static async getRemoteEnvServers() {
106
+ const akanConfig = await GlobalConfig.#getAkanGlobalConfig();
107
+ return akanConfig.remoteEnvServers;
108
+ }
109
+ static async setRemoteEnvServer(name, config) {
110
+ const akanConfig = await GlobalConfig.#getAkanGlobalConfig();
111
+ await GlobalConfig.#setAkanGlobalConfig({
112
+ ...akanConfig,
113
+ remoteEnvServers: {
114
+ ...akanConfig.remoteEnvServers,
115
+ [name]: config
116
+ }
117
+ });
118
+ }
119
+ static async removeRemoteEnvServer(name) {
120
+ const akanConfig = await GlobalConfig.#getAkanGlobalConfig();
121
+ const { [name]: _, ...remoteEnvServers } = akanConfig.remoteEnvServers;
122
+ await GlobalConfig.#setAkanGlobalConfig({
123
+ ...akanConfig,
124
+ remoteEnvServers
125
+ });
126
+ }
127
+ static needRefreshToken(accessToken) {
128
+ return !!accessToken?.expiresAt?.isBefore(dayjs().add(1, "hour"));
129
+ }
130
+ static toAccessToken(accessToken) {
131
+ return {
132
+ jwt: accessToken.jwt,
133
+ refreshToken: accessToken.refreshToken ?? null,
134
+ expiresAt: accessToken.expiresAt ? dayjs(accessToken.expiresAt) : null
135
+ };
136
+ }
137
+ static toAccessTokenDto(accessToken) {
138
+ return {
139
+ jwt: accessToken.jwt,
140
+ refreshToken: accessToken.refreshToken ?? null,
141
+ expiresAt: accessToken.expiresAt?.toString() ?? null
142
+ };
143
+ }
144
+ static toHostConfigDto(hostConfig) {
145
+ return {
146
+ auth: {
147
+ accessToken: hostConfig.auth?.accessToken ? GlobalConfig.toAccessTokenDto(hostConfig.auth.accessToken) : undefined,
148
+ self: hostConfig.auth?.self
149
+ }
150
+ };
151
+ }
152
+ static toHostConfig(hostConfigDto) {
153
+ return {
154
+ auth: {
155
+ accessToken: hostConfigDto.auth?.accessToken ? GlobalConfig.toAccessToken(hostConfigDto.auth.accessToken) : undefined,
156
+ self: hostConfigDto.auth?.self
157
+ }
158
+ };
159
+ }
160
+ }
161
+
162
+ // pkgs/@akanjs/devkit/cloud/cloudApi.ts
163
+ class HttpClient {
164
+ baseUrl;
165
+ headers = {};
166
+ constructor(baseUrl, headers = {}) {
167
+ this.baseUrl = baseUrl;
168
+ this.headers = headers;
169
+ }
170
+ async get(url, { headers } = {}) {
171
+ const response = await fetch(`${this.baseUrl}${url}`, {
172
+ headers: {
173
+ "Content-Type": "application/json",
174
+ ...this.headers,
175
+ ...headers
176
+ }
177
+ });
178
+ return response.json();
179
+ }
180
+ async getFile(url, localPath, headers) {
181
+ const response = await fetch(`${this.baseUrl}${url}`, {
182
+ headers: { ...this.headers, ...headers }
183
+ });
184
+ if (!response.ok)
185
+ throw new Error(`Failed to download file: ${response.status} ${response.statusText}`);
186
+ await Bun.write(localPath, response);
187
+ }
188
+ async post(url, data, { headers } = {}) {
189
+ const isFormData = data instanceof FormData;
190
+ const response = await fetch(`${this.baseUrl}${url}`, {
191
+ method: "POST",
192
+ body: isFormData ? data : JSON.stringify(data),
193
+ headers: isFormData ? { ...this.headers, ...headers } : { "Content-Type": "application/json", ...this.headers, ...headers }
194
+ });
195
+ return response.json();
196
+ }
197
+ setHeaders(headers) {
198
+ Object.assign(this.headers, headers);
199
+ return this;
200
+ }
201
+ }
98
202
 
203
+ class CloudApi {
204
+ #api;
205
+ #accessToken = null;
206
+ static async fromHost(host) {
207
+ const hostConfig = await GlobalConfig.getHostConfig(host);
208
+ return new CloudApi(hostConfig);
209
+ }
210
+ constructor(hostConfig) {
211
+ const host = akanCloudHost;
212
+ this.#api = new HttpClient(`${host}/api`);
213
+ this.#accessToken = hostConfig.auth?.accessToken ?? null;
214
+ if (this.#accessToken && !GlobalConfig.needRefreshToken(this.#accessToken))
215
+ this.#api.setHeaders({
216
+ Authorization: `Bearer ${this.#accessToken.jwt}`
217
+ });
218
+ }
219
+ async uploadEnv(devProjectId, file) {
220
+ const formData = new FormData;
221
+ formData.append("devProjectId", devProjectId);
222
+ formData.append("file", file);
223
+ const data = await this.#api.post(`/uploadEnv/${devProjectId}`, formData);
224
+ return data;
225
+ }
226
+ async downloadEnv(devProjectId, localPath) {
227
+ await this.#api.getFile(`/downloadEnv/${devProjectId}`, localPath);
228
+ }
229
+ async getRemoteAuthToken(remoteId) {
230
+ try {
231
+ if (this.#accessToken) {
232
+ if (GlobalConfig.needRefreshToken(this.#accessToken))
233
+ return await this.refreshAuthToken();
234
+ else
235
+ return await this.refreshAuthToken();
236
+ }
237
+ const accessToken = await this.#api.get(`/getRemoteAuthToken/${remoteId}`);
238
+ this.#accessToken = GlobalConfig.toAccessToken(accessToken);
239
+ this.#api.setHeaders({
240
+ Authorization: `Bearer ${this.#accessToken.jwt}`
241
+ });
242
+ return this.#accessToken;
243
+ } catch (_) {
244
+ return null;
245
+ }
246
+ }
247
+ async refreshAuthToken() {
248
+ const response = await this.#api.post(`/refreshRemoteAuthToken`, {
249
+ refreshToken: this.#accessToken?.refreshToken
250
+ });
251
+ this.#accessToken = GlobalConfig.toAccessToken(response);
252
+ this.#api.setHeaders({ Authorization: `Bearer ${this.#accessToken.jwt}` });
253
+ return this.#accessToken;
254
+ }
255
+ async getRemoteSelf() {
256
+ try {
257
+ const data = await this.#api.get(`/getRemoteSelf`);
258
+ return data;
259
+ } catch {
260
+ return null;
261
+ }
262
+ }
263
+ }
99
264
  // pkgs/@akanjs/devkit/spinner.ts
100
265
  import ora from "ora";
101
266
 
@@ -158,12 +323,18 @@ class Spinner {
158
323
 
159
324
  // pkgs/@akanjs/devkit/aiEditor.ts
160
325
  var MAX_ASK_TRY = 300;
161
- var supportedLlmModels = ["deepseek-chat", "deepseek-reasoner"];
326
+ var supportedLlmModels = [
327
+ "deepseek-chat",
328
+ "deepseek-reasoner"
329
+ ];
162
330
 
163
331
  class AiSession {
164
332
  static #cacheDir = "node_modules/.cache/akan/aiSession";
165
333
  static #chat = null;
166
- static async init({ temperature = 0, useExisting = true } = {}) {
334
+ static async init({
335
+ temperature = 0,
336
+ useExisting = true
337
+ } = {}) {
167
338
  if (useExisting) {
168
339
  const llmConfig2 = await AiSession.getLlmConfig();
169
340
  if (llmConfig2) {
@@ -190,22 +361,24 @@ class AiSession {
190
361
  return AiSession;
191
362
  }
192
363
  static async getLlmConfig() {
193
- const akanConfig = await getAkanGlobalConfig();
194
- return akanConfig.llm ?? null;
364
+ return await GlobalConfig.getLlmConfig();
195
365
  }
196
366
  static async setLlmConfig(llmConfig) {
197
- const akanConfig = await getAkanGlobalConfig();
198
- akanConfig.llm = llmConfig;
199
- await setAkanGlobalConfig(akanConfig);
367
+ await GlobalConfig.setLlmConfig(llmConfig);
200
368
  return AiSession;
201
369
  }
202
370
  static async#requestLlmConfig() {
203
- const model = await select({ message: "Select a LLM model", choices: supportedLlmModels });
371
+ const model = await select({
372
+ message: "Select a LLM model",
373
+ choices: supportedLlmModels
374
+ });
204
375
  const apiKey = await input({ message: "Enter your API key" });
205
376
  return { model, apiKey };
206
377
  }
207
378
  static async#validateApiKey(modelName, apiKey) {
208
- const spinner = new Spinner("Validating LLM API key...", { prefix: `\uD83E\uDD16akan-editor` }).start();
379
+ const spinner = new Spinner("Validating LLM API key...", {
380
+ prefix: `\uD83E\uDD16akan-editor`
381
+ }).start();
209
382
  const chat = new ChatOpenAI({
210
383
  modelName,
211
384
  temperature: 0,
@@ -228,7 +401,11 @@ class AiSession {
228
401
  sessionKey;
229
402
  isCacheLoaded = false;
230
403
  workspace;
231
- constructor(type, { workspace, cacheKey, isContinued }) {
404
+ constructor(type, {
405
+ workspace,
406
+ cacheKey,
407
+ isContinued
408
+ }) {
232
409
  this.workspace = workspace;
233
410
  this.sessionKey = `${type}${cacheKey ? `-${cacheKey}` : ""}`;
234
411
  if (isContinued)
@@ -303,7 +480,13 @@ class AiSession {
303
480
  throw new Error("Failed to stream response");
304
481
  }
305
482
  }
306
- async edit(question, { onChunk, onReasoning, maxTry = MAX_ASK_TRY, validate, approve } = {}) {
483
+ async edit(question, {
484
+ onChunk,
485
+ onReasoning,
486
+ maxTry = MAX_ASK_TRY,
487
+ validate,
488
+ approve
489
+ } = {}) {
307
490
  for (let tryCount = 0;tryCount < maxTry; tryCount++) {
308
491
  let response = await this.ask(question, { onChunk, onReasoning });
309
492
  if (validate?.length && tryCount === 0) {
@@ -356,7 +539,9 @@ ${validate.map((v) => `- ${v}`).join(`
356
539
  async#tryFixTypescripts(writes, executor, options = {}) {
357
540
  const MAX_EDIT_TRY = 5;
358
541
  for (let tryCount = 0;tryCount < MAX_EDIT_TRY; tryCount++) {
359
- const loader = new Spinner(`Type checking and linting...`, { prefix: `\uD83E\uDD16akan-editor` }).start();
542
+ const loader = new Spinner(`Type checking and linting...`, {
543
+ prefix: `\uD83E\uDD16akan-editor`
544
+ }).start();
360
545
  const fileChecks = await Promise.all(writes.map(async ({ filePath }) => {
361
546
  const typeCheckResult = executor.typeCheck(filePath);
362
547
  const lintResult = await executor.lint(filePath);
@@ -2164,11 +2349,26 @@ class Executor {
2164
2349
  });
2165
2350
  return new Promise((resolve, reject) => {
2166
2351
  proc.on("error", (error) => {
2167
- reject(new CommandExecutionError({ command, cwd, code: null, signal: null, stdout, stderr, cause: error }));
2352
+ reject(new CommandExecutionError({
2353
+ command,
2354
+ cwd,
2355
+ code: null,
2356
+ signal: null,
2357
+ stdout,
2358
+ stderr,
2359
+ cause: error
2360
+ }));
2168
2361
  });
2169
2362
  proc.on("exit", (code, signal) => {
2170
2363
  if (!!code || signal)
2171
- reject(new CommandExecutionError({ command, cwd, code, signal, stdout, stderr }));
2364
+ reject(new CommandExecutionError({
2365
+ command,
2366
+ cwd,
2367
+ code,
2368
+ signal,
2369
+ stdout,
2370
+ stderr
2371
+ }));
2172
2372
  else
2173
2373
  resolve({ code, signal });
2174
2374
  });
@@ -2194,11 +2394,28 @@ class Executor {
2194
2394
  });
2195
2395
  return new Promise((resolve, reject) => {
2196
2396
  proc.on("error", (error) => {
2197
- reject(new CommandExecutionError({ command, args, cwd, code: null, signal: null, stdout, stderr, cause: error }));
2397
+ reject(new CommandExecutionError({
2398
+ command,
2399
+ args,
2400
+ cwd,
2401
+ code: null,
2402
+ signal: null,
2403
+ stdout,
2404
+ stderr,
2405
+ cause: error
2406
+ }));
2198
2407
  });
2199
2408
  proc.on("close", (code, signal) => {
2200
2409
  if (code !== 0 || signal)
2201
- reject(new CommandExecutionError({ command, args, cwd, code, signal, stdout, stderr }));
2410
+ reject(new CommandExecutionError({
2411
+ command,
2412
+ args,
2413
+ cwd,
2414
+ code,
2415
+ signal,
2416
+ stdout,
2417
+ stderr
2418
+ }));
2202
2419
  else
2203
2420
  resolve(stdout);
2204
2421
  });
@@ -2242,7 +2459,15 @@ class Executor {
2242
2459
  });
2243
2460
  proc.on("exit", (code, signal) => {
2244
2461
  if (!!code || signal)
2245
- reject(new CommandExecutionError({ command: modulePath, args, cwd, code, signal, stdout, stderr }));
2462
+ reject(new CommandExecutionError({
2463
+ command: modulePath,
2464
+ args,
2465
+ cwd,
2466
+ code,
2467
+ signal,
2468
+ stdout,
2469
+ stderr
2470
+ }));
2246
2471
  else
2247
2472
  resolve({ code, signal });
2248
2473
  });
@@ -2389,7 +2614,10 @@ class Executor {
2389
2614
  const result = {
2390
2615
  ...extendsTsconfig,
2391
2616
  ...tsconfig,
2392
- compilerOptions: { ...extendsTsconfig.compilerOptions, ...tsconfig.compilerOptions }
2617
+ compilerOptions: {
2618
+ ...extendsTsconfig.compilerOptions,
2619
+ ...tsconfig.compilerOptions
2620
+ }
2393
2621
  };
2394
2622
  this.#tsconfig = result;
2395
2623
  return result;
@@ -2444,7 +2672,9 @@ class Executor {
2444
2672
  const convertedTargetPath = Object.entries(dict).reduce((path8, [key, value]) => path8.replace(new RegExp(`__${key}__`, "g"), value), targetPath.slice(0, -9));
2445
2673
  const convertedContent = Object.entries(dict).reduce((data, [key, value]) => data.replace(new RegExp(`<%= ${key} %>`, "g"), value), content);
2446
2674
  this.logger.verbose(`Apply template ${templatePath} to ${convertedTargetPath}`);
2447
- return this.writeFile(convertedTargetPath, convertedContent, { overwrite });
2675
+ return this.writeFile(convertedTargetPath, convertedContent, {
2676
+ overwrite
2677
+ });
2448
2678
  } else if (staticTemplateFileExtensions.has(path7.extname(targetPath).toLowerCase())) {
2449
2679
  const convertedTargetPath = Object.entries(dict).reduce((path8, [key, value]) => path8.replace(new RegExp(`__${key}__`, "g"), value), targetPath);
2450
2680
  const writePath = this.getPath(convertedTargetPath);
@@ -2470,14 +2700,24 @@ class Executor {
2470
2700
  const prefixTemplatePath = templatePath;
2471
2701
  if ((await stat2(prefixTemplatePath)).isFile()) {
2472
2702
  const filename = path7.basename(prefixTemplatePath);
2473
- const fileContent = await this.#applyTemplateFile({ templatePath: prefixTemplatePath, targetPath: path7.join(basePath2, filename), scanInfo, overwrite }, dict, options);
2703
+ const fileContent = await this.#applyTemplateFile({
2704
+ templatePath: prefixTemplatePath,
2705
+ targetPath: path7.join(basePath2, filename),
2706
+ scanInfo,
2707
+ overwrite
2708
+ }, dict, options);
2474
2709
  return fileContent ? [fileContent] : [];
2475
2710
  } else {
2476
2711
  const subdirs = await readDirEntries(templatePath);
2477
2712
  const fileContents = (await Promise.all(subdirs.map(async (subdir) => {
2478
2713
  const subpath = path7.join(templatePath, subdir);
2479
2714
  if ((await stat2(subpath)).isFile()) {
2480
- const fileContent = await this.#applyTemplateFile({ templatePath: subpath, targetPath: path7.join(basePath2, subdir), scanInfo, overwrite }, dict, options);
2715
+ const fileContent = await this.#applyTemplateFile({
2716
+ templatePath: subpath,
2717
+ targetPath: path7.join(basePath2, subdir),
2718
+ scanInfo,
2719
+ overwrite
2720
+ }, dict, options);
2481
2721
  return fileContent ? [fileContent] : [];
2482
2722
  } else
2483
2723
  return await this._applyTemplate({
@@ -2528,7 +2768,10 @@ class Executor {
2528
2768
  async lint(filePath, { fix = false, dryRun = false } = {}) {
2529
2769
  const path8 = this.getPath(filePath);
2530
2770
  const linter = this.getLinter();
2531
- const { results, errors, warnings } = await linter.lint(path8, { fix, dryRun });
2771
+ const { results, errors, warnings } = await linter.lint(path8, {
2772
+ fix,
2773
+ dryRun
2774
+ });
2532
2775
  const message = linter.formatLintResults(results);
2533
2776
  return { results, message, errors, warnings };
2534
2777
  }
@@ -2554,6 +2797,7 @@ class WorkspaceExecutor extends Executor {
2554
2797
  const sourceEnv = envPath ? { ...process.env, ...parseEnvFile(envPath) } : process.env;
2555
2798
  const appName = sourceEnv.AKAN_PUBLIC_APP_NAME;
2556
2799
  const workspaceRoot = sourceEnv.AKAN_WORKSPACE_ROOT;
2800
+ const workspaceId = sourceEnv.AKAN_WORKSPACE_ID;
2557
2801
  const repoName = sourceEnv.AKAN_PUBLIC_REPO_NAME;
2558
2802
  if (!repoName)
2559
2803
  throw new Error("AKAN_PUBLIC_REPO_NAME is not set");
@@ -2564,7 +2808,15 @@ class WorkspaceExecutor extends Executor {
2564
2808
  const env = sourceEnv.AKAN_PUBLIC_ENV ?? "debug";
2565
2809
  if (!env)
2566
2810
  throw new Error("AKAN_PUBLIC_ENV is not set");
2567
- return { ...appName ? { appName } : {}, workspaceRoot, repoName, serveDomain, env, portOffset };
2811
+ return { ...appName ? { appName } : {}, workspaceRoot, repoName, serveDomain, env, portOffset, workspaceId };
2812
+ }
2813
+ getWorkspaceId({
2814
+ allowEmpty
2815
+ } = {}) {
2816
+ const { workspaceId } = WorkspaceExecutor.getBaseDevEnv();
2817
+ if (!workspaceId && !allowEmpty)
2818
+ throw new Error("Workspace ID is not found");
2819
+ return workspaceId;
2568
2820
  }
2569
2821
  async scan() {
2570
2822
  return await WorkspaceInfo.fromExecutor(this);
@@ -2768,7 +3020,11 @@ class SysExecutor extends Executor {
2768
3020
  } = {}) {
2769
3021
  if (this.#scanInfo && !refresh)
2770
3022
  return this.#scanInfo;
2771
- const scanInfo = this.type === "app" ? await AppInfo.fromExecutor(this, { refresh }) : await LibInfo.fromExecutor(this, { refresh });
3023
+ const scanInfo = this.type === "app" ? await AppInfo.fromExecutor(this, {
3024
+ refresh
3025
+ }) : await LibInfo.fromExecutor(this, {
3026
+ refresh
3027
+ });
2772
3028
  if (write) {
2773
3029
  await Promise.all(this.#getScanTemplateTasks(scanInfo));
2774
3030
  await this.writeJson(`akan.${this.type}.json`, scanInfo.getScanResult());
@@ -2877,7 +3133,11 @@ class SysExecutor extends Executor {
2877
3133
  ...Object.fromEntries(Object.entries(options.dict ?? {}).map(([key, value]) => [capitalize(key), capitalize(value)]))
2878
3134
  };
2879
3135
  const scanInfo = await this.scan();
2880
- const fileContents = await this._applyTemplate({ ...options, scanInfo, dict });
3136
+ const fileContents = await this._applyTemplate({
3137
+ ...options,
3138
+ scanInfo,
3139
+ dict
3140
+ });
2881
3141
  await this.scan();
2882
3142
  return fileContents;
2883
3143
  }
@@ -2983,7 +3243,11 @@ class AppExecutor extends SysExecutor {
2983
3243
  this.#pageKeys = [];
2984
3244
  return this.#pageKeys;
2985
3245
  }
2986
- for await (const rel of glob.scan({ cwd: pageDir, absolute: false, onlyFiles: true })) {
3246
+ for await (const rel of glob.scan({
3247
+ cwd: pageDir,
3248
+ absolute: false,
3249
+ onlyFiles: true
3250
+ })) {
2987
3251
  const segments = rel.split(path7.sep);
2988
3252
  if (segments.some((s) => s === "node_modules"))
2989
3253
  continue;
@@ -2993,7 +3257,10 @@ class AppExecutor extends SysExecutor {
2993
3257
  if (!isRouteSourceFile(posix))
2994
3258
  continue;
2995
3259
  const key = `./${posix}`;
2996
- validateSubRoutePageKey(key, akanConfig2.basePaths, { appName: this.name, filePath: absPath });
3260
+ validateSubRoutePageKey(key, akanConfig2.basePaths, {
3261
+ appName: this.name,
3262
+ filePath: absPath
3263
+ });
2997
3264
  const parsed = parseRouteModuleKey(key);
2998
3265
  if (parsed.isInternalRootLayout) {
2999
3266
  throw new Error(`[route-convention] __root_layout is reserved for Akan.js generated root layout: ${absPath}`);
@@ -3033,7 +3300,11 @@ class AppExecutor extends SysExecutor {
3033
3300
  ]);
3034
3301
  }
3035
3302
  async scanSync({ refresh = false, write = true } = {}) {
3036
- const scanInfo = await this.scan({ refresh, write, writeLib: write });
3303
+ const scanInfo = await this.scan({
3304
+ refresh,
3305
+ write,
3306
+ writeLib: write
3307
+ });
3037
3308
  if (write)
3038
3309
  await this.syncAssets(scanInfo.getScanResult().libDeps);
3039
3310
  return scanInfo;
@@ -3097,7 +3368,10 @@ class PkgExecutor extends Executor {
3097
3368
  return scanInfo;
3098
3369
  }
3099
3370
  async#getDependencyVersion(rootPackageJson, dep) {
3100
- const rootDeps = { ...rootPackageJson.dependencies, ...rootPackageJson.devDependencies };
3371
+ const rootDeps = {
3372
+ ...rootPackageJson.dependencies,
3373
+ ...rootPackageJson.devDependencies
3374
+ };
3101
3375
  const rootVersion = rootDeps[dep];
3102
3376
  if (rootVersion)
3103
3377
  return rootVersion;
@@ -3153,7 +3427,14 @@ class PkgExecutor extends Executor {
3153
3427
  const distPkgJson = {
3154
3428
  ...pkgJson,
3155
3429
  type: "module",
3156
- exports: { ...pkgJson.exports, ".": { import: "./index.ts", types: "./index.ts", default: "./index.ts" } },
3430
+ exports: {
3431
+ ...pkgJson.exports,
3432
+ ".": {
3433
+ import: "./index.ts",
3434
+ types: "./index.ts",
3435
+ default: "./index.ts"
3436
+ }
3437
+ },
3157
3438
  engines: { bun: ">=1.3.13" },
3158
3439
  ...dependencyMaps
3159
3440
  };
@@ -7447,7 +7728,7 @@ class ApplicationBuildRunner {
7447
7728
  import { cp, mkdir as mkdir8, rm as rm3 } from "fs/promises";
7448
7729
 
7449
7730
  // pkgs/@akanjs/devkit/uploadRelease.ts
7450
- import { HttpClient, Logger as Logger10 } from "akanjs/common";
7731
+ import { HttpClient as HttpClient2, Logger as Logger10 } from "akanjs/common";
7451
7732
  var spinning = (message) => {
7452
7733
  const spinner = new Spinner(message, { prefix: message, enableSpin: true }).start();
7453
7734
  return spinner;
@@ -7462,7 +7743,7 @@ var uploadRelease = async (appName, {
7462
7743
  }) => {
7463
7744
  const logger = new Logger10("uploadRelease");
7464
7745
  const basePath2 = local ? "http://localhost:8282/backend" : "https://cloud.akanjs.com/backend";
7465
- const httpClient = new HttpClient(basePath2);
7746
+ const httpClient = new HttpClient2(basePath2);
7466
7747
  const buildPath = `${workspaceRoot}/releases/builds/${appName}-release.tar.gz`;
7467
7748
  const appBuildPath = `${workspaceRoot}/releases/builds/${appName}-appBuild.zip`;
7468
7749
  const sourcePath = `${workspaceRoot}/releases/sources/${appName}-source.tar.gz`;
@@ -9449,75 +9730,6 @@ import { Box as Box2, Newline, Text as Text2, useInput as useInput2 } from "ink"
9449
9730
  import { useEffect as useEffect3, useState as useState3 } from "react";
9450
9731
  import { jsxDEV as jsxDEV2, Fragment as Fragment2 } from "react/jsx-dev-runtime";
9451
9732
  "use client";
9452
- // pkgs/@akanjs/devkit/cloud/cloudApi.ts
9453
- class HttpClient2 {
9454
- baseUrl;
9455
- constructor(baseUrl) {
9456
- this.baseUrl = baseUrl;
9457
- }
9458
- async get(url, { headers } = {}) {
9459
- const response = await fetch(`${this.baseUrl}${url}`, {
9460
- headers: { "Content-Type": "application/json", ...headers }
9461
- });
9462
- return response.json();
9463
- }
9464
- async post(url, data, { headers } = {}) {
9465
- const isFormData = data instanceof FormData;
9466
- const response = await fetch(`${this.baseUrl}${url}`, {
9467
- method: "POST",
9468
- body: isFormData ? data : JSON.stringify(data),
9469
- headers: isFormData ? headers : { "Content-Type": "application/json", ...headers }
9470
- });
9471
- return response.json();
9472
- }
9473
- }
9474
-
9475
- class CloudApi {
9476
- api;
9477
- #accessToken = null;
9478
- constructor(host, { accessToken } = {}) {
9479
- this.api = new HttpClient2(`${host}/api`);
9480
- this.#accessToken = accessToken ?? null;
9481
- }
9482
- async uploadEnv(devProjectId, fileStream) {
9483
- const formData = new FormData;
9484
- formData.append("devProjectId", devProjectId);
9485
- formData.append("fileStream", await new Response(fileStream).blob());
9486
- const response = await this.api.post(`/uploadEnv/${devProjectId}`, formData);
9487
- return response.success;
9488
- }
9489
- async downloadEnv(devProjectId) {
9490
- const response = await this.api.get(`/downloadEnv/${devProjectId}`);
9491
- return response.success;
9492
- }
9493
- async getRemoteAuthToken(remoteId) {
9494
- if (this.#needRefreshToken())
9495
- return await this.refreshAuthToken();
9496
- else if (this.#accessToken)
9497
- return this.#accessToken;
9498
- const accessToken = await this.api.get(`/getRemoteAuthToken/${remoteId}`);
9499
- this.#accessToken = {
9500
- jwt: accessToken.jwt,
9501
- refreshToken: accessToken.refreshToken,
9502
- expiresAt: new Date(accessToken.expiresAt)
9503
- };
9504
- return accessToken;
9505
- }
9506
- async refreshAuthToken() {
9507
- const response = await this.api.post(`/refreshRemoteAuthToken`, {
9508
- refreshToken: this.#accessToken?.refreshToken
9509
- });
9510
- this.#accessToken = {
9511
- jwt: response.jwt,
9512
- refreshToken: response.refreshToken,
9513
- expiresAt: new Date(response.expiresAt)
9514
- };
9515
- return response;
9516
- }
9517
- #needRefreshToken() {
9518
- return !!(this.#accessToken?.expiresAt && this.#accessToken.expiresAt.getTime() < Date.now() - 1000 * 60 * 60);
9519
- }
9520
- }
9521
9733
  // pkgs/@akanjs/cli/application/application.command.ts
9522
9734
  import { select as select6 } from "@inquirer/prompts";
9523
9735
 
@@ -10404,7 +10616,7 @@ class PackageScript extends script("package", [PackageRunner]) {
10404
10616
 
10405
10617
  // pkgs/@akanjs/cli/cloud/cloud.runner.ts
10406
10618
  import path38 from "path";
10407
- import { confirm as confirm3 } from "@inquirer/prompts";
10619
+ import { confirm as confirm3, input as input5, select as select7 } from "@inquirer/prompts";
10408
10620
  import { Logger as Logger15, sleep } from "akanjs/common";
10409
10621
  import chalk7 from "chalk";
10410
10622
  import * as QRcode from "qrcode";
@@ -10426,6 +10638,9 @@ async function getLatestPackageVersion(packageName, tag = "latest", registryUrl)
10426
10638
  }
10427
10639
 
10428
10640
  // pkgs/@akanjs/cli/cloud/cloud.runner.ts
10641
+ var addRemoteEnvServerValue = "__addRemoteEnvServer";
10642
+ var removeRemoteEnvServerValue = "__removeRemoteEnvServer";
10643
+
10429
10644
  class CloudRunner extends runner("cloud") {
10430
10645
  #akanFrameworkPackages = new Set([
10431
10646
  "akanjs",
@@ -10450,9 +10665,126 @@ class CloudRunner extends runner("cloud") {
10450
10665
  NPM_CONFIG_REGISTRY: getNpmRegistryUrl(registryUrl)
10451
10666
  } : process.env;
10452
10667
  }
10668
+ async#addRemoteEnvServer() {
10669
+ const name = (await input5({
10670
+ message: "Remote server name: ",
10671
+ validate: (value) => value.trim() ? true : "Remote server name is required"
10672
+ })).trim();
10673
+ const host = (await input5({
10674
+ message: "Remote server host: ",
10675
+ validate: (value) => value.trim() ? true : "Remote server host is required"
10676
+ })).trim();
10677
+ const username = (await input5({ message: "Remote server username (optional): " })).trim() || undefined;
10678
+ const portInput = (await input5({
10679
+ message: "Remote server SSH port (optional): ",
10680
+ validate: (value) => {
10681
+ const trimmed = value.trim();
10682
+ if (!trimmed)
10683
+ return true;
10684
+ const port = Number(trimmed);
10685
+ return Number.isInteger(port) && port > 0 ? true : "SSH port must be a positive integer";
10686
+ }
10687
+ })).trim();
10688
+ const config = {
10689
+ host,
10690
+ ...username ? { username } : {},
10691
+ ...portInput ? { port: Number(portInput) } : {}
10692
+ };
10693
+ await GlobalConfig.setRemoteEnvServer(name, config);
10694
+ return { name, config };
10695
+ }
10696
+ async#selectRemoteEnvServer() {
10697
+ const servers = await GlobalConfig.getRemoteEnvServers();
10698
+ const serverEntries = Object.entries(servers).sort(([nameA], [nameB]) => nameA.localeCompare(nameB));
10699
+ if (serverEntries.length === 0) {
10700
+ Logger15.info("No remote env servers configured. Add the first remote server for SCP mode.");
10701
+ return await this.#addRemoteEnvServer();
10702
+ }
10703
+ const selectedName = await select7({
10704
+ message: "Select the remote env server",
10705
+ choices: [
10706
+ ...serverEntries.map(([name, config2]) => ({
10707
+ name: `${name} (${config2.username ? `${config2.username}@` : ""}${config2.host}${config2.port ? `:${config2.port}` : ""})`,
10708
+ value: name
10709
+ })),
10710
+ { name: "Add new remote server", value: addRemoteEnvServerValue },
10711
+ { name: "Remove remote server", value: removeRemoteEnvServerValue }
10712
+ ]
10713
+ });
10714
+ if (selectedName === addRemoteEnvServerValue)
10715
+ return await this.#addRemoteEnvServer();
10716
+ if (selectedName === removeRemoteEnvServerValue) {
10717
+ await this.#removeRemoteEnvServer(serverEntries);
10718
+ return await this.#selectRemoteEnvServer();
10719
+ }
10720
+ const config = servers[selectedName];
10721
+ if (!config)
10722
+ throw new Error(`Remote env server is not found: ${selectedName}`);
10723
+ return { name: selectedName, config };
10724
+ }
10725
+ async#removeRemoteEnvServer(serverEntries) {
10726
+ const selectedName = await select7({
10727
+ message: "Select the remote env server to remove",
10728
+ choices: serverEntries.map(([name, config]) => ({
10729
+ name: `${name} (${config.username ? `${config.username}@` : ""}${config.host}${config.port ? `:${config.port}` : ""})`,
10730
+ value: name
10731
+ }))
10732
+ });
10733
+ const shouldRemove = await confirm3({
10734
+ message: `Remove remote env server "${selectedName}"?`,
10735
+ default: false
10736
+ });
10737
+ if (!shouldRemove)
10738
+ return;
10739
+ await GlobalConfig.removeRemoteEnvServer(selectedName);
10740
+ Logger15.info(`Removed remote env server "${selectedName}"`);
10741
+ }
10742
+ async#getRemoteEnvServerWithUsername() {
10743
+ const remoteServer = await this.#selectRemoteEnvServer();
10744
+ if (remoteServer.config.username)
10745
+ return remoteServer;
10746
+ const username = (await input5({
10747
+ message: `SSH username for ${remoteServer.config.host} (optional): `
10748
+ })).trim();
10749
+ return {
10750
+ ...remoteServer,
10751
+ config: {
10752
+ ...remoteServer.config,
10753
+ ...username ? { username } : {}
10754
+ }
10755
+ };
10756
+ }
10757
+ #getRemoteEnvArchivePath() {
10758
+ return `${this.#getRemoteEnvArchiveDir()}/env.tar`;
10759
+ }
10760
+ #getRemoteEnvArchiveDir() {
10761
+ const { repoName } = WorkspaceExecutor.getBaseDevEnv();
10762
+ return `~/secrets/${repoName}`;
10763
+ }
10764
+ #getScpTarget(config, remotePath) {
10765
+ return `${config.username ? `${config.username}@` : ""}${config.host}:${remotePath}`;
10766
+ }
10767
+ #getSshTarget(config) {
10768
+ return `${config.username ? `${config.username}@` : ""}${config.host}`;
10769
+ }
10770
+ #getScpArgs(config, source, target) {
10771
+ return [
10772
+ ...config.port ? ["-P", config.port.toString()] : [],
10773
+ source,
10774
+ target
10775
+ ];
10776
+ }
10777
+ #getSshArgs(config, command3) {
10778
+ return [
10779
+ ...config.port ? ["-p", config.port.toString()] : [],
10780
+ this.#getSshTarget(config),
10781
+ command3
10782
+ ];
10783
+ }
10453
10784
  async login() {
10454
- const config = await getHostConfig();
10455
- const self = config.auth ? await getSelf(config.auth.token) : null;
10785
+ const config = await GlobalConfig.getHostConfig();
10786
+ const cloudApi2 = new CloudApi(config);
10787
+ const self = config.auth ? await cloudApi2.getRemoteSelf() : null;
10456
10788
  if (self) {
10457
10789
  Logger15.rawLog(chalk7.green(`
10458
10790
  \u2713 Already logged in akan cloud as ${self.nickname}
@@ -10483,11 +10815,12 @@ ${chalk7.green("\u27A4")} Authentication Required`));
10483
10815
  Logger15.rawLog(chalk7.dim("Waiting for authentication..."));
10484
10816
  const MAX_RETRY = 300;
10485
10817
  for (let i = 0;i < MAX_RETRY; i++) {
10486
- const res = await fetch(`${akanCloudUrl}/user/getRemoteAuthToken/${remoteId}`);
10487
- const { jwt } = await res.json();
10488
- const self2 = jwt ? await getSelf(jwt) : null;
10489
- if (jwt && self2) {
10490
- setHostConfig(akanCloudHost, { auth: { token: jwt, self: self2 } });
10818
+ const accessToken = await cloudApi2.getRemoteAuthToken(remoteId);
10819
+ const self2 = await cloudApi2.getRemoteSelf();
10820
+ if (accessToken && self2) {
10821
+ await GlobalConfig.setHostConfig(akanCloudHost, {
10822
+ auth: { accessToken, self: self2 }
10823
+ });
10491
10824
  Logger15.rawLog(chalk7.green(`\r\u2713 Authentication successful!`));
10492
10825
  Logger15.rawLog(chalk7.green.bold(`
10493
10826
  \u2728 Welcome aboard, ${self2.nickname}!`));
@@ -10500,9 +10833,9 @@ ${chalk7.green("\u27A4")} Authentication Required`));
10500
10833
  throw new Error(chalk7.red("\u2716 Authentication timed out after 10 minutes. Please try again."));
10501
10834
  }
10502
10835
  async logout() {
10503
- const config = await getHostConfig();
10504
- if (config.auth) {
10505
- setHostConfig(akanCloudHost, {});
10836
+ const config = await GlobalConfig.getHostConfig();
10837
+ if (config.auth?.self) {
10838
+ await GlobalConfig.setHostConfig(akanCloudHost, {});
10506
10839
  Logger15.rawLog(chalk7.magenta.bold(`
10507
10840
  \uD83D\uDC4B Goodbye, ${config.auth.self.nickname}!`));
10508
10841
  Logger15.rawLog(chalk7.dim(`\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
@@ -10637,20 +10970,78 @@ ${chalk7.green("\u27A4")} Authentication Required`));
10637
10970
  }
10638
10971
  return normalized;
10639
10972
  }
10640
- async downloadEnv(workspace) {
10641
- const repoName = workspace.repoName;
10642
- const config = await getHostConfig();
10643
- const self = config.auth ? await getSelf(config.auth.token) : null;
10644
- if (!self)
10645
- throw new Error("Not logged in");
10646
- const res = await fetch(`${akanCloudUrl}/api/akasys/akasys/${repoName}`, {
10647
- headers: { Authorization: `Bearer ${config.auth?.token}` }
10973
+ async downloadEnv(cloudApi2, workspace, workspaceId) {
10974
+ const envArchivePath = "local/env.tar";
10975
+ await workspace.mkdir("local");
10976
+ await workspace.remove(envArchivePath);
10977
+ await cloudApi2.downloadEnv(workspaceId, path38.join(workspace.workspaceRoot, envArchivePath));
10978
+ await workspace.spawn("tar", ["-xf", envArchivePath], {
10979
+ cwd: workspace.workspaceRoot
10980
+ });
10981
+ await workspace.remove(envArchivePath);
10982
+ }
10983
+ async uploadEnv(cloudApi2, workspaceId, filePath) {
10984
+ const file = new File([Bun.file(filePath)], path38.basename(filePath));
10985
+ await cloudApi2.uploadEnv(workspaceId, file);
10986
+ }
10987
+ async downloadEnvByScp(workspace) {
10988
+ const envArchivePath = "local/env.tar";
10989
+ const remoteServer = await this.#getRemoteEnvServerWithUsername();
10990
+ const remoteArchivePath = this.#getRemoteEnvArchivePath();
10991
+ const remoteTarget = this.#getScpTarget(remoteServer.config, remoteArchivePath);
10992
+ await workspace.mkdir("local");
10993
+ await workspace.remove(envArchivePath);
10994
+ try {
10995
+ Logger15.info(`Downloading env archive from remote server "${remoteServer.name}"...`);
10996
+ await workspace.spawn("scp", this.#getScpArgs(remoteServer.config, remoteTarget, envArchivePath), {
10997
+ cwd: workspace.workspaceRoot,
10998
+ stdio: "inherit"
10999
+ });
11000
+ await workspace.spawn("tar", ["-xf", envArchivePath], {
11001
+ cwd: workspace.workspaceRoot
11002
+ });
11003
+ await workspace.remove(envArchivePath);
11004
+ } catch (error) {
11005
+ throw new Error(`Failed to download env archive from remote server "${remoteServer.name}"`, { cause: error });
11006
+ }
11007
+ }
11008
+ async uploadEnvByScp(workspace, filePath) {
11009
+ const remoteServer = await this.#getRemoteEnvServerWithUsername();
11010
+ const remoteArchiveDir = this.#getRemoteEnvArchiveDir();
11011
+ const remoteArchivePath = this.#getRemoteEnvArchivePath();
11012
+ const remoteTarget = this.#getScpTarget(remoteServer.config, remoteArchivePath);
11013
+ try {
11014
+ await workspace.spawn("ssh", this.#getSshArgs(remoteServer.config, `mkdir -p ${remoteArchiveDir}`), {
11015
+ cwd: workspace.workspaceRoot,
11016
+ stdio: "inherit"
11017
+ });
11018
+ Logger15.info(`Uploading env archive to remote server "${remoteServer.name}"...`);
11019
+ await workspace.spawn("scp", this.#getScpArgs(remoteServer.config, filePath, remoteTarget), {
11020
+ cwd: workspace.workspaceRoot,
11021
+ stdio: "inherit"
11022
+ });
11023
+ } catch (error) {
11024
+ throw new Error(`Failed to upload env archive to remote server "${remoteServer.name}"`, { cause: error });
11025
+ }
11026
+ }
11027
+ async gatherEnvFiles(workspace) {
11028
+ const envFilePattern = /^env\.(client|server)\.(?!(type|example)\.ts$).+\.ts$/;
11029
+ const [appNames, libNames] = await workspace.getExecs();
11030
+ const envDirs = [
11031
+ ...appNames.map((appName) => `apps/${appName}/env`),
11032
+ ...libNames.map((libName) => `libs/${libName}/env`)
11033
+ ];
11034
+ const envFilePaths = (await Promise.all(envDirs.map(async (envDir) => (await workspace.readdir(envDir)).filter((fileName) => envFilePattern.test(fileName)).map((fileName) => `${envDir}/${fileName}`)))).flat().sort();
11035
+ await workspace.mkdir("local");
11036
+ await workspace.remove("local/env.tar");
11037
+ if (envFilePaths.length === 0)
11038
+ throw new Error("No environment files found to archive");
11039
+ await workspace.spawn("tar", ["-cf", "local/env.tar", ...envFilePaths], {
11040
+ cwd: workspace.workspaceRoot
10648
11041
  });
10649
- const env = await res.json();
10650
- Logger15.info(`Downloading environment variables from cloud...`);
10651
- Logger15.info(`Environment variables: ${JSON.stringify(env.env, null, 2)}`);
11042
+ Logger15.info(`Archived ${envFilePaths.length} environment files to local/env.tar`);
11043
+ return { files: envFilePaths, path: "local/env.tar" };
10652
11044
  }
10653
- async uploadEnv(workspace) {}
10654
11045
  }
10655
11046
 
10656
11047
  // pkgs/@akanjs/cli/cloud/cloud.script.ts
@@ -10676,10 +11067,23 @@ class CloudScript extends script("cloud", [
10676
11067
  await session.ask(question);
10677
11068
  }
10678
11069
  async downloadEnv(workspace) {
10679
- await this.cloudRunner.downloadEnv(workspace);
11070
+ const workspaceId = workspace.getWorkspaceId({ allowEmpty: true });
11071
+ if (workspaceId) {
11072
+ const cloudApi2 = await CloudApi.fromHost();
11073
+ await this.cloudRunner.downloadEnv(cloudApi2, workspace, workspaceId);
11074
+ return;
11075
+ }
11076
+ await this.cloudRunner.downloadEnvByScp(workspace);
10680
11077
  }
10681
11078
  async uploadEnv(workspace) {
10682
- await this.cloudRunner.uploadEnv(workspace);
11079
+ const workspaceId = workspace.getWorkspaceId({ allowEmpty: true });
11080
+ const { path: path39 } = await this.cloudRunner.gatherEnvFiles(workspace);
11081
+ if (workspaceId) {
11082
+ const cloudApi2 = await CloudApi.fromHost();
11083
+ await this.cloudRunner.uploadEnv(cloudApi2, workspaceId, path39);
11084
+ return;
11085
+ }
11086
+ await this.cloudRunner.uploadEnvByScp(workspace, path39);
10683
11087
  }
10684
11088
  async deployAkan(workspace, { test = true, registryUrl } = {}) {
10685
11089
  const akanPkgs = await this.cloudRunner.getAkanPkgs(workspace);
@@ -10718,7 +11122,10 @@ class CloudCommand extends command("cloud", [CloudScript], ({ public: target })
10718
11122
  resetLlm: target({ desc: "Reset LLM configuration to default" }).with(Workspace).exec(function(workspace) {
10719
11123
  this.cloudScript.resetLlm(workspace);
10720
11124
  }),
10721
- ask: target({ desc: "Ask AI assistant a question about your project" }).option("question", String, { ask: "question to ask" }).with(Workspace).exec(async function(question, workspace) {
11125
+ ask: target({
11126
+ devOnly: true,
11127
+ desc: "Ask AI assistant a question about your project"
11128
+ }).option("question", String, { ask: "question to ask" }).with(Workspace).exec(async function(question, workspace) {
10722
11129
  await this.cloudScript.ask(question, workspace);
10723
11130
  }),
10724
11131
  deployAkan: target({
@@ -10753,10 +11160,14 @@ class CloudCommand extends command("cloud", [CloudScript], ({ public: target })
10753
11160
  registryUrl: resolveRegistryUrl(registry)
10754
11161
  });
10755
11162
  }),
10756
- downloadEnv: target({ desc: "Download environment variables from cloud" }).with(Workspace).exec(async function(workspace) {
11163
+ downloadEnv: target({
11164
+ desc: "Download environment variables from cloud or SCP server"
11165
+ }).with(Workspace).exec(async function(workspace) {
10757
11166
  await this.cloudScript.downloadEnv(workspace);
10758
11167
  }),
10759
- uploadEnv: target({ desc: "Upload environment variables to cloud" }).with(Workspace).exec(async function(workspace) {
11168
+ uploadEnv: target({
11169
+ desc: "Upload environment variables to cloud or SCP server"
11170
+ }).with(Workspace).exec(async function(workspace) {
10760
11171
  await this.cloudScript.uploadEnv(workspace);
10761
11172
  })
10762
11173
  })) {
@@ -11230,7 +11641,7 @@ class LocalRegistryCommand extends command("local-registry", [LocalRegistryScrip
11230
11641
  import { lowerlize } from "akanjs/common";
11231
11642
 
11232
11643
  // pkgs/@akanjs/cli/module/module.script.ts
11233
- import { input as input5 } from "@inquirer/prompts";
11644
+ import { input as input6 } from "@inquirer/prompts";
11234
11645
  import { capitalize as capitalize6, randomPicks as randomPicks2 } from "akanjs/common";
11235
11646
 
11236
11647
  // pkgs/@akanjs/cli/page/page.runner.ts
@@ -11657,8 +12068,8 @@ class ModuleScript extends script("module", [ModuleRunner, PageScript]) {
11657
12068
  const { constant, dictionary } = await this.moduleRunner.createModuleTemplate(executor);
11658
12069
  if (page && sys3.type === "app")
11659
12070
  await this.pageScript.createCrudPage(executor, { app: sys3, basePath: null, single: false });
11660
- const modelDesc = description ?? await input5({ message: "description of module" });
11661
- const modelSchemaDesign = schemaDescription ?? await input5({ message: "schema description of module" });
12071
+ const modelDesc = description ?? await input6({ message: "description of module" });
12072
+ const modelSchemaDesign = schemaDescription ?? await input6({ message: "schema description of module" });
11662
12073
  const config = await sys3.getConfig();
11663
12074
  const moduleRequest = new ModuleRequest({ sysType: sys3.type, sysName: sys3.name, modelName: name, config });
11664
12075
  const constantRequestPrompt = await moduleRequest.requestModelConstant({
@@ -11829,7 +12240,7 @@ class PageCommand extends command("page", [PageScript], ({ public: target }) =>
11829
12240
  import { lowerlize as lowerlize2 } from "akanjs/common";
11830
12241
 
11831
12242
  // pkgs/@akanjs/cli/scalar/scalar.prompt.ts
11832
- import { input as input6 } from "@inquirer/prompts";
12243
+ import { input as input7 } from "@inquirer/prompts";
11833
12244
  class ScalarPrompt extends Prompter {
11834
12245
  sys;
11835
12246
  name;
@@ -11839,13 +12250,13 @@ class ScalarPrompt extends Prompter {
11839
12250
  this.name = name;
11840
12251
  }
11841
12252
  async requestUpdateConstant() {
11842
- const request = await input6({ message: `What do you want to change?` });
12253
+ const request = await input7({ message: `What do you want to change?` });
11843
12254
  return { request, validate: undefined };
11844
12255
  }
11845
12256
  async requestCreateConstant() {
11846
12257
  const constantFiles = await this.sys.getConstantFilesWithLibs();
11847
- const description = await input6({ message: "description of scalar" });
11848
- const schemaDescription = await input6({ message: "schema description of scalar" });
12258
+ const description = await input7({ message: "description of scalar" });
12259
+ const schemaDescription = await input7({ message: "schema description of scalar" });
11849
12260
  await this.sys.applyTemplate({
11850
12261
  basePath: "./lib/__scalar",
11851
12262
  template: "__scalar",