@agentsoc/beacon 0.0.1 → 0.0.3

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/README.md CHANGED
@@ -12,50 +12,36 @@ The Syslog Beacon CLI allows you to configure, install, and run a lightweight lo
12
12
 
13
13
  ## Installation
14
14
 
15
- ### 🚀 Quick Install (One-Line Commands)
15
+ Installers are **hosted on the marketing site** (not duplicated in this repo):
16
16
 
17
- #### macOS / Linux
17
+ | Method | URL / command |
18
+ |--------|----------------|
19
+ | **macOS / Linux (curl)** | `curl -fsSL https://agentsoc.com/connectors/beacon.sh \| bash` |
20
+ | **Windows PowerShell** | `irm https://agentsoc.com/connectors/beacon-install.ps1 \| iex` |
21
+ | **Windows (batch)** | Download and run [beacon-install.bat](https://agentsoc.com/connectors/beacon-install.bat) |
22
+ | **npm** | `npm install -g @agentsoc/beacon` |
18
23
 
19
- ```bash
20
- curl -fsSL https://raw.githubusercontent.com/yourusername/platform.agentsoc.com/main/apps/connectors/syslog-beacon-cli/install.sh | bash
21
- ```
22
-
23
- #### Windows PowerShell
24
-
25
- ```powershell
26
- irm https://raw.githubusercontent.com/yourusername/platform.agentsoc.com/main/apps/connectors/syslog-beacon-cli/install.ps1 | iex
27
- ```
28
-
29
- #### Standard npm
30
-
31
- Published as [`@agentsoc/beacon`](https://www.npmjs.com/package/@agentsoc/beacon) (npm requires a lowercase scope; same AgentSOC package).
32
-
33
- ```bash
34
- npm install -g @agentsoc/beacon
35
- ```
24
+ Published package: [`@agentsoc/beacon`](https://www.npmjs.com/package/@agentsoc/beacon) (npm scope is lowercase `agentsoc`).
36
25
 
37
26
  The `syslog-beacon` command is also installed as an alias for the same CLI.
38
27
 
39
- ### Option 2: Run from source with Bun
28
+ ### Run from source (this monorepo)
40
29
 
41
30
  If you have the repository cloned and [Bun](https://bun.sh/) installed:
42
31
 
43
32
  ```bash
44
33
  cd apps/connectors/syslog-beacon-cli
45
34
  bun install
46
- bun run src/cli.ts config --url <url> --key <key>
47
- bun run src/cli.ts run
35
+ bun run build:simple
36
+ npm install -g .
48
37
  ```
49
38
 
50
- ### Installation Scripts
51
-
52
- Automated installation scripts are provided for easy setup:
53
-
54
- - **`install.sh`** - macOS/Linux installer
55
- - **`install.ps1`** - Windows PowerShell installer
56
- - **`install.bat`** - Windows batch installer
39
+ Then use `beacon` / `syslog-beacon` like a normal global install:
57
40
 
58
- These scripts automatically check for Node.js/npm, install the package, and verify the installation.
41
+ ```bash
42
+ beacon config --key <key> [--env production|development]
43
+ beacon run
44
+ ```
59
45
 
60
46
  ## Usage
61
47
 
@@ -66,21 +52,22 @@ The CLI provides the following commands:
66
52
  Configure the AgentSOC connection (saves to a local configuration file):
67
53
 
68
54
  ```bash
69
- beacon config --url <url> --key <key>
55
+ beacon config --key <key> [--env production|development]
70
56
  ```
71
57
 
72
- **Required Parameters:**
58
+ **Parameters:**
73
59
 
74
- - `--url, -u <url>`: AgentSOC ingest URL (e.g., `https://api.agentsoc.com/ingest`)
75
- - `--key, -k <key>`: AgentSOC API Key (e.g., `sk_1234567890abcdef`)
60
+ - `--key, -k <key>`: AgentSOC API Key (required; e.g., `sk_1234567890abcdef`)
61
+ - `--env, -e <env>`: `production` (default) or `development` — selects default URLs for **log ingest** and for **`beacon status`** (platform API). Production: ingest `https://ingest.agentsoc.com/api/v1/webhooks/syslog`, platform `https://api.agentsoc.com`. Development: ingest `http://localhost:8110/api/v1/webhooks/syslog`, platform `http://localhost:8100`.
76
62
 
77
- **Example:**
63
+ **Examples:**
78
64
 
79
65
  ```bash
80
- beacon config --url https://api.agentsoc.com/ingest --key sk_1234567890abcdef
66
+ beacon config --key sk_1234567890abcdef
67
+ beacon config --key sk_1234567890abcdef --env development
81
68
  ```
82
69
 
83
- **Note:** If you run `bun run src/cli.ts config` without the required parameters, you'll receive an error instructing you to provide the `-u` and `-k` options.
70
+ **Note:** If you run `bun run src/cli.ts config` without `--key`, you'll receive usage help. URLs are not set in `config.json` (use `--env` or `AGENTSOC_ENV`). At runtime you can override **ingest** with `AGENTSOC_INGEST_URL` and the **platform API** (for `beacon status` only) with `AGENTSOC_PLATFORM_URL` or `AGENTSOC_PLATFORM_API_BASE_URL`.
84
71
 
85
72
  ### Run Foreground Daemon
86
73
 
@@ -101,48 +88,52 @@ beacon run [options]
101
88
  beacon run --batch-size 50 --flush-ms 5000
102
89
  ```
103
90
 
104
- _Note: You can also use environment variables `AGENTSOC_API_KEY` and `AGENTSOC_INGEST_URL` instead of running the `config` command._
91
+ _Note: You can use `AGENTSOC_API_KEY` instead of saving a key in config. Use `AGENTSOC_ENV` (`production` / `development`) to override the saved environment for a single run. `AGENTSOC_INGEST_URL` overrides log shipping only. For `beacon status`, the CLI calls `GET {platform}/api/v1/siem/beacon/validate/key` (default platform from env above); override the platform origin with `AGENTSOC_PLATFORM_URL` or `AGENTSOC_PLATFORM_API_BASE_URL` if ingest and API live on different hosts._
105
92
 
106
93
  ### Install Service
107
94
 
108
95
  Install the background service daemon (systemd on Linux, launchd on macOS):
109
96
 
110
97
  ```bash
111
- beacon install
98
+ sudo beacon install
112
99
  ```
113
100
 
114
- ## Development
115
-
116
- - Start the daemon using npm script: `npm start` or `bun run start`
117
- - Install service using npm script: `npm run install-service` or `bun run install-service`
118
- - Build the distributable: `npm run build:simple` (compiles TypeScript to dist/)
101
+ On macOS the plist is written under `/Library/LaunchDaemons/`, and on Linux the unit file goes under `/etc/systemd/system/`—both require root, so use `sudo`. If `sudo` cannot find `beacon` (for example with nvm), run `sudo env "PATH=$PATH" beacon install` or invoke the CLI with the full path to the global binary.
119
102
 
120
- ## Building & Distribution
103
+ ### Status and stats
121
104
 
122
- ### Build the Package
123
-
124
- To build the TypeScript files into the `dist/` folder:
105
+ Show whether the systemd/launchd service is present, resolve **organization name and API key label** via the platform API (`GET /api/v1/siem/beacon/validate/key` using your ingest key), and print forwarding counters from the local stats file:
125
106
 
126
107
  ```bash
127
- npm run build:simple
108
+ beacon status
128
109
  ```
129
110
 
130
- ### Share Installation Scripts
111
+ Use `beacon status --json` for machine-readable output (includes `validateKeyUrl` on errors).
131
112
 
132
- Users can install the package using one of the provided installation scripts:
113
+ After successful batches, the CLI updates **`stats.json`** next to your config (same config directory as `config.json`—for example `~/Library/Application Support/agentsoc-beacon/` on macOS). The file tracks `logsForwarded`, `batchesSucceeded`, `batchesFailed`, optional `lastError`, and `updatedAt`. If the beacon runs as another user (e.g. root), that user’s config directory holds the stats file.
133
114
 
134
- - Copy the `install.sh` script to your repository (macOS/Linux users)
135
- - Copy the `install.ps1` script to your repository (Windows PowerShell users)
136
- - Copy the `install.bat` script to your repository (Windows batch users)
115
+ The marketing site’s product demo terminal runs through **`beacon status`** so visitors can see a sample of this output.
137
116
 
138
- Users can then run:
117
+ ## Development
118
+
119
+ - Start the daemon using npm script: `npm start` or `bun run start`
120
+ - Install service using npm script: `npm run install-service` or `bun run install-service`
121
+ - Build the distributable: `npm run build:simple` (compiles TypeScript to `dist/`)
122
+
123
+ ## Building & distribution
124
+
125
+ ### Build the package
139
126
 
140
127
  ```bash
141
- curl -fsSL https://raw.githubusercontent.com/yourusername/platform.agentsoc.com/main/apps/connectors/syslog-beacon-cli/install.sh | bash
128
+ npm run build:simple
142
129
  ```
143
130
 
144
- Or use npm:
131
+ ### Publish to npm
132
+
133
+ From this directory, after bumping `version` in `package.json`:
145
134
 
146
135
  ```bash
147
- npm install -g @agentsoc/beacon
136
+ npm publish --access public
148
137
  ```
138
+
139
+ Public install entrypoints live under **`https://agentsoc.com/connectors/`** (`beacon.sh`, `beacon-install.ps1`, `beacon-install.bat`).
@@ -0,0 +1,14 @@
1
+ export interface BeaconContextOk {
2
+ ok: true;
3
+ organizationName: string | null;
4
+ organizationSlug: string | null;
5
+ apiKeyName: string;
6
+ }
7
+ export interface BeaconContextErr {
8
+ ok: false;
9
+ message: string;
10
+ }
11
+ export type BeaconContextResult = BeaconContextOk | BeaconContextErr;
12
+ /** Calls platform `GET /api/v1/siem/beacon/validate/key` with the ingest API key. */
13
+ export declare function fetchBeaconContext(validateKeyUrl: string, apiKey: string): Promise<BeaconContextResult>;
14
+ //# sourceMappingURL=beacon-context.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"beacon-context.d.ts","sourceRoot":"","sources":["../src/beacon-context.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,IAAI,CAAC;IACT,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,KAAK,CAAC;IACV,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,MAAM,mBAAmB,GAAG,eAAe,GAAG,gBAAgB,CAAC;AAWrE,qFAAqF;AACrF,wBAAsB,kBAAkB,CACtC,cAAc,EAAE,MAAM,EACtB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,mBAAmB,CAAC,CAgD9B"}
@@ -0,0 +1,49 @@
1
+ /** Calls platform `GET /api/v1/siem/beacon/validate/key` with the ingest API key. */
2
+ export async function fetchBeaconContext(validateKeyUrl, apiKey) {
3
+ const controller = new AbortController();
4
+ const t = setTimeout(() => controller.abort(), 12_000);
5
+ try {
6
+ const res = await fetch(validateKeyUrl, {
7
+ method: "GET",
8
+ headers: { "X-API-Key": apiKey },
9
+ signal: controller.signal,
10
+ });
11
+ const text = await res.text();
12
+ let body;
13
+ try {
14
+ body = text ? JSON.parse(text) : null;
15
+ }
16
+ catch {
17
+ return { ok: false, message: "Invalid response from platform API" };
18
+ }
19
+ if (!res.ok) {
20
+ const err = body;
21
+ const msg = err && typeof err.error === "string"
22
+ ? err.error
23
+ : `HTTP ${res.status}`;
24
+ return { ok: false, message: msg };
25
+ }
26
+ const data = body;
27
+ if (!data?.success || !data.apiKey?.name) {
28
+ return { ok: false, message: "Unexpected response from platform API" };
29
+ }
30
+ return {
31
+ ok: true,
32
+ organizationName: data.organization?.name ?? null,
33
+ organizationSlug: data.organization?.slug ?? null,
34
+ apiKeyName: data.apiKey.name,
35
+ };
36
+ }
37
+ catch (e) {
38
+ if (e instanceof Error && e.name === "AbortError") {
39
+ return { ok: false, message: "Request timed out" };
40
+ }
41
+ return {
42
+ ok: false,
43
+ message: e instanceof Error ? e.message : String(e),
44
+ };
45
+ }
46
+ finally {
47
+ clearTimeout(t);
48
+ }
49
+ }
@@ -0,0 +1,23 @@
1
+ export type BeaconUpdateInfo = {
2
+ success: boolean;
3
+ package: string;
4
+ latestVersion: string | null;
5
+ registryResolved: boolean;
6
+ install: {
7
+ npm: string;
8
+ bun: string;
9
+ pnpm: string;
10
+ };
11
+ docsUrl: string;
12
+ installScriptUrl: string;
13
+ };
14
+ export type BeaconUpdateFetchResult = {
15
+ ok: true;
16
+ info: BeaconUpdateInfo;
17
+ } | {
18
+ ok: false;
19
+ message: string;
20
+ };
21
+ /** Latest @agentsoc/beacon version from the public npm registry (no AgentSOC API). */
22
+ export declare function fetchBeaconUpdateFromNpm(): Promise<BeaconUpdateFetchResult>;
23
+ //# sourceMappingURL=beacon-update-info.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"beacon-update-info.d.ts","sourceRoot":"","sources":["../src/beacon-update-info.ts"],"names":[],"mappings":"AAMA,MAAM,MAAM,gBAAgB,GAAG;IAC7B,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,gBAAgB,EAAE,OAAO,CAAC;IAC1B,OAAO,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IACpD,OAAO,EAAE,MAAM,CAAC;IAChB,gBAAgB,EAAE,MAAM,CAAC;CAC1B,CAAC;AAEF,MAAM,MAAM,uBAAuB,GAC/B;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,gBAAgB,CAAA;CAAE,GACpC;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAsBnC,sFAAsF;AACtF,wBAAsB,wBAAwB,IAAI,OAAO,CAAC,uBAAuB,CAAC,CAyCjF"}
@@ -0,0 +1,62 @@
1
+ const BEACON_PACKAGE = "@agentsoc/beacon";
2
+ const NPM_REGISTRY_URL = `https://registry.npmjs.org/${encodeURIComponent(BEACON_PACKAGE)}`;
3
+ const DOC_URL = "https://agentsoc.com/products/agentsoc-beacon";
4
+ const INSTALL_SCRIPT_URL = "https://agentsoc.com/connectors/beacon.sh";
5
+ function buildInfo(latestVersion, registryResolved) {
6
+ return {
7
+ success: true,
8
+ package: BEACON_PACKAGE,
9
+ latestVersion,
10
+ registryResolved,
11
+ install: {
12
+ npm: `npm install -g ${BEACON_PACKAGE}@latest`,
13
+ bun: `bun add -g ${BEACON_PACKAGE}@latest`,
14
+ pnpm: `pnpm add -g ${BEACON_PACKAGE}@latest`,
15
+ },
16
+ docsUrl: DOC_URL,
17
+ installScriptUrl: INSTALL_SCRIPT_URL,
18
+ };
19
+ }
20
+ /** Latest @agentsoc/beacon version from the public npm registry (no AgentSOC API). */
21
+ export async function fetchBeaconUpdateFromNpm() {
22
+ const controller = new AbortController();
23
+ const t = setTimeout(() => controller.abort(), 12_000);
24
+ try {
25
+ const res = await fetch(NPM_REGISTRY_URL, {
26
+ headers: { Accept: "application/json" },
27
+ signal: controller.signal,
28
+ });
29
+ const text = await res.text();
30
+ let body;
31
+ try {
32
+ body = text ? JSON.parse(text) : null;
33
+ }
34
+ catch {
35
+ return { ok: false, message: "Invalid JSON from registry.npmjs.org" };
36
+ }
37
+ if (!res.ok) {
38
+ return {
39
+ ok: false,
40
+ message: `registry.npmjs.org returned HTTP ${res.status}`,
41
+ };
42
+ }
43
+ const data = body;
44
+ const latest = data["dist-tags"]?.latest?.trim() ?? null;
45
+ return {
46
+ ok: true,
47
+ info: buildInfo(latest, true),
48
+ };
49
+ }
50
+ catch (e) {
51
+ if (e instanceof Error && e.name === "AbortError") {
52
+ return { ok: false, message: "Request timed out" };
53
+ }
54
+ return {
55
+ ok: false,
56
+ message: e instanceof Error ? e.message : String(e),
57
+ };
58
+ }
59
+ finally {
60
+ clearTimeout(t);
61
+ }
62
+ }
@@ -0,0 +1,5 @@
1
+ /** Resolve @agentsoc/beacon version from the installed package.json (works for global npm install). */
2
+ export declare function readBeaconCliVersion(): string;
3
+ /** Simple semver compare for numeric x.y.z (ignores prerelease tail for ordering). */
4
+ export declare function semverLessThan(a: string, b: string): boolean;
5
+ //# sourceMappingURL=beacon-version.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"beacon-version.d.ts","sourceRoot":"","sources":["../src/beacon-version.ts"],"names":[],"mappings":"AAIA,uGAAuG;AACvG,wBAAgB,oBAAoB,IAAI,MAAM,CAsB7C;AAOD,sFAAsF;AACtF,wBAAgB,cAAc,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,OAAO,CAW5D"}
@@ -0,0 +1,45 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { dirname, join } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ /** Resolve @agentsoc/beacon version from the installed package.json (works for global npm install). */
5
+ export function readBeaconCliVersion() {
6
+ let dir = dirname(fileURLToPath(import.meta.url));
7
+ for (let i = 0; i < 8; i++) {
8
+ const pkgPath = join(dir, "package.json");
9
+ if (existsSync(pkgPath)) {
10
+ try {
11
+ const j = JSON.parse(readFileSync(pkgPath, "utf8"));
12
+ if (j.name === "@agentsoc/beacon" && typeof j.version === "string") {
13
+ return j.version;
14
+ }
15
+ }
16
+ catch {
17
+ /* try parent */
18
+ }
19
+ }
20
+ const parent = dirname(dir);
21
+ if (parent === dir)
22
+ break;
23
+ dir = parent;
24
+ }
25
+ return "0.0.0";
26
+ }
27
+ function stripPrerelease(v) {
28
+ const i = v.indexOf("-");
29
+ return i === -1 ? v : v.slice(0, i);
30
+ }
31
+ /** Simple semver compare for numeric x.y.z (ignores prerelease tail for ordering). */
32
+ export function semverLessThan(a, b) {
33
+ const pa = stripPrerelease(a).split(".").map((x) => parseInt(x, 10) || 0);
34
+ const pb = stripPrerelease(b).split(".").map((x) => parseInt(x, 10) || 0);
35
+ const n = Math.max(pa.length, pb.length);
36
+ for (let i = 0; i < n; i++) {
37
+ const da = pa[i] ?? 0;
38
+ const db = pb[i] ?? 0;
39
+ if (da < db)
40
+ return true;
41
+ if (da > db)
42
+ return false;
43
+ }
44
+ return false;
45
+ }
package/dist/cli.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":""}
package/dist/cli.js CHANGED
@@ -1,35 +1,56 @@
1
1
  #!/usr/bin/env node
2
+ import { execFileSync } from "node:child_process";
2
3
  import { Command } from "commander";
3
- import { loadConfig, saveConfig } from "./config.js";
4
+ import { BEACON_CONFIG_REQUIRED_MESSAGE, coerceBeaconEnvironment, loadConfig, parseBeaconEnvironment, resolveBeaconApiKey, resolveSiemBeaconValidateKeyUrl, resolveIngestUrl, saveConfig, } from "./config.js";
5
+ import { fetchBeaconContext } from "./beacon-context.js";
6
+ import { fetchBeaconUpdateFromNpm } from "./beacon-update-info.js";
7
+ import { readBeaconCliVersion, semverLessThan } from "./beacon-version.js";
4
8
  import { runBeacon } from "./run-beacon.js";
5
9
  import { installService } from "./service-install.js";
10
+ import { probeServiceStatus } from "./service-status.js";
11
+ import { getStatsFilePath, readStats } from "./stats.js";
12
+ const BEACON_VERSION = readBeaconCliVersion();
6
13
  const program = new Command();
7
14
  program
8
15
  .name("beacon")
9
16
  .description("AgentSOC Syslog Beacon - Lightweight background log forwarder")
10
- .version("1.0.0", "-v, --version");
17
+ .version(BEACON_VERSION, "-v, --version");
11
18
  program
12
19
  .command("config")
13
20
  .description("Configure the AgentSOC connection")
14
- .option("-u, --url <url>", "AgentSOC ingest URL")
21
+ .option("-e, --env <env>", "Environment: production (default) or development")
15
22
  .option("-k, --key <key>", "AgentSOC API Key")
16
23
  .action(async (options) => {
17
- if (!options.url || !options.key) {
18
- console.log("\n[beacon] Missing required options.\n");
19
- console.log("Usage: beacon config --url <url> --key <key>\n");
24
+ if (!options.key) {
25
+ console.log("\n[beacon] Missing required option.\n");
26
+ console.log("Usage: beacon config --key <key> [--env production|development]\n");
20
27
  console.log("Examples:");
21
- console.log(" bun run src/cli.ts config -u https://api.agentsoc.com/ingest -k sk_1234567890abcdef");
22
- console.log(" beacon config --url https://api.agentsoc.com/ingest --key sk_1234567890abcdef\n");
28
+ console.log(" beacon config --key sk_1234567890abcdef");
29
+ console.log(" beacon config --key sk_1234567890abcdef --env development\n");
23
30
  console.log("Options:");
24
- console.log(" -u, --url <url> AgentSOC ingest URL");
25
- console.log(" -k, --key <key> AgentSOC API Key\n");
31
+ console.log(" -k, --key <key> AgentSOC API Key (required)");
32
+ console.log(" -e, --env <env> production (default) or development; sets ingest + platform API defaults\n");
33
+ process.exit(1);
34
+ }
35
+ const existing = await loadConfig();
36
+ let environment;
37
+ try {
38
+ environment =
39
+ options.env !== undefined
40
+ ? parseBeaconEnvironment(options.env)
41
+ : (coerceBeaconEnvironment(existing.environment) ?? "production");
42
+ }
43
+ catch (e) {
44
+ console.error("[beacon]", e instanceof Error ? e.message : String(e));
26
45
  process.exit(1);
27
46
  }
28
47
  await saveConfig({
29
- ingestUrl: options.url,
30
48
  apiKey: options.key,
49
+ environment,
50
+ agentId: existing.agentId,
31
51
  });
32
52
  console.log("[beacon] Config saved.");
53
+ console.log(`[beacon] Environment: ${environment}`);
33
54
  });
34
55
  program
35
56
  .command("install")
@@ -43,6 +64,153 @@ program
43
64
  process.exit(1);
44
65
  }
45
66
  });
67
+ program
68
+ .command("status")
69
+ .description("Show service state and forwarding statistics")
70
+ .option("--json", "Print machine-readable JSON on stdout")
71
+ .action(async (opts) => {
72
+ const svc = probeServiceStatus();
73
+ const stats = await readStats();
74
+ const statsPath = getStatsFilePath();
75
+ const cfg = await loadConfig();
76
+ const apiKey = resolveBeaconApiKey(cfg);
77
+ const validateKeyUrl = resolveSiemBeaconValidateKeyUrl(cfg);
78
+ const context = apiKey
79
+ ? await fetchBeaconContext(validateKeyUrl, apiKey)
80
+ : ({
81
+ ok: false,
82
+ message: BEACON_CONFIG_REQUIRED_MESSAGE,
83
+ });
84
+ if (opts.json) {
85
+ console.log(JSON.stringify({
86
+ service: svc,
87
+ stats,
88
+ statsPath,
89
+ validateKeyUrl,
90
+ context: context.ok
91
+ ? {
92
+ organizationName: context.organizationName,
93
+ organizationSlug: context.organizationSlug,
94
+ apiKeyName: context.apiKeyName,
95
+ }
96
+ : { error: context.message },
97
+ }, null, 2));
98
+ return;
99
+ }
100
+ const labelW = 26;
101
+ const line = (label, value) => {
102
+ console.log(` ${label.padEnd(labelW)}${value}`);
103
+ };
104
+ console.log("\n AgentSOC Beacon — Status\n");
105
+ console.log(" Account");
106
+ if (context.ok) {
107
+ line("Organization", context.organizationName ??
108
+ "(name unavailable — check platform API / database)");
109
+ line("API key", context.apiKeyName);
110
+ }
111
+ else {
112
+ line("Connection", context.message);
113
+ }
114
+ console.log("\n Service");
115
+ line(`Daemon (${svc.manager})`, svc.installed ? svc.stateLabel : "not installed");
116
+ if (svc.active === true)
117
+ line("Running", "yes");
118
+ else if (svc.active === false)
119
+ line("Running", "no");
120
+ else
121
+ line("Running", "n/a");
122
+ if (svc.detail)
123
+ line("Note", svc.detail);
124
+ console.log("\n Forwarding (this machine)");
125
+ line("Log entries forwarded", String(stats.logsForwarded));
126
+ line("Successful batches", String(stats.batchesSucceeded));
127
+ line("Failed batches", String(stats.batchesFailed));
128
+ if (stats.updatedAt && stats.updatedAt !== new Date(0).toISOString()) {
129
+ line("Last stats update", stats.updatedAt);
130
+ }
131
+ else {
132
+ line("Last stats update", "(none yet)");
133
+ }
134
+ if (stats.lastError) {
135
+ line("Last error", stats.lastError);
136
+ }
137
+ console.log();
138
+ });
139
+ program
140
+ .command("update")
141
+ .description("Check for a newer Beacon CLI and optionally install it")
142
+ .option("--json", "Print machine-readable JSON on stdout")
143
+ .option("-y, --yes", "Run npm install -g @agentsoc/beacon@latest (non-interactive upgrade)")
144
+ .action(async (opts) => {
145
+ const current = BEACON_VERSION;
146
+ const fetched = await fetchBeaconUpdateFromNpm();
147
+ if (opts.json) {
148
+ const latest = fetched.ok ? fetched.info.latestVersion : null;
149
+ const outdated = latest != null &&
150
+ latest.length > 0 &&
151
+ semverLessThan(current, latest);
152
+ console.log(JSON.stringify({
153
+ currentVersion: current,
154
+ registry: "https://registry.npmjs.org/@agentsoc%2fbeacon",
155
+ remote: fetched.ok ? fetched.info : null,
156
+ error: fetched.ok ? undefined : fetched.message,
157
+ updateAvailable: fetched.ok ? outdated : null,
158
+ }, null, 2));
159
+ if (!fetched.ok)
160
+ process.exit(1);
161
+ return;
162
+ }
163
+ if (!fetched.ok) {
164
+ console.error(`[beacon] Could not reach npm registry: ${fetched.message}`);
165
+ console.error("[beacon] You can still run: npm install -g @agentsoc/beacon@latest");
166
+ process.exit(1);
167
+ }
168
+ const { info } = fetched;
169
+ const latest = info.latestVersion;
170
+ console.log("\n AgentSOC Beacon — Update check\n");
171
+ console.log(` This install ${current}`);
172
+ if (latest) {
173
+ console.log(` Latest on registry ${latest}`);
174
+ }
175
+ else {
176
+ console.log(" Latest on registry (unavailable — unexpected npm response)");
177
+ }
178
+ const canCompare = Boolean(latest && latest.length > 0);
179
+ const outdated = canCompare && semverLessThan(current, latest);
180
+ if (canCompare && !outdated) {
181
+ console.log("\n You are on the latest published version.\n");
182
+ return;
183
+ }
184
+ if (canCompare && outdated) {
185
+ console.log("\n A newer version is available.\n");
186
+ }
187
+ else {
188
+ console.log("\n Compare versions manually, or reinstall with:\n");
189
+ }
190
+ console.log(` ${info.install.npm}`);
191
+ console.log(` ${info.install.bun}`);
192
+ console.log(` ${info.install.pnpm}`);
193
+ if (info.docsUrl) {
194
+ console.log(`\n Docs: ${info.docsUrl}`);
195
+ }
196
+ if (opts.yes) {
197
+ if (!outdated && canCompare) {
198
+ console.log("\n[beacon] Already up to date — skipping install.\n");
199
+ return;
200
+ }
201
+ console.log("\n[beacon] Running npm install -g @agentsoc/beacon@latest …\n");
202
+ const npm = process.platform === "win32" ? "npm.cmd" : "npm";
203
+ try {
204
+ execFileSync(npm, ["install", "-g", "@agentsoc/beacon@latest"], { stdio: "inherit", env: process.env });
205
+ console.log("\n[beacon] Update finished. Run `beacon --version` to verify.\n");
206
+ }
207
+ catch {
208
+ process.exit(1);
209
+ }
210
+ return;
211
+ }
212
+ console.log("\n To upgrade now, run the same command with --yes\n");
213
+ });
46
214
  program
47
215
  .command("run")
48
216
  .description("Run the forwarder daemon (foreground)")
@@ -50,10 +218,10 @@ program
50
218
  .option("--flush-ms <n>", "Flush interval in ms", "2000")
51
219
  .action(async (options) => {
52
220
  const config = await loadConfig();
53
- const apiKey = process.env.AGENTSOC_API_KEY || config.apiKey;
54
- const ingestUrl = process.env.AGENTSOC_INGEST_URL || config.ingestUrl;
55
- if (!apiKey || !ingestUrl) {
56
- console.error("[beacon] Missing apiKey or ingestUrl. Run `beacon config` or set ENV vars.");
221
+ const apiKey = resolveBeaconApiKey(config);
222
+ const ingestUrl = resolveIngestUrl(config);
223
+ if (!apiKey) {
224
+ console.error(`[beacon] ${BEACON_CONFIG_REQUIRED_MESSAGE}`);
57
225
  process.exit(1);
58
226
  }
59
227
  console.log("[beacon] Starting in foreground...");
@@ -0,0 +1,45 @@
1
+ export declare const INGEST_URL_PRODUCTION = "https://ingest.agentsoc.com/api/v1/webhooks/syslog";
2
+ export declare const INGEST_URL_DEVELOPMENT = "http://localhost:8110/api/v1/webhooks/syslog";
3
+ /** Platform API (for `beacon status` key validation — not log ingest). */
4
+ export declare const PLATFORM_API_PRODUCTION = "https://api.agentsoc.com";
5
+ export declare const PLATFORM_API_DEVELOPMENT = "http://localhost:8100";
6
+ /** Path on the platform API for SIEM ingest key validation (`beacon status`). */
7
+ export declare const SIEM_BEACON_VALIDATE_KEY_PATH = "/api/v1/siem/beacon/validate/key";
8
+ export type BeaconEnvironment = "production" | "development";
9
+ /** API key from saved config or process environment (trimmed). */
10
+ export declare function resolveBeaconApiKey(config: BeaconConfig): string | undefined;
11
+ /** Shown when run/install needs a key and none is configured. */
12
+ export declare const BEACON_CONFIG_REQUIRED_MESSAGE = "Beacon is not configured. Run: beacon config --key <your-api-key> [--env production|development]. Or set AGENTSOC_API_KEY in your environment.";
13
+ export interface BeaconConfig {
14
+ apiKey?: string;
15
+ /** @deprecated Resolved from environment; not set by the CLI */
16
+ ingestUrl?: string;
17
+ /** Defaults to production when omitted */
18
+ environment?: BeaconEnvironment;
19
+ agentId?: string;
20
+ }
21
+ export declare function parseBeaconEnvironment(input: string): BeaconEnvironment;
22
+ export declare function coerceBeaconEnvironment(value: unknown): BeaconEnvironment | undefined;
23
+ export declare function ingestUrlForEnvironment(env: BeaconEnvironment): string;
24
+ export declare function effectiveEnvironment(config: BeaconConfig): BeaconEnvironment;
25
+ /**
26
+ * Ingest URL used at runtime. Optional AGENTSOC_INGEST_URL overrides (legacy
27
+ * / advanced); otherwise derived from AGENTSOC_ENV or saved environment.
28
+ */
29
+ export declare function resolveIngestUrl(config: BeaconConfig): string;
30
+ /**
31
+ * Platform API origin (no trailing slash).
32
+ * Override with `AGENTSOC_PLATFORM_URL` or `AGENTSOC_PLATFORM_API_BASE_URL`.
33
+ */
34
+ export declare function resolvePlatformApiBase(config: BeaconConfig): string;
35
+ /**
36
+ * Full URL for `GET …/siem/beacon/validate/key` (organization + API key label for `beacon status`).
37
+ */
38
+ export declare function resolveSiemBeaconValidateKeyUrl(config: BeaconConfig): string;
39
+ /** @deprecated Use {@link resolveSiemBeaconValidateKeyUrl} */
40
+ export declare function resolveBeaconContextUrl(config: BeaconConfig): string;
41
+ export declare function getConfigDir(): string;
42
+ export declare function getConfigFile(): string;
43
+ export declare function loadConfig(): Promise<BeaconConfig>;
44
+ export declare function saveConfig(config: BeaconConfig): Promise<void>;
45
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,qBAAqB,uDACoB,CAAC;AAEvD,eAAO,MAAM,sBAAsB,iDACa,CAAC;AAEjD,0EAA0E;AAC1E,eAAO,MAAM,uBAAuB,6BAA6B,CAAC;AAElE,eAAO,MAAM,wBAAwB,0BAA0B,CAAC;AAEhE,iFAAiF;AACjF,eAAO,MAAM,6BAA6B,qCACN,CAAC;AAErC,MAAM,MAAM,iBAAiB,GAAG,YAAY,GAAG,aAAa,CAAC;AAE7D,kEAAkE;AAClE,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,YAAY,GAAG,MAAM,GAAG,SAAS,CAK5E;AAED,iEAAiE;AACjE,eAAO,MAAM,8BAA8B,mJACuG,CAAC;AAEnJ,MAAM,WAAW,YAAY;IAC3B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,gEAAgE;IAChE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,0CAA0C;IAC1C,WAAW,CAAC,EAAE,iBAAiB,CAAC;IAChC,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,MAAM,GAAG,iBAAiB,CAOvE;AAED,wBAAgB,uBAAuB,CACrC,KAAK,EAAE,OAAO,GACb,iBAAiB,GAAG,SAAS,CAU/B;AAED,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,iBAAiB,GAAG,MAAM,CAItE;AAmBD,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,YAAY,GAAG,iBAAiB,CAM5E;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,YAAY,GAAG,MAAM,CAI7D;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,YAAY,GAAG,MAAM,CAMnE;AAED;;GAEG;AACH,wBAAgB,+BAA+B,CAC7C,MAAM,EAAE,YAAY,GACnB,MAAM,CAER;AAED,8DAA8D;AAC9D,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,YAAY,GAAG,MAAM,CAEpE;AAED,wBAAgB,YAAY,IAAI,MAAM,CAarC;AAED,wBAAgB,aAAa,IAAI,MAAM,CAEtC;AAED,wBAAsB,UAAU,IAAI,OAAO,CAAC,YAAY,CAAC,CAQxD;AAED,wBAAsB,UAAU,CAAC,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAepE"}
package/dist/config.js CHANGED
@@ -1,6 +1,100 @@
1
1
  import os from "node:os";
2
2
  import path from "node:path";
3
3
  import fs from "node:fs/promises";
4
+ export const INGEST_URL_PRODUCTION = "https://ingest.agentsoc.com/api/v1/webhooks/syslog";
5
+ export const INGEST_URL_DEVELOPMENT = "http://localhost:8110/api/v1/webhooks/syslog";
6
+ /** Platform API (for `beacon status` key validation — not log ingest). */
7
+ export const PLATFORM_API_PRODUCTION = "https://api.agentsoc.com";
8
+ export const PLATFORM_API_DEVELOPMENT = "http://localhost:8100";
9
+ /** Path on the platform API for SIEM ingest key validation (`beacon status`). */
10
+ export const SIEM_BEACON_VALIDATE_KEY_PATH = "/api/v1/siem/beacon/validate/key";
11
+ /** API key from saved config or process environment (trimmed). */
12
+ export function resolveBeaconApiKey(config) {
13
+ const fromEnv = process.env.AGENTSOC_API_KEY?.trim();
14
+ if (fromEnv)
15
+ return fromEnv;
16
+ const fromFile = config.apiKey?.trim();
17
+ return fromFile || undefined;
18
+ }
19
+ /** Shown when run/install needs a key and none is configured. */
20
+ export const BEACON_CONFIG_REQUIRED_MESSAGE = "Beacon is not configured. Run: beacon config --key <your-api-key> [--env production|development]. Or set AGENTSOC_API_KEY in your environment.";
21
+ export function parseBeaconEnvironment(input) {
22
+ const s = input.toLowerCase().trim();
23
+ if (s === "production" || s === "prod")
24
+ return "production";
25
+ if (s === "development" || s === "dev")
26
+ return "development";
27
+ throw new Error(`Invalid environment "${input}". Use production or development (prod / dev).`);
28
+ }
29
+ export function coerceBeaconEnvironment(value) {
30
+ if (value === "production" || value === "development")
31
+ return value;
32
+ if (typeof value === "string") {
33
+ try {
34
+ return parseBeaconEnvironment(value);
35
+ }
36
+ catch {
37
+ return undefined;
38
+ }
39
+ }
40
+ return undefined;
41
+ }
42
+ export function ingestUrlForEnvironment(env) {
43
+ return env === "development"
44
+ ? INGEST_URL_DEVELOPMENT
45
+ : INGEST_URL_PRODUCTION;
46
+ }
47
+ function platformApiBaseForEnvironment(env) {
48
+ return env === "development" ? PLATFORM_API_DEVELOPMENT : PLATFORM_API_PRODUCTION;
49
+ }
50
+ function environmentFromProcessEnv() {
51
+ const raw = process.env.AGENTSOC_ENV?.trim();
52
+ if (!raw)
53
+ return undefined;
54
+ try {
55
+ return parseBeaconEnvironment(raw);
56
+ }
57
+ catch {
58
+ console.warn(`[beacon] Ignoring invalid AGENTSOC_ENV="${raw}" (use production or development).`);
59
+ return undefined;
60
+ }
61
+ }
62
+ export function effectiveEnvironment(config) {
63
+ return (environmentFromProcessEnv() ??
64
+ coerceBeaconEnvironment(config.environment) ??
65
+ "production");
66
+ }
67
+ /**
68
+ * Ingest URL used at runtime. Optional AGENTSOC_INGEST_URL overrides (legacy
69
+ * / advanced); otherwise derived from AGENTSOC_ENV or saved environment.
70
+ */
71
+ export function resolveIngestUrl(config) {
72
+ const override = process.env.AGENTSOC_INGEST_URL?.trim();
73
+ if (override)
74
+ return override;
75
+ return ingestUrlForEnvironment(effectiveEnvironment(config));
76
+ }
77
+ /**
78
+ * Platform API origin (no trailing slash).
79
+ * Override with `AGENTSOC_PLATFORM_URL` or `AGENTSOC_PLATFORM_API_BASE_URL`.
80
+ */
81
+ export function resolvePlatformApiBase(config) {
82
+ const override = process.env.AGENTSOC_PLATFORM_URL?.trim() ||
83
+ process.env.AGENTSOC_PLATFORM_API_BASE_URL?.trim();
84
+ if (override)
85
+ return override.replace(/\/$/, "");
86
+ return platformApiBaseForEnvironment(effectiveEnvironment(config));
87
+ }
88
+ /**
89
+ * Full URL for `GET …/siem/beacon/validate/key` (organization + API key label for `beacon status`).
90
+ */
91
+ export function resolveSiemBeaconValidateKeyUrl(config) {
92
+ return `${resolvePlatformApiBase(config)}${SIEM_BEACON_VALIDATE_KEY_PATH}`;
93
+ }
94
+ /** @deprecated Use {@link resolveSiemBeaconValidateKeyUrl} */
95
+ export function resolveBeaconContextUrl(config) {
96
+ return resolveSiemBeaconValidateKeyUrl(config);
97
+ }
4
98
  export function getConfigDir() {
5
99
  const home = os.homedir();
6
100
  if (process.platform === "win32") {
@@ -0,0 +1,9 @@
1
+ export interface HostInfo {
2
+ agentId: string;
3
+ hostname: string;
4
+ ip: string;
5
+ osPlatform: string;
6
+ mac: string;
7
+ }
8
+ export declare function getHostInfo(): Promise<HostInfo>;
9
+ //# sourceMappingURL=enrich.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"enrich.d.ts","sourceRoot":"","sources":["../src/enrich.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,QAAQ;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,EAAE,EAAE,MAAM,CAAC;IACX,UAAU,EAAE,MAAM,CAAC;IACnB,GAAG,EAAE,MAAM,CAAC;CACb;AAED,wBAAsB,WAAW,IAAI,OAAO,CAAC,QAAQ,CAAC,CA8BrD"}
@@ -0,0 +1,20 @@
1
+ import { HostInfo } from "./enrich.js";
2
+ export interface ForwarderOptions {
3
+ ingestUrl: string;
4
+ apiKey: string;
5
+ batchSize?: number;
6
+ flushMs?: number;
7
+ host: HostInfo;
8
+ }
9
+ export declare class Forwarder {
10
+ private opts;
11
+ private buffer;
12
+ private timer;
13
+ private readonly batchSize;
14
+ private readonly flushMs;
15
+ constructor(opts: ForwarderOptions);
16
+ pushLog(line: string): void;
17
+ private flush;
18
+ stop(): Promise<void>;
19
+ }
20
+ //# sourceMappingURL=forwarder.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"forwarder.d.ts","sourceRoot":"","sources":["../src/forwarder.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAGvC,MAAM,WAAW,gBAAgB;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,QAAQ,CAAC;CAChB;AA6ED,qBAAa,SAAS;IAMR,OAAO,CAAC,IAAI;IALxB,OAAO,CAAC,MAAM,CAAqB;IACnC,OAAO,CAAC,KAAK,CAA+B;IAC5C,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;gBAEb,IAAI,EAAE,gBAAgB;IAKnC,OAAO,CAAC,IAAI,EAAE,MAAM;YAoCb,KAAK;IA4CN,IAAI;CAKlB"}
package/dist/forwarder.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { recordFailedBatch, recordSuccessfulBatch } from "./stats.js";
1
2
  function parseSyslogPri(line) {
2
3
  // Default mapping if line does not start with <PRI>
3
4
  let severity = 5; // notice (maps to 'low' severity in our logic)
@@ -114,14 +115,18 @@ export class Forwarder {
114
115
  });
115
116
  if (!res.ok) {
116
117
  const text = await res.text();
118
+ const msg = `HTTP ${res.status} ${text}`;
117
119
  console.error(`[syslog-beacon] Failed to push logs: HTTP ${res.status} ${text}`);
120
+ await recordFailedBatch(msg);
118
121
  }
119
122
  else {
120
123
  console.log(`[syslog-beacon] Successfully pushed ${payload.length} logs`);
124
+ await recordSuccessfulBatch(payload.length);
121
125
  }
122
126
  }
123
127
  catch (err) {
124
128
  console.error(`[syslog-beacon] Ingest error:`, err);
129
+ await recordFailedBatch(err instanceof Error ? err.message : String(err));
125
130
  }
126
131
  }
127
132
  async stop() {
@@ -0,0 +1,5 @@
1
+ import { ForwarderOptions } from './forwarder.js';
2
+ export declare function runBeacon(opts: Omit<ForwarderOptions, 'host'>): Promise<{
3
+ stop: () => Promise<void>;
4
+ }>;
5
+ //# sourceMappingURL=run-beacon.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run-beacon.d.ts","sourceRoot":"","sources":["../src/run-beacon.ts"],"names":[],"mappings":"AACA,OAAO,EAAa,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAG7D,wBAAsB,SAAS,CAAC,IAAI,EAAE,IAAI,CAAC,gBAAgB,EAAE,MAAM,CAAC;;GAiBnE"}
@@ -0,0 +1,2 @@
1
+ export declare function installService(): Promise<void>;
2
+ //# sourceMappingURL=service-install.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"service-install.d.ts","sourceRoot":"","sources":["../src/service-install.ts"],"names":[],"mappings":"AAKA,wBAAsB,cAAc,kBAgEnC"}
@@ -1,11 +1,12 @@
1
1
  import fs from 'node:fs/promises';
2
2
  import path from 'node:path';
3
3
  import { execSync } from 'node:child_process';
4
- import { loadConfig } from './config.js';
4
+ import { BEACON_CONFIG_REQUIRED_MESSAGE, loadConfig } from './config.js';
5
5
  export async function installService() {
6
6
  const config = await loadConfig();
7
- if (!config.apiKey || !config.ingestUrl) {
8
- throw new Error('Config missing apiKey or ingestUrl. Please configure first.');
7
+ // Service units do not inherit the install shell's env — key must be saved in config.
8
+ if (!config.apiKey?.trim()) {
9
+ throw new Error(BEACON_CONFIG_REQUIRED_MESSAGE);
9
10
  }
10
11
  const isBun = process.execPath.endsWith('bun') || process.execPath.endsWith('bun.exe');
11
12
  const runner = isBun ? process.execPath : 'node';
@@ -0,0 +1,11 @@
1
+ export type ServiceManager = "systemd" | "launchd" | "none";
2
+ export interface ServiceStatus {
3
+ manager: ServiceManager;
4
+ installed: boolean;
5
+ active: boolean | null;
6
+ /** Human-readable state from the platform (e.g. active, inactive, running) */
7
+ stateLabel: string;
8
+ detail?: string;
9
+ }
10
+ export declare function probeServiceStatus(): ServiceStatus;
11
+ //# sourceMappingURL=service-status.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"service-status.d.ts","sourceRoot":"","sources":["../src/service-status.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,cAAc,GAAG,SAAS,GAAG,SAAS,GAAG,MAAM,CAAC;AAE5D,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,cAAc,CAAC;IACxB,SAAS,EAAE,OAAO,CAAC;IACnB,MAAM,EAAE,OAAO,GAAG,IAAI,CAAC;IACvB,8EAA8E;IAC9E,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAOD,wBAAgB,kBAAkB,IAAI,aAAa,CA4FlD"}
@@ -0,0 +1,95 @@
1
+ import fs from "node:fs";
2
+ import { execFileSync } from "node:child_process";
3
+ function trimDetail(s, max = 200) {
4
+ const one = s.replace(/\s+/g, " ").trim();
5
+ return one.length <= max ? one : `${one.slice(0, max)}…`;
6
+ }
7
+ export function probeServiceStatus() {
8
+ if (process.platform === "linux") {
9
+ const unitPath = "/etc/systemd/system/syslog-beacon.service";
10
+ const installed = fs.existsSync(unitPath);
11
+ if (!installed) {
12
+ return {
13
+ manager: "systemd",
14
+ installed: false,
15
+ active: null,
16
+ stateLabel: "not installed",
17
+ detail: "No unit at /etc/systemd/system/syslog-beacon.service",
18
+ };
19
+ }
20
+ try {
21
+ const out = execFileSync("systemctl", ["is-active", "syslog-beacon"], {
22
+ encoding: "utf8",
23
+ stdio: ["ignore", "pipe", "pipe"],
24
+ }).trim();
25
+ const active = out === "active";
26
+ return {
27
+ manager: "systemd",
28
+ installed: true,
29
+ active,
30
+ stateLabel: out || "unknown",
31
+ };
32
+ }
33
+ catch (err) {
34
+ const status = err.status;
35
+ const stderr = err.stderr?.toString?.() ?? "";
36
+ if (status === 3) {
37
+ return {
38
+ manager: "systemd",
39
+ installed: true,
40
+ active: false,
41
+ stateLabel: "inactive",
42
+ };
43
+ }
44
+ return {
45
+ manager: "systemd",
46
+ installed: true,
47
+ active: false,
48
+ stateLabel: "unknown",
49
+ detail: trimDetail(stderr || String(err)),
50
+ };
51
+ }
52
+ }
53
+ if (process.platform === "darwin") {
54
+ const plistPath = "/Library/LaunchDaemons/com.agentsoc.syslog-beacon.plist";
55
+ const installed = fs.existsSync(plistPath);
56
+ if (!installed) {
57
+ return {
58
+ manager: "launchd",
59
+ installed: false,
60
+ active: null,
61
+ stateLabel: "not installed",
62
+ detail: `No plist at ${plistPath}`,
63
+ };
64
+ }
65
+ try {
66
+ const out = execFileSync("launchctl", ["print", "system/com.agentsoc.syslog-beacon"], { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
67
+ const m = out.match(/^\s*state\s*=\s*(\S+)/m);
68
+ const state = m?.[1] ?? "unknown";
69
+ const active = state === "running";
70
+ return {
71
+ manager: "launchd",
72
+ installed: true,
73
+ active,
74
+ stateLabel: state,
75
+ };
76
+ }
77
+ catch (err) {
78
+ const stderr = err.stderr?.toString?.() ?? "";
79
+ return {
80
+ manager: "launchd",
81
+ installed: true,
82
+ active: null,
83
+ stateLabel: "unknown",
84
+ detail: trimDetail(stderr || String(err)),
85
+ };
86
+ }
87
+ }
88
+ return {
89
+ manager: "none",
90
+ installed: false,
91
+ active: null,
92
+ stateLabel: "unsupported",
93
+ detail: "Automatic service detection is only available on Linux (systemd) and macOS (launchd).",
94
+ };
95
+ }
@@ -0,0 +1,15 @@
1
+ declare const STATS_VERSION: 1;
2
+ export interface BeaconStatsFile {
3
+ version: typeof STATS_VERSION;
4
+ updatedAt: string;
5
+ logsForwarded: number;
6
+ batchesSucceeded: number;
7
+ batchesFailed: number;
8
+ lastError?: string;
9
+ }
10
+ export declare function getStatsFilePath(): string;
11
+ export declare function readStats(): Promise<BeaconStatsFile>;
12
+ export declare function recordSuccessfulBatch(logCount: number): Promise<void>;
13
+ export declare function recordFailedBatch(message: string): Promise<void>;
14
+ export {};
15
+ //# sourceMappingURL=stats.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stats.d.ts","sourceRoot":"","sources":["../src/stats.ts"],"names":[],"mappings":"AAIA,QAAA,MAAM,aAAa,EAAG,CAAU,CAAC;AAEjC,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,OAAO,aAAa,CAAC;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;IACtB,gBAAgB,EAAE,MAAM,CAAC;IACzB,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAUD,wBAAgB,gBAAgB,IAAI,MAAM,CAEzC;AAED,wBAAsB,SAAS,IAAI,OAAO,CAAC,eAAe,CAAC,CAa1D;AAWD,wBAAsB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAc3E;AAED,wBAAsB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAetE"}
package/dist/stats.js ADDED
@@ -0,0 +1,71 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { getConfigDir } from "./config.js";
4
+ const STATS_VERSION = 1;
5
+ const emptyStats = () => ({
6
+ version: STATS_VERSION,
7
+ updatedAt: new Date(0).toISOString(),
8
+ logsForwarded: 0,
9
+ batchesSucceeded: 0,
10
+ batchesFailed: 0,
11
+ });
12
+ export function getStatsFilePath() {
13
+ return path.join(getConfigDir(), "stats.json");
14
+ }
15
+ export async function readStats() {
16
+ try {
17
+ const raw = await fs.readFile(getStatsFilePath(), "utf8");
18
+ const parsed = JSON.parse(raw);
19
+ if (parsed.version !== STATS_VERSION)
20
+ return emptyStats();
21
+ return {
22
+ ...emptyStats(),
23
+ ...parsed,
24
+ version: STATS_VERSION,
25
+ };
26
+ }
27
+ catch {
28
+ return emptyStats();
29
+ }
30
+ }
31
+ async function writeStatsAtomic(stats) {
32
+ const file = getStatsFilePath();
33
+ const dir = path.dirname(file);
34
+ await fs.mkdir(dir, { recursive: true });
35
+ const tmp = `${file}.${process.pid}.tmp`;
36
+ await fs.writeFile(tmp, JSON.stringify(stats, null, 2), "utf8");
37
+ await fs.rename(tmp, file);
38
+ }
39
+ export async function recordSuccessfulBatch(logCount) {
40
+ try {
41
+ const cur = await readStats();
42
+ await writeStatsAtomic({
43
+ version: STATS_VERSION,
44
+ updatedAt: new Date().toISOString(),
45
+ logsForwarded: cur.logsForwarded + logCount,
46
+ batchesSucceeded: cur.batchesSucceeded + 1,
47
+ batchesFailed: cur.batchesFailed,
48
+ lastError: undefined,
49
+ });
50
+ }
51
+ catch {
52
+ // stats are best-effort; never break forwarding
53
+ }
54
+ }
55
+ export async function recordFailedBatch(message) {
56
+ try {
57
+ const cur = await readStats();
58
+ const trimmed = message.replace(/\s+/g, " ").slice(0, 500);
59
+ await writeStatsAtomic({
60
+ version: STATS_VERSION,
61
+ updatedAt: new Date().toISOString(),
62
+ logsForwarded: cur.logsForwarded,
63
+ batchesSucceeded: cur.batchesSucceeded,
64
+ batchesFailed: cur.batchesFailed + 1,
65
+ lastError: trimmed || "Unknown error",
66
+ });
67
+ }
68
+ catch {
69
+ // best-effort
70
+ }
71
+ }
package/dist/tail.d.ts ADDED
@@ -0,0 +1,10 @@
1
+ export type LogCallback = (line: string) => void;
2
+ export declare class SyslogSource {
3
+ private onLog;
4
+ private tailProc?;
5
+ private udpServer?;
6
+ constructor(onLog: LogCallback);
7
+ start(): void;
8
+ stop(): void;
9
+ }
10
+ //# sourceMappingURL=tail.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tail.d.ts","sourceRoot":"","sources":["../src/tail.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,WAAW,GAAG,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;AAEjD,qBAAa,YAAY;IAIX,OAAO,CAAC,KAAK;IAHzB,OAAO,CAAC,QAAQ,CAAC,CAAe;IAChC,OAAO,CAAC,SAAS,CAAC,CAAe;gBAEb,KAAK,EAAE,WAAW;IAE/B,KAAK;IAkDL,IAAI;CAQZ"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentsoc/beacon",
3
- "version": "0.0.1",
3
+ "version": "0.0.3",
4
4
  "description": "Lightweight, background-running log forwarder (beacon) for AgentSOC",
5
5
  "type": "module",
6
6
  "bin": {
@@ -8,11 +8,31 @@
8
8
  "syslog-beacon": "./dist/cli.js"
9
9
  },
10
10
  "main": "./dist/cli.js",
11
+ "exports": {
12
+ ".": "./dist/cli.js",
13
+ "./package.json": "./package.json",
14
+ "./config": {
15
+ "types": "./dist/config.d.ts",
16
+ "import": "./dist/config.js"
17
+ },
18
+ "./stats": {
19
+ "types": "./dist/stats.d.ts",
20
+ "import": "./dist/stats.js"
21
+ },
22
+ "./service-status": {
23
+ "types": "./dist/service-status.d.ts",
24
+ "import": "./dist/service-status.js"
25
+ },
26
+ "./service-install": {
27
+ "types": "./dist/service-install.d.ts",
28
+ "import": "./dist/service-install.js"
29
+ }
30
+ },
11
31
  "files": [
12
32
  "dist"
13
33
  ],
14
34
  "scripts": {
15
- "build": "bun run --cwd ../../.. bun build --target bun --entry-points ./apps/connectors/syslog-beacon-cli/src/cli.ts --outdir ./apps/connectors/syslog-beacon-cli/dist --minify",
35
+ "build": "bun run --cwd ../../.. bun build --target node --entry-points ./apps/connectors/syslog-beacon-cli/src/cli.ts --outdir ./apps/connectors/syslog-beacon-cli/dist --minify",
16
36
  "build:simple": "tsc",
17
37
  "start": "bun run src/cli.ts run",
18
38
  "install-service": "bun run src/cli.ts install"