@mithran-hq/aegis 0.0.0-stage → 0.9.5
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/LICENSE +18 -0
- package/README.md +135 -2
- package/aegis.mjs +234 -0
- package/package.json +41 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
Apache License
|
|
2
|
+
Version 2.0, January 2004
|
|
3
|
+
https://www.apache.org/licenses/
|
|
4
|
+
|
|
5
|
+
Copyright 2026 Mithran
|
|
6
|
+
|
|
7
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
8
|
+
you may not use this file except in compliance with the License.
|
|
9
|
+
You may obtain a copy of the License at
|
|
10
|
+
|
|
11
|
+
https://www.apache.org/licenses/LICENSE-2.0
|
|
12
|
+
|
|
13
|
+
Unless required by applicable law or agreed to in writing, software
|
|
14
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
15
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
16
|
+
See the License for the specific language governing permissions and
|
|
17
|
+
limitations under the License.
|
|
18
|
+
|
package/README.md
CHANGED
|
@@ -1,3 +1,136 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Aegis
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Aegis is the Mithran CLI for account authentication, agent gateway
|
|
4
|
+
configuration, and Forge deploys. This package is the npm launcher; it runs the
|
|
5
|
+
native `aegis` binary from the matching `@mithran-hq/aegis-darwin-arm64` package.
|
|
6
|
+
|
|
7
|
+
## Requirements
|
|
8
|
+
|
|
9
|
+
- macOS 15 or later
|
|
10
|
+
- Apple silicon (arm64)
|
|
11
|
+
- Node.js and npm
|
|
12
|
+
|
|
13
|
+
The launcher refuses unsupported platforms and macOS versions before it starts
|
|
14
|
+
the native binary.
|
|
15
|
+
|
|
16
|
+
## Install
|
|
17
|
+
|
|
18
|
+
Prerelease builds are published under the npm dist-tag `next`:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npm install -g @mithran-hq/aegis@next
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
The release flow writes the `next` tag and preserves an existing `latest` tag;
|
|
25
|
+
it does not promote `latest`. Install a pinned version when you need a fixed
|
|
26
|
+
build:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
npm install -g @mithran-hq/aegis@0.9.5
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Check the installed version:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
aegis version
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Sign in
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
aegis auth login
|
|
42
|
+
aegis auth status
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
`aegis auth login` opens the browser for sign-in and always prints the sign-in
|
|
46
|
+
URL. Pass `--no-browser` to skip the browser launch and open that URL yourself.
|
|
47
|
+
Sign out with `aegis auth logout`.
|
|
48
|
+
|
|
49
|
+
## Gateway
|
|
50
|
+
|
|
51
|
+
Enable, inspect, or disable the gateway for one supported agent:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
aegis gateway enable --agent claude
|
|
55
|
+
aegis gateway status --agent claude
|
|
56
|
+
aegis gateway disable --agent claude
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Supported agents are `claude` and `codex`. Gateway commands require a bound
|
|
60
|
+
account; run `aegis auth login` first.
|
|
61
|
+
|
|
62
|
+
## Forge
|
|
63
|
+
|
|
64
|
+
Initialize a Forge app in the current directory, then commit and push it before
|
|
65
|
+
deploying the committed source:
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
mkdir my-app
|
|
69
|
+
cd my-app
|
|
70
|
+
aegis forge init
|
|
71
|
+
git init
|
|
72
|
+
git add mithran.yaml index.html
|
|
73
|
+
git commit -m "Initialize app"
|
|
74
|
+
git remote add origin git@github.com:<owner>/<repo>.git
|
|
75
|
+
git push -u origin HEAD
|
|
76
|
+
aegis forge deploy
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
`aegis forge init` writes `mithran.yaml` and `index.html`. `aegis forge deploy`
|
|
80
|
+
resolves the committed source from the Git origin and `HEAD`; pass
|
|
81
|
+
`--repo <owner>/<repo>` when the origin is not a GitHub repository, and pass
|
|
82
|
+
`--ref` or `--sha` to select another committed source. Deploy requires a bound
|
|
83
|
+
account.
|
|
84
|
+
|
|
85
|
+
## Managed Forge skill
|
|
86
|
+
|
|
87
|
+
A normal global user install also installs the `aegis-forge` instruction skill
|
|
88
|
+
for Claude and Codex:
|
|
89
|
+
|
|
90
|
+
- `~/.claude/skills/aegis-forge/SKILL.md`
|
|
91
|
+
- `~/.agents/skills/aegis-forge/SKILL.md`
|
|
92
|
+
|
|
93
|
+
Automatic installation runs only for a normal global user installation. It
|
|
94
|
+
skips when npm runs as root or through sudo, under CI, from a local or
|
|
95
|
+
dependency install, or through npx. A skip is reported and changes no files;
|
|
96
|
+
run `aegis skills install` to install explicitly.
|
|
97
|
+
|
|
98
|
+
If npm installs with scripts disabled, install the skill afterwards:
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
aegis skills install
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Check the installed state with `aegis skills status`. Existing files Aegis does
|
|
105
|
+
not own, changed Aegis-owned files and unexpected entries are preserved and
|
|
106
|
+
reported; unsafe paths are not followed. Automatic installation uses the
|
|
107
|
+
standard OS-account homes. A custom agent home is selected only by an explicit
|
|
108
|
+
`aegis skills install --agent-home <path>`.
|
|
109
|
+
|
|
110
|
+
npm has no uninstall hook, so remove the skill before removing the CLI:
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
aegis skills remove
|
|
114
|
+
npm uninstall -g @mithran-hq/aegis
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
The skill explains `forge init` and deploying committed source. Installing it
|
|
118
|
+
grants no deployment authority: authentication, operator decisions and
|
|
119
|
+
point-of-use authorization still apply.
|
|
120
|
+
|
|
121
|
+
## Documentation
|
|
122
|
+
|
|
123
|
+
User and developer documentation: https://mithran.ai/docs
|
|
124
|
+
|
|
125
|
+
## Uninstall
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
npm uninstall -g @mithran-hq/aegis
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
## Support and security
|
|
132
|
+
|
|
133
|
+
Questions and bug reports: https://github.com/mithran-hq/aegis/issues
|
|
134
|
+
|
|
135
|
+
Security policy: https://github.com/mithran-hq/aegis/blob/main/SECURITY.md
|
|
136
|
+
Do not include live credentials in a report.
|
package/aegis.mjs
ADDED
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
3
|
+
import { readFileSync, statSync, writeSync } from "node:fs";
|
|
4
|
+
import { createRequire } from "node:module";
|
|
5
|
+
import { dirname, join } from "node:path";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
7
|
+
import { spawn } from "node:child_process";
|
|
8
|
+
import { release as osRelease } from "node:os";
|
|
9
|
+
|
|
10
|
+
const MANIFEST_SCHEMA = "aegis.npm.platform.v1";
|
|
11
|
+
const CAPABILITY = "aegis:product:reduced-default";
|
|
12
|
+
const PAYLOAD_PATH = "payload/aegis";
|
|
13
|
+
const MANIFEST_NAME = "aegis.manifest.json";
|
|
14
|
+
const MINIMUM_MACOS_MAJOR = 15;
|
|
15
|
+
const MH_MAGIC_64 = 0xfeedfacf;
|
|
16
|
+
const CPU_TYPE_ARM64 = 0x0100000c;
|
|
17
|
+
const FORWARDED_SIGNALS = ["SIGHUP", "SIGINT", "SIGTERM", "SIGQUIT"];
|
|
18
|
+
const SIGNAL_NUMBERS = { SIGHUP: 1, SIGINT: 2, SIGQUIT: 3, SIGTERM: 15 };
|
|
19
|
+
|
|
20
|
+
function refuse(message) {
|
|
21
|
+
const text = Buffer.from(`aegis: ${message}\n`, "utf8");
|
|
22
|
+
try {
|
|
23
|
+
writeSync(2, text);
|
|
24
|
+
} catch {
|
|
25
|
+
process.stderr.write(text.toString("utf8"));
|
|
26
|
+
}
|
|
27
|
+
process.exit(1);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function readJson(path, label) {
|
|
31
|
+
let bytes;
|
|
32
|
+
try {
|
|
33
|
+
bytes = readFileSync(path);
|
|
34
|
+
} catch {
|
|
35
|
+
refuse(`${label} is missing at ${path}`);
|
|
36
|
+
}
|
|
37
|
+
let parsed;
|
|
38
|
+
try {
|
|
39
|
+
parsed = JSON.parse(bytes.toString("utf8"));
|
|
40
|
+
} catch {
|
|
41
|
+
refuse(`${label} is malformed at ${path}`);
|
|
42
|
+
}
|
|
43
|
+
if (!isPlainObject(parsed)) {
|
|
44
|
+
refuse(`${label} is malformed at ${path}: expected a JSON object`);
|
|
45
|
+
}
|
|
46
|
+
return parsed;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function isPlainObject(value) {
|
|
50
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function macOSMajorVersion() {
|
|
54
|
+
const system = typeof process.getSystemVersion === "function" ? process.getSystemVersion() : "";
|
|
55
|
+
const fromSystem = Number.parseInt(system, 10);
|
|
56
|
+
if (Number.isInteger(fromSystem)) {
|
|
57
|
+
return fromSystem;
|
|
58
|
+
}
|
|
59
|
+
const kernelMajor = Number.parseInt(String(osRelease()).split(".")[0], 10);
|
|
60
|
+
if (Number.isInteger(kernelMajor)) {
|
|
61
|
+
return kernelMajor - 9;
|
|
62
|
+
}
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (process.platform !== "darwin" || process.arch !== "arm64") {
|
|
67
|
+
refuse(
|
|
68
|
+
`unsupported host: this launcher runs only on macOS ${MINIMUM_MACOS_MAJOR}+ arm64, ` +
|
|
69
|
+
`found ${process.platform} ${process.arch}. No native payload was started.`,
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
const macOSMajor = macOSMajorVersion();
|
|
73
|
+
if (macOSMajor === null) {
|
|
74
|
+
refuse(
|
|
75
|
+
`unable to determine the macOS version; macOS ${MINIMUM_MACOS_MAJOR}+ arm64 is required. ` +
|
|
76
|
+
"No native payload was started.",
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
if (macOSMajor < MINIMUM_MACOS_MAJOR) {
|
|
80
|
+
refuse(
|
|
81
|
+
`macOS ${MINIMUM_MACOS_MAJOR} or newer is required; found macOS ${macOSMajor}. ` +
|
|
82
|
+
"Upgrade macOS to run aegis.",
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const launcherDir = dirname(fileURLToPath(import.meta.url));
|
|
87
|
+
const launcher = readJson(join(launcherDir, "package.json"), "launcher package metadata");
|
|
88
|
+
if (typeof launcher.name !== "string" || typeof launcher.version !== "string") {
|
|
89
|
+
refuse("launcher package metadata is malformed: name and version are required");
|
|
90
|
+
}
|
|
91
|
+
const launcherName = launcher.name;
|
|
92
|
+
const launcherVersion = launcher.version;
|
|
93
|
+
const suffix = "/aegis";
|
|
94
|
+
const scope = launcherName.endsWith(suffix) ? launcherName.slice(0, -suffix.length) : "";
|
|
95
|
+
if (!/^@[a-z0-9._-]+$/.test(scope)) {
|
|
96
|
+
refuse(`launcher package name is invalid: ${JSON.stringify(launcherName)}`);
|
|
97
|
+
}
|
|
98
|
+
const nativeName = `${scope}/aegis-darwin-arm64`;
|
|
99
|
+
if (!isPlainObject(launcher.optionalDependencies)) {
|
|
100
|
+
refuse("launcher package metadata is malformed: optionalDependencies is required");
|
|
101
|
+
}
|
|
102
|
+
const dependencyKeys = Object.keys(launcher.optionalDependencies);
|
|
103
|
+
if (dependencyKeys.length !== 1 || dependencyKeys[0] !== nativeName) {
|
|
104
|
+
refuse(
|
|
105
|
+
`launcher declares unexpected platform dependencies: ${JSON.stringify(dependencyKeys)}; ` +
|
|
106
|
+
`only ${nativeName} is supported`,
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
if (launcher.optionalDependencies[nativeName] !== launcherVersion) {
|
|
110
|
+
refuse(
|
|
111
|
+
`launcher platform dependency ${nativeName} is pinned to ` +
|
|
112
|
+
`${JSON.stringify(launcher.optionalDependencies[nativeName])}, not ${launcherVersion}`,
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const require = createRequire(import.meta.url);
|
|
117
|
+
let nativePackagePath;
|
|
118
|
+
try {
|
|
119
|
+
nativePackagePath = require.resolve(`${nativeName}/package.json`);
|
|
120
|
+
} catch {
|
|
121
|
+
refuse(
|
|
122
|
+
`native payload package ${nativeName}@${launcherVersion} is not installed. ` +
|
|
123
|
+
`Reinstall the launcher so its optional dependency lands: ` +
|
|
124
|
+
`npm install ${launcherName}@${launcherVersion} on a macOS ${MINIMUM_MACOS_MAJOR}+ arm64 host.`,
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
const nativeDir = dirname(nativePackagePath);
|
|
128
|
+
const native = readJson(nativePackagePath, "native package metadata");
|
|
129
|
+
if (native.name !== nativeName || native.version !== launcherVersion) {
|
|
130
|
+
refuse(
|
|
131
|
+
`native package identity mismatch: expected ${nativeName}@${launcherVersion}, ` +
|
|
132
|
+
`found ${JSON.stringify(native.name)}@${JSON.stringify(native.version)}`,
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const manifest = readJson(join(nativeDir, MANIFEST_NAME), "native manifest");
|
|
137
|
+
if (manifest.schema !== MANIFEST_SCHEMA) {
|
|
138
|
+
refuse(`native manifest schema is invalid: expected ${MANIFEST_SCHEMA}`);
|
|
139
|
+
}
|
|
140
|
+
if (manifest.version !== launcherVersion) {
|
|
141
|
+
refuse(
|
|
142
|
+
`native manifest version ${JSON.stringify(manifest.version)} does not match ` +
|
|
143
|
+
`launcher ${launcherVersion}`,
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
if (!isPlainObject(manifest.payload) || manifest.payload.path !== PAYLOAD_PATH) {
|
|
147
|
+
refuse(`native manifest payload path is invalid: expected ${PAYLOAD_PATH}`);
|
|
148
|
+
}
|
|
149
|
+
if (
|
|
150
|
+
!Array.isArray(manifest.capabilities) ||
|
|
151
|
+
manifest.capabilities.length !== 1 ||
|
|
152
|
+
manifest.capabilities[0] !== CAPABILITY
|
|
153
|
+
) {
|
|
154
|
+
refuse(`native manifest capability contract is invalid: expected ${CAPABILITY}`);
|
|
155
|
+
}
|
|
156
|
+
const compatibility = manifest.compatibility;
|
|
157
|
+
if (
|
|
158
|
+
!isPlainObject(compatibility) ||
|
|
159
|
+
compatibility.os !== "darwin" ||
|
|
160
|
+
compatibility.cpu !== "arm64" ||
|
|
161
|
+
compatibility.libc !== "none" ||
|
|
162
|
+
compatibility.minimum_glibc !== null
|
|
163
|
+
) {
|
|
164
|
+
refuse("native manifest compatibility contract is invalid");
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const payloadPath = join(nativeDir, PAYLOAD_PATH);
|
|
168
|
+
let payloadStat;
|
|
169
|
+
try {
|
|
170
|
+
payloadStat = statSync(payloadPath);
|
|
171
|
+
} catch {
|
|
172
|
+
refuse(`native payload is missing at ${payloadPath}`);
|
|
173
|
+
}
|
|
174
|
+
if (!payloadStat.isFile()) {
|
|
175
|
+
refuse(`native payload is not a regular file at ${payloadPath}`);
|
|
176
|
+
}
|
|
177
|
+
let payload;
|
|
178
|
+
try {
|
|
179
|
+
payload = readFileSync(payloadPath);
|
|
180
|
+
} catch (error) {
|
|
181
|
+
refuse(`native payload cannot be read: ${error.message}`);
|
|
182
|
+
}
|
|
183
|
+
const digest = createHash("sha256").update(payload).digest("hex");
|
|
184
|
+
if (typeof manifest.payload.sha256 !== "string" || digest !== manifest.payload.sha256) {
|
|
185
|
+
refuse("native payload checksum does not match the manifest");
|
|
186
|
+
}
|
|
187
|
+
if (manifest.payload.byte_count !== payload.length) {
|
|
188
|
+
refuse("native payload byte count does not match the manifest");
|
|
189
|
+
}
|
|
190
|
+
if ((payloadStat.mode & 0o111) === 0) {
|
|
191
|
+
refuse("native payload is not executable");
|
|
192
|
+
}
|
|
193
|
+
if (
|
|
194
|
+
payload.length < 8 ||
|
|
195
|
+
payload.readUInt32LE(0) !== MH_MAGIC_64 ||
|
|
196
|
+
payload.readUInt32LE(4) !== CPU_TYPE_ARM64
|
|
197
|
+
) {
|
|
198
|
+
refuse("native payload is not a Darwin arm64 Mach-O binary");
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const child = spawn(payloadPath, process.argv.slice(2), { stdio: "inherit" });
|
|
202
|
+
const forwarders = new Map();
|
|
203
|
+
for (const signal of FORWARDED_SIGNALS) {
|
|
204
|
+
const handler = () => {
|
|
205
|
+
try {
|
|
206
|
+
child.kill(signal);
|
|
207
|
+
} catch {
|
|
208
|
+
// The child already exited; its own status decides the outcome.
|
|
209
|
+
}
|
|
210
|
+
};
|
|
211
|
+
forwarders.set(signal, handler);
|
|
212
|
+
process.on(signal, handler);
|
|
213
|
+
}
|
|
214
|
+
child.on("error", (error) => {
|
|
215
|
+
refuse(`failed to start the native payload: ${error.message}`);
|
|
216
|
+
});
|
|
217
|
+
child.on("exit", (code, signal) => {
|
|
218
|
+
for (const [name, handler] of forwarders) {
|
|
219
|
+
process.removeListener(name, handler);
|
|
220
|
+
}
|
|
221
|
+
if (signal) {
|
|
222
|
+
for (const name of FORWARDED_SIGNALS) {
|
|
223
|
+
process.removeAllListeners(name);
|
|
224
|
+
}
|
|
225
|
+
try {
|
|
226
|
+
process.kill(process.pid, signal);
|
|
227
|
+
} catch {
|
|
228
|
+
// Fall through to the conventional 128+signal status below.
|
|
229
|
+
}
|
|
230
|
+
process.exitCode = 128 + (SIGNAL_NUMBERS[signal] ?? 0);
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
process.exitCode = typeof code === "number" ? code : 1;
|
|
234
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,44 @@
|
|
|
1
1
|
{
|
|
2
|
+
"bin": {
|
|
3
|
+
"aegis": "aegis.mjs"
|
|
4
|
+
},
|
|
5
|
+
"bugs": {
|
|
6
|
+
"url": "https://github.com/mithran-hq/aegis/issues"
|
|
7
|
+
},
|
|
8
|
+
"cpu": [
|
|
9
|
+
"arm64"
|
|
10
|
+
],
|
|
11
|
+
"description": "Aegis auth, gateway, and Forge CLI for macOS 15+ Apple silicon",
|
|
12
|
+
"files": [
|
|
13
|
+
"package.json",
|
|
14
|
+
"aegis.mjs",
|
|
15
|
+
"README.md",
|
|
16
|
+
"LICENSE"
|
|
17
|
+
],
|
|
18
|
+
"homepage": "https://mithran.ai/docs",
|
|
19
|
+
"keywords": [
|
|
20
|
+
"aegis",
|
|
21
|
+
"auth",
|
|
22
|
+
"gateway",
|
|
23
|
+
"forge",
|
|
24
|
+
"cli",
|
|
25
|
+
"macos",
|
|
26
|
+
"arm64"
|
|
27
|
+
],
|
|
28
|
+
"license": "Apache-2.0",
|
|
2
29
|
"name": "@mithran-hq/aegis",
|
|
3
|
-
"
|
|
4
|
-
|
|
5
|
-
|
|
30
|
+
"optionalDependencies": {
|
|
31
|
+
"@mithran-hq/aegis-darwin-arm64": "0.9.5"
|
|
32
|
+
},
|
|
33
|
+
"os": [
|
|
34
|
+
"darwin"
|
|
35
|
+
],
|
|
36
|
+
"repository": {
|
|
37
|
+
"type": "git",
|
|
38
|
+
"url": "git+https://github.com/mithran-hq/aegis.git"
|
|
39
|
+
},
|
|
40
|
+
"scripts": {
|
|
41
|
+
"postinstall": "node aegis.mjs skills install --npm-postinstall"
|
|
42
|
+
},
|
|
43
|
+
"version": "0.9.5"
|
|
6
44
|
}
|