Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 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
52 changes: 51 additions & 1 deletion src/lib/agent-interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,12 @@
import { LINTING_TOOLS } from './safe-tools';

// Dynamic import cache for ESM module
let _sdkModule: any = null;

Check warning on line 16 in src/lib/agent-interface.ts

View workflow job for this annotation

GitHub Actions / Lint

Unexpected any. Specify a different type
async function getSDKModule(): Promise<any> {

Check warning on line 17 in src/lib/agent-interface.ts

View workflow job for this annotation

GitHub Actions / Lint

Unexpected any. Specify a different type
if (!_sdkModule) {
_sdkModule = await import('@anthropic-ai/claude-agent-sdk');
}
return _sdkModule;

Check warning on line 21 in src/lib/agent-interface.ts

View workflow job for this annotation

GitHub Actions / Lint

Unsafe return of an `any` typed value
}

/**
Expand All @@ -33,8 +33,8 @@

// Using `any` because typed imports from ESM modules require import attributes
// syntax which prettier cannot parse. See PR discussion for details.
type SDKMessage = any;

Check warning on line 36 in src/lib/agent-interface.ts

View workflow job for this annotation

GitHub Actions / Lint

Unexpected any. Specify a different type
type McpServersConfig = any;

Check warning on line 37 in src/lib/agent-interface.ts

View workflow job for this annotation

GitHub Actions / Lint

Unexpected any. Specify a different type

export const AgentSignals = {
/** Signal emitted when the agent reports progress to the user */
Expand Down Expand Up @@ -105,9 +105,28 @@
/**
* Dangerous shell operators that could allow command injection.
* Note: We handle `2>&1` and `| tail/head` separately as safe patterns.
* Note: `&&` is allowed for specific safe patterns like skill installation.
*/
const DANGEROUS_OPERATORS = /[;`$()]/;

/**
* Pattern for PostHog skill installation commands from MCP.
* Format: mkdir -p .claude/skills/{id} && curl -sL "{url}" -o /tmp/posthog-skill-{id}.zip && unzip -o /tmp/posthog-skill-{id}.zip -d .claude/skills/{id} && rm /tmp/posthog-skill-{id}.zip
*
* Security: Only allows:
* - mkdir to .claude/skills/ paths
* - curl from GitHub/localhost URLs (for dev)
* - unzip to .claude/skills/ paths
* - rm of the specific temp file
*/
function isSkillInstallCommand(command: string): boolean {
// Match the exact pattern generated by MCP
const pattern =
/^mkdir -p \.claude\/skills\/([\w-]+) && curl -sL "(https:\/\/github\.com\/[^"]+|http:\/\/localhost:\d+\/[^"]+)" -o \/tmp\/posthog-skill-\1\.zip && unzip -o \/tmp\/posthog-skill-\1\.zip -d \.claude\/skills\/\1 && rm \/tmp\/posthog-skill-\1\.zip$/;

return pattern.test(command);
}

/**
* Check if command is an allowed package manager command.
* Matches: <pkg-manager> [run|exec] <safe-script> [args...]
Expand Down Expand Up @@ -140,6 +159,7 @@
* - Build/typecheck/lint commands for verification
* - Piping to tail/head for output limiting is allowed
* - Stderr redirection (2>&1) is allowed
* - PostHog skill installation commands from MCP
*/
export function wizardCanUseTool(
toolName: string,
Expand All @@ -156,6 +176,14 @@
typeof input.command === 'string' ? input.command : ''
).trim();

// Check for PostHog skill installation command (before dangerous operator check)
// These commands use && chaining but are generated by MCP with a strict format
if (isSkillInstallCommand(command)) {
logToFile(`Allowing skill installation command: ${command}`);
debug(`Allowing skill installation command: ${command}`);
return { behavior: 'allow', updatedInput: input };
}

// Block definitely dangerous operators: ; ` $ ( )
if (DANGEROUS_OPERATORS.test(command)) {
logToFile(`Denying bash command with dangerous operators: ${command}`);
Expand Down Expand Up @@ -216,7 +244,7 @@
};
}

// Check if command starts with any allowed prefix
// Check if command starts with any allowed prefix (package manager commands)
if (matchesAllowedPrefix(normalized)) {
logToFile(`Allowing bash command: ${command}`);
debug(`Allowing bash command: ${command}`);
Expand Down Expand Up @@ -273,7 +301,7 @@

const agentRunConfig: AgentRunConfig = {
workingDirectory: config.workingDirectory,
mcpServers,

Check warning on line 304 in src/lib/agent-interface.ts

View workflow job for this annotation

GitHub Actions / Lint

Unsafe assignment of an `any` value
model: 'claude-opus-4-5-20251101',
};

Expand Down Expand Up @@ -329,7 +357,7 @@
errorMessage = 'Integration failed',
} = config ?? {};

const { query } = await getSDKModule();

Check warning on line 360 in src/lib/agent-interface.ts

View workflow job for this annotation

GitHub Actions / Lint

Unsafe assignment of an `any` value

clack.log.step(
`This whole process should take about ${estimatedDurationMinutes} minutes including error checking and fixes.\n\nGrab some coffee!`,
Expand Down Expand Up @@ -366,13 +394,35 @@
await resultReceived;
};

// Tools needed for the wizard:
// - File operations: Read, Write, Edit
// - Search: Glob, Grep
// - Commands: Bash (with restrictions via canUseTool)
// - MCP discovery: ListMcpResourcesTool (to find available skills)
// - Skills: Skill (to load installed PostHog skills)
// MCP tools (PostHog) come from mcpServers, not allowedTools
const allowedTools = [
'Read',
'Write',
'Edit',
'Glob',
'Grep',
'Bash',
'ListMcpResourcesTool',
'Skill',
];

const response = query({

Check warning on line 415 in src/lib/agent-interface.ts

View workflow job for this annotation

GitHub Actions / Lint

Unsafe call of an `any` typed value

Check warning on line 415 in src/lib/agent-interface.ts

View workflow job for this annotation

GitHub Actions / Lint

Unsafe assignment of an `any` value
prompt: createPromptStream(),
options: {
model: agentConfig.model,
cwd: agentConfig.workingDirectory,
permissionMode: 'acceptEdits',
mcpServers: agentConfig.mcpServers,
// Load skills from project's .claude/skills/ directory
settingSources: ['project'],
// Explicitly enable required tools including Skill
allowedTools,
env: { ...process.env },
canUseTool: (toolName: string, input: unknown) => {
logToFile('canUseTool called:', { toolName, input });
Expand Down
31 changes: 19 additions & 12 deletions src/lib/agent-runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,7 @@ function buildIntegrationPrompt(
? '\n' + additionalLines.map((line) => `- ${line}`).join('\n')
: '';

return `You have access to the PostHog MCP server which provides an integration resource to integrate PostHog into this ${
return `You have access to the PostHog MCP server which provides skills to integrate PostHog into this ${
config.metadata.name
} project.

Expand All @@ -288,23 +288,30 @@ Project context:
- PostHog API Key: ${context.projectApiKey}
- PostHog Host: ${context.host}${additionalContext}

Instructions:
Instructions (follow these steps IN ORDER - do not skip or reorder):

1. Call the PostHog MCP's resource for setup: posthog://workflows/basic-integration/begin
2. Follow all instructions provided; do package installation as soon as possible.
3. Set up environment variables for PostHog in a .env file with the API key and host provided above, using the appropriate naming convention for ${
config.metadata.name
}. Make sure to use these environment variables in the code files you create instead of hardcoding the API key and host.
STEP 1: List available skills from the PostHog MCP server using ListMcpResourcesTool.
Review the skill descriptions and choose the one that best matches this project's framework and configuration.
If no suitable skill is found, emit: ${
AgentSignals.ERROR_RESOURCE_MISSING
} Could not find a suitable skill for this project.

STEP 2: Fetch the chosen skill resource (e.g., posthog://skills/{skill-id}).
The resource returns a shell command to install the skill.

The PostHog MCP will provide specific integration code and instructions. Please follow them carefully. Be sure to look for lockfiles to determine the appropriate package manager to use when installing PostHog. Do not manually edit the package.json file.
STEP 3: Run the installation command using Bash:
- Execute the EXACT command returned by the resource (do not modify it)
- This will download and extract the skill to .claude/skills/{skill-id}/

Before beginning, confirm that you can access the PostHog MCP. If the PostHog MCP is not accessible, emit the following string:
STEP 4: Load the installed skill's SKILL.md file to understand what references are available.

${AgentSignals.ERROR_MCP_MISSING} Could not access the PostHog MCP.
STEP 5: Follow the skill's workflow files in sequence. Look for numbered workflow files in the references (e.g., files with patterns like "1.0-", "1.1-", "1.2-"). Start with the first one and proceed through each step until completion. Each workflow file will tell you what to do and which file comes next.

If the PostHog MCP is accessible, attempt to access the setup resource. If the setup resource is not accessible, emit the following string:
STEP 6: Set up environment variables for PostHog in a .env file with the API key and host provided above, using the appropriate naming convention for ${
config.metadata.name
}. Make sure to use these environment variables in the code files you create instead of hardcoding the API key and host.

${AgentSignals.ERROR_RESOURCE_MISSING} Could not access the setup resource.
Important: Look for lockfiles (pnpm-lock.yaml, package-lock.json, yarn.lock, bun.lockb) to determine the package manager. Do not manually edit package.json. Always install packages as a background task. Don't await completion; proceed with other work immediately after starting the installation.

`;
}
Loading