Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
- `agentguard subscribe --cron` now requires a saved agent host when `--cron-target auto` is used; run `agentguard init --agent <agent>` first or pass an explicit cron target.
- `agentguard status` now shows the saved agent host when one is configured.
- Install and postinstall guidance now recommends `agentguard init --agent <agent>`, `agentguard connect`, and `agentguard checkup` as the focused next steps.
- Postinstall now writes persistent next-step guidance to `~/.agentguard/next-steps.txt` and the package directory so agent installers can discover it even when npm hides lifecycle output.
- System cron installation now writes and invokes a validated AgentGuard wrapper script instead of embedding config-derived paths directly in crontab.

## [1.1.9] - 2026-05-20
Expand Down
5 changes: 3 additions & 2 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,10 +65,11 @@ async function main() {
console.log(`AgentGuard initialized at ${paths.home}`);
console.log(`Config: ${paths.configPath}`);
if (options.agent) {
if (!['claude-code', 'codex', 'openclaw', 'hermes', 'qclaw'].includes(options.agent)) {
const normalizedAgent = String(options.agent).trim().toLowerCase();
if (!['claude-code', 'codex', 'openclaw', 'hermes', 'qclaw'].includes(normalizedAgent)) {
throw new Error('Invalid agent. Use claude-code, codex, openclaw, hermes, or qclaw.');
}
const agent = options.agent as AgentInstaller;
const agent = normalizedAgent as AgentInstaller;
config.agentHost = agent;
saveConfig(config);
const result = installAgentTemplates(agent, { force: options.force });
Expand Down
31 changes: 27 additions & 4 deletions src/postinstall.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,43 @@
#!/usr/bin/env node

import { writeFileSync } from 'node:fs';
import { resolve } from 'node:path';
import { ensureConfig, getAgentGuardPaths } from './config.js';

const NEXT_STEPS = [
'Next steps:',
' agentguard init --agent <claude-code|codex|openclaw|hermes|qclaw>',
' agentguard connect',
' agentguard checkup',
'',
].join('\n');

function printNextSteps(): void {
console.log('Next steps:');
console.log(' agentguard init --agent <agent>');
console.log(' agentguard connect');
console.log(' agentguard checkup');
process.stdout.write(NEXT_STEPS);
}

function writeNextStepsFile(path: string, mode = 0o600): void {
try {
writeFileSync(path, NEXT_STEPS, { mode });
} catch {
// Installation guidance is best-effort and must never break npm install.
}
}

function writePackageNextStepsFile(): void {
if (process.env.AGENTGUARD_SKIP_PACKAGE_NEXT_STEPS === '1') return;
const packageDir = resolve(__dirname, '..');
writeNextStepsFile(resolve(packageDir, 'next-steps.txt'), 0o644);
}

try {
ensureConfig();
const paths = getAgentGuardPaths();
writeNextStepsFile(resolve(paths.home, 'next-steps.txt'));
console.log(`AgentGuard local config ready: ${paths.configPath}`);
} catch {
// Postinstall must never break package installation.
} finally {
writePackageNextStepsFile();
printNextSteps();
}
15 changes: 15 additions & 0 deletions src/tests/cli-init.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,19 @@ describe('init CLI', () => {
assert.equal(config.agentHost, agent);
}
});

it('normalizes --agent values to lowercase', async () => {
const home = mkdtempSync(join(tmpdir(), 'agentguard-init-uppercase-home-'));
const cwd = mkdtempSync(join(tmpdir(), 'agentguard-init-uppercase-cwd-'));
const cliPath = resolve('dist', 'cli.js');

const { stdout } = await execFileAsync(process.execPath, [cliPath, 'init', '--agent', 'Hermes', '--force'], {
cwd,
env: { ...process.env, AGENTGUARD_HOME: home },
});

const config = JSON.parse(readFileSync(join(home, 'config.json'), 'utf8')) as { agentHost?: string };
assert.equal(config.agentHost, 'hermes');
assert.match(stdout, /Installed hermes template:/);
});
});
11 changes: 8 additions & 3 deletions src/tests/postinstall.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { describe, it } from 'node:test';
import assert from 'node:assert/strict';
import { execFile } from 'node:child_process';
import { mkdtempSync } from 'node:fs';
import { mkdtempSync, readFileSync } from 'node:fs';
import { join, resolve } from 'node:path';
import { tmpdir } from 'node:os';
import { promisify } from 'node:util';
Expand All @@ -14,12 +14,17 @@ describe('postinstall', () => {
const postinstallPath = resolve('dist', 'postinstall.js');

const { stdout } = await execFileAsync(process.execPath, [postinstallPath], {
env: { ...process.env, AGENTGUARD_HOME: home },
env: { ...process.env, AGENTGUARD_HOME: home, AGENTGUARD_SKIP_PACKAGE_NEXT_STEPS: '1' },
});

assert.match(stdout, /AgentGuard local config ready:/);
assert.match(stdout, /agentguard init --agent <agent>/);
assert.match(stdout, /agentguard init --agent <claude-code\|codex\|openclaw\|hermes\|qclaw>/);
assert.match(stdout, /agentguard connect/);
assert.match(stdout, /agentguard checkup/);

const nextSteps = readFileSync(join(home, 'next-steps.txt'), 'utf8');
assert.match(nextSteps, /agentguard init --agent <claude-code\|codex\|openclaw\|hermes\|qclaw>/);
assert.match(nextSteps, /agentguard connect/);
assert.match(nextSteps, /agentguard checkup/);
});
});
Loading