@astrale-os/cli 0.7.0-alpha.0 → 0.8.0-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 (105) hide show
  1. package/README.md +3 -2
  2. package/dist/astrale.js +118 -95
  3. package/package.json +2 -3
  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/index-BaqEuIQJ.js +109 -0
  11. package/studio/client/dist/assets/index-jRdExahh.css +1 -0
  12. package/studio/client/dist/index.html +2 -2
  13. package/studio/server/agent/ask.test.ts +74 -0
  14. package/studio/server/agent/ask.ts +20 -22
  15. package/studio/server/agent/bridge/client.test.ts +49 -0
  16. package/studio/server/agent/bridge/client.ts +37 -0
  17. package/studio/server/agent/bridge/grant.test.ts +62 -0
  18. package/studio/server/agent/bridge/grant.ts +84 -0
  19. package/studio/server/agent/bridge/routes.test.ts +151 -0
  20. package/studio/server/agent/bridge/routes.ts +148 -0
  21. package/studio/server/agent/{bridge-mcp.ts → bridge/stdio.ts} +27 -21
  22. package/studio/server/agent/conversation.test.ts +84 -0
  23. package/studio/server/agent/conversation.ts +110 -0
  24. package/studio/server/agent/{types.ts → harness/adapter.ts} +63 -12
  25. package/studio/server/agent/harness/claude/adapter.test.ts +338 -0
  26. package/studio/server/agent/harness/claude/adapter.ts +71 -0
  27. package/studio/server/agent/harness/claude/ask.ts +114 -0
  28. package/studio/server/agent/harness/claude/capabilities.ts +27 -0
  29. package/studio/server/agent/harness/claude/command.ts +63 -0
  30. package/studio/server/agent/harness/claude/events.ts +215 -0
  31. package/studio/server/agent/harness/claude/loadout.ts +132 -0
  32. package/studio/server/agent/harness/claude/mcp.test.ts +36 -0
  33. package/studio/server/agent/harness/claude/mcp.ts +41 -0
  34. package/studio/server/agent/harness/claude/skills.ts +43 -0
  35. package/studio/server/agent/harness/codex/adapter.test.ts +269 -0
  36. package/studio/server/agent/harness/codex/adapter.ts +116 -0
  37. package/studio/server/agent/harness/codex/ask.test.ts +176 -0
  38. package/studio/server/agent/harness/codex/ask.ts +181 -0
  39. package/studio/server/agent/harness/codex/command.test.ts +69 -0
  40. package/studio/server/agent/harness/codex/command.ts +43 -0
  41. package/studio/server/agent/harness/codex/events.test.ts +94 -0
  42. package/studio/server/agent/harness/codex/events.ts +132 -0
  43. package/studio/server/agent/harness/codex/exec.ts +113 -0
  44. package/studio/server/agent/harness/codex/loadout.ts +63 -0
  45. package/studio/server/agent/harness/codex/mcp.test.ts +32 -0
  46. package/studio/server/agent/harness/codex/mcp.ts +27 -0
  47. package/studio/server/agent/harness/codex/models.test.ts +167 -0
  48. package/studio/server/agent/harness/codex/models.ts +211 -0
  49. package/studio/server/agent/harness/codex/skills.ts +33 -0
  50. package/studio/server/agent/harness/gateway/config.test.ts +98 -0
  51. package/studio/server/{state/harness-gateway.ts → agent/harness/gateway/config.ts} +30 -8
  52. package/studio/server/agent/harness/gateway/token.test.ts +133 -0
  53. package/studio/server/agent/harness/gateway/token.ts +166 -0
  54. package/studio/server/agent/harness/mock/adapter.ts +190 -0
  55. package/studio/server/agent/harness/mock/domain-edit.ts +41 -0
  56. package/studio/server/agent/harness/process.test.ts +46 -0
  57. package/studio/server/agent/harness/process.ts +104 -0
  58. package/studio/server/agent/harness/registry.ts +37 -0
  59. package/studio/server/agent/harness/selection.test.ts +75 -0
  60. package/studio/server/agent/harness/selection.ts +77 -0
  61. package/studio/server/agent/harness/skills.test.ts +95 -0
  62. package/studio/server/agent/harness/skills.ts +100 -0
  63. package/studio/server/agent/layout.test.ts +95 -0
  64. package/studio/server/agent/notify.ts +12 -0
  65. package/studio/server/agent/{schema-map.ts → prompts/anchors.ts} +2 -2
  66. package/studio/server/agent/prompts/ask.ts +34 -0
  67. package/studio/server/agent/{prompt.ts → prompts/system.ts} +3 -107
  68. package/studio/server/agent/prompts/turn.ts +57 -0
  69. package/studio/server/agent/routes.test.ts +176 -0
  70. package/studio/server/agent/routes.ts +214 -0
  71. package/studio/server/agent/run/completion.ts +193 -0
  72. package/studio/server/agent/run/coordinator.test.ts +337 -0
  73. package/studio/server/agent/run/coordinator.ts +104 -0
  74. package/studio/server/agent/run/live-state.ts +73 -0
  75. package/studio/server/agent/run/preparation.ts +162 -0
  76. package/studio/server/agent/run/transcript.test.ts +47 -0
  77. package/studio/server/agent/run/transcript.ts +30 -0
  78. package/studio/server/agent/run/usage.test.ts +46 -0
  79. package/studio/server/{state → agent/run}/usage.ts +4 -4
  80. package/studio/server/agent/stream.ts +37 -0
  81. package/studio/server/agent/{session-id.ts → telemetry.ts} +1 -1
  82. package/studio/server/api-agent-loadout.test.ts +69 -0
  83. package/studio/server/api.ts +3 -155
  84. package/studio/server/index.ts +6 -5
  85. package/studio/server/state/comments.test.ts +29 -0
  86. package/studio/server/state/comments.ts +3 -3
  87. package/studio/server/state/settings.test.ts +78 -0
  88. package/studio/server/state/settings.ts +28 -1
  89. package/studio/shared/agent-effort.test.ts +12 -0
  90. package/studio/shared/agent-effort.ts +15 -0
  91. package/studio/shared/agent-models.test.ts +9 -0
  92. package/studio/shared/agent-models.ts +14 -0
  93. package/studio/shared/settings-values.test.ts +18 -0
  94. package/studio/shared/settings-values.ts +20 -0
  95. package/studio/shared/types.ts +58 -7
  96. package/src/connect-core.test.ts +0 -42
  97. package/src/connect-core.ts +0 -53
  98. package/studio/client/dist/assets/index-DAC1a9vW.js +0 -109
  99. package/studio/client/dist/assets/index-huaFafBC.css +0 -1
  100. package/studio/server/agent/bridge.ts +0 -188
  101. package/studio/server/agent/claude.ts +0 -678
  102. package/studio/server/agent/mock.ts +0 -186
  103. package/studio/server/agent/registry.ts +0 -29
  104. package/studio/server/agent/runner.ts +0 -487
  105. package/studio/server/state/harness-token.ts +0 -0
package/README.md CHANGED
@@ -30,8 +30,9 @@ 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
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
35
36
  (the rest of the CLI needs only Node ≥ 20). The curl-installed standalone binary
36
37
  above does not include the studio — use the npm install for `astrale studio`.
37
38
 
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.0-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",
@@ -59080,24 +59079,14 @@ async function obtainToken(idp, opts, scope2) {
59080
59079
  scope: scope2,
59081
59080
  audience: opts.audience
59082
59081
  });
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
- }
59082
+ if (device.verification_uri_complete)
59083
+ log.info(`Open: ${device.verification_uri_complete}`);
59084
+ else if (device.verification_uri)
59085
+ log.info(`Open: ${device.verification_uri}`);
59086
+ if (device.user_code)
59087
+ log.info(`Code: ${device.user_code}`);
59088
+ if (device.message)
59089
+ log.dim(` ${device.message}`);
59101
59090
  return pollDeviceToken({
59102
59091
  idp,
59103
59092
  deviceCode: device.device_code,
@@ -69909,11 +69898,59 @@ var init_resolve7 = __esm(() => {
69909
69898
  VIEW_PATH_RE = /^\/:[^\s/:@]+:view\.[a-z][a-z0-9-]*$/;
69910
69899
  });
69911
69900
 
69912
- // src/lib/view/server.ts
69901
+ // src/lib/view/assets.ts
69913
69902
  import { existsSync as existsSync5 } from "node:fs";
69903
+ import { copyFile as copyFile2 } from "node:fs/promises";
69904
+ import { dirname as dirname8, join as join12 } from "node:path";
69905
+ import { fileURLToPath } from "node:url";
69906
+ function viewerDistDir(moduleUrl = import.meta.url, entry = process.argv[1] ?? ".") {
69907
+ const override = process.env.ASTRALE_VIEWER_DIR;
69908
+ if (override)
69909
+ return override;
69910
+ const moduleDirectory = dirname8(fileURLToPath(moduleUrl));
69911
+ const published = join12(moduleDirectory, "..", "viewer", "dist");
69912
+ const source = join12(moduleDirectory, "..", "..", "..", "viewer", "dist");
69913
+ const legacy = join12(dirname8(entry), "..", "viewer", "dist");
69914
+ const complete = [published, source, legacy].find(hasViewerBundle);
69915
+ if (complete)
69916
+ return complete;
69917
+ if (hasViewerSource(join12(source, "..")))
69918
+ return source;
69919
+ return published;
69920
+ }
69921
+ async function ensureViewerAssets(moduleUrl = import.meta.url, entry = process.argv[1] ?? ".") {
69922
+ const dist = viewerDistDir(moduleUrl, entry);
69923
+ if (hasViewerBundle(dist))
69924
+ return dist;
69925
+ const srcDir = join12(dist, "..");
69926
+ const bun = globalThis.Bun;
69927
+ if (bun && hasViewerSource(srcDir)) {
69928
+ const result = await bun.build({
69929
+ entrypoints: [join12(srcDir, "main.ts")],
69930
+ outdir: dist,
69931
+ target: "browser",
69932
+ minify: false
69933
+ });
69934
+ if (!result.success)
69935
+ throw new Error(`viewer build failed: ${result.logs.join(`
69936
+ `)}`);
69937
+ await copyFile2(join12(srcDir, "index.html"), join12(dist, "index.html"));
69938
+ return dist;
69939
+ }
69940
+ throw new Error(`viewer bundle missing at ${dist} — reinstall the CLI (or run \`bun scripts/build.ts\` in a dev checkout)`);
69941
+ }
69942
+ function hasViewerBundle(directory) {
69943
+ return existsSync5(join12(directory, "main.js")) && existsSync5(join12(directory, "index.html"));
69944
+ }
69945
+ function hasViewerSource(directory) {
69946
+ return existsSync5(join12(directory, "main.ts")) && existsSync5(join12(directory, "index.html"));
69947
+ }
69948
+ var init_assets = () => {};
69949
+
69950
+ // src/lib/view/server.ts
69914
69951
  import { readFile as readFile11 } from "node:fs/promises";
69915
69952
  import { createServer } from "node:http";
69916
- import { dirname as dirname8, join as join12 } from "node:path";
69953
+ import { join as join13 } from "node:path";
69917
69954
  import { Readable } from "node:stream";
69918
69955
  function startViewServer(config2) {
69919
69956
  const { session, proxy } = config2;
@@ -69971,11 +70008,11 @@ function startViewServer(config2) {
69971
70008
  return;
69972
70009
  }
69973
70010
  if (sub === "/" || sub === "/index.html") {
69974
- await serveAsset(res, join12(hostDir, "index.html"), "text/html; charset=utf-8");
70011
+ await serveAsset(res, join13(hostDir, "index.html"), "text/html; charset=utf-8");
69975
70012
  return;
69976
70013
  }
69977
70014
  if (sub === "/main.js") {
69978
- await serveAsset(res, join12(hostDir, "main.js"), "text/javascript; charset=utf-8");
70015
+ await serveAsset(res, join13(hostDir, "main.js"), "text/javascript; charset=utf-8");
69979
70016
  return;
69980
70017
  }
69981
70018
  if (sub === "/config.json" && req.method === "GET") {
@@ -70060,34 +70097,6 @@ function startViewServer(config2) {
70060
70097
  server.listen(session.port, "127.0.0.1");
70061
70098
  return server;
70062
70099
  }
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
70100
  function jwtExpiry(token) {
70092
70101
  try {
70093
70102
  const payload = JSON.parse(Buffer.from(token.split(".")[1], "base64url").toString("utf8"));
@@ -70144,7 +70153,9 @@ var TOKEN_REFRESH_MARGIN_MS, FALLBACK_TOKEN_TTL_MS = 3600000, IDLE_SWEEP_MS = 60
70144
70153
  var init_server = __esm(() => {
70145
70154
  init_kernel2();
70146
70155
  init_ca_fetch();
70156
+ init_assets();
70147
70157
  init_session2();
70158
+ init_assets();
70148
70159
  TOKEN_REFRESH_MARGIN_MS = 5 * 60000;
70149
70160
  });
70150
70161
 
@@ -70225,7 +70236,7 @@ __export(exports_view, {
70225
70236
  import { randomBytes } from "node:crypto";
70226
70237
  import { closeSync, existsSync as existsSync6, openSync, statSync } from "node:fs";
70227
70238
  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";
70239
+ import { dirname as dirname9, join as join14 } from "node:path";
70229
70240
  async function resolveSession(spec, opts) {
70230
70241
  const parsed = spec ? parseViewSpec(spec) : undefined;
70231
70242
  if (parsed?.kind === "target" && opts.target) {
@@ -70301,7 +70312,7 @@ async function resolveServeRuntime() {
70301
70312
  if (node2 && entry?.endsWith(".js") && existsSync6(entry))
70302
70313
  return { file: node2, args: [entry] };
70303
70314
  if (node2 && entry?.endsWith(".ts")) {
70304
- const dist = join13(dirname9(entry), "..", "dist", "astrale.js");
70315
+ const dist = join14(dirname9(entry), "..", "dist", "astrale.js");
70305
70316
  await ensureDevDist(entry, dist);
70306
70317
  if (existsSync6(dist))
70307
70318
  return { file: node2, args: [dist] };
@@ -70319,7 +70330,7 @@ async function ensureDevDist(entry, dist) {
70319
70330
  const bun = globalThis.Bun;
70320
70331
  if (!bun)
70321
70332
  return;
70322
- const srcDir = join13(dirname9(entry), "..", "src");
70333
+ const srcDir = join14(dirname9(entry), "..", "src");
70323
70334
  if (existsSync6(dist) && !await newerThan(srcDir, statSync(dist).mtimeMs))
70324
70335
  return;
70325
70336
  console.error("(dev) building dist/astrale.js for the session server…");
@@ -70335,7 +70346,7 @@ async function newerThan(dir, mtimeMs) {
70335
70346
  for (const item of entries) {
70336
70347
  if (!item.isFile())
70337
70348
  continue;
70338
- if (statSync(join13(item.parentPath, item.name)).mtimeMs > mtimeMs)
70349
+ if (statSync(join14(item.parentPath, item.name)).mtimeMs > mtimeMs)
70339
70350
  return true;
70340
70351
  }
70341
70352
  return false;
@@ -70944,28 +70955,28 @@ __export(exports_studio, {
70944
70955
  default: () => studio_default
70945
70956
  });
70946
70957
  import { existsSync as existsSync8, realpathSync } from "node:fs";
70947
- import { dirname as dirname10, join as join14, resolve } from "node:path";
70958
+ import { dirname as dirname10, join as join15, resolve } from "node:path";
70948
70959
  function resolveStudioDir() {
70949
70960
  const candidates = [];
70950
70961
  if (process.env.ASTRALE_STUDIO_DIR)
70951
70962
  candidates.push(process.env.ASTRALE_STUDIO_DIR);
70952
70963
  try {
70953
70964
  const entryDir = dirname10(realpathSync(process.argv[1] ?? ""));
70954
- candidates.push(join14(entryDir, "..", "studio"), join14(entryDir, "studio"));
70965
+ candidates.push(join15(entryDir, "..", "studio"), join15(entryDir, "studio"));
70955
70966
  } catch {}
70956
70967
  for (const c of candidates) {
70957
- if (existsSync8(join14(c, "server", "index.ts")))
70968
+ if (existsSync8(join15(c, "server", "index.ts")))
70958
70969
  return resolve(c);
70959
70970
  }
70960
70971
  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
70972
  }
70962
70973
  function isDevSource(studioDir) {
70963
- return existsSync8(join14(studioDir, "vite.config.ts")) && existsSync8(join14(studioDir, "client", "src"));
70974
+ return existsSync8(join15(studioDir, "vite.config.ts")) && existsSync8(join15(studioDir, "client", "src"));
70964
70975
  }
70965
70976
  function resolveViteBin(studioDir) {
70966
70977
  for (const c of [
70967
- join14(studioDir, "node_modules", ".bin", "vite"),
70968
- join14(studioDir, "..", "..", "node_modules", ".bin", "vite")
70978
+ join15(studioDir, "node_modules", ".bin", "vite"),
70979
+ join15(studioDir, "..", "..", "node_modules", ".bin", "vite")
70969
70980
  ]) {
70970
70981
  if (existsSync8(c))
70971
70982
  return c;
@@ -71040,6 +71051,11 @@ var init_studio = __esm(() => {
71040
71051
  },
71041
71052
  { flags: "--prod", description: "Serve the prebuilt client (the default)" },
71042
71053
  { flags: "--schema-dir <dir>", description: "Schema entry dir to scan for (default: schema)" },
71054
+ {
71055
+ flags: "--harness <name>",
71056
+ description: "Lock the agent harness for this Studio process",
71057
+ choices: ["claude", "codex"]
71058
+ },
71043
71059
  ...RAW_OUTPUT_OPTIONS
71044
71060
  ],
71045
71061
  afterHelpText: `
@@ -71063,6 +71079,10 @@ Behavior:
71063
71079
  By default the command just PRINTS the URL — it does not pop a browser (that's
71064
71080
  invasive when you already have a tab open). Pass --open to launch one.
71065
71081
 
71082
+ Agent harness: choose Claude Code or Codex per domain in Studio Settings. Pass
71083
+ --harness claude|codex to lock this process to one harness. Each harness uses
71084
+ its existing local authentication and keeps an independent resumable session.
71085
+
71066
71086
  The command stays attached and supervises its child processes; Ctrl-C tears
71067
71087
  them all down (a second Ctrl-C force-kills). With --json it prints a
71068
71088
  { url, port, mode, workspace } descriptor.
@@ -71071,6 +71091,7 @@ Examples:
71071
71091
  $ astrale studio # start the studio + print its URL (no browser)
71072
71092
  $ astrale studio --open # …and open it in a browser
71073
71093
  $ astrale studio ./my-domain
71094
+ $ astrale studio --harness codex
71074
71095
  $ astrale studio --port 4400
71075
71096
  $ astrale studio --dev # live-edit the studio itself (from source)
71076
71097
  `,
@@ -71173,12 +71194,13 @@ Examples:
71173
71194
  DOMAIN_STUDIO_DEV: "1",
71174
71195
  VITE_URL: `http://127.0.0.1:${vitePort}`,
71175
71196
  PORT: String(studioPort),
71176
- DOMAIN_STUDIO_HOST: "127.0.0.1"
71197
+ DOMAIN_STUDIO_HOST: "127.0.0.1",
71198
+ ...opts.harness ? { DOMAIN_STUDIO_HARNESS: opts.harness } : {}
71177
71199
  }
71178
71200
  }), "server");
71179
71201
  } else {
71180
- const dist = join14(studioDir, "client", "dist");
71181
- if (!existsSync8(join14(dist, "index.html"))) {
71202
+ const dist = join15(studioDir, "client", "dist");
71203
+ if (!existsSync8(join15(dist, "index.html"))) {
71182
71204
  throw new Error(`studio client not built at ${dist} — run: pnpm --filter @astrale-os/studio build`);
71183
71205
  }
71184
71206
  serverChild = supervise(spawnHandle("bun", ["server/index.ts", workspace, "--port", String(studioPort), "--no-open"], {
@@ -71188,7 +71210,8 @@ Examples:
71188
71210
  ...process.env,
71189
71211
  DOMAIN_STUDIO_DIST: dist,
71190
71212
  PORT: String(studioPort),
71191
- DOMAIN_STUDIO_HOST: "127.0.0.1"
71213
+ DOMAIN_STUDIO_HOST: "127.0.0.1",
71214
+ ...opts.harness ? { DOMAIN_STUDIO_HARNESS: opts.harness } : {}
71192
71215
  }
71193
71216
  }), "server");
71194
71217
  }
@@ -73033,21 +73056,21 @@ var init_status4 = __esm(() => {
73033
73056
 
73034
73057
  // src/telemetry/store.ts
73035
73058
  import { existsSync as existsSync9, readdirSync as readdirSync2, readFileSync as readFileSync3, statSync as statSync2 } from "node:fs";
73036
- import { join as join15 } from "node:path";
73059
+ import { join as join16 } from "node:path";
73037
73060
  function sessionsRoot() {
73038
- return join15(createPaths().home, "sessions");
73061
+ return join16(createPaths().home, "sessions");
73039
73062
  }
73040
73063
  function sessionDir(id) {
73041
- return join15(sessionsRoot(), id);
73064
+ return join16(sessionsRoot(), id);
73042
73065
  }
73043
73066
  function eventsPath(id) {
73044
- return join15(sessionDir(id), "events.jsonl");
73067
+ return join16(sessionDir(id), "events.jsonl");
73045
73068
  }
73046
73069
  function metaPath(id) {
73047
- return join15(sessionDir(id), "meta.json");
73070
+ return join16(sessionDir(id), "meta.json");
73048
73071
  }
73049
73072
  function markerPath(id) {
73050
- return join15(sessionDir(id), ".analyzed");
73073
+ return join16(sessionDir(id), ".analyzed");
73051
73074
  }
73052
73075
  function readJsonSafe(path4) {
73053
73076
  try {
@@ -73125,12 +73148,12 @@ var init_list4 = __esm(() => {
73125
73148
  // src/telemetry/adapters/claude-code.ts
73126
73149
  import { existsSync as existsSync10, readdirSync as readdirSync3, statSync as statSync3 } from "node:fs";
73127
73150
  import { homedir as homedir3 } from "node:os";
73128
- import { join as join16 } from "node:path";
73151
+ import { join as join17 } from "node:path";
73129
73152
  function mungeCwd(cwd) {
73130
73153
  return cwd.replace(/[^a-zA-Z0-9]/g, "-");
73131
73154
  }
73132
- function claudeCodeAdapter(base2 = join16(homedir3(), ".claude")) {
73133
- const projectsDir = join16(base2, "projects");
73155
+ function claudeCodeAdapter(base2 = join17(homedir3(), ".claude")) {
73156
+ const projectsDir = join17(base2, "projects");
73134
73157
  function detect2() {
73135
73158
  try {
73136
73159
  return existsSync10(projectsDir);
@@ -73154,7 +73177,7 @@ function claudeCodeAdapter(base2 = join16(homedir3(), ".claude")) {
73154
73177
  for (const dir of dirs) {
73155
73178
  if (dir !== munged && !dir.startsWith(prefix))
73156
73179
  continue;
73157
- const projectPath = join16(projectsDir, dir);
73180
+ const projectPath = join17(projectsDir, dir);
73158
73181
  let files;
73159
73182
  try {
73160
73183
  files = readdirSync3(projectPath);
@@ -73164,7 +73187,7 @@ function claudeCodeAdapter(base2 = join16(homedir3(), ".claude")) {
73164
73187
  for (const file2 of files) {
73165
73188
  if (!file2.endsWith(".jsonl"))
73166
73189
  continue;
73167
- const transcriptPath = join16(projectPath, file2);
73190
+ const transcriptPath = join17(projectPath, file2);
73168
73191
  try {
73169
73192
  const st = statSync3(transcriptPath);
73170
73193
  const mtimeMs = st.mtime.getTime();
@@ -73200,7 +73223,7 @@ var init_claude_code = __esm(() => {
73200
73223
  import { existsSync as existsSync11, openSync as openSync2, readdirSync as readdirSync4, readSync, statSync as statSync4 } from "node:fs";
73201
73224
  import { closeSync as closeSync2 } from "node:fs";
73202
73225
  import { homedir as homedir4 } from "node:os";
73203
- import { join as join17 } from "node:path";
73226
+ import { join as join18 } from "node:path";
73204
73227
  function readFirstLine(path4) {
73205
73228
  let fd = null;
73206
73229
  try {
@@ -73240,8 +73263,8 @@ function numericDirs(path4) {
73240
73263
  return [];
73241
73264
  }
73242
73265
  }
73243
- function codexAdapter(base2 = join17(homedir4(), ".codex")) {
73244
- const sessionsDir = join17(base2, "sessions");
73266
+ function codexAdapter(base2 = join18(homedir4(), ".codex")) {
73267
+ const sessionsDir = join18(base2, "sessions");
73245
73268
  function detect2() {
73246
73269
  try {
73247
73270
  return existsSync11(sessionsDir);
@@ -73256,12 +73279,12 @@ function codexAdapter(base2 = join17(homedir4(), ".codex")) {
73256
73279
  const endMs = window2.end.getTime();
73257
73280
  const lowerMs = startMs - DAY_MS;
73258
73281
  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))) {
73282
+ for (const mm of numericDirs(join18(sessionsDir, yyyy))) {
73283
+ for (const dd of numericDirs(join18(sessionsDir, yyyy, mm))) {
73261
73284
  const dayStart = Date.UTC(Number(yyyy), Number(mm) - 1, Number(dd));
73262
73285
  if (dayStart > endMs + DAY_MS || dayStart + DAY_MS <= lowerMs)
73263
73286
  continue;
73264
- scanDay(join17(sessionsDir, yyyy, mm, dd), root, startMs, endMs, sessions);
73287
+ scanDay(join18(sessionsDir, yyyy, mm, dd), root, startMs, endMs, sessions);
73265
73288
  }
73266
73289
  }
73267
73290
  }
@@ -73283,7 +73306,7 @@ function scanDay(dayPath, root, startMs, endMs, out) {
73283
73306
  for (const file2 of files) {
73284
73307
  if (!file2.startsWith("rollout-") || !file2.endsWith(".jsonl"))
73285
73308
  continue;
73286
- const transcriptPath = join17(dayPath, file2);
73309
+ const transcriptPath = join18(dayPath, file2);
73287
73310
  try {
73288
73311
  const st = statSync4(transcriptPath);
73289
73312
  const mtimeMs = st.mtime.getTime();
@@ -73430,7 +73453,7 @@ var init_gate = () => {};
73430
73453
  // src/telemetry/analyze.ts
73431
73454
  import { spawn as spawn3 } from "node:child_process";
73432
73455
  import { appendFileSync, writeFileSync as writeFileSync2 } from "node:fs";
73433
- import { join as join18 } from "node:path";
73456
+ import { join as join19 } from "node:path";
73434
73457
  function writeMarker(id, marker) {
73435
73458
  writeFileSync2(markerPath(id), JSON.stringify(marker, null, 2) + `
73436
73459
  `);
@@ -73526,7 +73549,7 @@ async function analyzeSession(id, opts = {}) {
73526
73549
  const guides = new Map(adapters.map((a) => [a.name, a.readingGuide]));
73527
73550
  const prompt = buildPrompt({ id, root, signals: signals2, guides, file: opts.file ?? false });
73528
73551
  const dir = sessionDir(id);
73529
- writeFileSync2(join18(dir, "analyzer-prompt.md"), prompt);
73552
+ writeFileSync2(join19(dir, "analyzer-prompt.md"), prompt);
73530
73553
  const outcome = await runClaude(prompt, dir, opts);
73531
73554
  const marker = {
73532
73555
  analyzedAt: new Date().toISOString(),
@@ -73534,7 +73557,7 @@ async function analyzeSession(id, opts = {}) {
73534
73557
  note: outcome.note
73535
73558
  };
73536
73559
  writeMarker(id, marker);
73537
- return { ...marker, reportPath: join18(dir, "report.md") };
73560
+ return { ...marker, reportPath: join19(dir, "report.md") };
73538
73561
  }
73539
73562
  function runClaude(prompt, cwd, opts) {
73540
73563
  return new Promise((resolve2) => {
@@ -73564,7 +73587,7 @@ function runClaude(prompt, cwd, opts) {
73564
73587
  child.on("close", (code) => {
73565
73588
  clearTimeout(timer);
73566
73589
  try {
73567
- appendFileSync(join18(cwd, "analyzer.log"), out + (err ? `
73590
+ appendFileSync(join19(cwd, "analyzer.log"), out + (err ? `
73568
73591
  --- stderr ---
73569
73592
  ${err}` : ""));
73570
73593
  } catch {}
@@ -73614,9 +73637,9 @@ var init_settings = __esm(() => {
73614
73637
 
73615
73638
  // src/telemetry/trigger.ts
73616
73639
  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";
73640
+ import { join as join20 } from "node:path";
73618
73641
  function lockPath() {
73619
- return join19(sessionsRoot(), ".analyzer.lock");
73642
+ return join20(sessionsRoot(), ".analyzer.lock");
73620
73643
  }
73621
73644
  function releaseLock() {
73622
73645
  try {
@@ -74375,12 +74398,12 @@ function redactArgv(argv) {
74375
74398
  init_store2();
74376
74399
  import { createHash as createHash2 } from "node:crypto";
74377
74400
  import { existsSync as existsSync13, mkdirSync as mkdirSync3, writeFileSync as writeFileSync4 } from "node:fs";
74378
- import { dirname as dirname12, join as join20 } from "node:path";
74401
+ import { dirname as dirname12, join as join21 } from "node:path";
74379
74402
  var MAX_ID_LEN = 64;
74380
74403
  function findGitRoot(start) {
74381
74404
  let dir = start;
74382
74405
  for (;; ) {
74383
- if (existsSync13(join20(dir, ".git")))
74406
+ if (existsSync13(join21(dir, ".git")))
74384
74407
  return dir;
74385
74408
  const parent = dirname12(dir);
74386
74409
  if (parent === dir)
@@ -74481,7 +74504,7 @@ init_settings();
74481
74504
  init_store2();
74482
74505
  import { spawn as spawn4 } from "node:child_process";
74483
74506
  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";
74507
+ import { join as join22 } from "node:path";
74485
74508
  var LOCK_STALE_MS2 = 30 * 60 * 1000;
74486
74509
  var GC_AGE_MS2 = 30 * 24 * 60 * 60 * 1000;
74487
74510
  var GC_MAX_PER_RUN = 20;
@@ -74500,7 +74523,7 @@ function gcOldSessions(sessions) {
74500
74523
  } catch {}
74501
74524
  }
74502
74525
  function lockPath2() {
74503
- return join21(sessionsRoot(), ".analyzer.lock");
74526
+ return join22(sessionsRoot(), ".analyzer.lock");
74504
74527
  }
74505
74528
  function claimLock() {
74506
74529
  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.0-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",
@@ -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
  ),