@aegisagentics/agent 0.1.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.
@@ -0,0 +1,186 @@
1
+ import { spawn } from "node:child_process";
2
+ import path from "node:path";
3
+
4
+ export const DEFAULT_CODEX_PLUGIN_ID = "aegis@aegis";
5
+ export const DEFAULT_CODEX_PLUGIN_NAME = "aegis";
6
+
7
+ export function codexMarketplaceFile(paths) {
8
+ return path.join(paths.codexMarketplaceDir, ".agents", "plugins", "marketplace.json");
9
+ }
10
+
11
+ export function buildCodexPluginInstallRequest({
12
+ id = 2,
13
+ marketplaceFile,
14
+ pluginName = DEFAULT_CODEX_PLUGIN_NAME
15
+ }) {
16
+ return {
17
+ jsonrpc: "2.0",
18
+ id,
19
+ method: "plugin/install",
20
+ params: {
21
+ marketplacePath: marketplaceFile,
22
+ pluginName
23
+ }
24
+ };
25
+ }
26
+
27
+ export function buildCodexPluginUninstallRequest({
28
+ id = 2,
29
+ pluginId = DEFAULT_CODEX_PLUGIN_ID
30
+ } = {}) {
31
+ return {
32
+ jsonrpc: "2.0",
33
+ id,
34
+ method: "plugin/uninstall",
35
+ params: {
36
+ pluginId
37
+ }
38
+ };
39
+ }
40
+
41
+ export async function installCodexPlugin({
42
+ codexCommand = "codex",
43
+ marketplaceFile,
44
+ pluginName = DEFAULT_CODEX_PLUGIN_NAME,
45
+ timeoutMs = 15_000
46
+ }) {
47
+ const request = buildCodexPluginInstallRequest({
48
+ marketplaceFile,
49
+ pluginName
50
+ });
51
+ return runCodexPluginRequest({
52
+ codexCommand,
53
+ request,
54
+ timeoutMs
55
+ });
56
+ }
57
+
58
+ export async function uninstallCodexPlugin({
59
+ codexCommand = "codex",
60
+ pluginId = DEFAULT_CODEX_PLUGIN_ID,
61
+ timeoutMs = 15_000
62
+ } = {}) {
63
+ const request = buildCodexPluginUninstallRequest({ pluginId });
64
+ return runCodexPluginRequest({
65
+ codexCommand,
66
+ request,
67
+ timeoutMs
68
+ });
69
+ }
70
+
71
+ async function runCodexPluginRequest({ codexCommand, request, timeoutMs }) {
72
+ const appServer = spawn(codexCommand, ["app-server", "--listen", "stdio://"], {
73
+ stdio: ["pipe", "pipe", "pipe"]
74
+ });
75
+
76
+ let stderr = "";
77
+ let stdoutBuffer = "";
78
+ let settled = false;
79
+
80
+ return new Promise((resolve, reject) => {
81
+ const timer = setTimeout(() => {
82
+ finish(
83
+ reject,
84
+ new Error(
85
+ `Timed out waiting for Codex app-server response to ${request.method}`
86
+ )
87
+ );
88
+ }, timeoutMs);
89
+
90
+ appServer.on("error", (error) => {
91
+ clearTimeout(timer);
92
+ finish(
93
+ reject,
94
+ new Error(
95
+ `Unable to start Codex CLI (${codexCommand}) for plugin management: ${error.message}`
96
+ )
97
+ );
98
+ });
99
+
100
+ appServer.stderr.setEncoding("utf8");
101
+ appServer.stderr.on("data", (chunk) => {
102
+ stderr += chunk;
103
+ });
104
+
105
+ appServer.stdout.setEncoding("utf8");
106
+ appServer.stdout.on("data", (chunk) => {
107
+ stdoutBuffer += chunk;
108
+ const lines = stdoutBuffer.split(/\r?\n/);
109
+ stdoutBuffer = lines.pop() ?? "";
110
+ for (const line of lines) {
111
+ if (line.trim() === "") {
112
+ continue;
113
+ }
114
+ let message;
115
+ try {
116
+ message = JSON.parse(line);
117
+ } catch {
118
+ continue;
119
+ }
120
+ handleMessage(message);
121
+ }
122
+ });
123
+
124
+ appServer.on("close", (code) => {
125
+ if (!settled) {
126
+ clearTimeout(timer);
127
+ finish(
128
+ reject,
129
+ new Error(
130
+ `Codex app-server exited before ${request.method} completed (exit ${code ?? "unknown"}): ${stderr.trim()}`
131
+ )
132
+ );
133
+ }
134
+ });
135
+
136
+ writeJsonRpc({
137
+ jsonrpc: "2.0",
138
+ id: 1,
139
+ method: "initialize",
140
+ params: {
141
+ capabilities: {},
142
+ clientInfo: {
143
+ name: "aegis-agent",
144
+ version: "0.1.0"
145
+ }
146
+ }
147
+ });
148
+
149
+ function handleMessage(message) {
150
+ if (message.id === 1) {
151
+ writeJsonRpc(request);
152
+ return;
153
+ }
154
+ if (message.id !== request.id) {
155
+ return;
156
+ }
157
+ clearTimeout(timer);
158
+ if (message.error) {
159
+ finish(
160
+ reject,
161
+ new Error(
162
+ `Codex ${request.method} failed: ${message.error.message ?? JSON.stringify(message.error)}`
163
+ )
164
+ );
165
+ return;
166
+ }
167
+ finish(resolve, message.result ?? {});
168
+ }
169
+
170
+ function writeJsonRpc(message) {
171
+ appServer.stdin.write(`${JSON.stringify(message)}\n`);
172
+ }
173
+
174
+ function finish(callback, value) {
175
+ if (settled) {
176
+ return;
177
+ }
178
+ settled = true;
179
+ clearTimeout(timer);
180
+ if (!appServer.killed) {
181
+ appServer.kill();
182
+ }
183
+ callback(value);
184
+ }
185
+ });
186
+ }
@@ -0,0 +1,81 @@
1
+ import { spawnSync } from "node:child_process";
2
+ import fs from "node:fs/promises";
3
+ import path from "node:path";
4
+
5
+ import { writeCodexMarketplace } from "./codex.js";
6
+ import { codexMarketplaceFile } from "./codex-plugin.js";
7
+ import { aegisPaths } from "./config.js";
8
+
9
+ export function detectCodexHome() {
10
+ if (process.env.AEGIS_CODEX_HOME?.trim()) {
11
+ return process.env.AEGIS_CODEX_HOME.trim();
12
+ }
13
+
14
+ const result = spawnSync(
15
+ "snap",
16
+ ["run", "--shell", "codex", "-c", "printf '%s' \"$HOME\""],
17
+ {
18
+ encoding: "utf8",
19
+ stdio: ["ignore", "pipe", "ignore"]
20
+ }
21
+ );
22
+ if (result.status !== 0) {
23
+ return null;
24
+ }
25
+ const home = result.stdout.trim();
26
+ return home === "" ? null : home;
27
+ }
28
+
29
+ export async function prepareCodexPluginInstallTarget({
30
+ codexHome = detectCodexHome(),
31
+ userPaths
32
+ }) {
33
+ if (codexHome === null || samePath(codexHome, userPaths.homeDir)) {
34
+ await writeCodexMarketplace({
35
+ binaryPath: userPaths.binaryFile,
36
+ marketplaceDir: userPaths.codexMarketplaceDir
37
+ });
38
+ return {
39
+ binaryFile: userPaths.binaryFile,
40
+ codexHome: userPaths.homeDir,
41
+ marketplaceFile: codexMarketplaceFile(userPaths),
42
+ mirrored: false
43
+ };
44
+ }
45
+
46
+ const codexPaths = aegisPaths(codexHome);
47
+ await copyExecutable(userPaths.binaryFile, codexPaths.binaryFile);
48
+ await copyPrivateFile(userPaths.configFile, codexPaths.configFile);
49
+ await copyPrivateFile(userPaths.credentialsFile, codexPaths.credentialsFile);
50
+ await writeCodexMarketplace({
51
+ binaryPath: codexPaths.binaryFile,
52
+ marketplaceDir: codexPaths.codexMarketplaceDir
53
+ });
54
+
55
+ return {
56
+ binaryFile: codexPaths.binaryFile,
57
+ codexHome,
58
+ marketplaceFile: codexMarketplaceFile(codexPaths),
59
+ mirrored: true
60
+ };
61
+ }
62
+
63
+ function samePath(left, right) {
64
+ return path.resolve(left) === path.resolve(right);
65
+ }
66
+
67
+ async function copyExecutable(source, destination) {
68
+ await copyFile(source, destination);
69
+ const sourceMode = (await fs.stat(source)).mode & 0o777;
70
+ await fs.chmod(destination, sourceMode || 0o755);
71
+ }
72
+
73
+ async function copyPrivateFile(source, destination) {
74
+ await copyFile(source, destination);
75
+ await fs.chmod(destination, 0o600);
76
+ }
77
+
78
+ async function copyFile(source, destination) {
79
+ await fs.mkdir(path.dirname(destination), { recursive: true });
80
+ await fs.copyFile(source, destination);
81
+ }
@@ -0,0 +1,110 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+
4
+ const HOOK_EVENTS = [
5
+ ["SessionStart", "startup|resume|clear"],
6
+ ["UserPromptSubmit", null],
7
+ ["PreToolUse", "*"],
8
+ ["PostToolUse", "*"],
9
+ ["Stop", null]
10
+ ];
11
+
12
+ export function buildCodexMarketplace({ binaryPath }) {
13
+ const marketplace = {
14
+ interface: {
15
+ developerName: "Aegis Agentics",
16
+ displayName: "Aegis ControlTower",
17
+ shortDescription: "Governed telemetry for Codex agent sessions.",
18
+ websiteURL: "https://aegisagentics.com"
19
+ },
20
+ name: "aegis",
21
+ plugins: [
22
+ {
23
+ category: "Security",
24
+ name: "aegis",
25
+ policy: {
26
+ authentication: "ON_INSTALL",
27
+ installation: "AVAILABLE"
28
+ },
29
+ source: {
30
+ path: "./plugins/aegis",
31
+ source: "local"
32
+ }
33
+ }
34
+ ]
35
+ };
36
+
37
+ const plugin = {
38
+ author: {
39
+ name: "Aegis Agentics",
40
+ url: "https://aegisagentics.com"
41
+ },
42
+ description: "Aegis ControlTower monitoring for Codex sessions, turns, and tools.",
43
+ displayName: "Aegis ControlTower",
44
+ homepage: "https://aegisagentics.com",
45
+ hooks: "./hooks/hooks.json",
46
+ interface: {
47
+ capabilities: ["Read"],
48
+ category: "Security",
49
+ developerName: "Aegis Agentics",
50
+ displayName: "Aegis ControlTower",
51
+ shortDescription: "Monitor Codex agent activity in Aegis ControlTower"
52
+ },
53
+ keywords: ["aegis", "controltower", "governance", "telemetry", "codex"],
54
+ license: "UNLICENSED",
55
+ name: "aegis",
56
+ repository: "https://github.com/aegisagentics/controltower",
57
+ version: "0.1.0"
58
+ };
59
+
60
+ const hooks = buildCodexHooks({ binaryPath });
61
+
62
+ return { hooks, marketplace, plugin };
63
+ }
64
+
65
+ export function buildCodexHooks({ binaryPath }) {
66
+ return {
67
+ hooks: Object.fromEntries(
68
+ HOOK_EVENTS.map(([event, matcher]) => {
69
+ const group = {
70
+ hooks: [
71
+ {
72
+ command: `${binaryPath} hook --vendor=codex --event=${event}`,
73
+ timeout: event === "Stop" ? 30 : 10,
74
+ type: "command"
75
+ }
76
+ ]
77
+ };
78
+ if (matcher !== null) {
79
+ group.matcher = matcher;
80
+ }
81
+ return [event, [group]];
82
+ })
83
+ )
84
+ };
85
+ }
86
+
87
+ export async function writeCodexMarketplace({ binaryPath, marketplaceDir }) {
88
+ const { hooks, marketplace, plugin } = buildCodexMarketplace({ binaryPath });
89
+ const marketplaceFile = path.join(marketplaceDir, ".agents", "plugins", "marketplace.json");
90
+ const pluginFile = path.join(marketplaceDir, "plugins", "aegis", ".codex-plugin", "plugin.json");
91
+ const hooksFile = path.join(marketplaceDir, "plugins", "aegis", "hooks", "hooks.json");
92
+
93
+ await fs.mkdir(path.dirname(marketplaceFile), { recursive: true });
94
+ await fs.mkdir(path.dirname(pluginFile), { recursive: true });
95
+ await fs.mkdir(path.dirname(hooksFile), { recursive: true });
96
+
97
+ await writeJson(marketplaceFile, marketplace);
98
+ await writeJson(pluginFile, plugin);
99
+ await writeJson(hooksFile, hooks);
100
+
101
+ return {
102
+ hooksFile,
103
+ marketplaceFile,
104
+ pluginFile
105
+ };
106
+ }
107
+
108
+ async function writeJson(filePath, payload) {
109
+ await fs.writeFile(filePath, `${JSON.stringify(payload, null, 2)}\n`, "utf8");
110
+ }
@@ -0,0 +1,106 @@
1
+ import path from "node:path";
2
+
3
+ const VENDOR_ALIASES = new Map([
4
+ ["cc", "claude-code"],
5
+ ["claude", "claude-code"],
6
+ ["claude-code", "claude-code"],
7
+ ["claudecode", "claude-code"],
8
+ ["codex", "codex"],
9
+ ["cursor", "cursor"],
10
+ ["goose", "goose"]
11
+ ]);
12
+
13
+ const VENDOR_ORDER = ["codex", "claude-code", "cursor", "goose"];
14
+
15
+ export function normalizeVendorList(value) {
16
+ const raw = Array.isArray(value) ? value : String(value ?? "").split(",");
17
+ const seen = new Set();
18
+
19
+ for (const item of raw) {
20
+ const key = String(item).trim().toLowerCase();
21
+ if (key === "" || key === "all") {
22
+ if (key === "all") {
23
+ for (const vendor of VENDOR_ORDER) {
24
+ seen.add(vendor);
25
+ }
26
+ }
27
+ continue;
28
+ }
29
+ const normalized = VENDOR_ALIASES.get(key);
30
+ if (normalized === undefined) {
31
+ throw new Error(`Unknown Aegis agent vendor: ${item}`);
32
+ }
33
+ seen.add(normalized);
34
+ }
35
+
36
+ return VENDOR_ORDER.filter((vendor) => seen.has(vendor));
37
+ }
38
+
39
+ export function aegisPaths(homeDir = process.env.HOME) {
40
+ if (!homeDir) {
41
+ throw new Error("Unable to resolve home directory for Aegis agent install.");
42
+ }
43
+ const configDir = path.join(homeDir, ".config", "aegis-agent");
44
+ const dataDir = path.join(homeDir, ".local", "share", "aegis-agent");
45
+ const binaryDir = path.join(dataDir, "bin");
46
+
47
+ return {
48
+ binaryDir,
49
+ binaryFile: path.join(binaryDir, "aegis-agent"),
50
+ codexMarketplaceDir: path.join(dataDir, "codex-marketplace"),
51
+ configDir,
52
+ configFile: path.join(configDir, "config.json"),
53
+ credentialsFile: path.join(configDir, "credentials.json"),
54
+ dataDir,
55
+ homeDir,
56
+ queueDir: path.join(dataDir, "queue")
57
+ };
58
+ }
59
+
60
+ export function buildLocalConfig({
61
+ agentId,
62
+ apiBaseUrl,
63
+ capturePolicy,
64
+ installationId,
65
+ activityLogEndpoint,
66
+ otlpHttpBaseUrl,
67
+ otlpTracesEndpoint,
68
+ organizationId,
69
+ workspaceId,
70
+ workspaceKind
71
+ }) {
72
+ const normalizedApiBaseUrl = String(apiBaseUrl).replace(/\/+$/, "");
73
+ const normalizedOtlpHttpBaseUrl =
74
+ otlpHttpBaseUrl === undefined || otlpHttpBaseUrl === null || String(otlpHttpBaseUrl).trim() === ""
75
+ ? undefined
76
+ : String(otlpHttpBaseUrl).trim().replace(/\/+$/, "");
77
+ const normalizedOtlpTracesEndpoint =
78
+ otlpTracesEndpoint === undefined || otlpTracesEndpoint === null || String(otlpTracesEndpoint).trim() === ""
79
+ ? normalizedOtlpHttpBaseUrl === undefined
80
+ ? undefined
81
+ : `${normalizedOtlpHttpBaseUrl}/v1/traces`
82
+ : String(otlpTracesEndpoint).trim().replace(/\/+$/, "");
83
+ if (normalizedOtlpTracesEndpoint === undefined) {
84
+ throw new Error(
85
+ "OTLP HTTP base URL is required; pass otlpHttpBaseUrl or otlpTracesEndpoint"
86
+ );
87
+ }
88
+ const normalizedActivityLogEndpoint =
89
+ activityLogEndpoint === undefined || activityLogEndpoint === null || String(activityLogEndpoint).trim() === ""
90
+ ? `${normalizedApiBaseUrl}/v1/agent-activity/logs`
91
+ : String(activityLogEndpoint).trim().replace(/\/+$/, "");
92
+ return {
93
+ schemaVersion: "aegis.agent_config.v1",
94
+ agentId,
95
+ apiBaseUrl: normalizedApiBaseUrl,
96
+ activityLogEndpoint: normalizedActivityLogEndpoint,
97
+ captureEndpoint: `${normalizedApiBaseUrl}/v1/capture/artifacts`,
98
+ capturePolicy,
99
+ installationId,
100
+ organizationId,
101
+ otlpHttpBaseUrl: normalizedOtlpHttpBaseUrl,
102
+ otlpTracesEndpoint: normalizedOtlpTracesEndpoint,
103
+ workspaceId,
104
+ workspaceKind
105
+ };
106
+ }