@arvoretech/hub 0.16.0 → 0.17.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.
@@ -19,10 +19,12 @@ interface Repo {
19
19
  }
20
20
  interface Service {
21
21
  name: string;
22
- image: string;
22
+ type?: "sandbox" | string;
23
+ image?: string;
23
24
  port?: number;
24
25
  ports?: number[];
25
26
  env?: Record<string, string>;
27
+ workspace?: string;
26
28
  }
27
29
  interface MCPConfig {
28
30
  name: string;
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  generateCommand,
3
3
  generators
4
- } from "./chunk-ALJAZQ33.js";
4
+ } from "./chunk-Z4AZNX6V.js";
5
5
  import "./chunk-VMN4KGAK.js";
6
6
  export {
7
7
  generateCommand,
package/dist/index.js CHANGED
@@ -1,15 +1,19 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  checkAndAutoRegenerate,
4
- generateCommand
5
- } from "./chunk-ALJAZQ33.js";
4
+ colors,
5
+ generateCommand,
6
+ horizontalLine,
7
+ personaCommand,
8
+ symbols
9
+ } from "./chunk-Z4AZNX6V.js";
6
10
  import {
7
11
  loadHubConfig,
8
12
  resolveConfigPath
9
13
  } from "./chunk-VMN4KGAK.js";
10
14
 
11
15
  // src/index.ts
12
- import { Command as Command21 } from "commander";
16
+ import { Command as Command22 } from "commander";
13
17
 
14
18
  // src/commands/init.ts
15
19
  import { Command as Command2 } from "commander";
@@ -23,38 +27,6 @@ import { Box as Box10, useStdout as useStdout4 } from "ink";
23
27
 
24
28
  // src/tui/components/WelcomeStep.tsx
25
29
  import { Box, Text, useInput } from "ink";
26
-
27
- // src/tui/theme.ts
28
- var colors = {
29
- brand: "#22c55e",
30
- brandDim: "#16a34a",
31
- blue: "#3b82f6",
32
- purple: "#a78bfa",
33
- muted: "#6b7280",
34
- dim: "#4b5563",
35
- warning: "#eab308",
36
- error: "#ef4444",
37
- white: "#ffffff"
38
- };
39
- var symbols = {
40
- check: "\u2713",
41
- cross: "\u2717",
42
- arrow: "\u276F",
43
- dot: "\u25CF",
44
- circle: "\u25CB",
45
- tree: "\u{1F333}",
46
- line: "\u2500",
47
- corner: "\u256D",
48
- cornerEnd: "\u2570",
49
- vertical: "\u2502",
50
- cornerRight: "\u256E",
51
- cornerEndRight: "\u256F"
52
- };
53
- function horizontalLine(width) {
54
- return symbols.line.repeat(width);
55
- }
56
-
57
- // src/tui/components/WelcomeStep.tsx
58
30
  import { jsx, jsxs } from "react/jsx-runtime";
59
31
  var LOGO = [
60
32
  " \u{1F33F} ",
@@ -1565,12 +1537,38 @@ import chalk3 from "chalk";
1565
1537
 
1566
1538
  // src/core/docker-compose.ts
1567
1539
  import { stringify as stringify3 } from "yaml";
1568
- function generateDockerCompose(services) {
1540
+ import { resolve } from "path";
1541
+ var SANDBOX_IMAGE = "ghcr.io/agent-infra/sandbox:latest";
1542
+ var SANDBOX_PORT = 8080;
1543
+ function buildSandboxEntry(svc, hubDir) {
1544
+ const port = svc.port ?? SANDBOX_PORT;
1545
+ const workspacePath = resolve(hubDir, svc.workspace ?? ".");
1546
+ return {
1547
+ image: SANDBOX_IMAGE,
1548
+ container_name: svc.name,
1549
+ restart: "unless-stopped",
1550
+ security_opt: ["seccomp:unconfined"],
1551
+ shm_size: "2gb",
1552
+ extra_hosts: ["host.docker.internal:host-gateway"],
1553
+ ports: [`${port}:8080`],
1554
+ volumes: [`${workspacePath}:/workspace`],
1555
+ environment: {
1556
+ WORKSPACE: "/workspace",
1557
+ ...svc.env ?? {}
1558
+ }
1559
+ };
1560
+ }
1561
+ function generateDockerCompose(services, hubDir = process.cwd()) {
1569
1562
  const compose = {
1570
1563
  services: {}
1571
1564
  };
1572
1565
  const svcMap = compose.services;
1566
+ const volumes = {};
1573
1567
  for (const svc of services) {
1568
+ if (svc.type === "sandbox") {
1569
+ svcMap[svc.name] = buildSandboxEntry(svc, hubDir);
1570
+ continue;
1571
+ }
1574
1572
  const entry = {
1575
1573
  image: svc.image,
1576
1574
  restart: "unless-stopped"
@@ -1583,14 +1581,14 @@ function generateDockerCompose(services) {
1583
1581
  if (svc.env) {
1584
1582
  entry.environment = svc.env;
1585
1583
  }
1586
- entry.volumes = [`${svc.name}_data:/var/lib/${guessDataDir(svc.image)}`];
1584
+ const dataDir = guessDataDir(svc.image ?? svc.name);
1585
+ entry.volumes = [`${svc.name}_data:/var/lib/${dataDir}`];
1586
+ volumes[`${svc.name}_data`] = null;
1587
1587
  svcMap[svc.name] = entry;
1588
1588
  }
1589
- const volumes = {};
1590
- for (const svc of services) {
1591
- volumes[`${svc.name}_data`] = null;
1589
+ if (Object.keys(volumes).length > 0) {
1590
+ compose.volumes = volumes;
1592
1591
  }
1593
- compose.volumes = volumes;
1594
1592
  return stringify3(compose, { lineWidth: 120 });
1595
1593
  }
1596
1594
  function guessDataDir(image) {
@@ -1984,7 +1982,7 @@ async function ensureCompose(hubDir) {
1984
1982
  console.log(chalk5.yellow("No services defined in hub.yaml"));
1985
1983
  process.exit(1);
1986
1984
  }
1987
- const content = generateDockerCompose(config.services);
1985
+ const content = generateDockerCompose(config.services, hubDir);
1988
1986
  await writeFile6(composePath, content, "utf-8");
1989
1987
  console.log(chalk5.green("Generated docker-compose.yml"));
1990
1988
  }
@@ -2063,7 +2061,7 @@ var servicesCommand = new Command6("services").description("Manage Docker develo
2063
2061
  import { Command as Command7 } from "commander";
2064
2062
  import { existsSync as existsSync5, statSync } from "fs";
2065
2063
  import { mkdir as mkdir3, readdir, readFile as readFile3, rm, cp } from "fs/promises";
2066
- import { join as join8, resolve } from "path";
2064
+ import { join as join8, resolve as resolve2 } from "path";
2067
2065
  import { execSync as execSync4 } from "child_process";
2068
2066
  import chalk6 from "chalk";
2069
2067
  var DEFAULT_REGISTRY_REPO2 = process.env.HUB_REGISTRY || "arvoreeducacao/rhm";
@@ -2254,7 +2252,7 @@ async function listRemoteSkills(owner, repo) {
2254
2252
  }
2255
2253
  }
2256
2254
  async function addFromLocalPath(localPath, hubDir, opts) {
2257
- const absPath = resolve(localPath);
2255
+ const absPath = resolve2(localPath);
2258
2256
  if (!existsSync5(absPath)) {
2259
2257
  console.log(chalk6.red(` Path not found: ${absPath}`));
2260
2258
  return;
@@ -2403,7 +2401,7 @@ Removed skill: ${name}
2403
2401
  import { Command as Command8 } from "commander";
2404
2402
  import { existsSync as existsSync6, statSync as statSync2 } from "fs";
2405
2403
  import { mkdir as mkdir4, readdir as readdir2, readFile as readFile4, rm as rm2, copyFile, writeFile as writeFile7 } from "fs/promises";
2406
- import { join as join9, resolve as resolve2 } from "path";
2404
+ import { join as join9, resolve as resolve3 } from "path";
2407
2405
  import { execSync as execSync5 } from "child_process";
2408
2406
  import chalk7 from "chalk";
2409
2407
  var DEFAULT_REGISTRY_REPO3 = process.env.HUB_REGISTRY || "arvoreeducacao/rhm";
@@ -2451,7 +2449,7 @@ async function addFromRegistry2(agentName, hubDir, opts) {
2451
2449
  }
2452
2450
  }
2453
2451
  async function addFromLocalPath2(localPath, hubDir, opts) {
2454
- const absPath = resolve2(localPath);
2452
+ const absPath = resolve3(localPath);
2455
2453
  if (!existsSync6(absPath)) {
2456
2454
  console.log(chalk7.red(` Path not found: ${absPath}`));
2457
2455
  return;
@@ -2660,7 +2658,7 @@ Removed agent: ${name}
2660
2658
  import { Command as Command9 } from "commander";
2661
2659
  import { existsSync as existsSync7, statSync as statSync3 } from "fs";
2662
2660
  import { mkdir as mkdir5, readdir as readdir3, readFile as readFile5, rm as rm3, copyFile as copyFile2, cp as cp2 } from "fs/promises";
2663
- import { join as join10, resolve as resolve3 } from "path";
2661
+ import { join as join10, resolve as resolve4 } from "path";
2664
2662
  import { execSync as execSync6 } from "child_process";
2665
2663
  import chalk8 from "chalk";
2666
2664
  var DEFAULT_REGISTRY_REPO4 = process.env.HUB_REGISTRY || "arvoreeducacao/rhm";
@@ -2708,7 +2706,7 @@ async function addFromRegistry3(hookName, hubDir, opts) {
2708
2706
  }
2709
2707
  }
2710
2708
  async function addFromLocalPath3(localPath, hubDir, opts) {
2711
- const absPath = resolve3(localPath);
2709
+ const absPath = resolve4(localPath);
2712
2710
  if (!existsSync7(absPath)) {
2713
2711
  console.log(chalk8.red(` Path not found: ${absPath}`));
2714
2712
  return;
@@ -2854,7 +2852,7 @@ Hook '${name}' not found in hooks/
2854
2852
  import { Command as Command10 } from "commander";
2855
2853
  import { existsSync as existsSync8, statSync as statSync4 } from "fs";
2856
2854
  import { mkdir as mkdir6, readdir as readdir4, readFile as readFile6, rm as rm4, copyFile as copyFile3, writeFile as writeFile8 } from "fs/promises";
2857
- import { join as join11, resolve as resolve4 } from "path";
2855
+ import { join as join11, resolve as resolve5 } from "path";
2858
2856
  import { execSync as execSync7 } from "child_process";
2859
2857
  import chalk9 from "chalk";
2860
2858
  var DEFAULT_REGISTRY_REPO5 = process.env.HUB_REGISTRY || "arvoreeducacao/rhm";
@@ -2903,7 +2901,7 @@ async function addFromRegistry4(commandName, hubDir, opts) {
2903
2901
  }
2904
2902
  }
2905
2903
  async function addFromLocalPath4(localPath, hubDir, opts) {
2906
- const absPath = resolve4(localPath);
2904
+ const absPath = resolve5(localPath);
2907
2905
  if (!existsSync8(absPath)) {
2908
2906
  console.log(chalk9.red(` Path not found: ${absPath}`));
2909
2907
  return;
@@ -3638,7 +3636,7 @@ function extractVersion(s) {
3638
3636
  import { Command as Command15 } from "commander";
3639
3637
  import { existsSync as existsSync13 } from "fs";
3640
3638
  import { mkdir as mkdir8, readdir as readdir5, readFile as readFile7, writeFile as writeFile10, rm as rm5, appendFile as appendFile2 } from "fs/promises";
3641
- import { join as join16, resolve as resolve5, basename as basename2 } from "path";
3639
+ import { join as join16, resolve as resolve6, basename as basename2 } from "path";
3642
3640
  import chalk14 from "chalk";
3643
3641
  async function ensureLanceDbIgnored(memoriesDir, hubDir) {
3644
3642
  const relative = memoriesDir.replace(hubDir + "/", "");
@@ -3665,7 +3663,7 @@ var VALID_CATEGORIES = [
3665
3663
  "gotchas"
3666
3664
  ];
3667
3665
  function getMemoriesPath(hubDir, configPath) {
3668
- return resolve5(hubDir, configPath || "memories");
3666
+ return resolve6(hubDir, configPath || "memories");
3669
3667
  }
3670
3668
  function parseFrontmatter(raw) {
3671
3669
  const match = raw.match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)$/);
@@ -4726,7 +4724,7 @@ function buildConsolidationPrompt(batchPath, memoriesPath) {
4726
4724
  ].join("\n");
4727
4725
  }
4728
4726
  function spawnEditorCli(cli, prompt, cwd) {
4729
- return new Promise((resolve6) => {
4727
+ return new Promise((resolve7) => {
4730
4728
  let args;
4731
4729
  switch (cli) {
4732
4730
  case "kiro-cli":
@@ -4759,10 +4757,10 @@ function spawnEditorCli(cli, prompt, cwd) {
4759
4757
  stderr += data.toString();
4760
4758
  });
4761
4759
  proc.on("exit", (code) => {
4762
- resolve6({ code: code ?? 1, stdout, stderr });
4760
+ resolve7({ code: code ?? 1, stdout, stderr });
4763
4761
  });
4764
4762
  proc.on("error", (err) => {
4765
- resolve6({ code: 1, stdout, stderr: err.message });
4763
+ resolve7({ code: 1, stdout, stderr: err.message });
4766
4764
  });
4767
4765
  });
4768
4766
  }
@@ -4941,14 +4939,144 @@ var consolidateCommand = new Command20("consolidate").description(
4941
4939
  }
4942
4940
  );
4943
4941
 
4942
+ // src/commands/sandbox.ts
4943
+ import { Command as Command21 } from "commander";
4944
+ import { execSync as execSync16, exec } from "child_process";
4945
+ import { existsSync as existsSync17 } from "fs";
4946
+ import { writeFile as writeFile13 } from "fs/promises";
4947
+ import { join as join21 } from "path";
4948
+ import { promisify } from "util";
4949
+ import chalk20 from "chalk";
4950
+ var execAsync = promisify(exec);
4951
+ function getSandboxConfig(config) {
4952
+ return config.services?.find((s) => s.type === "sandbox") ?? null;
4953
+ }
4954
+ async function ensureComposeFile(hubDir) {
4955
+ const composePath = join21(hubDir, "docker-compose.yml");
4956
+ if (!existsSync17(composePath)) {
4957
+ const config = await loadHubConfig(hubDir);
4958
+ const content = generateDockerCompose(config.services ?? [], hubDir);
4959
+ await writeFile13(composePath, content, "utf-8");
4960
+ }
4961
+ return composePath;
4962
+ }
4963
+ function isDockerRunning2() {
4964
+ try {
4965
+ execSync16("docker info", { stdio: ["pipe", "pipe", "pipe"] });
4966
+ return true;
4967
+ } catch {
4968
+ return false;
4969
+ }
4970
+ }
4971
+ async function isSandboxRunning(name) {
4972
+ try {
4973
+ const { stdout } = await execAsync(`docker inspect --format='{{.State.Running}}' ${name} 2>/dev/null`);
4974
+ return stdout.trim() === "true";
4975
+ } catch {
4976
+ return false;
4977
+ }
4978
+ }
4979
+ var sandboxCommand = new Command21("sandbox").description("Manage the AIO Sandbox environment").argument("[action]", "up, down, status, open, logs", "status").action(async (action) => {
4980
+ if (!isDockerRunning2()) {
4981
+ console.log(chalk20.red("\nDocker daemon is not running."));
4982
+ console.log(chalk20.dim("Start Docker Desktop or the Docker daemon and try again.\n"));
4983
+ return;
4984
+ }
4985
+ const hubDir = process.cwd();
4986
+ const config = await loadHubConfig(hubDir);
4987
+ const svc = getSandboxConfig(config);
4988
+ if (!svc) {
4989
+ console.log(chalk20.red("\nNo sandbox service found in hub.yaml."));
4990
+ console.log(chalk20.dim("Add a service with type: sandbox to your hub.yaml:\n"));
4991
+ console.log(chalk20.dim(" services:"));
4992
+ console.log(chalk20.dim(" - name: sandbox"));
4993
+ console.log(chalk20.dim(" type: sandbox"));
4994
+ console.log(chalk20.dim(" port: 8080\n"));
4995
+ return;
4996
+ }
4997
+ const port = svc.port ?? 8080;
4998
+ const name = svc.name;
4999
+ switch (action) {
5000
+ case "up":
5001
+ case "start": {
5002
+ const running = await isSandboxRunning(name);
5003
+ if (running) {
5004
+ console.log(chalk20.yellow(`
5005
+ Sandbox is already running.`));
5006
+ printUrls(port);
5007
+ return;
5008
+ }
5009
+ console.log(chalk20.blue(`
5010
+ Starting sandbox...
5011
+ `));
5012
+ const composePath = await ensureComposeFile(hubDir);
5013
+ execSync16(`docker compose -f ${composePath} up -d ${name}`, { stdio: "inherit", cwd: hubDir });
5014
+ console.log(chalk20.green("\nSandbox started."));
5015
+ printUrls(port);
5016
+ break;
5017
+ }
5018
+ case "down":
5019
+ case "stop": {
5020
+ console.log(chalk20.blue(`
5021
+ Stopping sandbox...
5022
+ `));
5023
+ const composePath = await ensureComposeFile(hubDir);
5024
+ execSync16(`docker compose -f ${composePath} stop ${name}`, { stdio: "inherit", cwd: hubDir });
5025
+ console.log(chalk20.green("\nSandbox stopped.\n"));
5026
+ break;
5027
+ }
5028
+ case "logs": {
5029
+ const composePath = await ensureComposeFile(hubDir);
5030
+ execSync16(`docker compose -f ${composePath} logs -f ${name}`, { stdio: "inherit", cwd: hubDir });
5031
+ break;
5032
+ }
5033
+ case "open": {
5034
+ const running = await isSandboxRunning(name);
5035
+ if (!running) {
5036
+ console.log(chalk20.red("\nSandbox is not running. Start it first with: hub sandbox up\n"));
5037
+ return;
5038
+ }
5039
+ const url = `http://localhost:${port}/code-server/`;
5040
+ console.log(chalk20.blue(`
5041
+ Opening VSCode Server at ${url}
5042
+ `));
5043
+ execSync16(`open "${url}"`, { stdio: "inherit" });
5044
+ break;
5045
+ }
5046
+ case "status":
5047
+ default: {
5048
+ const running = await isSandboxRunning(name);
5049
+ if (running) {
5050
+ console.log(chalk20.green(`
5051
+ Sandbox is running.`));
5052
+ printUrls(port);
5053
+ } else {
5054
+ console.log(chalk20.yellow(`
5055
+ Sandbox is not running.`));
5056
+ console.log(chalk20.dim(` Start it with: hub sandbox up
5057
+ `));
5058
+ }
5059
+ break;
5060
+ }
5061
+ }
5062
+ });
5063
+ function printUrls(port) {
5064
+ console.log();
5065
+ console.log(chalk20.dim(` MCP: `) + chalk20.cyan(`http://localhost:${port}/mcp`));
5066
+ console.log(chalk20.dim(` VSCode: `) + chalk20.cyan(`http://localhost:${port}/code-server/`));
5067
+ console.log(chalk20.dim(` Browser: `) + chalk20.cyan(`http://localhost:${port}/vnc/index.html?autoconnect=true`));
5068
+ console.log(chalk20.dim(` Docs: `) + chalk20.cyan(`http://localhost:${port}/v1/docs`));
5069
+ console.log();
5070
+ }
5071
+
4944
5072
  // src/index.ts
4945
5073
  import { readFileSync as readFileSync2 } from "fs";
4946
5074
  import { fileURLToPath as fileURLToPath2 } from "url";
4947
- import { dirname as dirname2, join as join21 } from "path";
5075
+ import { dirname as dirname2, join as join22 } from "path";
4948
5076
  var __filename = fileURLToPath2(import.meta.url);
4949
5077
  var __dirname = dirname2(__filename);
4950
- var pkg = JSON.parse(readFileSync2(join21(__dirname, "..", "package.json"), "utf-8"));
4951
- var program = new Command21();
5078
+ var pkg = JSON.parse(readFileSync2(join22(__dirname, "..", "package.json"), "utf-8"));
5079
+ var program = new Command22();
4952
5080
  program.name("hub").description(
4953
5081
  "Give your AI coding assistant the full picture. Multi-repo context, agent orchestration, and end-to-end workflows."
4954
5082
  ).version(pkg.version).enablePositionalOptions();
@@ -4975,4 +5103,6 @@ program.addCommand(directoryCommand);
4975
5103
  program.addCommand(scanCommand);
4976
5104
  program.addCommand(cloneCommand);
4977
5105
  program.addCommand(consolidateCommand);
5106
+ program.addCommand(personaCommand);
5107
+ program.addCommand(sandboxCommand);
4978
5108
  program.parse();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arvoretech/hub",
3
- "version": "0.16.0",
3
+ "version": "0.17.0",
4
4
  "description": "CLI for managing AI-aware multi-repository workspaces",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",