Skip to content

Commit 5f4f27c

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

4 files changed

Lines changed: 90 additions & 3 deletions

File tree

integ-tests/help.test.ts

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
1+
import { spawnAndCollect } from '../src/test-utils/cli-runner.js';
12
import { runCLI } from '../src/test-utils/index.js';
2-
import { describe, expect, it } from 'vitest';
3+
import { readdirSync } from 'node:fs';
4+
import { mkdir, readFile, rm, writeFile } from 'node:fs/promises';
5+
import { tmpdir } from 'node:os';
6+
import { join } from 'node:path';
7+
import { afterAll, beforeAll, describe, expect, it } from 'vitest';
38

49
const COMMANDS = [
510
'create',
@@ -38,3 +43,57 @@ describe('CLI help', () => {
3843
}
3944
});
4045
});
46+
47+
describe('help modes telemetry', () => {
48+
let testConfigDir: string;
49+
const cliPath = join(__dirname, '..', 'dist', 'cli', 'index.mjs');
50+
51+
beforeAll(async () => {
52+
testConfigDir = join(tmpdir(), `agentcore-help-telemetry-${Date.now()}`);
53+
await mkdir(testConfigDir, { recursive: true });
54+
});
55+
afterAll(() => rm(testConfigDir, { recursive: true, force: true }));
56+
57+
function run(args: string[]) {
58+
return spawnAndCollect('node', [cliPath, ...args], tmpdir(), {
59+
AGENTCORE_SKIP_INSTALL: '1',
60+
AGENTCORE_CONFIG_DIR: testConfigDir,
61+
});
62+
}
63+
64+
it('writes JSONL audit file when audit is enabled', async () => {
65+
await writeFile(join(testConfigDir, 'config.json'), JSON.stringify({ telemetry: { audit: true } }));
66+
67+
const result = await run(['help', 'modes']);
68+
expect(result.exitCode).toBe(0);
69+
70+
const telemetryDir = join(testConfigDir, 'telemetry');
71+
const files = readdirSync(telemetryDir).filter(f => f.startsWith('help-'));
72+
expect(files).toHaveLength(1);
73+
74+
const content = await readFile(join(telemetryDir, files[0]!), 'utf-8');
75+
const entry = JSON.parse(content.trim());
76+
expect(entry.attrs).toMatchObject({
77+
command_group: 'help',
78+
command: 'help.modes',
79+
exit_reason: 'success',
80+
});
81+
expect(entry.value).toBeGreaterThanOrEqual(0);
82+
});
83+
84+
it('does not write audit file when audit is disabled', async () => {
85+
await writeFile(join(testConfigDir, 'config.json'), JSON.stringify({ telemetry: { audit: false } }));
86+
const telemetryDir = join(testConfigDir, 'telemetry');
87+
await rm(telemetryDir, { recursive: true, force: true });
88+
89+
const result = await run(['help', 'modes']);
90+
expect(result.exitCode).toBe(0);
91+
92+
try {
93+
const files = readdirSync(telemetryDir);
94+
expect(files).toHaveLength(0);
95+
} catch {
96+
// telemetry dir doesn't exist — correct
97+
}
98+
});
99+
});

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)