@astrale-os/cli 0.7.0-alpha.0 → 0.8.1-alpha.0

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 (111) hide show
  1. package/README.md +7 -3
  2. package/dist/astrale.js +128 -98
  3. package/package.json +11 -5
  4. package/src/commands/studio.ts +13 -0
  5. package/src/lib/__tests__/view-assets.test.ts +114 -0
  6. package/src/lib/instance.ts +0 -11
  7. package/src/lib/login-flow.ts +4 -41
  8. package/src/lib/view/assets.ts +64 -0
  9. package/src/lib/view/server.ts +4 -37
  10. package/studio/client/dist/assets/elk-api-DVw5WDTH.js +1 -0
  11. package/studio/client/dist/assets/elk-worker.min-C9JGDOE-.js +6312 -0
  12. package/studio/client/dist/assets/index-BQnJ5sgd.css +1 -0
  13. package/studio/client/dist/assets/index-CTbkDp3z.js +81 -0
  14. package/studio/client/dist/assets/index-ChOr3yP0.css +1 -0
  15. package/studio/client/dist/assets/index-Dl709Ra1.js +8 -0
  16. package/studio/client/dist/index.html +2 -2
  17. package/studio/server/agent/ask.test.ts +74 -0
  18. package/studio/server/agent/ask.ts +20 -22
  19. package/studio/server/agent/bridge/client.test.ts +49 -0
  20. package/studio/server/agent/bridge/client.ts +37 -0
  21. package/studio/server/agent/bridge/grant.test.ts +62 -0
  22. package/studio/server/agent/bridge/grant.ts +84 -0
  23. package/studio/server/agent/bridge/routes.test.ts +151 -0
  24. package/studio/server/agent/bridge/routes.ts +148 -0
  25. package/studio/server/agent/{bridge-mcp.ts → bridge/stdio.ts} +27 -21
  26. package/studio/server/agent/conversation.test.ts +84 -0
  27. package/studio/server/agent/conversation.ts +110 -0
  28. package/studio/server/agent/{types.ts → harness/adapter.ts} +63 -12
  29. package/studio/server/agent/harness/claude/adapter.test.ts +338 -0
  30. package/studio/server/agent/harness/claude/adapter.ts +71 -0
  31. package/studio/server/agent/harness/claude/ask.ts +114 -0
  32. package/studio/server/agent/harness/claude/capabilities.ts +27 -0
  33. package/studio/server/agent/harness/claude/command.ts +63 -0
  34. package/studio/server/agent/harness/claude/events.ts +215 -0
  35. package/studio/server/agent/harness/claude/loadout.ts +132 -0
  36. package/studio/server/agent/harness/claude/mcp.test.ts +36 -0
  37. package/studio/server/agent/harness/claude/mcp.ts +41 -0
  38. package/studio/server/agent/harness/claude/skills.ts +43 -0
  39. package/studio/server/agent/harness/codex/adapter.test.ts +269 -0
  40. package/studio/server/agent/harness/codex/adapter.ts +116 -0
  41. package/studio/server/agent/harness/codex/ask.test.ts +176 -0
  42. package/studio/server/agent/harness/codex/ask.ts +181 -0
  43. package/studio/server/agent/harness/codex/command.test.ts +69 -0
  44. package/studio/server/agent/harness/codex/command.ts +43 -0
  45. package/studio/server/agent/harness/codex/events.test.ts +94 -0
  46. package/studio/server/agent/harness/codex/events.ts +132 -0
  47. package/studio/server/agent/harness/codex/exec.ts +113 -0
  48. package/studio/server/agent/harness/codex/loadout.ts +63 -0
  49. package/studio/server/agent/harness/codex/mcp.test.ts +32 -0
  50. package/studio/server/agent/harness/codex/mcp.ts +27 -0
  51. package/studio/server/agent/harness/codex/models.test.ts +167 -0
  52. package/studio/server/agent/harness/codex/models.ts +211 -0
  53. package/studio/server/agent/harness/codex/skills.ts +33 -0
  54. package/studio/server/agent/harness/gateway/config.test.ts +98 -0
  55. package/studio/server/{state/harness-gateway.ts → agent/harness/gateway/config.ts} +30 -8
  56. package/studio/server/agent/harness/gateway/token.test.ts +133 -0
  57. package/studio/server/agent/harness/gateway/token.ts +166 -0
  58. package/studio/server/agent/harness/mock/adapter.ts +190 -0
  59. package/studio/server/agent/harness/mock/domain-edit.ts +41 -0
  60. package/studio/server/agent/harness/process.test.ts +46 -0
  61. package/studio/server/agent/harness/process.ts +104 -0
  62. package/studio/server/agent/harness/registry.ts +37 -0
  63. package/studio/server/agent/harness/selection.test.ts +75 -0
  64. package/studio/server/agent/harness/selection.ts +77 -0
  65. package/studio/server/agent/harness/skills.test.ts +95 -0
  66. package/studio/server/agent/harness/skills.ts +100 -0
  67. package/studio/server/agent/layout.test.ts +95 -0
  68. package/studio/server/agent/notify.ts +12 -0
  69. package/studio/server/agent/{schema-map.ts → prompts/anchors.ts} +2 -2
  70. package/studio/server/agent/prompts/ask.ts +34 -0
  71. package/studio/server/agent/{prompt.ts → prompts/system.ts} +3 -107
  72. package/studio/server/agent/prompts/turn.ts +57 -0
  73. package/studio/server/agent/routes.test.ts +176 -0
  74. package/studio/server/agent/routes.ts +214 -0
  75. package/studio/server/agent/run/completion.ts +193 -0
  76. package/studio/server/agent/run/coordinator.test.ts +337 -0
  77. package/studio/server/agent/run/coordinator.ts +104 -0
  78. package/studio/server/agent/run/live-state.ts +73 -0
  79. package/studio/server/agent/run/preparation.ts +162 -0
  80. package/studio/server/agent/run/transcript.test.ts +47 -0
  81. package/studio/server/agent/run/transcript.ts +30 -0
  82. package/studio/server/agent/run/usage.test.ts +46 -0
  83. package/studio/server/{state → agent/run}/usage.ts +4 -4
  84. package/studio/server/agent/stream.ts +37 -0
  85. package/studio/server/agent/{session-id.ts → telemetry.ts} +1 -1
  86. package/studio/server/api-agent-loadout.test.ts +69 -0
  87. package/studio/server/api.ts +3 -155
  88. package/studio/server/index.ts +6 -5
  89. package/studio/server/state/comments.test.ts +29 -0
  90. package/studio/server/state/comments.ts +3 -3
  91. package/studio/server/state/settings.test.ts +78 -0
  92. package/studio/server/state/settings.ts +28 -1
  93. package/studio/shared/agent-effort.test.ts +12 -0
  94. package/studio/shared/agent-effort.ts +15 -0
  95. package/studio/shared/agent-models.test.ts +9 -0
  96. package/studio/shared/agent-models.ts +14 -0
  97. package/studio/shared/settings-values.test.ts +18 -0
  98. package/studio/shared/settings-values.ts +20 -0
  99. package/studio/shared/types.ts +58 -7
  100. package/studio/tsconfig.json +23 -0
  101. package/viewer/tsconfig.json +13 -0
  102. package/src/connect-core.test.ts +0 -42
  103. package/src/connect-core.ts +0 -53
  104. package/studio/client/dist/assets/index-DAC1a9vW.js +0 -109
  105. package/studio/client/dist/assets/index-huaFafBC.css +0 -1
  106. package/studio/server/agent/bridge.ts +0 -188
  107. package/studio/server/agent/claude.ts +0 -678
  108. package/studio/server/agent/mock.ts +0 -186
  109. package/studio/server/agent/registry.ts +0 -29
  110. package/studio/server/agent/runner.ts +0 -487
  111. package/studio/server/state/harness-token.ts +0 -0
package/README.md CHANGED
@@ -30,9 +30,10 @@ npm install -g @astrale-os/cli
30
30
  ```
31
31
 
32
32
  The npm build bundles all dependencies, so no private-registry access is needed.
33
- `astrale studio` launches a local web GUI to author and inspect a domain; it runs
34
- on [Bun](https://bun.sh), so install Bun and keep it on `PATH` to use that command
35
- (the rest of the CLI needs only Node 20). The curl-installed standalone binary
33
+ `astrale studio` launches a local web GUI to author and inspect a domain, with
34
+ local Claude Code and Codex agent harnesses (`--harness claude|codex`); it runs on
35
+ [Bun](https://bun.sh), so install Bun and keep it on `PATH` to use that command
36
+ (the rest of the CLI needs only Node ≥ 22). The curl-installed standalone binary
36
37
  above does not include the studio — use the npm install for `astrale studio`.
37
38
 
38
39
  > Pre-1.0, `npm install -g @astrale-os/cli` installs the latest published
@@ -139,6 +140,9 @@ CLI state lives under `~/.astrale/`:
139
140
 
140
141
  ## Development
141
142
 
143
+ Contributors use Node.js 24.18.0 and pnpm 11.13.1. The source-worktree runtime
144
+ contract is enforced separately from the published CLI's Node ≥ 22 contract.
145
+
142
146
  ```bash
143
147
  # From the workspace root
144
148
  pnpm install
package/dist/astrale.js CHANGED
@@ -2576,7 +2576,7 @@ var package_default;
2576
2576
  var init_package = __esm(() => {
2577
2577
  package_default = {
2578
2578
  name: "@astrale-os/cli",
2579
- version: "0.7.0-alpha.0",
2579
+ version: "0.8.1-alpha.0",
2580
2580
  description: "Astrale CLI — connect to existing Astrale kernels",
2581
2581
  keywords: [
2582
2582
  "astrale",
@@ -2606,8 +2606,7 @@ var init_package = __esm(() => {
2606
2606
  type: "module",
2607
2607
  exports: {
2608
2608
  "./keys": "./src/lib/keys.ts",
2609
- "./paths": "./src/lib/paths.ts",
2610
- "./connect-core": "./src/connect-core.ts"
2609
+ "./paths": "./src/lib/paths.ts"
2611
2610
  },
2612
2611
  publishConfig: {
2613
2612
  access: "public",
@@ -2618,7 +2617,7 @@ var init_package = __esm(() => {
2618
2617
  prepare: "husky",
2619
2618
  build: "bun scripts/build.ts",
2620
2619
  prepack: "bun scripts/build.ts",
2621
- typecheck: "tsgo --noEmit && tsgo --noEmit -p viewer",
2620
+ typecheck: "tsgo --noEmit && tsgo --noEmit -p viewer && tsgo --noEmit -p studio",
2622
2621
  lint: "oxlint .",
2623
2622
  "lint:fix": "oxlint --fix .",
2624
2623
  format: "pnpm exec oxfmt --write .",
@@ -2664,10 +2663,17 @@ var init_package = __esm(() => {
2664
2663
  "oxfmt --write"
2665
2664
  ]
2666
2665
  },
2666
+ devEngines: {
2667
+ runtime: {
2668
+ name: "node",
2669
+ version: ">=24 <25",
2670
+ onFail: "error"
2671
+ }
2672
+ },
2667
2673
  engines: {
2668
- node: ">=20"
2674
+ node: ">=22"
2669
2675
  },
2670
- packageManager: "pnpm@10.28.0"
2676
+ packageManager: "pnpm@11.13.1"
2671
2677
  };
2672
2678
  });
2673
2679
 
@@ -59080,24 +59086,14 @@ async function obtainToken(idp, opts, scope2) {
59080
59086
  scope: scope2,
59081
59087
  audience: opts.audience
59082
59088
  });
59083
- if (opts.onVerification) {
59084
- opts.onVerification({
59085
- verificationUri: device.verification_uri ?? device.verification_uri_complete ?? "",
59086
- verificationUriComplete: device.verification_uri_complete,
59087
- userCode: device.user_code,
59088
- expiresIn: device.expires_in,
59089
- message: device.message
59090
- });
59091
- } else {
59092
- if (device.verification_uri_complete)
59093
- log.info(`Open: ${device.verification_uri_complete}`);
59094
- else if (device.verification_uri)
59095
- log.info(`Open: ${device.verification_uri}`);
59096
- if (device.user_code)
59097
- log.info(`Code: ${device.user_code}`);
59098
- if (device.message)
59099
- log.dim(` ${device.message}`);
59100
- }
59089
+ if (device.verification_uri_complete)
59090
+ log.info(`Open: ${device.verification_uri_complete}`);
59091
+ else if (device.verification_uri)
59092
+ log.info(`Open: ${device.verification_uri}`);
59093
+ if (device.user_code)
59094
+ log.info(`Code: ${device.user_code}`);
59095
+ if (device.message)
59096
+ log.dim(` ${device.message}`);
59101
59097
  return pollDeviceToken({
59102
59098
  idp,
59103
59099
  deviceCode: device.device_code,
@@ -69909,11 +69905,59 @@ var init_resolve7 = __esm(() => {
69909
69905
  VIEW_PATH_RE = /^\/:[^\s/:@]+:view\.[a-z][a-z0-9-]*$/;
69910
69906
  });
69911
69907
 
69912
- // src/lib/view/server.ts
69908
+ // src/lib/view/assets.ts
69913
69909
  import { existsSync as existsSync5 } from "node:fs";
69910
+ import { copyFile as copyFile2 } from "node:fs/promises";
69911
+ import { dirname as dirname8, join as join12 } from "node:path";
69912
+ import { fileURLToPath } from "node:url";
69913
+ function viewerDistDir(moduleUrl = import.meta.url, entry = process.argv[1] ?? ".") {
69914
+ const override = process.env.ASTRALE_VIEWER_DIR;
69915
+ if (override)
69916
+ return override;
69917
+ const moduleDirectory = dirname8(fileURLToPath(moduleUrl));
69918
+ const published = join12(moduleDirectory, "..", "viewer", "dist");
69919
+ const source = join12(moduleDirectory, "..", "..", "..", "viewer", "dist");
69920
+ const legacy = join12(dirname8(entry), "..", "viewer", "dist");
69921
+ const complete = [published, source, legacy].find(hasViewerBundle);
69922
+ if (complete)
69923
+ return complete;
69924
+ if (hasViewerSource(join12(source, "..")))
69925
+ return source;
69926
+ return published;
69927
+ }
69928
+ async function ensureViewerAssets(moduleUrl = import.meta.url, entry = process.argv[1] ?? ".") {
69929
+ const dist = viewerDistDir(moduleUrl, entry);
69930
+ if (hasViewerBundle(dist))
69931
+ return dist;
69932
+ const srcDir = join12(dist, "..");
69933
+ const bun = globalThis.Bun;
69934
+ if (bun && hasViewerSource(srcDir)) {
69935
+ const result = await bun.build({
69936
+ entrypoints: [join12(srcDir, "main.ts")],
69937
+ outdir: dist,
69938
+ target: "browser",
69939
+ minify: false
69940
+ });
69941
+ if (!result.success)
69942
+ throw new Error(`viewer build failed: ${result.logs.join(`
69943
+ `)}`);
69944
+ await copyFile2(join12(srcDir, "index.html"), join12(dist, "index.html"));
69945
+ return dist;
69946
+ }
69947
+ throw new Error(`viewer bundle missing at ${dist} — reinstall the CLI (or run \`bun scripts/build.ts\` in a dev checkout)`);
69948
+ }
69949
+ function hasViewerBundle(directory) {
69950
+ return existsSync5(join12(directory, "main.js")) && existsSync5(join12(directory, "index.html"));
69951
+ }
69952
+ function hasViewerSource(directory) {
69953
+ return existsSync5(join12(directory, "main.ts")) && existsSync5(join12(directory, "index.html"));
69954
+ }
69955
+ var init_assets = () => {};
69956
+
69957
+ // src/lib/view/server.ts
69914
69958
  import { readFile as readFile11 } from "node:fs/promises";
69915
69959
  import { createServer } from "node:http";
69916
- import { dirname as dirname8, join as join12 } from "node:path";
69960
+ import { join as join13 } from "node:path";
69917
69961
  import { Readable } from "node:stream";
69918
69962
  function startViewServer(config2) {
69919
69963
  const { session, proxy } = config2;
@@ -69971,11 +70015,11 @@ function startViewServer(config2) {
69971
70015
  return;
69972
70016
  }
69973
70017
  if (sub === "/" || sub === "/index.html") {
69974
- await serveAsset(res, join12(hostDir, "index.html"), "text/html; charset=utf-8");
70018
+ await serveAsset(res, join13(hostDir, "index.html"), "text/html; charset=utf-8");
69975
70019
  return;
69976
70020
  }
69977
70021
  if (sub === "/main.js") {
69978
- await serveAsset(res, join12(hostDir, "main.js"), "text/javascript; charset=utf-8");
70022
+ await serveAsset(res, join13(hostDir, "main.js"), "text/javascript; charset=utf-8");
69979
70023
  return;
69980
70024
  }
69981
70025
  if (sub === "/config.json" && req.method === "GET") {
@@ -70060,34 +70104,6 @@ function startViewServer(config2) {
70060
70104
  server.listen(session.port, "127.0.0.1");
70061
70105
  return server;
70062
70106
  }
70063
- function viewerDistDir() {
70064
- const override = process.env.ASTRALE_VIEWER_DIR;
70065
- if (override)
70066
- return override;
70067
- return join12(dirname8(process.argv[1] ?? "."), "..", "viewer", "dist");
70068
- }
70069
- async function ensureViewerAssets() {
70070
- const dist = viewerDistDir();
70071
- if (existsSync5(join12(dist, "main.js")) && existsSync5(join12(dist, "index.html")))
70072
- return dist;
70073
- const srcDir = join12(dist, "..");
70074
- const bun = globalThis.Bun;
70075
- if (bun && existsSync5(join12(srcDir, "main.ts"))) {
70076
- const result = await bun.build({
70077
- entrypoints: [join12(srcDir, "main.ts")],
70078
- outdir: dist,
70079
- target: "browser",
70080
- minify: false
70081
- });
70082
- if (!result.success)
70083
- throw new Error(`viewer build failed: ${result.logs.join(`
70084
- `)}`);
70085
- const { copyFile: copyFile2 } = await import("node:fs/promises");
70086
- await copyFile2(join12(srcDir, "index.html"), join12(dist, "index.html"));
70087
- return dist;
70088
- }
70089
- throw new Error(`viewer bundle missing at ${dist} — reinstall the CLI (or run \`bun scripts/build.ts\` in a dev checkout)`);
70090
- }
70091
70107
  function jwtExpiry(token) {
70092
70108
  try {
70093
70109
  const payload = JSON.parse(Buffer.from(token.split(".")[1], "base64url").toString("utf8"));
@@ -70144,7 +70160,9 @@ var TOKEN_REFRESH_MARGIN_MS, FALLBACK_TOKEN_TTL_MS = 3600000, IDLE_SWEEP_MS = 60
70144
70160
  var init_server = __esm(() => {
70145
70161
  init_kernel2();
70146
70162
  init_ca_fetch();
70163
+ init_assets();
70147
70164
  init_session2();
70165
+ init_assets();
70148
70166
  TOKEN_REFRESH_MARGIN_MS = 5 * 60000;
70149
70167
  });
70150
70168
 
@@ -70225,7 +70243,7 @@ __export(exports_view, {
70225
70243
  import { randomBytes } from "node:crypto";
70226
70244
  import { closeSync, existsSync as existsSync6, openSync, statSync } from "node:fs";
70227
70245
  import { mkdir as mkdir10, readdir as readdir3, readFile as readFile12, writeFile as writeFile9 } from "node:fs/promises";
70228
- import { dirname as dirname9, join as join13 } from "node:path";
70246
+ import { dirname as dirname9, join as join14 } from "node:path";
70229
70247
  async function resolveSession(spec, opts) {
70230
70248
  const parsed = spec ? parseViewSpec(spec) : undefined;
70231
70249
  if (parsed?.kind === "target" && opts.target) {
@@ -70301,7 +70319,7 @@ async function resolveServeRuntime() {
70301
70319
  if (node2 && entry?.endsWith(".js") && existsSync6(entry))
70302
70320
  return { file: node2, args: [entry] };
70303
70321
  if (node2 && entry?.endsWith(".ts")) {
70304
- const dist = join13(dirname9(entry), "..", "dist", "astrale.js");
70322
+ const dist = join14(dirname9(entry), "..", "dist", "astrale.js");
70305
70323
  await ensureDevDist(entry, dist);
70306
70324
  if (existsSync6(dist))
70307
70325
  return { file: node2, args: [dist] };
@@ -70319,7 +70337,7 @@ async function ensureDevDist(entry, dist) {
70319
70337
  const bun = globalThis.Bun;
70320
70338
  if (!bun)
70321
70339
  return;
70322
- const srcDir = join13(dirname9(entry), "..", "src");
70340
+ const srcDir = join14(dirname9(entry), "..", "src");
70323
70341
  if (existsSync6(dist) && !await newerThan(srcDir, statSync(dist).mtimeMs))
70324
70342
  return;
70325
70343
  console.error("(dev) building dist/astrale.js for the session server…");
@@ -70335,7 +70353,7 @@ async function newerThan(dir, mtimeMs) {
70335
70353
  for (const item of entries) {
70336
70354
  if (!item.isFile())
70337
70355
  continue;
70338
- if (statSync(join13(item.parentPath, item.name)).mtimeMs > mtimeMs)
70356
+ if (statSync(join14(item.parentPath, item.name)).mtimeMs > mtimeMs)
70339
70357
  return true;
70340
70358
  }
70341
70359
  return false;
@@ -70944,28 +70962,28 @@ __export(exports_studio, {
70944
70962
  default: () => studio_default
70945
70963
  });
70946
70964
  import { existsSync as existsSync8, realpathSync } from "node:fs";
70947
- import { dirname as dirname10, join as join14, resolve } from "node:path";
70965
+ import { dirname as dirname10, join as join15, resolve } from "node:path";
70948
70966
  function resolveStudioDir() {
70949
70967
  const candidates = [];
70950
70968
  if (process.env.ASTRALE_STUDIO_DIR)
70951
70969
  candidates.push(process.env.ASTRALE_STUDIO_DIR);
70952
70970
  try {
70953
70971
  const entryDir = dirname10(realpathSync(process.argv[1] ?? ""));
70954
- candidates.push(join14(entryDir, "..", "studio"), join14(entryDir, "studio"));
70972
+ candidates.push(join15(entryDir, "..", "studio"), join15(entryDir, "studio"));
70955
70973
  } catch {}
70956
70974
  for (const c of candidates) {
70957
- if (existsSync8(join14(c, "server", "index.ts")))
70975
+ if (existsSync8(join15(c, "server", "index.ts")))
70958
70976
  return resolve(c);
70959
70977
  }
70960
70978
  throw new Error(`Domain Studio assets not found (looked in: ${candidates.join(", ") || "<none>"}). ` + `Reinstall the astrale CLI, or set ASTRALE_STUDIO_DIR to a studio checkout.`);
70961
70979
  }
70962
70980
  function isDevSource(studioDir) {
70963
- return existsSync8(join14(studioDir, "vite.config.ts")) && existsSync8(join14(studioDir, "client", "src"));
70981
+ return existsSync8(join15(studioDir, "vite.config.ts")) && existsSync8(join15(studioDir, "client", "src"));
70964
70982
  }
70965
70983
  function resolveViteBin(studioDir) {
70966
70984
  for (const c of [
70967
- join14(studioDir, "node_modules", ".bin", "vite"),
70968
- join14(studioDir, "..", "..", "node_modules", ".bin", "vite")
70985
+ join15(studioDir, "node_modules", ".bin", "vite"),
70986
+ join15(studioDir, "..", "..", "node_modules", ".bin", "vite")
70969
70987
  ]) {
70970
70988
  if (existsSync8(c))
70971
70989
  return c;
@@ -71040,6 +71058,11 @@ var init_studio = __esm(() => {
71040
71058
  },
71041
71059
  { flags: "--prod", description: "Serve the prebuilt client (the default)" },
71042
71060
  { flags: "--schema-dir <dir>", description: "Schema entry dir to scan for (default: schema)" },
71061
+ {
71062
+ flags: "--harness <name>",
71063
+ description: "Lock the agent harness for this Studio process",
71064
+ choices: ["claude", "codex"]
71065
+ },
71043
71066
  ...RAW_OUTPUT_OPTIONS
71044
71067
  ],
71045
71068
  afterHelpText: `
@@ -71063,6 +71086,10 @@ Behavior:
71063
71086
  By default the command just PRINTS the URL — it does not pop a browser (that's
71064
71087
  invasive when you already have a tab open). Pass --open to launch one.
71065
71088
 
71089
+ Agent harness: choose Claude Code or Codex per domain in Studio Settings. Pass
71090
+ --harness claude|codex to lock this process to one harness. Each harness uses
71091
+ its existing local authentication and keeps an independent resumable session.
71092
+
71066
71093
  The command stays attached and supervises its child processes; Ctrl-C tears
71067
71094
  them all down (a second Ctrl-C force-kills). With --json it prints a
71068
71095
  { url, port, mode, workspace } descriptor.
@@ -71071,6 +71098,7 @@ Examples:
71071
71098
  $ astrale studio # start the studio + print its URL (no browser)
71072
71099
  $ astrale studio --open # …and open it in a browser
71073
71100
  $ astrale studio ./my-domain
71101
+ $ astrale studio --harness codex
71074
71102
  $ astrale studio --port 4400
71075
71103
  $ astrale studio --dev # live-edit the studio itself (from source)
71076
71104
  `,
@@ -71173,12 +71201,13 @@ Examples:
71173
71201
  DOMAIN_STUDIO_DEV: "1",
71174
71202
  VITE_URL: `http://127.0.0.1:${vitePort}`,
71175
71203
  PORT: String(studioPort),
71176
- DOMAIN_STUDIO_HOST: "127.0.0.1"
71204
+ DOMAIN_STUDIO_HOST: "127.0.0.1",
71205
+ ...opts.harness ? { DOMAIN_STUDIO_HARNESS: opts.harness } : {}
71177
71206
  }
71178
71207
  }), "server");
71179
71208
  } else {
71180
- const dist = join14(studioDir, "client", "dist");
71181
- if (!existsSync8(join14(dist, "index.html"))) {
71209
+ const dist = join15(studioDir, "client", "dist");
71210
+ if (!existsSync8(join15(dist, "index.html"))) {
71182
71211
  throw new Error(`studio client not built at ${dist} — run: pnpm --filter @astrale-os/studio build`);
71183
71212
  }
71184
71213
  serverChild = supervise(spawnHandle("bun", ["server/index.ts", workspace, "--port", String(studioPort), "--no-open"], {
@@ -71188,7 +71217,8 @@ Examples:
71188
71217
  ...process.env,
71189
71218
  DOMAIN_STUDIO_DIST: dist,
71190
71219
  PORT: String(studioPort),
71191
- DOMAIN_STUDIO_HOST: "127.0.0.1"
71220
+ DOMAIN_STUDIO_HOST: "127.0.0.1",
71221
+ ...opts.harness ? { DOMAIN_STUDIO_HARNESS: opts.harness } : {}
71192
71222
  }
71193
71223
  }), "server");
71194
71224
  }
@@ -73033,21 +73063,21 @@ var init_status4 = __esm(() => {
73033
73063
 
73034
73064
  // src/telemetry/store.ts
73035
73065
  import { existsSync as existsSync9, readdirSync as readdirSync2, readFileSync as readFileSync3, statSync as statSync2 } from "node:fs";
73036
- import { join as join15 } from "node:path";
73066
+ import { join as join16 } from "node:path";
73037
73067
  function sessionsRoot() {
73038
- return join15(createPaths().home, "sessions");
73068
+ return join16(createPaths().home, "sessions");
73039
73069
  }
73040
73070
  function sessionDir(id) {
73041
- return join15(sessionsRoot(), id);
73071
+ return join16(sessionsRoot(), id);
73042
73072
  }
73043
73073
  function eventsPath(id) {
73044
- return join15(sessionDir(id), "events.jsonl");
73074
+ return join16(sessionDir(id), "events.jsonl");
73045
73075
  }
73046
73076
  function metaPath(id) {
73047
- return join15(sessionDir(id), "meta.json");
73077
+ return join16(sessionDir(id), "meta.json");
73048
73078
  }
73049
73079
  function markerPath(id) {
73050
- return join15(sessionDir(id), ".analyzed");
73080
+ return join16(sessionDir(id), ".analyzed");
73051
73081
  }
73052
73082
  function readJsonSafe(path4) {
73053
73083
  try {
@@ -73125,12 +73155,12 @@ var init_list4 = __esm(() => {
73125
73155
  // src/telemetry/adapters/claude-code.ts
73126
73156
  import { existsSync as existsSync10, readdirSync as readdirSync3, statSync as statSync3 } from "node:fs";
73127
73157
  import { homedir as homedir3 } from "node:os";
73128
- import { join as join16 } from "node:path";
73158
+ import { join as join17 } from "node:path";
73129
73159
  function mungeCwd(cwd) {
73130
73160
  return cwd.replace(/[^a-zA-Z0-9]/g, "-");
73131
73161
  }
73132
- function claudeCodeAdapter(base2 = join16(homedir3(), ".claude")) {
73133
- const projectsDir = join16(base2, "projects");
73162
+ function claudeCodeAdapter(base2 = join17(homedir3(), ".claude")) {
73163
+ const projectsDir = join17(base2, "projects");
73134
73164
  function detect2() {
73135
73165
  try {
73136
73166
  return existsSync10(projectsDir);
@@ -73154,7 +73184,7 @@ function claudeCodeAdapter(base2 = join16(homedir3(), ".claude")) {
73154
73184
  for (const dir of dirs) {
73155
73185
  if (dir !== munged && !dir.startsWith(prefix))
73156
73186
  continue;
73157
- const projectPath = join16(projectsDir, dir);
73187
+ const projectPath = join17(projectsDir, dir);
73158
73188
  let files;
73159
73189
  try {
73160
73190
  files = readdirSync3(projectPath);
@@ -73164,7 +73194,7 @@ function claudeCodeAdapter(base2 = join16(homedir3(), ".claude")) {
73164
73194
  for (const file2 of files) {
73165
73195
  if (!file2.endsWith(".jsonl"))
73166
73196
  continue;
73167
- const transcriptPath = join16(projectPath, file2);
73197
+ const transcriptPath = join17(projectPath, file2);
73168
73198
  try {
73169
73199
  const st = statSync3(transcriptPath);
73170
73200
  const mtimeMs = st.mtime.getTime();
@@ -73200,7 +73230,7 @@ var init_claude_code = __esm(() => {
73200
73230
  import { existsSync as existsSync11, openSync as openSync2, readdirSync as readdirSync4, readSync, statSync as statSync4 } from "node:fs";
73201
73231
  import { closeSync as closeSync2 } from "node:fs";
73202
73232
  import { homedir as homedir4 } from "node:os";
73203
- import { join as join17 } from "node:path";
73233
+ import { join as join18 } from "node:path";
73204
73234
  function readFirstLine(path4) {
73205
73235
  let fd = null;
73206
73236
  try {
@@ -73240,8 +73270,8 @@ function numericDirs(path4) {
73240
73270
  return [];
73241
73271
  }
73242
73272
  }
73243
- function codexAdapter(base2 = join17(homedir4(), ".codex")) {
73244
- const sessionsDir = join17(base2, "sessions");
73273
+ function codexAdapter(base2 = join18(homedir4(), ".codex")) {
73274
+ const sessionsDir = join18(base2, "sessions");
73245
73275
  function detect2() {
73246
73276
  try {
73247
73277
  return existsSync11(sessionsDir);
@@ -73256,12 +73286,12 @@ function codexAdapter(base2 = join17(homedir4(), ".codex")) {
73256
73286
  const endMs = window2.end.getTime();
73257
73287
  const lowerMs = startMs - DAY_MS;
73258
73288
  for (const yyyy of numericDirs(sessionsDir)) {
73259
- for (const mm of numericDirs(join17(sessionsDir, yyyy))) {
73260
- for (const dd of numericDirs(join17(sessionsDir, yyyy, mm))) {
73289
+ for (const mm of numericDirs(join18(sessionsDir, yyyy))) {
73290
+ for (const dd of numericDirs(join18(sessionsDir, yyyy, mm))) {
73261
73291
  const dayStart = Date.UTC(Number(yyyy), Number(mm) - 1, Number(dd));
73262
73292
  if (dayStart > endMs + DAY_MS || dayStart + DAY_MS <= lowerMs)
73263
73293
  continue;
73264
- scanDay(join17(sessionsDir, yyyy, mm, dd), root, startMs, endMs, sessions);
73294
+ scanDay(join18(sessionsDir, yyyy, mm, dd), root, startMs, endMs, sessions);
73265
73295
  }
73266
73296
  }
73267
73297
  }
@@ -73283,7 +73313,7 @@ function scanDay(dayPath, root, startMs, endMs, out) {
73283
73313
  for (const file2 of files) {
73284
73314
  if (!file2.startsWith("rollout-") || !file2.endsWith(".jsonl"))
73285
73315
  continue;
73286
- const transcriptPath = join17(dayPath, file2);
73316
+ const transcriptPath = join18(dayPath, file2);
73287
73317
  try {
73288
73318
  const st = statSync4(transcriptPath);
73289
73319
  const mtimeMs = st.mtime.getTime();
@@ -73430,7 +73460,7 @@ var init_gate = () => {};
73430
73460
  // src/telemetry/analyze.ts
73431
73461
  import { spawn as spawn3 } from "node:child_process";
73432
73462
  import { appendFileSync, writeFileSync as writeFileSync2 } from "node:fs";
73433
- import { join as join18 } from "node:path";
73463
+ import { join as join19 } from "node:path";
73434
73464
  function writeMarker(id, marker) {
73435
73465
  writeFileSync2(markerPath(id), JSON.stringify(marker, null, 2) + `
73436
73466
  `);
@@ -73526,7 +73556,7 @@ async function analyzeSession(id, opts = {}) {
73526
73556
  const guides = new Map(adapters.map((a) => [a.name, a.readingGuide]));
73527
73557
  const prompt = buildPrompt({ id, root, signals: signals2, guides, file: opts.file ?? false });
73528
73558
  const dir = sessionDir(id);
73529
- writeFileSync2(join18(dir, "analyzer-prompt.md"), prompt);
73559
+ writeFileSync2(join19(dir, "analyzer-prompt.md"), prompt);
73530
73560
  const outcome = await runClaude(prompt, dir, opts);
73531
73561
  const marker = {
73532
73562
  analyzedAt: new Date().toISOString(),
@@ -73534,7 +73564,7 @@ async function analyzeSession(id, opts = {}) {
73534
73564
  note: outcome.note
73535
73565
  };
73536
73566
  writeMarker(id, marker);
73537
- return { ...marker, reportPath: join18(dir, "report.md") };
73567
+ return { ...marker, reportPath: join19(dir, "report.md") };
73538
73568
  }
73539
73569
  function runClaude(prompt, cwd, opts) {
73540
73570
  return new Promise((resolve2) => {
@@ -73564,7 +73594,7 @@ function runClaude(prompt, cwd, opts) {
73564
73594
  child.on("close", (code) => {
73565
73595
  clearTimeout(timer);
73566
73596
  try {
73567
- appendFileSync(join18(cwd, "analyzer.log"), out + (err ? `
73597
+ appendFileSync(join19(cwd, "analyzer.log"), out + (err ? `
73568
73598
  --- stderr ---
73569
73599
  ${err}` : ""));
73570
73600
  } catch {}
@@ -73614,9 +73644,9 @@ var init_settings = __esm(() => {
73614
73644
 
73615
73645
  // src/telemetry/trigger.ts
73616
73646
  import { existsSync as existsSync12, mkdirSync as mkdirSync2, readFileSync as readFileSync6, rmSync as rmSync2, unlinkSync, writeFileSync as writeFileSync3 } from "node:fs";
73617
- import { join as join19 } from "node:path";
73647
+ import { join as join20 } from "node:path";
73618
73648
  function lockPath() {
73619
- return join19(sessionsRoot(), ".analyzer.lock");
73649
+ return join20(sessionsRoot(), ".analyzer.lock");
73620
73650
  }
73621
73651
  function releaseLock() {
73622
73652
  try {
@@ -74375,12 +74405,12 @@ function redactArgv(argv) {
74375
74405
  init_store2();
74376
74406
  import { createHash as createHash2 } from "node:crypto";
74377
74407
  import { existsSync as existsSync13, mkdirSync as mkdirSync3, writeFileSync as writeFileSync4 } from "node:fs";
74378
- import { dirname as dirname12, join as join20 } from "node:path";
74408
+ import { dirname as dirname12, join as join21 } from "node:path";
74379
74409
  var MAX_ID_LEN = 64;
74380
74410
  function findGitRoot(start) {
74381
74411
  let dir = start;
74382
74412
  for (;; ) {
74383
- if (existsSync13(join20(dir, ".git")))
74413
+ if (existsSync13(join21(dir, ".git")))
74384
74414
  return dir;
74385
74415
  const parent = dirname12(dir);
74386
74416
  if (parent === dir)
@@ -74481,7 +74511,7 @@ init_settings();
74481
74511
  init_store2();
74482
74512
  import { spawn as spawn4 } from "node:child_process";
74483
74513
  import { existsSync as existsSync14, mkdirSync as mkdirSync4, readFileSync as readFileSync7, rmSync as rmSync3, unlinkSync as unlinkSync2, writeFileSync as writeFileSync5 } from "node:fs";
74484
- import { join as join21 } from "node:path";
74514
+ import { join as join22 } from "node:path";
74485
74515
  var LOCK_STALE_MS2 = 30 * 60 * 1000;
74486
74516
  var GC_AGE_MS2 = 30 * 24 * 60 * 60 * 1000;
74487
74517
  var GC_MAX_PER_RUN = 20;
@@ -74500,7 +74530,7 @@ function gcOldSessions(sessions) {
74500
74530
  } catch {}
74501
74531
  }
74502
74532
  function lockPath2() {
74503
- return join21(sessionsRoot(), ".analyzer.lock");
74533
+ return join22(sessionsRoot(), ".analyzer.lock");
74504
74534
  }
74505
74535
  function claimLock() {
74506
74536
  const path4 = lockPath2();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astrale-os/cli",
3
- "version": "0.7.0-alpha.0",
3
+ "version": "0.8.1-alpha.0",
4
4
  "description": "Astrale CLI — connect to existing Astrale kernels",
5
5
  "keywords": [
6
6
  "astrale",
@@ -30,8 +30,7 @@
30
30
  "type": "module",
31
31
  "exports": {
32
32
  "./keys": "./src/lib/keys.ts",
33
- "./paths": "./src/lib/paths.ts",
34
- "./connect-core": "./src/connect-core.ts"
33
+ "./paths": "./src/lib/paths.ts"
35
34
  },
36
35
  "publishConfig": {
37
36
  "access": "public",
@@ -75,13 +74,20 @@
75
74
  "oxfmt --write"
76
75
  ]
77
76
  },
77
+ "devEngines": {
78
+ "runtime": {
79
+ "name": "node",
80
+ "version": ">=24 <25",
81
+ "onFail": "error"
82
+ }
83
+ },
78
84
  "engines": {
79
- "node": ">=20"
85
+ "node": ">=22"
80
86
  },
81
87
  "scripts": {
82
88
  "preinstall": "node .check-workspace.cjs",
83
89
  "build": "bun scripts/build.ts",
84
- "typecheck": "tsgo --noEmit && tsgo --noEmit -p viewer",
90
+ "typecheck": "tsgo --noEmit && tsgo --noEmit -p viewer && tsgo --noEmit -p studio",
85
91
  "lint": "oxlint .",
86
92
  "lint:fix": "oxlint --fix .",
87
93
  "format": "pnpm exec oxfmt --write .",
@@ -16,6 +16,7 @@ type StudioOpts = RawOutputOpts & {
16
16
  dev?: boolean
17
17
  prod?: boolean
18
18
  schemaDir?: string
19
+ harness?: string
19
20
  }
20
21
 
21
22
  // Uncommon, safe bands in the IANA Registered range (well below the OS
@@ -162,6 +163,11 @@ export default {
162
163
  },
163
164
  { flags: '--prod', description: 'Serve the prebuilt client (the default)' },
164
165
  { flags: '--schema-dir <dir>', description: 'Schema entry dir to scan for (default: schema)' },
166
+ {
167
+ flags: '--harness <name>',
168
+ description: 'Lock the agent harness for this Studio process',
169
+ choices: ['claude', 'codex'],
170
+ },
165
171
  ...RAW_OUTPUT_OPTIONS,
166
172
  ],
167
173
  afterHelpText: `
@@ -185,6 +191,10 @@ Behavior:
185
191
  By default the command just PRINTS the URL — it does not pop a browser (that's
186
192
  invasive when you already have a tab open). Pass --open to launch one.
187
193
 
194
+ Agent harness: choose Claude Code or Codex per domain in Studio Settings. Pass
195
+ --harness claude|codex to lock this process to one harness. Each harness uses
196
+ its existing local authentication and keeps an independent resumable session.
197
+
188
198
  The command stays attached and supervises its child processes; Ctrl-C tears
189
199
  them all down (a second Ctrl-C force-kills). With --json it prints a
190
200
  { url, port, mode, workspace } descriptor.
@@ -193,6 +203,7 @@ Examples:
193
203
  $ astrale studio # start the studio + print its URL (no browser)
194
204
  $ astrale studio --open # …and open it in a browser
195
205
  $ astrale studio ./my-domain
206
+ $ astrale studio --harness codex
196
207
  $ astrale studio --port 4400
197
208
  $ astrale studio --dev # live-edit the studio itself (from source)
198
209
  `,
@@ -331,6 +342,7 @@ Examples:
331
342
  VITE_URL: `http://127.0.0.1:${vitePort}`,
332
343
  PORT: String(studioPort),
333
344
  DOMAIN_STUDIO_HOST: '127.0.0.1',
345
+ ...(opts.harness ? { DOMAIN_STUDIO_HARNESS: opts.harness } : {}),
334
346
  },
335
347
  },
336
348
  ),
@@ -355,6 +367,7 @@ Examples:
355
367
  DOMAIN_STUDIO_DIST: dist,
356
368
  PORT: String(studioPort),
357
369
  DOMAIN_STUDIO_HOST: '127.0.0.1',
370
+ ...(opts.harness ? { DOMAIN_STUDIO_HARNESS: opts.harness } : {}),
358
371
  },
359
372
  },
360
373
  ),