@sonde/agent 0.2.5 → 0.2.6

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/src/index.ts CHANGED
@@ -1,13 +1,24 @@
1
1
  #!/usr/bin/env node
2
2
 
3
+ import { spawn } from 'node:child_process';
3
4
  import os from 'node:os';
4
- import { handlePacksCommand } from './cli/packs.js';
5
+ import { packRegistry } from '@sonde/packs';
6
+ import { buildEnabledPacks, handlePacksCommand } from './cli/packs.js';
5
7
  import { checkForUpdate, performUpdate } from './cli/update.js';
6
- import { type AgentConfig, getConfigPath, loadConfig, saveConfig } from './config.js';
8
+ import {
9
+ type AgentConfig,
10
+ getConfigPath,
11
+ loadConfig,
12
+ removePidFile,
13
+ saveConfig,
14
+ stopRunningAgent,
15
+ writePidFile,
16
+ } from './config.js';
7
17
  import { AgentConnection, type ConnectionEvents, enrollWithHub } from './runtime/connection.js';
8
18
  import { ProbeExecutor } from './runtime/executor.js';
9
19
  import { checkNotRoot } from './runtime/privilege.js';
10
20
  import { buildPatterns } from './runtime/scrubber.js';
21
+ import { createSystemChecker, scanForSoftware } from './system/scanner.js';
11
22
  import { VERSION } from './version.js';
12
23
 
13
24
  const args = process.argv.slice(2);
@@ -25,6 +36,8 @@ function printUsage(): void {
25
36
  console.log(' install Interactive guided setup (enroll + scan + packs)');
26
37
  console.log(' enroll Enroll this agent with a hub');
27
38
  console.log(' start Start the agent (TUI by default, --headless for daemon)');
39
+ console.log(' stop Stop the background agent');
40
+ console.log(' restart Restart the agent in background');
28
41
  console.log(' status Show agent status');
29
42
  console.log(' packs Manage packs (list, scan, install, uninstall)');
30
43
  console.log(' update Check for and install agent updates');
@@ -61,7 +74,10 @@ function createRuntime(events: ConnectionEvents): Runtime {
61
74
  process.exit(1);
62
75
  }
63
76
 
64
- const executor = new ProbeExecutor(undefined, undefined, buildPatterns(config.scrubPatterns));
77
+ const enabledPacks = buildEnabledPacks(
78
+ packRegistry, config.disabledPacks ?? [],
79
+ );
80
+ const executor = new ProbeExecutor(enabledPacks, undefined, buildPatterns(config.scrubPatterns));
65
81
  const connection = new AgentConnection(config, executor, events);
66
82
 
67
83
  return { config, executor, connection };
@@ -120,6 +136,20 @@ async function cmdEnroll(): Promise<void> {
120
136
  config.enrollmentToken = undefined;
121
137
  saveConfig(config);
122
138
 
139
+ // Auto-detect packs
140
+ const manifests = [...packRegistry.values()].map((p) => p.manifest);
141
+ const checker = createSystemChecker();
142
+ const scanResults = scanForSoftware(manifests, checker);
143
+ const detectedNames = scanResults
144
+ .filter((r) => r.detected)
145
+ .map((r) => r.packName);
146
+ const allNames = manifests.map((m) => m.name);
147
+ const enabledNames = ['system', ...detectedNames.filter((n) => n !== 'system')];
148
+ const disabledNames = allNames.filter((n) => !enabledNames.includes(n));
149
+
150
+ config.disabledPacks = disabledNames;
151
+ saveConfig(config);
152
+
123
153
  console.log('Agent enrolled successfully.');
124
154
  console.log(` Hub: ${hubUrl}`);
125
155
  console.log(` Name: ${agentName}`);
@@ -129,6 +159,14 @@ async function cmdEnroll(): Promise<void> {
129
159
  console.log(' mTLS: Client certificate issued and saved');
130
160
  }
131
161
  console.log('');
162
+ console.log('Pack detection:');
163
+ for (const name of enabledNames) {
164
+ console.log(` ✓ ${name}`);
165
+ }
166
+ for (const name of disabledNames) {
167
+ console.log(` ✗ ${name} (not detected)`);
168
+ }
169
+ console.log('');
132
170
  console.log('Run "sonde start" to connect.');
133
171
  }
134
172
 
@@ -160,23 +198,66 @@ function cmdStart(): void {
160
198
  console.log('');
161
199
 
162
200
  connection.start();
201
+ writePidFile(process.pid);
163
202
  process.stdin.unref();
164
203
 
165
204
  const shutdown = () => {
166
205
  console.log('\nShutting down...');
167
206
  connection.stop();
207
+ removePidFile();
168
208
  process.exit(0);
169
209
  };
170
210
  process.on('SIGINT', shutdown);
171
211
  process.on('SIGTERM', shutdown);
172
212
  }
173
213
 
214
+ function spawnBackgroundAgent(): number {
215
+ const child = spawn(process.execPath, [process.argv[1]!, 'start', '--headless'], {
216
+ detached: true,
217
+ stdio: 'ignore',
218
+ });
219
+ child.unref();
220
+ return child.pid!;
221
+ }
222
+
174
223
  async function cmdManager(): Promise<void> {
224
+ stopRunningAgent();
225
+
226
+ let detached = false;
175
227
  const { render } = await import('ink');
176
228
  const { createElement } = await import('react');
177
229
  const { ManagerApp } = await import('./tui/manager/ManagerApp.js');
178
- const { waitUntilExit } = render(createElement(ManagerApp, { createRuntime }));
230
+
231
+ const onDetach = () => {
232
+ detached = true;
233
+ spawnBackgroundAgent();
234
+ };
235
+
236
+ const { waitUntilExit } = render(
237
+ createElement(ManagerApp, { createRuntime, onDetach }),
238
+ );
179
239
  await waitUntilExit();
240
+
241
+ if (detached) {
242
+ console.log('Agent detached to background.');
243
+ console.log(' sonde stop — stop the background agent');
244
+ console.log(' sonde start — reattach the TUI');
245
+ console.log(' sonde restart — restart in background');
246
+ }
247
+ }
248
+
249
+ function cmdStop(): void {
250
+ if (stopRunningAgent()) {
251
+ console.log('Agent stopped.');
252
+ } else {
253
+ console.log('No running agent found.');
254
+ }
255
+ }
256
+
257
+ function cmdRestart(): void {
258
+ stopRunningAgent();
259
+ const pid = spawnBackgroundAgent();
260
+ console.log(`Agent restarted in background (PID: ${pid}).`);
180
261
  }
181
262
 
182
263
  function cmdStatus(): void {
@@ -187,7 +268,7 @@ function cmdStatus(): void {
187
268
  return;
188
269
  }
189
270
 
190
- console.log('Sonde Agent Status');
271
+ console.log(`Sonde Agent v${VERSION}`);
191
272
  console.log(` Name: ${config.agentName}`);
192
273
  console.log(` Hub: ${config.hubUrl}`);
193
274
  console.log(` Agent ID: ${config.agentId ?? '(not yet assigned)'}`);
@@ -246,6 +327,12 @@ switch (command) {
246
327
  });
247
328
  }
248
329
  break;
330
+ case 'stop':
331
+ cmdStop();
332
+ break;
333
+ case 'restart':
334
+ cmdRestart();
335
+ break;
249
336
  case 'status':
250
337
  cmdStatus();
251
338
  break;
@@ -1,7 +1,9 @@
1
+ import { packRegistry } from '@sonde/packs';
1
2
  import type { PackManifest } from '@sonde/shared';
2
3
  import { Box, Text, useApp, useInput } from 'ink';
3
4
  import Spinner from 'ink-spinner';
4
5
  import { useEffect, useState } from 'react';
6
+ import { buildEnabledPacks } from '../../cli/packs.js';
5
7
  import { type AgentConfig, saveConfig } from '../../config.js';
6
8
  import { enrollWithHub } from '../../runtime/connection.js';
7
9
  import { ProbeExecutor } from '../../runtime/executor.js';
@@ -19,14 +21,23 @@ export function StepComplete({ hubConfig, selectedPacks }: StepCompleteProps): J
19
21
  const [error, setError] = useState('');
20
22
 
21
23
  useEffect(() => {
24
+ const selectedNames = new Set(selectedPacks.map((p) => p.name));
25
+ const disabledPacks = [...packRegistry.keys()]
26
+ .filter((name) => !selectedNames.has(name));
22
27
  const config: AgentConfig = {
23
28
  hubUrl: hubConfig.hubUrl,
24
29
  apiKey: hubConfig.apiKey,
25
30
  agentName: hubConfig.agentName,
31
+ disabledPacks: disabledPacks.length > 0
32
+ ? disabledPacks
33
+ : undefined,
26
34
  };
27
35
  saveConfig(config);
28
36
 
29
- const executor = new ProbeExecutor();
37
+ const enabledPacks = buildEnabledPacks(
38
+ packRegistry, disabledPacks,
39
+ );
40
+ const executor = new ProbeExecutor(enabledPacks);
30
41
 
31
42
  enrollWithHub(config, executor)
32
43
  .then(({ agentId: id, apiKey: mintedKey }) => {
@@ -26,11 +26,12 @@ interface Runtime {
26
26
 
27
27
  interface ManagerAppProps {
28
28
  createRuntime: (events: ConnectionEvents) => Runtime;
29
+ onDetach?: () => void;
29
30
  }
30
31
 
31
32
  const MAX_ACTIVITY = 50;
32
33
 
33
- export function ManagerApp({ createRuntime }: ManagerAppProps): JSX.Element {
34
+ export function ManagerApp({ createRuntime, onDetach }: ManagerAppProps): JSX.Element {
34
35
  const { exit } = useApp();
35
36
  const [view, setView] = useState<View>('status');
36
37
  const [connected, setConnected] = useState(false);
@@ -91,6 +92,7 @@ export function ManagerApp({ createRuntime }: ManagerAppProps): JSX.Element {
91
92
  break;
92
93
  case 'q':
93
94
  runtimeRef.current?.connection.stop();
95
+ onDetach?.();
94
96
  exit();
95
97
  break;
96
98
  }
@@ -150,7 +152,7 @@ export function ManagerApp({ createRuntime }: ManagerAppProps): JSX.Element {
150
152
  a:audit
151
153
  </Text>
152
154
  <Text> </Text>
153
- <Text color="gray">q:quit</Text>
155
+ <Text color="gray">q:detach</Text>
154
156
  </Box>
155
157
  </Box>
156
158
  );