@tridha643/hestia 1.0.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.
- package/NOTICES.md +10 -0
- package/README.md +194 -0
- package/dist/assets/portless/dist/chunk-SD2PIWJU.js +1030 -0
- package/dist/assets/portless/dist/cli.d.ts +1 -0
- package/dist/assets/portless/dist/cli.js +7231 -0
- package/dist/assets/portless/dist/index.d.ts +222 -0
- package/dist/assets/portless/dist/index.js +48 -0
- package/dist/assets/portless/hestia-hardening.patch +177 -0
- package/dist/assets/portless/package.json +63 -0
- package/dist/assets/portless/provenance.json +5 -0
- package/dist/cli.js +16396 -0
- package/dist/cli.js.map +146 -0
- package/dist/daemon.js +13785 -0
- package/dist/daemon.js.map +129 -0
- package/dist/proc-relay.js +117 -0
- package/dist/proc-relay.js.map +10 -0
- package/package.json +49 -0
- package/skills/hestia/SKILL.md +148 -0
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
// @bun
|
|
3
|
+
|
|
4
|
+
// packages/engine/src/proc/proc-relay.ts
|
|
5
|
+
import {
|
|
6
|
+
chmodSync,
|
|
7
|
+
closeSync,
|
|
8
|
+
existsSync,
|
|
9
|
+
openSync,
|
|
10
|
+
renameSync,
|
|
11
|
+
rmSync,
|
|
12
|
+
statSync,
|
|
13
|
+
writeSync
|
|
14
|
+
} from "fs";
|
|
15
|
+
import { spawn } from "child_process";
|
|
16
|
+
var MAX_LOG_BYTES = 25 * 1024 * 1024;
|
|
17
|
+
var ARCHIVE_COUNT = 3;
|
|
18
|
+
var RELAY_SPEC_ENV = "HESTIA_PROC_RELAY_SPEC";
|
|
19
|
+
function relaySpec() {
|
|
20
|
+
const encoded = process.env[RELAY_SPEC_ENV];
|
|
21
|
+
delete process.env[RELAY_SPEC_ENV];
|
|
22
|
+
if (encoded === undefined)
|
|
23
|
+
throw new Error(`${RELAY_SPEC_ENV} is missing`);
|
|
24
|
+
const value = JSON.parse(Buffer.from(encoded, "base64").toString("utf8"));
|
|
25
|
+
if (!Array.isArray(value.argv) || value.argv.length === 0 || typeof value.cwd !== "string" || typeof value.logPath !== "string") {
|
|
26
|
+
throw new Error("invalid proc relay spec");
|
|
27
|
+
}
|
|
28
|
+
return value;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
class RotatingLogWriter {
|
|
32
|
+
path;
|
|
33
|
+
maxLogBytes;
|
|
34
|
+
archiveCount;
|
|
35
|
+
#fd;
|
|
36
|
+
#bytes;
|
|
37
|
+
constructor(path, maxLogBytes = MAX_LOG_BYTES, archiveCount = ARCHIVE_COUNT) {
|
|
38
|
+
this.path = path;
|
|
39
|
+
this.maxLogBytes = maxLogBytes;
|
|
40
|
+
this.archiveCount = archiveCount;
|
|
41
|
+
this.#fd = openSync(path, "a", 384);
|
|
42
|
+
chmodSync(path, 384);
|
|
43
|
+
this.#bytes = statSync(path).size;
|
|
44
|
+
}
|
|
45
|
+
write(chunk) {
|
|
46
|
+
let offset = 0;
|
|
47
|
+
while (offset < chunk.byteLength) {
|
|
48
|
+
if (this.#bytes >= this.maxLogBytes)
|
|
49
|
+
this.rotate();
|
|
50
|
+
const length = Math.min(chunk.byteLength - offset, this.maxLogBytes - this.#bytes);
|
|
51
|
+
writeSync(this.#fd, chunk, offset, length);
|
|
52
|
+
offset += length;
|
|
53
|
+
this.#bytes += length;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
close() {
|
|
57
|
+
closeSync(this.#fd);
|
|
58
|
+
}
|
|
59
|
+
rotate() {
|
|
60
|
+
closeSync(this.#fd);
|
|
61
|
+
rmSync(`${this.path}.${this.archiveCount}`, { force: true });
|
|
62
|
+
for (let index = this.archiveCount - 1;index >= 1; index -= 1) {
|
|
63
|
+
const source = `${this.path}.${index}`;
|
|
64
|
+
if (existsSync(source))
|
|
65
|
+
renameSync(source, `${this.path}.${index + 1}`);
|
|
66
|
+
}
|
|
67
|
+
if (existsSync(this.path))
|
|
68
|
+
renameSync(this.path, `${this.path}.1`);
|
|
69
|
+
this.#fd = openSync(this.path, "a", 384);
|
|
70
|
+
chmodSync(this.path, 384);
|
|
71
|
+
this.#bytes = 0;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
function forwardSignal(child, signal) {
|
|
75
|
+
if (child.exitCode !== null || child.signalCode !== null)
|
|
76
|
+
return;
|
|
77
|
+
try {
|
|
78
|
+
child.kill(signal);
|
|
79
|
+
} catch {}
|
|
80
|
+
}
|
|
81
|
+
async function main() {
|
|
82
|
+
const spec = relaySpec();
|
|
83
|
+
const writer = new RotatingLogWriter(spec.logPath);
|
|
84
|
+
const child = spawn(spec.argv[0], spec.argv.slice(1), {
|
|
85
|
+
cwd: spec.cwd,
|
|
86
|
+
env: spec.env,
|
|
87
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
88
|
+
});
|
|
89
|
+
child.stdout?.on("data", (chunk) => writer.write(chunk));
|
|
90
|
+
child.stderr?.on("data", (chunk) => writer.write(chunk));
|
|
91
|
+
process.on("SIGTERM", () => forwardSignal(child, "SIGTERM"));
|
|
92
|
+
process.on("SIGINT", () => forwardSignal(child, "SIGINT"));
|
|
93
|
+
const result = await new Promise((resolve) => {
|
|
94
|
+
child.once("error", (error) => {
|
|
95
|
+
writer.write(Buffer.from(`hestia proc relay: ${error.message}
|
|
96
|
+
`));
|
|
97
|
+
resolve({ code: 127, signal: null });
|
|
98
|
+
});
|
|
99
|
+
child.once("exit", (code, signal) => resolve({ code, signal }));
|
|
100
|
+
});
|
|
101
|
+
writer.close();
|
|
102
|
+
if (result.signal !== null)
|
|
103
|
+
process.exit(1);
|
|
104
|
+
process.exit(result.code ?? 1);
|
|
105
|
+
}
|
|
106
|
+
if (import.meta.main) {
|
|
107
|
+
main().catch((error) => {
|
|
108
|
+
process.stderr.write(`hestia proc relay failed: ${error.message}
|
|
109
|
+
`);
|
|
110
|
+
process.exit(1);
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
export {
|
|
114
|
+
RotatingLogWriter
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
//# debugId=D0D371B26D9A99F264756E2164756E21
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../packages/engine/src/proc/proc-relay.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"#!/usr/bin/env bun\nimport {\n chmodSync,\n closeSync,\n existsSync,\n openSync,\n renameSync,\n rmSync,\n statSync,\n writeSync,\n} from \"node:fs\";\nimport { spawn, type ChildProcess } from \"node:child_process\";\n\nconst MAX_LOG_BYTES = 25 * 1024 * 1024;\nconst ARCHIVE_COUNT = 3;\nconst RELAY_SPEC_ENV = \"HESTIA_PROC_RELAY_SPEC\";\n\ninterface RelaySpec {\n argv: string[];\n cwd: string;\n env: Record<string, string | undefined>;\n logPath: string;\n}\n\nfunction relaySpec(): RelaySpec {\n const encoded = process.env[RELAY_SPEC_ENV];\n delete process.env[RELAY_SPEC_ENV];\n if (encoded === undefined) throw new Error(`${RELAY_SPEC_ENV} is missing`);\n const value = JSON.parse(Buffer.from(encoded, \"base64\").toString(\"utf8\")) as RelaySpec;\n if (!Array.isArray(value.argv) || value.argv.length === 0 ||\n typeof value.cwd !== \"string\" || typeof value.logPath !== \"string\") {\n throw new Error(\"invalid proc relay spec\");\n }\n return value;\n}\n\nexport class RotatingLogWriter {\n #fd: number;\n #bytes: number;\n\n constructor(\n readonly path: string,\n readonly maxLogBytes = MAX_LOG_BYTES,\n readonly archiveCount = ARCHIVE_COUNT,\n ) {\n this.#fd = openSync(path, \"a\", 0o600);\n chmodSync(path, 0o600);\n this.#bytes = statSync(path).size;\n }\n\n write(chunk: Buffer): void {\n let offset = 0;\n while (offset < chunk.byteLength) {\n if (this.#bytes >= this.maxLogBytes) this.rotate();\n const length = Math.min(chunk.byteLength - offset, this.maxLogBytes - this.#bytes);\n writeSync(this.#fd, chunk, offset, length);\n offset += length;\n this.#bytes += length;\n }\n }\n\n close(): void {\n closeSync(this.#fd);\n }\n\n private rotate(): void {\n closeSync(this.#fd);\n rmSync(`${this.path}.${this.archiveCount}`, { force: true });\n for (let index = this.archiveCount - 1; index >= 1; index -= 1) {\n const source = `${this.path}.${index}`;\n if (existsSync(source)) renameSync(source, `${this.path}.${index + 1}`);\n }\n if (existsSync(this.path)) renameSync(this.path, `${this.path}.1`);\n this.#fd = openSync(this.path, \"a\", 0o600);\n chmodSync(this.path, 0o600);\n this.#bytes = 0;\n }\n}\n\nfunction forwardSignal(child: ChildProcess, signal: NodeJS.Signals): void {\n if (child.exitCode !== null || child.signalCode !== null) return;\n try {\n child.kill(signal);\n } catch {}\n}\n\nasync function main(): Promise<void> {\n const spec = relaySpec();\n const writer = new RotatingLogWriter(spec.logPath);\n const child = spawn(spec.argv[0]!, spec.argv.slice(1), {\n cwd: spec.cwd,\n env: spec.env as NodeJS.ProcessEnv,\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n });\n child.stdout?.on(\"data\", (chunk: Buffer) => writer.write(chunk));\n child.stderr?.on(\"data\", (chunk: Buffer) => writer.write(chunk));\n process.on(\"SIGTERM\", () => forwardSignal(child, \"SIGTERM\"));\n process.on(\"SIGINT\", () => forwardSignal(child, \"SIGINT\"));\n const result = await new Promise<{ code: number | null; signal: NodeJS.Signals | null }>((resolve) => {\n child.once(\"error\", (error) => {\n writer.write(Buffer.from(`hestia proc relay: ${error.message}\\n`));\n resolve({ code: 127, signal: null });\n });\n child.once(\"exit\", (code, signal) => resolve({ code, signal }));\n });\n writer.close();\n if (result.signal !== null) process.exit(1);\n process.exit(result.code ?? 1);\n}\n\nif (import.meta.main) {\n void main().catch((error) => {\n process.stderr.write(`hestia proc relay failed: ${(error as Error).message}\\n`);\n process.exit(1);\n });\n}\n"
|
|
6
|
+
],
|
|
7
|
+
"mappings": "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",
|
|
8
|
+
"debugId": "D0D371B26D9A99F264756E2164756E21",
|
|
9
|
+
"names": []
|
|
10
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@tridha643/hestia",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Per-worktree isolated development stacks for humans and coding agents",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "https://github.com/tridha643/hestia"
|
|
8
|
+
},
|
|
9
|
+
"type": "module",
|
|
10
|
+
"workspaces": ["packages/*"],
|
|
11
|
+
"bin": {
|
|
12
|
+
"hestia": "./dist/cli.js"
|
|
13
|
+
},
|
|
14
|
+
"scripts": {
|
|
15
|
+
"hestia": "bun run packages/cli/src/index.ts",
|
|
16
|
+
"test": "bun test",
|
|
17
|
+
"test:tui": "bun test packages/tui/test test/pty",
|
|
18
|
+
"build": "bun run scripts/build-dist.ts",
|
|
19
|
+
"prepack": "bun run build"
|
|
20
|
+
},
|
|
21
|
+
"devDependencies": {
|
|
22
|
+
"@types/bun": "latest",
|
|
23
|
+
"@types/react": "19.2.14",
|
|
24
|
+
"portless": "0.15.1",
|
|
25
|
+
"tuistory": "0.0.16",
|
|
26
|
+
"typescript": "^5.9.0"
|
|
27
|
+
},
|
|
28
|
+
"dependencies": {
|
|
29
|
+
"@opentui/core": "0.4.3",
|
|
30
|
+
"@opentui/react": "0.4.3",
|
|
31
|
+
"react": "19.2.4",
|
|
32
|
+
"yaml": "^2.6.0"
|
|
33
|
+
},
|
|
34
|
+
"engines": {
|
|
35
|
+
"bun": ">=1.3.0"
|
|
36
|
+
},
|
|
37
|
+
"os": ["darwin"],
|
|
38
|
+
"cpu": ["arm64", "x64"],
|
|
39
|
+
"files": [
|
|
40
|
+
"dist",
|
|
41
|
+
"skills/hestia",
|
|
42
|
+
"README.md",
|
|
43
|
+
"NOTICES.md"
|
|
44
|
+
],
|
|
45
|
+
"publishConfig": {
|
|
46
|
+
"access": "public",
|
|
47
|
+
"provenance": true
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: hestia
|
|
3
|
+
description: Discover, configure, run, inspect, expose, and tear down isolated macOS/Bun development workloads in parallel Git worktrees.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Hestia agent workflow
|
|
7
|
+
|
|
8
|
+
Hestia owns development workload lifecycle and reachability for this worktree.
|
|
9
|
+
Do not choose ports, start configured dev servers directly, or run a second
|
|
10
|
+
Cloudflare connector beside Hestia.
|
|
11
|
+
|
|
12
|
+
## Start with discovery
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
hestia version --json
|
|
16
|
+
hestia discover --json
|
|
17
|
+
hestia doctor --json
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Discovery is read-only. Inspect:
|
|
21
|
+
|
|
22
|
+
- `repository`: exact repo, `repoId`, branch, and absolute worktree.
|
|
23
|
+
- `runnableWorkloads` and `candidateWorkloads`.
|
|
24
|
+
- `bindings` and configured endpoint aliases.
|
|
25
|
+
- `decisionSource`: `discovery`, `repository`, `machine`, or `worktree`.
|
|
26
|
+
- `missingDecisions`, `conflicts`, and `suggestions`.
|
|
27
|
+
|
|
28
|
+
Do not guess when discovery reports a missing decision. If repository changes
|
|
29
|
+
are authorized, use a suggested `hestia init ... --scope repository --write`.
|
|
30
|
+
For personal/non-portable setup use `--scope machine --write`. Without
|
|
31
|
+
`--write`, init prints the complete proposed TOML and changes nothing. Hestia
|
|
32
|
+
never commits.
|
|
33
|
+
|
|
34
|
+
Examples:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
hestia init dockerfile web Dockerfile --scope repository
|
|
38
|
+
hestia init proc consumer --scope machine --no-port -- bun run consume
|
|
39
|
+
hestia init endpoint dashboard web 3000/tcp http --scope repository --write
|
|
40
|
+
hestia init wrangler slack-worker apps/slack/wrangler.toml --scope repository --write
|
|
41
|
+
hestia discover --json
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Configuration layers are worktree runtime intent, machine repository overlay
|
|
45
|
+
at `~/.hestia/repositories/<repoId>.toml`, optional committed `hestia.toml`,
|
|
46
|
+
then automatic discovery. Conflicts fail rather than silently overriding a
|
|
47
|
+
workload source. `.hestia/` must remain ignored.
|
|
48
|
+
|
|
49
|
+
## Run and consume structured endpoints
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
hestia up --json
|
|
53
|
+
hestia endpoint list --json
|
|
54
|
+
hestia endpoint get dashboard --json
|
|
55
|
+
hestia logs web -f --json
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
A workload is a lifecycle/logging unit. A binding is an owned socket such as
|
|
59
|
+
`api:8080/tcp`. An endpoint alias assigns protocol meaning to a binding.
|
|
60
|
+
Resolution order is exact alias, canonical `workload:target/protocol`, then a
|
|
61
|
+
uniquely-bound workload. Handle `service-port-ambiguous` by using a reported
|
|
62
|
+
selector or configured alias.
|
|
63
|
+
|
|
64
|
+
Canonical port variables look like `HESTIA_API_9090_TCP_PORT`. Only a unique
|
|
65
|
+
binding receives `HESTIA_API_PORT`. HTTP aliases also receive direct/local URL
|
|
66
|
+
variables. Never invent an HTTP URL for a raw TCP/UDP endpoint.
|
|
67
|
+
|
|
68
|
+
Use `hestia run` only for explicit ad-hoc processes not already declared as a
|
|
69
|
+
workload:
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
hestia run --name web -- bun run dev
|
|
73
|
+
hestia run --name consumer --no-port -- bun run consume
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Hestia injects `$PORT` and substitutes `{port}`. Raw `--env` values remain in
|
|
77
|
+
memory and are not persisted. Logs are bounded by the rotating relay.
|
|
78
|
+
|
|
79
|
+
## Routes and exposure
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
hestia route add dashboard --json
|
|
83
|
+
hestia route disable dashboard --json
|
|
84
|
+
hestia route reset dashboard --json
|
|
85
|
+
hestia open dashboard --local --json
|
|
86
|
+
hestia expose dashboard --json
|
|
87
|
+
hestia expose dashboard --tunnel tri --zone example.dev --json
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Local route overrides are per worktree. `disable` suppresses repository or
|
|
91
|
+
machine defaults; `reset` removes the override and restores defaults.
|
|
92
|
+
|
|
93
|
+
Public ingress is fail-closed but unauthenticated. Hestiad verifies the current
|
|
94
|
+
process/container identity and port ownership before every origin connection.
|
|
95
|
+
If a port is recycled, the foreign listener receives zero bytes.
|
|
96
|
+
|
|
97
|
+
Named mode never writes DNS. It requires:
|
|
98
|
+
|
|
99
|
+
```text
|
|
100
|
+
*.<zone> CNAME <tunnel-uuid>.cfargotunnel.com
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Handle `dns-route-required` by reporting its `wildcardTarget` to the human.
|
|
104
|
+
Never use the removed `--overwrite-dns`. Named mode may use
|
|
105
|
+
`--keep-host-header`; quick mode rejects it. Never start another
|
|
106
|
+
`cloudflared tunnel run` for an adopted tunnel.
|
|
107
|
+
|
|
108
|
+
## Inspect and finish
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
hestia status --json
|
|
112
|
+
hestia env --json
|
|
113
|
+
hestia doctor --json
|
|
114
|
+
hestia tui
|
|
115
|
+
hestia down
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Fleet shows the repository, selected branch, absolute worktree, project,
|
|
119
|
+
workloads, endpoints, and logs. It is for human operation; agents should use
|
|
120
|
+
JSON/NDJSON commands.
|
|
121
|
+
|
|
122
|
+
Run `down` before switching or deleting the branch. After deletion, use the
|
|
123
|
+
recorded project with `hestia down --project <project>`. Docker workloads
|
|
124
|
+
cannot be stopped individually (`backend-not-stoppable`); proc/Wrangler
|
|
125
|
+
workloads can. Named volumes are retained unless `--destroy` is explicit.
|
|
126
|
+
|
|
127
|
+
## Stable error remedies
|
|
128
|
+
|
|
129
|
+
| code | remedy |
|
|
130
|
+
|---|---|
|
|
131
|
+
| `setup-required` | follow `discover --json` suggestions with explicit authorization |
|
|
132
|
+
| `config-conflict` / `config-invalid` | fix the reported layer/path; do not guess precedence |
|
|
133
|
+
| `state-not-ignored` | add the exact `.hestia/` line reported by `doctor` |
|
|
134
|
+
| `state-corrupt` | inspect the path, then use the reported `down --project` recovery |
|
|
135
|
+
| `migration-required` | inspect if needed, then down the legacy stack |
|
|
136
|
+
| `stack-identity-changed` | down the recorded project before continuing on this checkout |
|
|
137
|
+
| `env-key-conflict` | rename one workload/alias so normalized env keys differ |
|
|
138
|
+
| `compose-unsupported` | replace the unsupported global/shared construct or use a supported topology |
|
|
139
|
+
| `service-port-ambiguous` | use a canonical selector or endpoint alias |
|
|
140
|
+
| `proc-ready-timeout` | inspect logs; use `--no-port` only for non-servers |
|
|
141
|
+
| `stack-limit` | down an owned stack or retry with `--wait=120` |
|
|
142
|
+
| `dns-route-required` | configure its wildcard CNAME target |
|
|
143
|
+
| `tunnel-busy` | stop the foreign connector; do not kill processes Hestia did not start |
|
|
144
|
+
| `route-origin-unavailable` | restart the workload through Hestia |
|
|
145
|
+
| `backend-not-stoppable` | use `hestia down` for Docker workloads |
|
|
146
|
+
|
|
147
|
+
`--json` failures are `{ "error": { "code", "message", "details"? } }`.
|
|
148
|
+
`hestia logs --json` is NDJSON, one `LogLine` per line.
|