Skip to content

Commit 6d61a36

Browse files
committed
feat: instrument help.modes with telemetry, add audit integ test
1 parent 85582a1 commit 6d61a36

4 files changed

Lines changed: 87 additions & 2 deletions

File tree

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import { spawnAndCollect } from '../src/test-utils/cli-runner.js';
2+
import { mkdtempSync, readdirSync } from 'node:fs';
3+
import { mkdir, readFile, rm, writeFile } from 'node:fs/promises';
4+
import { tmpdir } from 'node:os';
5+
import { join } from 'node:path';
6+
import { afterAll, describe, expect, it } from 'vitest';
7+
8+
const testConfigDir = mkdtempSync(join(tmpdir(), 'agentcore-audit-'));
9+
const telemetryDir = join(testConfigDir, 'telemetry');
10+
const cliPath = join(__dirname, '..', 'dist', 'cli', 'index.mjs');
11+
12+
function run(args: string[]) {
13+
return spawnAndCollect('node', [cliPath, ...args], tmpdir(), {
14+
AGENTCORE_SKIP_INSTALL: '1',
15+
AGENTCORE_CONFIG_DIR: testConfigDir,
16+
});
17+
}
18+
19+
describe('telemetry audit', () => {
20+
afterAll(() => rm(testConfigDir, { recursive: true, force: true }));
21+
22+
it('help modes writes JSONL audit file when audit is enabled', async () => {
23+
await mkdir(testConfigDir, { recursive: true });
24+
await writeFile(join(testConfigDir, 'config.json'), JSON.stringify({ telemetry: { audit: true } }));
25+
26+
const result = await run(['help', 'modes']);
27+
expect(result.exitCode).toBe(0);
28+
29+
const files = readdirSync(telemetryDir).filter(f => f.startsWith('help-'));
30+
expect(files).toHaveLength(1);
31+
32+
const content = await readFile(join(telemetryDir, files[0]!), 'utf-8');
33+
const entry = JSON.parse(content.trim());
34+
expect(entry.attrs).toMatchObject({
35+
command_group: 'help',
36+
command: 'help.modes',
37+
exit_reason: 'success',
38+
});
39+
expect(entry.value).toBeGreaterThanOrEqual(0);
40+
});
41+
42+
it('help modes does not write audit file when audit is disabled', async () => {
43+
await mkdir(testConfigDir, { recursive: true });
44+
await writeFile(join(testConfigDir, 'config.json'), JSON.stringify({ telemetry: { audit: false } }));
45+
await rm(telemetryDir, { recursive: true, force: true });
46+
47+
const result = await run(['help', 'modes']);
48+
expect(result.exitCode).toBe(0);
49+
50+
try {
51+
const files = readdirSync(telemetryDir);
52+
expect(files).toHaveLength(0);
53+
} catch {
54+
// telemetry dir doesn't exist — that's correct
55+
}
56+
});
57+
});

src/cli/commands/help/command.tsx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { bootstrapTelemetry } from '../../telemetry/bootstrap.js';
12
import type { Command } from '@commander-js/extra-typings';
23

34
const MODES_HELP = `
@@ -49,7 +50,12 @@ export const registerHelp = (program: Command) => {
4950
helpCmd
5051
.command('modes')
5152
.description('Explain interactive vs non-interactive modes')
52-
.action(() => {
53-
console.log(MODES_HELP);
53+
.action(async () => {
54+
const client = await bootstrapTelemetry('help');
55+
await client.withCommandRun('help.modes', () => {
56+
console.log(MODES_HELP);
57+
return {};
58+
});
59+
await client.shutdown();
5460
});
5561
};

src/cli/telemetry/bootstrap.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { GLOBAL_CONFIG_DIR, readGlobalConfig } from '../global-config.js';
2+
import { TelemetryClient } from './client.js';
3+
import { resolveAuditFilePath, resolveResourceAttributes } from './config.js';
4+
import { FilesystemSink } from './sinks/filesystem-sink.js';
5+
import { InMemorySink } from './sinks/in-memory-sink.js';
6+
import { join } from 'path';
7+
8+
export async function bootstrapTelemetry(entrypoint: string, mode: 'cli' | 'tui' = 'cli'): Promise<TelemetryClient> {
9+
const [resource, config] = await Promise.all([resolveResourceAttributes(mode), readGlobalConfig()]);
10+
11+
if (config.telemetry?.audit) {
12+
const filePath = resolveAuditFilePath(
13+
join(GLOBAL_CONFIG_DIR, 'telemetry'),
14+
entrypoint,
15+
resource['agentcore-cli.session_id']
16+
);
17+
return new TelemetryClient(new FilesystemSink({ filePath }));
18+
}
19+
20+
return new TelemetryClient(new InMemorySink());
21+
}

src/cli/telemetry/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
export { resolveTelemetryPreference, resolveResourceAttributes, resolveAuditFilePath } from './config.js';
22
export type { TelemetryPreference } from './config.js';
3+
export { bootstrapTelemetry } from './bootstrap.js';
34
export { TelemetryClient, CANCELLED } from './client.js';
45
export { type MetricSink, CompositeSink } from './sinks/metric-sink.js';
56
export { OtelMetricSink, type OtelMetricSinkConfig } from './sinks/otel-metric-sink.js';

0 commit comments

Comments
 (0)