mirror of
https://github.com/KeygraphHQ/shannon.git
synced 2026-05-26 18:38:18 +02:00
feat: improve Temporal client UX and env handling
- Change default to fire-and-forget (--wait flag to opt-in) - Add splash screen and improve console output formatting - Add .env to gitignore, remove from dockerignore for container access - Add Taskfile for common development commands
This commit is contained in:
@@ -18,7 +18,6 @@ xben-benchmark-results/
|
||||
# Development files
|
||||
*.md
|
||||
!CLAUDE.md
|
||||
.env*
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
node_modules/
|
||||
.shannon-store.json
|
||||
.env
|
||||
agent-logs/
|
||||
/audit-logs/
|
||||
dist/
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
version: '3'
|
||||
|
||||
dotenv: ['.env']
|
||||
|
||||
vars:
|
||||
COMPOSE_FILE: docker-compose.yml
|
||||
|
||||
tasks:
|
||||
default:
|
||||
silent: true
|
||||
cmds: [task help]
|
||||
|
||||
help:
|
||||
desc: Show usage information
|
||||
silent: true
|
||||
cmds:
|
||||
- |
|
||||
echo "Shannon - AI Penetration Testing Framework"
|
||||
echo ""
|
||||
echo "Usage:"
|
||||
echo " task start URL=<url> REPO=<path> Start a pentest workflow"
|
||||
echo " task logs View real-time worker logs"
|
||||
echo " task query ID=<workflow-id> Query workflow progress"
|
||||
echo " task stop Stop all containers"
|
||||
echo " task help Show this help message"
|
||||
echo ""
|
||||
echo "Examples:"
|
||||
echo " task start URL=https://example.com REPO=/path/to/repo"
|
||||
echo " task start URL=https://example.com REPO=/path/to/repo -- --pipeline-testing"
|
||||
echo " task start URL=https://example.com REPO=/path/to/repo -- --config ./config.yaml"
|
||||
echo " task query ID=shannon-1234567890"
|
||||
echo ""
|
||||
echo "Monitor workflows at http://localhost:8233"
|
||||
|
||||
start:
|
||||
desc: Start a pentest workflow
|
||||
silent: true
|
||||
requires:
|
||||
vars: [URL, REPO]
|
||||
cmds:
|
||||
- |
|
||||
if [ -z "$ANTHROPIC_API_KEY" ] && [ -z "$CLAUDE_CODE_OAUTH_TOKEN" ]; then
|
||||
echo "ERROR: Set ANTHROPIC_API_KEY or CLAUDE_CODE_OAUTH_TOKEN in .env"
|
||||
exit 1
|
||||
fi
|
||||
- TARGET_REPO={{.REPO}} docker compose -f {{.COMPOSE_FILE}} up -d --build
|
||||
- |
|
||||
for i in $(seq 1 30); do
|
||||
docker compose -f {{.COMPOSE_FILE}} exec -T temporal \
|
||||
temporal operator cluster health --address localhost:7233 2>/dev/null | grep -q "SERVING" && break
|
||||
[ $i -eq 30 ] && echo "Timeout waiting for Temporal" && exit 1
|
||||
sleep 2
|
||||
done
|
||||
- |
|
||||
docker compose -f {{.COMPOSE_FILE}} exec -T worker \
|
||||
node dist/temporal/client.js "{{.URL}}" "/target-repo" {{.CLI_ARGS}}
|
||||
|
||||
logs:
|
||||
desc: View real-time worker logs
|
||||
silent: true
|
||||
cmds:
|
||||
- docker compose -f {{.COMPOSE_FILE}} logs -f worker {{.CLI_ARGS}}
|
||||
|
||||
query:
|
||||
desc: Query workflow progress
|
||||
silent: true
|
||||
requires:
|
||||
vars: [ID]
|
||||
cmds:
|
||||
- |
|
||||
docker compose -f {{.COMPOSE_FILE}} exec -T worker \
|
||||
node dist/temporal/query.js "{{.ID}}"
|
||||
|
||||
stop:
|
||||
desc: Stop all containers
|
||||
silent: true
|
||||
cmds:
|
||||
- |
|
||||
if [ "{{.CLI_ARGS}}" = "--clean" ]; then
|
||||
docker compose -f {{.COMPOSE_FILE}} down -v
|
||||
else
|
||||
docker compose -f {{.COMPOSE_FILE}} down
|
||||
fi
|
||||
+26
-18
@@ -20,7 +20,7 @@
|
||||
* --output <path> Output directory for audit logs
|
||||
* --pipeline-testing Use minimal prompts for fast testing
|
||||
* --workflow-id <id> Custom workflow ID (default: shannon-<timestamp>)
|
||||
* --no-wait Start workflow and exit without waiting
|
||||
* --wait Wait for workflow completion with progress polling
|
||||
*
|
||||
* Environment:
|
||||
* TEMPORAL_ADDRESS - Temporal server address (default: localhost:7233)
|
||||
@@ -29,6 +29,7 @@
|
||||
import { Connection, Client } from '@temporalio/client';
|
||||
import dotenv from 'dotenv';
|
||||
import chalk from 'chalk';
|
||||
import { displaySplashScreen } from '../splash-screen.js';
|
||||
// Import types only - these don't pull in workflow runtime code
|
||||
import type { PipelineInput, PipelineState, PipelineProgress } from './shared.js';
|
||||
|
||||
@@ -51,7 +52,7 @@ function showUsage(): void {
|
||||
console.log(
|
||||
' --workflow-id <id> Custom workflow ID (default: shannon-<timestamp>)'
|
||||
);
|
||||
console.log(' --no-wait Start workflow and exit without waiting\n');
|
||||
console.log(' --wait Wait for workflow completion with progress polling\n');
|
||||
console.log(chalk.yellow('Examples:'));
|
||||
console.log(' node dist/temporal/client.js https://example.com /path/to/repo');
|
||||
console.log(
|
||||
@@ -74,7 +75,7 @@ async function startPipeline(): Promise<void> {
|
||||
let outputPath: string | undefined;
|
||||
let pipelineTestingMode = false;
|
||||
let customWorkflowId: string | undefined;
|
||||
let waitForCompletion = true;
|
||||
let waitForCompletion = false;
|
||||
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
const arg = args[i];
|
||||
@@ -98,8 +99,8 @@ async function startPipeline(): Promise<void> {
|
||||
}
|
||||
} else if (arg === '--pipeline-testing') {
|
||||
pipelineTestingMode = true;
|
||||
} else if (arg === '--no-wait') {
|
||||
waitForCompletion = false;
|
||||
} else if (arg === '--wait') {
|
||||
waitForCompletion = true;
|
||||
} else if (arg && !arg.startsWith('-')) {
|
||||
if (!webUrl) {
|
||||
webUrl = arg;
|
||||
@@ -115,8 +116,11 @@ async function startPipeline(): Promise<void> {
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Display splash screen
|
||||
await displaySplashScreen();
|
||||
|
||||
const address = process.env.TEMPORAL_ADDRESS || 'localhost:7233';
|
||||
console.log(chalk.cyan(`Connecting to Temporal at ${address}...`));
|
||||
console.log(chalk.gray(`Connecting to Temporal at ${address}...`));
|
||||
|
||||
const connection = await Connection.connect({ address });
|
||||
const client = new Client({ connection });
|
||||
@@ -132,14 +136,17 @@ async function startPipeline(): Promise<void> {
|
||||
...(pipelineTestingMode && { pipelineTestingMode }),
|
||||
};
|
||||
|
||||
console.log(chalk.green(`\nStarting workflow: ${workflowId}`));
|
||||
console.log(chalk.gray(`Target: ${webUrl}`));
|
||||
console.log(chalk.gray(`Repository: ${repoPath}`));
|
||||
console.log(
|
||||
chalk.blue(
|
||||
`Web UI: http://localhost:8233/namespaces/default/workflows/${workflowId}\n`
|
||||
)
|
||||
);
|
||||
console.log(chalk.green.bold(`✓ Workflow started: ${workflowId}`));
|
||||
console.log();
|
||||
console.log(chalk.white(' Target: ') + chalk.cyan(webUrl));
|
||||
console.log(chalk.white(' Repository: ') + chalk.cyan(repoPath));
|
||||
if (configPath) {
|
||||
console.log(chalk.white(' Config: ') + chalk.cyan(configPath));
|
||||
}
|
||||
if (pipelineTestingMode) {
|
||||
console.log(chalk.white(' Mode: ') + chalk.yellow('Pipeline Testing'));
|
||||
}
|
||||
console.log();
|
||||
|
||||
// Start workflow by name (not by importing the function)
|
||||
const handle = await client.workflow.start<(input: PipelineInput) => Promise<PipelineState>>(
|
||||
@@ -152,10 +159,11 @@ async function startPipeline(): Promise<void> {
|
||||
);
|
||||
|
||||
if (!waitForCompletion) {
|
||||
console.log(
|
||||
chalk.yellow('Workflow started in background. Use query tool to check progress.')
|
||||
);
|
||||
console.log(chalk.gray(` npm run temporal:query -- ${workflowId}`));
|
||||
console.log(chalk.bold('Monitor progress:'));
|
||||
console.log(chalk.white(' Web UI: ') + chalk.blue(`http://localhost:8233/namespaces/default/workflows/${workflowId}`));
|
||||
console.log(chalk.white(' Logs: ') + chalk.gray('task logs'));
|
||||
console.log(chalk.white(' Query: ') + chalk.gray(`task query ID=${workflowId}`));
|
||||
console.log();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user