@akanjs/cli 3.0.0-beta.19 → 3.0.0-beta.20

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.
Files changed (95) hide show
  1. package/.build-stamp +1 -1
  2. package/{CodeAgent-6cm97kay.js → CodeAgent-awv258wb.js} +2 -2
  3. package/{CodeTui-za4jsm41.js → CodeTui-1nqh35ez.js} +20 -22
  4. package/TunnelShare-scf4e4pd.js +9 -0
  5. package/{agent.command-e0wpbbyg.js → agent.command-1s9ag7sb.js} +8 -9
  6. package/akanCode.js +4 -4
  7. package/{application.command-8wyxqc8q.js → application.command-49hwy6va.js} +25 -56
  8. package/{applicationBuildRunner-n9gzaxd0.js → applicationBuildRunner-ybyp97pv.js} +33 -59
  9. package/{applicationReleasePackager-vpggfqgq.js → applicationReleasePackager-esnrphw8.js} +40 -60
  10. package/buildBatch.proc.js +19 -24
  11. package/{capacitorApp-q3z0qht0.js → capacitorApp-1synz4en.js} +3 -4
  12. package/{cloud.command-4zwa5dsx.js → cloud.command-d7rqtzt9.js} +16 -26
  13. package/cloud.script-bkbpcr96.js +19 -0
  14. package/{code.command-468c9d7s.js → code.command-nn48xzej.js} +22 -37
  15. package/context.command-s3mcmvpa.js +48 -0
  16. package/context.runner-k99nb5pk.js +21 -0
  17. package/{dependencyScanner-0z2h2tks.js → dependencyScanner-ye2e6pdz.js} +2 -2
  18. package/{devTui-ffjsarqa.js → devTui-q7k7bakd.js} +5 -9
  19. package/fileSys-5ngz93ga.js +9 -0
  20. package/{guideline.command-vhw13pqr.js → guideline.command-sct4qnt5.js} +4 -5
  21. package/guidelines/conventions/conventions.instruction.md +5 -1
  22. package/guidelines/mcpRule/mcpRule.instruction.md +7 -5
  23. package/incrementalBuilder.proc.js +30 -48
  24. package/{index-s5rhm04y.js → index-0bq14vr1.js} +1 -1
  25. package/{index-10dkrrww.js → index-13ffe9wp.js} +4 -4
  26. package/{index-srmay5jw.js → index-22d57v4c.js} +84 -67
  27. package/{index-53176s7j.js → index-31yr441d.js} +10 -17
  28. package/index-3hx1z96c.js +87 -0
  29. package/{index-4m929vvn.js → index-3sq321ak.js} +10 -18
  30. package/{index-bhtmj233.js → index-3t7g9e4r.js} +41 -78
  31. package/{index-q0y2jq9j.js → index-5b6tavv3.js} +6 -3
  32. package/{index-47pj3aee.js → index-5e0n4hqx.js} +11 -12
  33. package/{index-7ep8qtvp.js → index-5pc1qrbv.js} +5 -8
  34. package/{index-esk82rvk.js → index-8mna0947.js} +34 -71
  35. package/{index-sq2dzkzm.js → index-8sf7wmdr.js} +6 -6
  36. package/{index-crmeqnmf.js → index-9d9bgcx4.js} +283 -722
  37. package/{index-bwf965h6.js → index-ahemdr5n.js} +6 -6
  38. package/{index-ab0c38ea.js → index-as7xmyz8.js} +20 -20
  39. package/{index-bc88j1ry.js → index-axzjaqve.js} +94 -142
  40. package/{index-3d9y066c.js → index-bjwxs904.js} +9 -8
  41. package/{index-667w0yah.js → index-c6eggbee.js} +2 -5
  42. package/{index-a1w6934z.js → index-ddxvdd1p.js} +169 -305
  43. package/{index-92zztqy1.js → index-dy0jx9ay.js} +2 -17
  44. package/{index-22xtgmhn.js → index-dzf3r49a.js} +1 -1
  45. package/{index-89hy31kf.js → index-fgq75haa.js} +172 -287
  46. package/{index-by5gpp8x.js → index-hj6w4ka1.js} +2 -1
  47. package/{index-hwkygn9s.js → index-khpfpcsd.js} +302 -33
  48. package/{index-vqx8bdxz.js → index-mxav6j3c.js} +78 -231
  49. package/{index-2446k7b4.js → index-myhc3c1j.js} +512 -588
  50. package/{index-3jrp517k.js → index-nv5976x3.js} +45 -128
  51. package/{index-zqgj2rt2.js → index-przybvy5.js} +69 -147
  52. package/{index-n3cv0tm4.js → index-smme4r47.js} +1 -1
  53. package/{index-qdgzsnhg.js → index-w2zdcw3k.js} +110 -231
  54. package/{index-14zrtgfa.js → index-wa5jbxj1.js} +6 -39
  55. package/{index-bftkz6cp.js → index-ww4z397j.js} +198 -555
  56. package/{index-kwaq3n9n.js → index-xqjwd384.js} +4 -7
  57. package/{index-txr7dj2g.js → index-xqpzbab5.js} +106 -240
  58. package/{index-e4v6vhxq.js → index-xsjh6nzh.js} +178 -165
  59. package/{index-18zybgkc.js → index-ygn77a5w.js} +47 -99
  60. package/{index-9d6nvkz2.js → index-ztp3e73t.js} +79 -35
  61. package/index.js +23 -24
  62. package/{library.command-cqr175dy.js → library.command-1gx04jgn.js} +4 -5
  63. package/{localRegistry.command-49z55qqz.js → localRegistry.command-zndxj1q6.js} +21 -42
  64. package/{module.command-9p8mm3b0.js → module.command-3waddk95.js} +6 -7
  65. package/{package.command-wxyz909t.js → package.command-zrc7br1y.js} +5 -6
  66. package/package.json +2 -2
  67. package/{page.command-gsrsfqqs.js → page.command-vpjgardh.js} +4 -5
  68. package/{primitive.command-ns1mptje.js → primitive.command-sz84tk4t.js} +9 -20
  69. package/{prompter-1f8d9pcc.js → prompter-ptaa3nk0.js} +2 -2
  70. package/{quality.command-e76k2q9k.js → quality.command-g869hafs.js} +129 -363
  71. package/{recipeScanner-fpagtgc1.js → recipeScanner-4b69ybe0.js} +1 -1
  72. package/{repair.command-pkrsm3cw.js → repair.command-chsnjvvv.js} +11 -35
  73. package/{scalar.command-0cpt3fc8.js → scalar.command-2v21newx.js} +5 -6
  74. package/skills/akan-code/SKILL.md +12 -4
  75. package/{subspace.command-fktpwp0g.js → subspace.command-ckcbwfp1.js} +39 -58
  76. package/templates/appSample/srvkit/{AuthGuard.ts → guards.ts} +3 -3
  77. package/templates/appSample/srvkit/{SessionInternalArg.ts → internalArgs.ts} +3 -3
  78. package/templates/client.ts +7 -30
  79. package/templates/lib/__lib/lib.dictionary.ts +0 -2
  80. package/templates/lib/__lib/lib.document.ts +0 -2
  81. package/templates/lib/sig.ts +8 -30
  82. package/templates/server.ts +0 -2
  83. package/{tunnel.command-d5jy9gk8.js → tunnel.command-kd2yr4fx.js} +5 -6
  84. package/{typeChecker-3xgrzc2v.js → typeChecker-px49mezc.js} +1 -1
  85. package/typecheck.proc.js +1 -1
  86. package/workflow.command-44252cqv.js +79 -0
  87. package/{workspace.command-gv84eh1p.js → workspace.command-fqcztt5p.js} +72 -107
  88. package/TunnelShare-2j6s511d.js +0 -9
  89. package/cloud.script-fwx3vb34.js +0 -20
  90. package/context.command-dsnw0jby.js +0 -80
  91. package/context.runner-rs4rj15d.js +0 -22
  92. package/devTuiView-1q1mdgrv.js +0 -9
  93. package/getDirname-dr3jz1xg.js +0 -7
  94. package/index-6wwzs1bc.js +0 -7
  95. package/workflow.command-6tkcysmx.js +0 -105
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  runner,
4
4
  script
5
- } from "./index-qdgzsnhg.js";
5
+ } from "./index-w2zdcw3k.js";
6
6
  import {
7
7
  AGENT_BLOCK_START,
8
8
  AGENT_BLOCK_END,
@@ -13,17 +13,17 @@ import {
13
13
  CLAUDE_COMMENT_RULE,
14
14
  AppExecutor,
15
15
  LibExecutor
16
- } from "./index-crmeqnmf.js";
16
+ } from "./index-9d9bgcx4.js";
17
17
  import {
18
18
  collectRecipeSources2,
19
19
  scanRecipes2
20
- } from "./index-by5gpp8x.js";
20
+ } from "./index-hj6w4ka1.js";
21
21
  import {
22
22
  AkanContextAnalyzer
23
- } from "./index-18zybgkc.js";
23
+ } from "./index-ygn77a5w.js";
24
24
  import {
25
25
  Prompter2
26
- } from "./index-s5rhm04y.js";
26
+ } from "./index-0bq14vr1.js";
27
27
 
28
28
  // pkgs/@akanjs/cli/agent/agent.script.ts
29
29
  import { Logger as Logger2 } from "akanjs/common";
@@ -126,7 +126,7 @@ var renderManagedBlock = async (workspace) => {
126
126
  const block = `## Workspace
127
127
 
128
128
  - Repo: ${context.repoName}
129
- - Apps: ${context.apps.map((app) => app.name).join(", ") || "none"}
129
+ - Apps: ${appNames.join(", ") || "none"}
130
130
  - Libraries: ${context.libs.map((lib) => lib.name).join(", ") || "none"}
131
131
  - Packages: ${context.pkgs.map((pkg) => pkg.name).join(", ") || "none"}
132
132
 
@@ -2,16 +2,16 @@
2
2
  import {
3
3
  runner,
4
4
  script
5
- } from "./index-qdgzsnhg.js";
5
+ } from "./index-w2zdcw3k.js";
6
6
  import {
7
- FileSys
8
- } from "./index-q0y2jq9j.js";
7
+ FileSys2
8
+ } from "./index-5b6tavv3.js";
9
9
  import {
10
10
  PkgExecutor
11
- } from "./index-crmeqnmf.js";
11
+ } from "./index-9d9bgcx4.js";
12
12
  import {
13
13
  TypeScriptDependencyScanner2
14
- } from "./index-bhtmj233.js";
14
+ } from "./index-3t7g9e4r.js";
15
15
 
16
16
  // pkgs/@akanjs/cli/package/package.runner.ts
17
17
  import path2 from "path";
@@ -112,7 +112,7 @@ class PackageRunner extends runner("package") {
112
112
  "create-akan-workspace"
113
113
  ];
114
114
  async version(workspace, { log = true } = {}) {
115
- const pkgJson = process.env.USE_AKANJS_PKGS === "true" ? await FileSys.readJson(`${workspace?.workspaceRoot ?? process.cwd()}/pkgs/akanjs/package.json`) : await this.#getInstalledPackageJson();
115
+ const pkgJson = process.env.USE_AKANJS_PKGS === "true" ? await FileSys2.readJson(`${workspace?.workspaceRoot ?? process.cwd()}/pkgs/akanjs/package.json`) : await this.#getInstalledPackageJson();
116
116
  const version = pkgJson.name === "akanjs" ? pkgJson.version : pkgJson.dependencies?.akanjs ?? pkgJson.version;
117
117
  if (log)
118
118
  Logger.rawLog(`akanjs@${version}`);
@@ -129,7 +129,7 @@ class PackageRunner extends runner("package") {
129
129
  for (const packageJsonPath of packageJsonCandidates) {
130
130
  if (!await Bun.file(packageJsonPath).exists())
131
131
  continue;
132
- const packageJson = await FileSys.readJson(packageJsonPath);
132
+ const packageJson = await FileSys2.readJson(packageJsonPath);
133
133
  if (packageJson.name === "akanjs" || packageJson.name === "@akanjs/cli")
134
134
  return packageJson;
135
135
  }
@@ -149,8 +149,7 @@ class PackageRunner extends runner("package") {
149
149
  await pkg.workspace.unsetPkgTsPaths(pkg.name);
150
150
  }
151
151
  async scanSync(pkg) {
152
- const scanResult = await pkg.scan();
153
- return scanResult;
152
+ return await pkg.scan();
154
153
  }
155
154
  async buildPackage(pkg) {
156
155
  await $`rm -rf ${pkg.dist.cwdPath}`;
@@ -184,8 +183,9 @@ class PackageRunner extends runner("package") {
184
183
  const forcedRuntimeDevDeps = packageRuntimeDevDependencies[pkg.name] ?? [];
185
184
  const [rootPackageJson, pkgJson] = await Promise.all([pkg.workspace.getPackageJson(), pkg.getPackageJson()]);
186
185
  const optionalPeerDeps = new Set(Object.entries(pkgJson.peerDependenciesMeta ?? {}).filter(([, meta]) => meta.optional).map(([dep]) => dep));
187
- const packageRuntimeDeps = [...new Set([...npmDeps, ...forcedRuntimeDeps])].filter((dep) => !optionalPeerDeps.has(dep) && !bundledRuntimeDeps.has(dep));
188
- const packageRuntimeDevDeps = [...new Set([...npmDevDeps, ...forcedRuntimeDevDeps])].filter((dep) => !optionalPeerDeps.has(dep) && !bundledRuntimeDeps.has(dep));
186
+ const isShipped = (dep) => !optionalPeerDeps.has(dep) && !bundledRuntimeDeps.has(dep);
187
+ const packageRuntimeDeps = [...new Set([...npmDeps, ...forcedRuntimeDeps])].filter(isShipped);
188
+ const packageRuntimeDevDeps = [...new Set([...npmDevDeps, ...forcedRuntimeDevDeps])].filter(isShipped);
189
189
  const rootDeps = {
190
190
  ...rootPackageJson.overrides,
191
191
  ...rootPackageJson.dependencies,
@@ -209,6 +209,7 @@ class PackageRunner extends runner("package") {
209
209
  });
210
210
  } else {
211
211
  await pkg.dist.cp(pkg.cwdPath, pkg.dist.cwdPath);
212
+ await $`rm -rf ${pkg.dist.cwdPath}/local`;
212
213
  await Promise.all([
213
214
  pkg.generateDistPackageJson(packageRuntimeDeps, packageRuntimeDevDeps),
214
215
  pkg.generateTsconfigJson()
@@ -282,7 +283,7 @@ class PackageRunner extends runner("package") {
282
283
  if (!await Bun.file(distPackageJsonPath).exists()) {
283
284
  throw new Error(`[package] dist package not found for ${pkg.name}. Run build-package first.`);
284
285
  }
285
- const pkgJson = await FileSys.readJson(distPackageJsonPath);
286
+ const pkgJson = await FileSys2.readJson(distPackageJsonPath);
286
287
  if (pkgJson.name !== pkg.name) {
287
288
  throw new Error(`[package] dist package name mismatch: expected ${pkg.name}, got ${pkgJson.name ?? "(missing)"}`);
288
289
  }
@@ -291,12 +292,9 @@ class PackageRunner extends runner("package") {
291
292
  if (pkgJson.publishConfig?.access !== "public") {
292
293
  throw new Error(`[package] ${pkg.name} must publish with publishConfig.access=public`);
293
294
  }
294
- if (!await Bun.file(`${pkg.dist.cwdPath}/README.md`).exists()) {
295
- throw new Error(`[package] README.md is missing from dist package ${pkg.name}`);
296
- }
297
- if (!await Bun.file(`${pkg.dist.cwdPath}/README.ko.md`).exists()) {
298
- throw new Error(`[package] README.ko.md is missing from dist package ${pkg.name}`);
299
- }
295
+ for (const readme of ["README.md", "README.ko.md"])
296
+ if (!await Bun.file(`${pkg.dist.cwdPath}/${readme}`).exists())
297
+ throw new Error(`[package] ${readme} is missing from dist package ${pkg.name}`);
300
298
  const binEntries = typeof pkgJson.bin === "string" ? [pkgJson.bin] : Object.values(pkgJson.bin ?? {});
301
299
  if (binEntries.some((binPath) => binPath.endsWith(".ts"))) {
302
300
  throw new Error(`[package] ${pkg.name} dist bin entries must not point at TypeScript sources`);
@@ -313,6 +311,9 @@ class PackageRunner extends runner("package") {
313
311
  cwd: pkg.workspace.workspaceRoot
314
312
  });
315
313
  const [packResult] = JSON.parse(packOutput);
314
+ const localFiles = (packResult?.files ?? []).filter((file) => file.path?.startsWith("local/"));
315
+ if (localFiles.length > 0)
316
+ throw new Error(`[package] ${pkg.name} would publish ${localFiles.length} test-run files under local/`);
316
317
  return {
317
318
  name: pkg.name,
318
319
  version: pkgJson.version,
@@ -329,9 +330,8 @@ class PackageRunner extends runner("package") {
329
330
  peerExportsMaps.set(pkg.name, await PackageExportsMap.from(pkg.dist.cwdPath));
330
331
  }
331
332
  const results = [];
332
- for (const pkg of pkgs) {
333
+ for (const pkg of pkgs)
333
334
  results.push(await this.verifyDistPackage(pkg, { peerExportsMaps }));
334
- }
335
335
  return results;
336
336
  }
337
337
  async#copyPackageReadmes(pkg) {
@@ -3,14 +3,14 @@ import {
3
3
  getDefaultHostConfig,
4
4
  GlobalConfig,
5
5
  CloudApi
6
- } from "./index-srmay5jw.js";
6
+ } from "./index-22d57v4c.js";
7
7
  import {
8
8
  runner
9
- } from "./index-qdgzsnhg.js";
9
+ } from "./index-w2zdcw3k.js";
10
10
  import {
11
11
  WorkspaceExecutor,
12
12
  AppExecutor
13
- } from "./index-crmeqnmf.js";
13
+ } from "./index-9d9bgcx4.js";
14
14
  import {
15
15
  openBrowser
16
16
  } from "./index-9dp031by.js";
@@ -145,6 +145,15 @@ class BunTestReport {
145
145
  class PlatformTestTarget {
146
146
  static installIdleMs = 5 * 60000;
147
147
  static testIdleMs = 2 * 60000;
148
+ static gitIdentityEnv = {
149
+ GIT_AUTHOR_NAME: "akan-test",
150
+ GIT_AUTHOR_EMAIL: "akan-test@localhost",
151
+ GIT_COMMITTER_NAME: "akan-test",
152
+ GIT_COMMITTER_EMAIL: "akan-test@localhost",
153
+ GIT_CONFIG_COUNT: "1",
154
+ GIT_CONFIG_KEY_0: "init.defaultBranch",
155
+ GIT_CONFIG_VALUE_0: "main"
156
+ };
148
157
  tolerateScriptFailures = [];
149
158
  async run(context) {
150
159
  const startedAt = performance.now();
@@ -204,6 +213,19 @@ class PlatformTestTarget {
204
213
  #logPath(context, name) {
205
214
  return path2.join(context.logDir, this.platform, `${name}.log`);
206
215
  }
216
+ static async spawnText(command, stdin) {
217
+ const proc = Bun.spawn(command, {
218
+ stdin: stdin === undefined ? "ignore" : new Response(stdin),
219
+ stdout: "pipe",
220
+ stderr: "pipe"
221
+ });
222
+ const [stdout, stderr, exitCode] = await Promise.all([
223
+ new Response(proc.stdout).text(),
224
+ new Response(proc.stderr).text(),
225
+ proc.exited
226
+ ]);
227
+ return { stdout, stderr, exitCode };
228
+ }
207
229
  }
208
230
 
209
231
  // pkgs/@akanjs/devkit/platformTest/LinuxDockerTarget.ts
@@ -227,12 +249,13 @@ class LinuxDockerTarget extends PlatformTestTarget {
227
249
  return `akan-test-linux:bun-${Bun.version}-${Bun.hash(LinuxDockerTarget.dockerfile).toString(16).slice(0, 8)}`;
228
250
  }
229
251
  async prepare(context) {
230
- if ((await LinuxDockerTarget.#docker(["info"])).exitCode !== 0)
252
+ if ((await LinuxDockerTarget.spawnText(["docker", "info"])).exitCode !== 0)
231
253
  throw new Error("Docker is not running \u2014 start Docker Desktop, or leave linux out of --platforms");
232
254
  await this.#ensureImage();
233
255
  const container = `akan-test-${context.runId}`;
234
256
  const cpus = this.#config.cpus ?? Math.max(2, Math.floor(os.availableParallelism() / 2));
235
- const started = await LinuxDockerTarget.#docker([
257
+ const started = await LinuxDockerTarget.spawnText([
258
+ "docker",
236
259
  "run",
237
260
  "-d",
238
261
  "--rm",
@@ -264,50 +287,34 @@ class LinuxDockerTarget extends PlatformTestTarget {
264
287
  const container = this.#container;
265
288
  if (!container)
266
289
  throw new Error("the linux container is not running");
267
- return await new IdleWatchedProcess(["docker", "exec", ...LinuxDockerTarget.#gitIdentityEnv(), container, "sh", "-c", command], {
290
+ return await new IdleWatchedProcess([
291
+ "docker",
292
+ "exec",
293
+ ...Object.entries(PlatformTestTarget.gitIdentityEnv).flatMap(([key, value]) => ["-e", `${key}=${value}`]),
294
+ container,
295
+ "sh",
296
+ "-c",
297
+ command
298
+ ], {
268
299
  logPath,
269
300
  idleMs,
270
301
  onIdle: async () => {
271
- await LinuxDockerTarget.#docker(["exec", container, "pkill", "-9", "bun"]);
302
+ await LinuxDockerTarget.spawnText(["docker", "exec", container, "pkill", "-9", "bun"]);
272
303
  }
273
304
  }).run();
274
305
  }
275
306
  async cleanup() {
276
307
  if (this.#container)
277
- await LinuxDockerTarget.#docker(["rm", "-f", this.#container]);
308
+ await LinuxDockerTarget.spawnText(["docker", "rm", "-f", this.#container]);
278
309
  this.#container = null;
279
310
  }
280
311
  async#ensureImage() {
281
- if ((await LinuxDockerTarget.#docker(["image", "inspect", this.image])).exitCode === 0)
312
+ if ((await LinuxDockerTarget.spawnText(["docker", "image", "inspect", this.image])).exitCode === 0)
282
313
  return;
283
- const built = await LinuxDockerTarget.#docker(["build", "-t", this.image, "-"], LinuxDockerTarget.dockerfile);
314
+ const built = await LinuxDockerTarget.spawnText(["docker", "build", "-t", this.image, "-"], LinuxDockerTarget.dockerfile);
284
315
  if (built.exitCode !== 0)
285
316
  throw new Error(`building ${this.image} failed: ${built.stderr.trim().slice(-400)}`);
286
317
  }
287
- static #gitIdentityEnv() {
288
- return Object.entries({
289
- GIT_AUTHOR_NAME: "akan-test",
290
- GIT_AUTHOR_EMAIL: "akan-test@localhost",
291
- GIT_COMMITTER_NAME: "akan-test",
292
- GIT_COMMITTER_EMAIL: "akan-test@localhost",
293
- GIT_CONFIG_COUNT: "1",
294
- GIT_CONFIG_KEY_0: "init.defaultBranch",
295
- GIT_CONFIG_VALUE_0: "main"
296
- }).flatMap(([key, value]) => ["-e", `${key}=${value}`]);
297
- }
298
- static async#docker(args, stdin) {
299
- const proc = Bun.spawn(["docker", ...args], {
300
- stdin: stdin === undefined ? "ignore" : new Response(stdin),
301
- stdout: "pipe",
302
- stderr: "pipe"
303
- });
304
- const [stdout, stderr, exitCode] = await Promise.all([
305
- new Response(proc.stdout).text(),
306
- new Response(proc.stderr).text(),
307
- proc.exited
308
- ]);
309
- return { stdout, stderr, exitCode };
310
- }
311
318
  }
312
319
 
313
320
  // pkgs/@akanjs/devkit/platformTest/TestSnapshot.ts
@@ -433,7 +440,7 @@ class WindowsSshTarget extends PlatformTestTarget {
433
440
  [context.snapshot.tarPath, "src.tar"],
434
441
  [context.envPath, "env"]
435
442
  ]) {
436
- const copied = await WindowsSshTarget.#run([
443
+ const copied = await WindowsSshTarget.spawnText([
437
444
  ...this.#scpBase(),
438
445
  local,
439
446
  this.#remoteScpPath(`${runDir}\\${remoteName}`)
@@ -454,13 +461,7 @@ class WindowsSshTarget extends PlatformTestTarget {
454
461
  "$ProgressPreference = 'SilentlyContinue'",
455
462
  "[Console]::OutputEncoding = [Text.Encoding]::UTF8",
456
463
  "'PID=' + $PID",
457
- "$env:GIT_AUTHOR_NAME = 'akan-test'",
458
- "$env:GIT_AUTHOR_EMAIL = 'akan-test@localhost'",
459
- "$env:GIT_COMMITTER_NAME = 'akan-test'",
460
- "$env:GIT_COMMITTER_EMAIL = 'akan-test@localhost'",
461
- "$env:GIT_CONFIG_COUNT = '1'",
462
- "$env:GIT_CONFIG_KEY_0 = 'init.defaultBranch'",
463
- "$env:GIT_CONFIG_VALUE_0 = 'main'",
464
+ ...Object.entries(PlatformTestTarget.gitIdentityEnv).map(([key, value]) => `$env:${key} = '${value}'`),
464
465
  `Set-Location -LiteralPath '${runDir}\\w'`,
465
466
  `cmd /c "${command} 2>&1"`,
466
467
  "exit $LASTEXITCODE"
@@ -493,11 +494,11 @@ class WindowsSshTarget extends PlatformTestTarget {
493
494
  const vm = this.#config.utmVm;
494
495
  if (!vm || !existsSync(WindowsSshTarget.utmctlPath))
495
496
  throw new Error(`cannot reach ${this.#config.user}@${this.#host} over ssh`);
496
- const status = (await WindowsSshTarget.#run([WindowsSshTarget.utmctlPath, "status", vm])).stdout.trim();
497
+ const status = (await WindowsSshTarget.spawnText([WindowsSshTarget.utmctlPath, "status", vm])).stdout.trim();
497
498
  if (status !== "started")
498
- await WindowsSshTarget.#run([WindowsSshTarget.utmctlPath, "start", vm]);
499
+ await WindowsSshTarget.spawnText([WindowsSshTarget.utmctlPath, "start", vm]);
499
500
  for (let attempt = 0;attempt < 24; attempt++) {
500
- const addresses = (await WindowsSshTarget.#run([WindowsSshTarget.utmctlPath, "ip-address", vm])).stdout;
501
+ const addresses = (await WindowsSshTarget.spawnText([WindowsSshTarget.utmctlPath, "ip-address", vm])).stdout;
501
502
  const ipv4 = addresses.split(/\s+/).find((address) => /^\d+\.\d+\.\d+\.\d+$/.test(address));
502
503
  if (ipv4)
503
504
  this.#host = ipv4;
@@ -531,16 +532,7 @@ class WindowsSshTarget extends PlatformTestTarget {
531
532
  return `${this.#config.user}@${this.#host}:/${windowsPath.replaceAll("\\", "/")}`;
532
533
  }
533
534
  async#ssh(script) {
534
- return await WindowsSshTarget.#run([...this.#sshBase(), script]);
535
- }
536
- static async#run(command) {
537
- const proc = Bun.spawn(command, { stdin: "ignore", stdout: "pipe", stderr: "pipe" });
538
- const [stdout, stderr, exitCode] = await Promise.all([
539
- new Response(proc.stdout).text(),
540
- new Response(proc.stderr).text(),
541
- proc.exited
542
- ]);
543
- return { stdout, stderr, exitCode };
535
+ return await WindowsSshTarget.spawnText([...this.#sshBase(), script]);
544
536
  }
545
537
  }
546
538
 
@@ -699,26 +691,20 @@ class CloudRunner extends runner("cloud") {
699
691
  NPM_CONFIG_REGISTRY: getNpmRegistryUrl(registryUrl)
700
692
  } : process.env;
701
693
  }
694
+ async#ask(message, validate) {
695
+ return (await input({ message, validate })).trim();
696
+ }
702
697
  async#addRemoteEnvServer() {
703
- const name = (await input({
704
- message: "Remote server name: ",
705
- validate: (value) => value.trim() ? true : "Remote server name is required"
706
- })).trim();
707
- const host = (await input({
708
- message: "Remote server host: ",
709
- validate: (value) => value.trim() ? true : "Remote server host is required"
710
- })).trim();
711
- const username = (await input({ message: "Remote server username (optional): " })).trim() || undefined;
712
- const portInput = (await input({
713
- message: "Remote server SSH port (optional): ",
714
- validate: (value) => {
715
- const trimmed = value.trim();
716
- if (!trimmed)
717
- return true;
718
- const port = Number(trimmed);
719
- return Number.isInteger(port) && port > 0 ? true : "SSH port must be a positive integer";
720
- }
721
- })).trim();
698
+ const name = await this.#ask("Remote server name: ", (value) => !!value.trim() || "Remote server name is required");
699
+ const host = await this.#ask("Remote server host: ", (value) => !!value.trim() || "Remote server host is required");
700
+ const username = await this.#ask("Remote server username (optional): ") || undefined;
701
+ const portInput = await this.#ask("Remote server SSH port (optional): ", (value) => {
702
+ const trimmed = value.trim();
703
+ if (!trimmed)
704
+ return true;
705
+ const port = Number(trimmed);
706
+ return Number.isInteger(port) && port > 0 ? true : "SSH port must be a positive integer";
707
+ });
722
708
  const config = {
723
709
  host,
724
710
  ...username ? { username } : {},
@@ -737,10 +723,7 @@ class CloudRunner extends runner("cloud") {
737
723
  const selectedName = await select({
738
724
  message: "Select the remote env server",
739
725
  choices: [
740
- ...serverEntries.map(([name, config]) => ({
741
- name: `${name} (${config.username ? `${config.username}@` : ""}${config.host}${config.port ? `:${config.port}` : ""})`,
742
- value: name
743
- })),
726
+ ...serverEntries.map((entry) => this.#serverChoice(entry)),
744
727
  { name: "Add new remote server", value: addRemoteEnvServerValue },
745
728
  { name: "Remove remote server", value: removeRemoteEnvServerValue }
746
729
  ]
@@ -759,34 +742,22 @@ class CloudRunner extends runner("cloud") {
759
742
  async#removeRemoteEnvServer(serverEntries) {
760
743
  const selectedName = await select({
761
744
  message: "Select the remote env server to remove",
762
- choices: serverEntries.map(([name, config]) => ({
763
- name: `${name} (${config.username ? `${config.username}@` : ""}${config.host}${config.port ? `:${config.port}` : ""})`,
764
- value: name
765
- }))
745
+ choices: serverEntries.map((entry) => this.#serverChoice(entry))
766
746
  });
767
- const shouldRemove = await confirm({
768
- message: `Remove remote env server "${selectedName}"?`,
769
- default: false
770
- });
771
- if (!shouldRemove)
747
+ if (!await confirm({ message: `Remove remote env server "${selectedName}"?`, default: false }))
772
748
  return;
773
749
  await GlobalConfig.removeRemoteEnvServer(selectedName);
774
750
  Logger.info(`Removed remote env server "${selectedName}"`);
775
751
  }
752
+ #serverChoice([name, config]) {
753
+ return { name: `${name} (${this.#getSshTarget(config)}${config.port ? `:${config.port}` : ""})`, value: name };
754
+ }
776
755
  async#getRemoteEnvServerWithUsername() {
777
756
  const remoteServer = await this.#selectRemoteEnvServer();
778
757
  if (remoteServer.config.username)
779
758
  return remoteServer;
780
- const username = (await input({
781
- message: `SSH username for ${remoteServer.config.host} (optional): `
782
- })).trim();
783
- return {
784
- ...remoteServer,
785
- config: {
786
- ...remoteServer.config,
787
- ...username ? { username } : {}
788
- }
789
- };
759
+ const username = await this.#ask(`SSH username for ${remoteServer.config.host} (optional): `);
760
+ return { ...remoteServer, config: { ...remoteServer.config, ...username ? { username } : {} } };
790
761
  }
791
762
  #getRemoteEnvArchivePath() {
792
763
  return `${this.#getRemoteEnvArchiveDir()}/env.tar`;
@@ -796,7 +767,7 @@ class CloudRunner extends runner("cloud") {
796
767
  return `~/secrets/${repoName}`;
797
768
  }
798
769
  #getScpTarget(config, remotePath) {
799
- return `${config.username ? `${config.username}@` : ""}${config.host}:${remotePath}`;
770
+ return `${this.#getSshTarget(config)}:${remotePath}`;
800
771
  }
801
772
  #getSshTarget(config) {
802
773
  return `${config.username ? `${config.username}@` : ""}${config.host}`;
@@ -808,9 +779,8 @@ class CloudRunner extends runner("cloud") {
808
779
  return [...config.port ? ["-p", config.port.toString()] : [], this.#getSshTarget(config), command];
809
780
  }
810
781
  async login(host, workspace) {
811
- const config = await GlobalConfig.getHostConfig(host);
812
- const cloudApi = new CloudApi(workspace, config);
813
- const self = config.auth ? await cloudApi.getRemoteSelf() : null;
782
+ const cloudApi = await CloudApi.fromHost(workspace, host);
783
+ const self = await cloudApi.getRemoteSelf();
814
784
  if (self) {
815
785
  Logger.rawLog(chalk2.green(`
816
786
  \u2713 Already logged in akan cloud as ${self.nickname}
@@ -844,7 +814,7 @@ ${chalk2.green("\u27A4")} Authentication Required`));
844
814
  const accessToken = await cloudApi.getRemoteAuthToken(remoteId);
845
815
  const self = await cloudApi.getRemoteSelf();
846
816
  if (accessToken && self) {
847
- await GlobalConfig.setHostConfig({ host: config.host, auth: { accessToken, self } });
817
+ await GlobalConfig.setHostConfig({ host: cloudApi.host, auth: { accessToken, self } });
848
818
  Logger.rawLog(chalk2.green(`\r\u2713 Authentication successful!`));
849
819
  Logger.rawLog(chalk2.green.bold(`
850
820
  \u2728 Welcome aboard, ${self.nickname ?? "anonymous"}!`));
@@ -927,11 +897,12 @@ ${run.format(results)}
927
897
  }
928
898
  async#askWindowsTestTarget() {
929
899
  Logger.info("No Windows test target is configured yet; it is saved to ~/.akan/config.json once entered.");
930
- const host = (await input({ message: "Windows host (ip or name): ", validate: (value) => !!value.trim() })).trim();
931
- const user = (await input({ message: "SSH user: ", validate: (value) => !!value.trim() })).trim();
932
- const identityFile = (await input({ message: "SSH private key path: ", validate: (value) => !!value.trim() })).trim();
933
- const knownHostsFile = (await input({ message: "known_hosts file (optional): " })).trim();
934
- const utmVm = (await input({ message: "UTM VM name, to start it and find its ip (optional): " })).trim();
900
+ const required = (value) => !!value.trim();
901
+ const host = await this.#ask("Windows host (ip or name): ", required);
902
+ const user = await this.#ask("SSH user: ", required);
903
+ const identityFile = await this.#ask("SSH private key path: ", required);
904
+ const knownHostsFile = await this.#ask("known_hosts file (optional): ");
905
+ const utmVm = await this.#ask("UTM VM name, to start it and find its ip (optional): ");
935
906
  return {
936
907
  host,
937
908
  user,
@@ -951,35 +922,22 @@ ${run.format(results)}
951
922
  const isOfficialRelease = !devPatchVersion;
952
923
  const targetVersionPrefix = isOfficialRelease ? `${majorVersion}.${minorVersion}` : `${majorVersion}.${minorVersion}.${patchVersion}`;
953
924
  const tag = distTag ?? (isOfficialRelease ? "latest" : patchVersion.split("-").at(1) ?? "dev");
954
- const getNextVersion = async (prefix, tag) => {
955
- try {
956
- const latestPublishedVersion = await getLatestPackageVersion("akanjs", tag, registry);
957
- const latestPatch = latestPublishedVersion.startsWith(prefix) ? parseInt(latestPublishedVersion.split(".").at(-1) ?? "-1") : -1;
958
- const nextVersion = `${prefix}.${latestPatch + 1}`;
959
- return { nextVersion, latestPublishedVersion };
960
- } catch {
961
- return { nextVersion: `${prefix}.0`, latestPublishedVersion: null };
962
- }
963
- };
964
- const { nextVersion, latestPublishedVersion } = await getNextVersion(targetVersionPrefix, tag);
925
+ const latestPublishedVersion = await getLatestPackageVersion("akanjs", tag, registry).catch(() => null);
926
+ const latestPatch = latestPublishedVersion?.startsWith(targetVersionPrefix) ? parseInt(latestPublishedVersion.split(".").at(-1) ?? "-1") : -1;
927
+ const nextVersion = `${targetVersionPrefix}.${latestPatch + 1}`;
965
928
  Logger.info(`Latest published version of akanjs: ${latestPublishedVersion ?? "none"}`);
966
929
  Logger.info(`Next version of akanjs: ${nextVersion}`);
967
930
  for (const library of akanPkgs) {
968
931
  const packageJson = await workspace.readJson(`pkgs/${library}/package.json`);
969
- const newPackageJsonStr = JSON.stringify(this.#normalizeAkanPackageJson(packageJson, library, nextVersion), null, 2);
970
- await workspace.writeFile(`pkgs/${library}/package.json`, newPackageJsonStr);
932
+ const newPackageJson = this.#normalizeAkanPackageJson(packageJson, library, nextVersion);
933
+ await workspace.writeFile(`pkgs/${library}/package.json`, JSON.stringify(newPackageJson, null, 2));
971
934
  const distPackageJson = await workspace.readJson(`dist/pkgs/${library}/package.json`);
972
935
  const newDistPackageJson = this.#normalizeAkanPackageJson(distPackageJson, library, nextVersion);
973
936
  await workspace.writeJson(`dist/pkgs/${library}/package.json`, newDistPackageJson);
974
937
  }
975
- if (confirmPublish) {
976
- const isDeployConfirmed = await confirm({
977
- message: "Are you sure you want to deploy the libraries?"
978
- });
979
- if (!isDeployConfirmed) {
980
- Logger.error("Deployment cancelled");
981
- return;
982
- }
938
+ if (confirmPublish && !await confirm({ message: "Are you sure you want to deploy the libraries?" })) {
939
+ Logger.error("Deployment cancelled");
940
+ return;
983
941
  }
984
942
  if (!registry) {
985
943
  Logger.info("Logging in to npm...");
@@ -1015,14 +973,12 @@ ${run.format(results)}
1015
973
  const rootPackageJson = await workspace.getPackageJson();
1016
974
  if (!rootPackageJson.dependencies)
1017
975
  throw new Error("No dependencies found in package.json");
1018
- if (rootPackageJson.dependencies.akanjs)
1019
- rootPackageJson.dependencies.akanjs = latestPublishedVersion;
1020
- if (rootPackageJson.devDependencies?.akanjs)
1021
- rootPackageJson.devDependencies.akanjs = latestPublishedVersion;
1022
- if (rootPackageJson.dependencies["@akanjs/devkit"])
1023
- rootPackageJson.dependencies["@akanjs/devkit"] = latestPublishedVersion;
1024
- if (rootPackageJson.devDependencies?.["@akanjs/devkit"])
1025
- rootPackageJson.devDependencies["@akanjs/devkit"] = latestPublishedVersion;
976
+ for (const name of ["akanjs", "@akanjs/devkit"]) {
977
+ if (rootPackageJson.dependencies[name])
978
+ rootPackageJson.dependencies[name] = latestPublishedVersion;
979
+ if (rootPackageJson.devDependencies?.[name])
980
+ rootPackageJson.devDependencies[name] = latestPublishedVersion;
981
+ }
1026
982
  await workspace.setPackageJson(rootPackageJson);
1027
983
  await workspace.spawn("bun", ["install", ...this.#getRegistryArgs(registryUrl)], {
1028
984
  env: this.#getRegistryEnv(registryUrl)
@@ -1065,9 +1021,7 @@ ${run.format(results)}
1065
1021
  cwd: workspace.workspaceRoot,
1066
1022
  stdio: "inherit"
1067
1023
  });
1068
- await workspace.spawn("tar", ["-xf", envArchivePath], {
1069
- cwd: workspace.workspaceRoot
1070
- });
1024
+ await workspace.spawn("tar", ["-xf", envArchivePath], { cwd: workspace.workspaceRoot });
1071
1025
  await workspace.remove(envArchivePath);
1072
1026
  } catch (error) {
1073
1027
  throw new Error(`Failed to download env archive from remote server "${remoteServer.name}"`, { cause: error });
@@ -1109,9 +1063,7 @@ ${run.format(results)}
1109
1063
  await workspace.remove(archivePath);
1110
1064
  if (envFilePaths.length === 0)
1111
1065
  throw new Error(scope ? `No environment files found to archive for ${appNames.join(", ") || "(no apps)"}` : "No environment files found to archive");
1112
- await workspace.spawn("tar", ["-cf", archivePath, ...envFilePaths], {
1113
- cwd: workspace.workspaceRoot
1114
- });
1066
+ await workspace.spawn("tar", ["-cf", archivePath, ...envFilePaths], { cwd: workspace.workspaceRoot });
1115
1067
  Logger.info(`Archived ${envFilePaths.length} environment files to ${archivePath}`);
1116
1068
  return { files: envFilePaths, path: archivePath };
1117
1069
  }
@@ -14,25 +14,26 @@ class LibSource {
14
14
  const relative = file.slice(this.#prefix.length);
15
15
  return !LibSource.unhashedDirs.includes(relative.split("/")[0] ?? "");
16
16
  }
17
- async#hashableContent(file) {
18
- const content = await this.#lib.workspace.readFile(file);
19
- if (file !== `${this.#prefix}package.json`)
20
- return content;
21
- const manifest = JSON.parse(content);
17
+ static unstamped(manifestContent) {
18
+ const manifest = JSON.parse(manifestContent);
22
19
  delete manifest[LibSource.manifestKey];
23
20
  return JSON.stringify(manifest);
24
21
  }
25
- async computeHash() {
26
- const files = (await this.#lib.workspace.listGitFiles([`libs/${this.#lib.name}`], { untracked: true })).filter((file) => this.#isHashed(file));
22
+ static async hashFiles(files, manifestFile, read) {
27
23
  const hasher = new Bun.CryptoHasher("sha256");
28
24
  for (const file of files) {
25
+ const content = await read(file);
29
26
  hasher.update(file);
30
27
  hasher.update("\x00");
31
- hasher.update(await this.#hashableContent(file));
28
+ hasher.update(file === manifestFile ? LibSource.unstamped(content) : content);
32
29
  hasher.update("\x00");
33
30
  }
34
31
  return hasher.digest("hex").slice(0, 32);
35
32
  }
33
+ async computeHash() {
34
+ const files = (await this.#lib.workspace.listGitFiles([`libs/${this.#lib.name}`], { untracked: true })).filter((file) => this.#isHashed(file));
35
+ return await LibSource.hashFiles(files, `${this.#prefix}package.json`, (file) => this.#lib.workspace.readFile(file));
36
+ }
36
37
  async read() {
37
38
  const manifest = await this.#lib.getPackageJson();
38
39
  const akan = manifest[LibSource.manifestKey];
@@ -59,12 +59,8 @@ class CodeAgentStreamPrinter2 {
59
59
  return;
60
60
  this.#streaming = true;
61
61
  return this.#write(chalk.dim(event.text));
62
- case "message":
63
- return;
64
62
  case "tool_start":
65
63
  return this.#line(chalk.cyan(`\u2192 ${event.tool.title}`));
66
- case "tool_progress":
67
- return;
68
64
  case "tool_end":
69
65
  return this.#line(CodeAgentStreamPrinter2.#toolEnd(event));
70
66
  case "session":
@@ -73,11 +69,12 @@ class CodeAgentStreamPrinter2 {
73
69
  if (event.stopReason === "truncated")
74
70
  return this.#line(chalk.yellow(codeAgentEventLabel(event)));
75
71
  return;
72
+ case "message":
73
+ case "tool_progress":
76
74
  case "turn_start":
77
75
  case "idle":
78
76
  case "queue":
79
77
  case "context":
80
- return;
81
78
  case "subagent":
82
79
  return;
83
80
  case "question":