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
34 changes: 34 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,40 @@

A wizard interface to create GitHub Agentic Workflows. Create ready-to-use [GitHub Agentic Workflows](https://github.github.com/gh-aw/) in minutes — just answer a few questions and get a workflow file you can drop into your repo.

## Customize the wizard

The dashboard is rendered at runtime from [`src/wizard.json`](src/wizard.json). The file defines the
landing-page text, footer labels and URLs, archetype ordering, trigger/output/extra/engine cards,
summary text, recommendation mappings, and the URLs of the pattern library and engine catalog.

To host a customized wizard in another repository:

1. Run `npm run build` and publish the contents of `dist/`.
2. Edit the published `wizard.json` without rebuilding the JavaScript bundle.
3. Point `patterns_url` at a compatible pattern manifest. Relative URLs are resolved from the
location of `wizard.json`, so the configuration and its pattern library can be hosted together.
The `recommendations.safe_outputs` map retains singular and plural aliases where upstream pattern
libraries use both forms (for example, `add-label` and `add-labels`).

The default page reads the configuration URL from:

```html
<meta name="gh-aw-wizard-config" content="wizard.json">
```

An embedding application can instead call `initWizard({ configUrl })`. Cross-origin configuration,
pattern, and engine endpoints must allow the embedding origin with CORS headers. Option IDs should
match definitions in the configured pattern library's `workflow-generation.json`.

## Development

```bash
npm install
npm run dev
npm test
npm run build
```

## License

MIT
2 changes: 2 additions & 0 deletions patterns/archetypes/code-improvement.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@
"Use schedule triggers for continuous maintenance coverage",
"Add pre-steps to run tests/linters before the agent starts \u2014 validates baseline",
"Avoid pr-fix and ci-doctor templates \u2014 both have <20% success in practice",
"Use explicit DO NOT constraints to keep changes scoped to the requested improvement",
"Use protected-files: fallback-to-issue for manifests, CI configuration, and agent instructions",
"Add a pre-activation step that skips scheduled runs once too many open PRs share your title prefix, to avoid spamming maintainers"
],
"anti_patterns": [
Expand Down
4 changes: 3 additions & 1 deletion patterns/archetypes/dependency-monitor.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,9 @@
"tips": [
"Use schedule triggers for reliable periodic checks",
"Include a checklist of specific dependencies to monitor \u2014 don't leave it open-ended",
"Enable network access for fetching upstream release data"
"Enable network access for fetching upstream release data",
"Use DO NOT constraints to prevent unrelated dependency or source changes",
"Use skip-if-match to prevent duplicate scheduled findings"
],
"anti_patterns": [
"skills-updater",
Expand Down
1 change: 1 addition & 0 deletions patterns/archetypes/documentation-updater.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
"80% success rate \u2014 reliable when scoped to specific doc areas",
"Add pre-steps to validate docs build before the agent starts",
"Use DO NOT constraints to prevent deleting existing content",
"Use protected-files: fallback-to-issue for manifests, CI configuration, and agent instructions",
"Add a pre-activation step that skips scheduled runs once too many open PRs share your title prefix, to avoid spamming maintainers"
],
"anti_patterns": [
Expand Down
3 changes: 2 additions & 1 deletion patterns/archetypes/status-report.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,8 @@
"tips": [
"Pre-fetch data in a steps: block \u2014 #1 predictor of workflow health",
"Use schedule triggers for repeatable reporting",
"Keep prompts focused on one report \u2014 multi-source reports need pre-steps"
"Keep prompts focused on one report \u2014 multi-source reports need pre-steps",
"Use DO NOT constraints to prevent repository changes while generating reports"
],
"anti_patterns": [
"daily-repo-status",
Expand Down
176 changes: 19 additions & 157 deletions src/index.html

Large diffs are not rendered by default.

11 changes: 9 additions & 2 deletions src/js/engines.js
Original file line number Diff line number Diff line change
Expand Up @@ -84,13 +84,20 @@ export function registerDefinitionEngines(engines) {
});
}

export function registerBuiltInEngines(engines) {
builtInEngineIds.clear();
engines.forEach((engine) => {
if (engine && ENGINE_ID_PATTERN.test(engine.id)) builtInEngineIds.add(engine.id);
});
}

export function isKnownEngine(engine) {
return builtInEngineIds.has(engine) || definitionEngineIds.has(engine);
}

export function loadDefinitionEngines(fetchImpl) {
export function loadDefinitionEngines(fetchImpl, url) {
fetchImpl = fetchImpl || fetch;
return fetchImpl(ENGINES_URL).then((response) => {
return fetchImpl(url || ENGINES_URL).then((response) => {
if (!response.ok) throw new Error('Unable to load gh-aw engines');
return response.json();
}).then(parseDefinitionEngines).catch(() => {
Expand Down
2 changes: 1 addition & 1 deletion src/js/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@

import { initWizard } from './ui.js';

document.addEventListener('DOMContentLoaded', initWizard);
document.addEventListener('DOMContentLoaded', () => initWizard());
40 changes: 13 additions & 27 deletions src/js/patterns.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,49 +71,35 @@ export function getArchetype(patterns, id) {
return null;
}

const SAFE_OUTPUT_MAP = {
'add-comment': ['add-comment'],
'add-label': ['add-labels'],
'add-labels': ['add-labels'],
'create-issue': ['create-issue'],
'create-pull-request': ['create-pull-request'],
'create-pull-request-review-comment': ['create-pull-request-review-comment'],
'commit-files': ['create-pull-request'],
'issues': ['add-comment', 'add-labels', 'create-issue'],
'pull-requests': ['create-pull-request', 'add-comment', 'create-pull-request-review-comment'],
'contents': ['create-pull-request']
};
const RECOMMENDABLE_TRIGGERS = [
'issues',
'pull_request',
'schedule',
'slash_command',
'label_command',
'push'
];

function wizardOutputs(safeOutputs) {
function wizardOutputs(safeOutputs, safeOutputMap) {
const outputs = [];
(safeOutputs || []).forEach((safeOutput) => {
(SAFE_OUTPUT_MAP[safeOutput] || []).forEach((output) => {
(safeOutputMap[safeOutput] || []).forEach((output) => {
if (outputs.indexOf(output) === -1) outputs.push(output);
});
});
return outputs;
}

export function getRecommendedConfiguration(patterns, id) {
export function getRecommendedConfiguration(patterns, id, wizardConfig) {
const archetype = getArchetype(patterns, id);
if (!archetype) return { triggers: [], outputs: [], profile: null };
const recommendations = wizardConfig && wizardConfig.recommendations
? wizardConfig.recommendations
: {};
const recommendableTriggers = Array.isArray(recommendations.triggers)
? recommendations.triggers
: [];
const safeOutputMap = recommendations.safe_outputs || {};

const profiles = (patterns.configuration_profiles || [])
.filter((profile) => {
return profile.archetype === id &&
Array.isArray(profile.triggers) &&
profile.triggers.every((trigger) => { return RECOMMENDABLE_TRIGGERS.indexOf(trigger) !== -1; }) &&
profile.triggers.every((trigger) => { return recommendableTriggers.indexOf(trigger) !== -1; }) &&
Array.isArray(profile.safe_outputs) &&
profile.safe_outputs.length > 0 &&
profile.safe_outputs.every((safeOutput) => { return SAFE_OUTPUT_MAP[safeOutput]; });
profile.safe_outputs.every((safeOutput) => { return safeOutputMap[safeOutput]; });
})
.slice()
.sort((a, b) => {
Expand All @@ -136,7 +122,7 @@ export function getRecommendedConfiguration(patterns, id) {

return {
triggers,
outputs: wizardOutputs(safeOutputs),
outputs: wizardOutputs(safeOutputs, safeOutputMap),
profile
};
}
71 changes: 24 additions & 47 deletions src/js/summary.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,43 +2,7 @@

import { getArchetype } from './patterns.js';
import { formatEngineLabel } from './engines.js';

const triggerLabels = {
issues: 'a new issue is opened',
pull_request: 'a pull request is opened',
pull_request_ready_for_review: 'a pull request is ready for review',
schedule: 'the schedule runs',
slash_command: 'a slash command is posted (not recommended)',
label_command: 'a matching label is added',
push: 'code is pushed to main'
};

const outputLabels = {
'add-comment': 'add comment',
'add-labels': 'add label',
'create-issue': 'create issue',
'create-pull-request': 'create pull request',
'create-pull-request-review-comment': 'add review comment',
comments: 'add comment',
labels: 'add label',
'new-issues': 'create issue',
'pull-requests': 'create pull request',
commits: 'commit changes'
};

const engineLabels = {
copilot: 'Copilot',
claude: 'Claude',
codex: 'Codex',
gemini: 'Gemini',
pi: 'Pi'
};

const extraLabels = {
memory: 'memory between runs',
charts: 'chart generation',
browser: 'browser access'
};
import { wizardOptions, wizardStep } from './wizard-config.js';

function readableList(values, conjunction) {
conjunction = conjunction || 'and';
Expand All @@ -51,8 +15,22 @@ function mapLabels(values, labels) {
return readableList(values.map((value) => { return labels[value] || value; }));
}

export function buildWorkflowSummary(answers, patterns) {
function optionLabels(config, stepId) {
return Object.fromEntries(wizardOptions(config, stepId).map((option) => {
return [option.id, option.summary || option.label || option.id];
}));
}

export function buildWorkflowSummary(answers, patterns, wizardConfig) {
const archetype = getArchetype(patterns, answers.archetype);
const triggerLabels = optionLabels(wizardConfig, 'trigger');
const outputLabels = optionLabels(wizardConfig, 'output');
const engineLabels = optionLabels(wizardConfig, 'engine');
const extraLabels = optionLabels(wizardConfig, 'extra');
const summaryOverrides = wizardConfig && wizardConfig.summary_overrides &&
wizardConfig.summary_overrides[answers.archetype]
? wizardConfig.summary_overrides[answers.archetype]
: {};
const purpose = answers.archetype === 'custom'
? answers.customDescription
: archetype && archetype.description;
Expand All @@ -63,30 +41,29 @@ export function buildWorkflowSummary(answers, patterns) {
trigger: {
value: answers.triggers.length
? readableList(answers.triggers.map((trigger) => {
if (trigger === 'pull_request' && answers.archetype === 'pr-review') {
return 'a pull request is ready for review';
}
return triggerLabels[trigger] || trigger;
return (summaryOverrides.trigger || {})[trigger] || triggerLabels[trigger] || trigger;
}), 'or')
: 'choose when it runs',
: wizardStep(wizardConfig, 'trigger').placeholder || '',
complete: answers.triggers.length > 0
},
purpose: {
value: purpose || 'choose what the agent should do',
value: purpose || wizardStep(wizardConfig, 'purpose').placeholder || '',
complete: Boolean(purpose)
},
output: {
value: answers.outputs.length
? mapLabels(answers.outputs, outputLabels)
: 'choose what it can write',
: wizardStep(wizardConfig, 'output').placeholder || '',
complete: answers.outputs.length > 0
},
extras: {
value: capabilities.length ? readableList(capabilities) : 'choose optional capabilities',
value: capabilities.length
? readableList(capabilities)
: wizardStep(wizardConfig, 'extra').placeholder || '',
complete: capabilities.length > 0
},
engine: {
value: engine || 'choose an agent',
value: engine || wizardStep(wizardConfig, 'engine').placeholder || '',
complete: Boolean(engine)
}
};
Expand Down
Loading
Loading