mirror of
https://github.com/github/awesome-copilot.git
synced 2026-02-20 02:15:12 +00:00
feat: add hooks functionality with automated workflows
- Introduced hooks to enable automated workflows triggered by specific events during GitHub Copilot sessions. - Added documentation for hooks in AGENTS.md and README.md. - Created a new directory structure for hooks, including README.md and hooks.json files. - Implemented two example hooks: Session Auto-Commit and Session Logger. - Developed scripts for logging session events and auto-committing changes. - Enhanced validation and parsing for hook metadata. - Updated build and validation scripts to accommodate new hooks functionality.
This commit is contained in:
39
AGENTS.md
39
AGENTS.md
@@ -8,6 +8,7 @@ The Awesome GitHub Copilot repository is a community-driven collection of custom
|
||||
- **Prompts** - Task-specific prompts for code generation and problem-solving
|
||||
- **Instructions** - Coding standards and best practices applied to specific file patterns
|
||||
- **Skills** - Self-contained folders with instructions and bundled resources for specialized tasks
|
||||
- **Hooks** - Automated workflows triggered by specific events during development
|
||||
- **Collections** - Curated collections organized around specific themes and workflows
|
||||
|
||||
## Repository Structure
|
||||
@@ -18,6 +19,7 @@ The Awesome GitHub Copilot repository is a community-driven collection of custom
|
||||
├── prompts/ # Task-specific prompts (.prompt.md files)
|
||||
├── instructions/ # Coding standards and guidelines (.instructions.md files)
|
||||
├── skills/ # Agent Skills folders (each with SKILL.md and optional bundled assets)
|
||||
├── hooks/ # Automated workflow hooks (.hook.md files)
|
||||
├── collections/ # Curated collections of resources (.md files)
|
||||
├── docs/ # Documentation for different resource types
|
||||
├── eng/ # Build and automation scripts
|
||||
@@ -48,9 +50,9 @@ npm run skill:create -- --name <skill-name>
|
||||
|
||||
## Development Workflow
|
||||
|
||||
### Working with Agents, Prompts, Instructions, and Skills
|
||||
### Working with Agents, Prompts, Instructions, Skills, and Hooks
|
||||
|
||||
All agent files (`*.agent.md`), prompt files (`*.prompt.md`), and instruction files (`*.instructions.md`) must include proper markdown front matter. Agent Skills are folders containing a `SKILL.md` file with frontmatter and optional bundled assets:
|
||||
All agent files (`*.agent.md`), prompt files (`*.prompt.md`), instruction files (`*.instructions.md`), and hook files (`*.hook.md`) must include proper markdown front matter. Agent Skills are folders containing a `SKILL.md` file with frontmatter and optional bundled assets:
|
||||
|
||||
#### Agent Files (*.agent.md)
|
||||
- Must have `description` field (wrapped in single quotes)
|
||||
@@ -80,9 +82,20 @@ All agent files (`*.agent.md`), prompt files (`*.prompt.md`), and instruction fi
|
||||
- Asset files should be reasonably sized (under 5MB per file)
|
||||
- Skills follow the [Agent Skills specification](https://agentskills.io/specification)
|
||||
|
||||
#### Hook Folders (hooks/*/README.md)
|
||||
- Each hook is a folder containing a `README.md` file with frontmatter
|
||||
- README.md must have `name` field (human-readable name)
|
||||
- README.md must have `description` field (wrapped in single quotes, not empty)
|
||||
- Must include a `hooks.json` file with hook configuration (hook events extracted from this file)
|
||||
- Folder names should be lower case with words separated by hyphens
|
||||
- Can include bundled assets (scripts, utilities, configuration files)
|
||||
- Bundled scripts should be referenced in the README.md and hooks.json
|
||||
- Follow the [GitHub Copilot hooks specification](https://docs.github.com/en/copilot/how-tos/use-copilot-agents/coding-agent/use-hooks)
|
||||
- Optionally includes `tags` field for categorization
|
||||
|
||||
### Adding New Resources
|
||||
|
||||
When adding a new agent, prompt, instruction, or skill:
|
||||
When adding a new agent, prompt, instruction, skill, or hook:
|
||||
|
||||
**For Agents, Prompts, and Instructions:**
|
||||
1. Create the file with proper front matter
|
||||
@@ -90,6 +103,16 @@ When adding a new agent, prompt, instruction, or skill:
|
||||
3. Update the README.md by running: `npm run build`
|
||||
4. Verify the resource appears in the generated README
|
||||
|
||||
**For Hooks:**
|
||||
1. Create a new folder in `hooks/` with a descriptive name
|
||||
2. Create `README.md` with proper frontmatter (name, description, hooks, tags)
|
||||
3. Create `hooks.json` with hook configuration following GitHub Copilot hooks spec
|
||||
4. Add any bundled scripts or assets to the folder
|
||||
5. Make scripts executable: `chmod +x script.sh`
|
||||
6. Update the README.md by running: `npm run build`
|
||||
7. Verify the hook appears in the generated README
|
||||
|
||||
|
||||
**For Skills:**
|
||||
1. Run `npm run skill:create` to scaffold a new skill folder
|
||||
2. Edit the generated SKILL.md file with your instructions
|
||||
@@ -186,6 +209,16 @@ For skills (skills/*/):
|
||||
- [ ] Any bundled assets are referenced in SKILL.md
|
||||
- [ ] Bundled assets are under 5MB per file
|
||||
|
||||
For hook folders (hooks/*/):
|
||||
- [ ] Folder contains a README.md file with markdown front matter
|
||||
- [ ] Has `name` field with human-readable name
|
||||
- [ ] Has non-empty `description` field wrapped in single quotes
|
||||
- [ ] Has `hooks.json` file with valid hook configuration (hook events extracted from this file)
|
||||
- [ ] Folder name is lower case with hyphens
|
||||
- [ ] Any bundled scripts are executable and referenced in README.md
|
||||
- [ ] Follows [GitHub Copilot hooks specification](https://docs.github.com/en/copilot/how-tos/use-copilot-agents/coding-agent/use-hooks)
|
||||
- [ ] Optionally includes `tags` array field for categorization
|
||||
|
||||
## Contributing
|
||||
|
||||
This is a community-driven project. Contributions are welcome! Please see:
|
||||
|
||||
@@ -11,6 +11,7 @@ This repository provides a comprehensive toolkit for enhancing GitHub Copilot wi
|
||||
- **👉 [Awesome Agents](docs/README.agents.md)** - Specialized GitHub Copilot agents that integrate with MCP servers to provide enhanced capabilities for specific workflows and tools
|
||||
- **👉 [Awesome Prompts](docs/README.prompts.md)** - Focused, task-specific prompts for generating code, documentation, and solving specific problems
|
||||
- **👉 [Awesome Instructions](docs/README.instructions.md)** - Comprehensive coding standards and best practices that apply to specific file patterns or entire projects
|
||||
- **👉 [Awesome Hooks](docs/README.hooks.md)** - Automated workflows triggered by specific events during development, testing, and deployment
|
||||
- **👉 [Awesome Skills](docs/README.skills.md)** - Self-contained folders with instructions and bundled resources that enhance AI capabilities for specialized tasks
|
||||
- **👉 [Awesome Collections](docs/README.collections.md)** - Curated collections of related prompts, instructions, agents, and skills organized around specific themes and workflows
|
||||
- **👉 [Awesome Cookbook Recipes](cookbook/README.md)** - Practical, copy-paste-ready code snippets and real-world examples for working with GitHub Copilot tools and features
|
||||
@@ -96,6 +97,10 @@ Use the `/` command in GitHub Copilot Chat to access prompts:
|
||||
|
||||
Instructions automatically apply to files based on their patterns and provide contextual guidance for coding standards, frameworks, and best practices.
|
||||
|
||||
### 🪝 Hooks
|
||||
|
||||
Hooks enable automated workflows triggered by specific events during GitHub Copilot coding agent sessions (like sessionStart, sessionEnd, userPromptSubmitted). They can automate tasks like logging, auto-committing changes, or integrating with external services.
|
||||
|
||||
## 🎯 Why Use Awesome GitHub Copilot?
|
||||
|
||||
- **Productivity**: Pre-built agents, prompts and instructions save time and provide consistent results.
|
||||
@@ -107,7 +112,7 @@ Instructions automatically apply to files based on their patterns and provide co
|
||||
|
||||
We welcome contributions! Please see our [Contributing Guidelines](CONTRIBUTING.md) for details on how to:
|
||||
|
||||
- Add new prompts, instructions, agents, or skills
|
||||
- Add new prompts, instructions, hooks, agents, or skills
|
||||
- Improve existing content
|
||||
- Report issues or suggest enhancements
|
||||
|
||||
@@ -148,7 +153,7 @@ The customizations in this repository are sourced from and created by third-part
|
||||
|
||||
---
|
||||
|
||||
**Ready to supercharge your coding experience?** Start exploring our [prompts](docs/README.prompts.md), [instructions](docs/README.instructions.md), and [custom agents](docs/README.agents.md)!
|
||||
**Ready to supercharge your coding experience?** Start exploring our [prompts](docs/README.prompts.md), [instructions](docs/README.instructions.md), [hooks](docs/README.hooks.md), and [custom agents](docs/README.agents.md)!
|
||||
|
||||
## Contributors ✨
|
||||
|
||||
|
||||
31
docs/README.hooks.md
Normal file
31
docs/README.hooks.md
Normal file
@@ -0,0 +1,31 @@
|
||||
# 🪝 Hooks
|
||||
|
||||
Hooks enable automated workflows triggered by specific events during GitHub Copilot coding agent sessions, such as session start, session end, user prompts, and tool usage.
|
||||
### How to Use Hooks
|
||||
|
||||
**What's Included:**
|
||||
- Each hook is a folder containing a `README.md` file and a `hooks.json` configuration
|
||||
- Hooks may include helper scripts, utilities, or other bundled assets
|
||||
- Hooks follow the [GitHub Copilot hooks specification](https://docs.github.com/en/copilot/how-tos/use-copilot-agents/coding-agent/use-hooks)
|
||||
|
||||
**To Install:**
|
||||
- Copy the hook folder to your repository's `.github/hooks/` directory
|
||||
- Ensure any bundled scripts are executable (`chmod +x script.sh`)
|
||||
- Commit the hook to your repository's default branch
|
||||
|
||||
**To Activate/Use:**
|
||||
- Hooks automatically execute during Copilot coding agent sessions
|
||||
- Configure hook events in the `hooks.json` file
|
||||
- Available events: `sessionStart`, `sessionEnd`, `userPromptSubmitted`, `preToolUse`, `postToolUse`, `errorOccurred`
|
||||
|
||||
**When to Use:**
|
||||
- Automate session logging and audit trails
|
||||
- Auto-commit changes at session end
|
||||
- Track usage analytics
|
||||
- Integrate with external tools and services
|
||||
- Custom session workflows
|
||||
|
||||
| Name | Description | Events | Bundled Assets |
|
||||
| ---- | ----------- | ------ | -------------- |
|
||||
| [Session Auto-Commit](../hooks/session-auto-commit/README.md) | Automatically commits and pushes changes when a Copilot coding agent session ends | sessionEnd | `auto-commit.sh`<br />`hooks.json` |
|
||||
| [Session Logger](../hooks/session-logger/README.md) | Logs all Copilot coding agent session activity for audit and analysis | sessionStart, sessionEnd, userPromptSubmitted | `hooks.json`<br />`log-prompt.sh`<br />`log-session-end.sh`<br />`log-session-start.sh` |
|
||||
@@ -158,6 +158,8 @@ function getDisplayName(filePath, kind) {
|
||||
return basename.replace(".agent.md", "");
|
||||
} else if (kind === "instruction") {
|
||||
return basename.replace(".instructions.md", "");
|
||||
} else if (kind === "hook") {
|
||||
return basename.replace(".hook.md", "");
|
||||
} else if (kind === "skill") {
|
||||
return path.basename(filePath);
|
||||
}
|
||||
@@ -221,6 +223,23 @@ function generateReadme(collection, items) {
|
||||
lines.push("");
|
||||
}
|
||||
|
||||
// Hooks
|
||||
const hooks = items.filter((item) => item.kind === "hook");
|
||||
if (hooks.length > 0) {
|
||||
lines.push("### Hooks");
|
||||
lines.push("");
|
||||
lines.push("| Hook | Description | Event |");
|
||||
lines.push("|------|-------------|-------|");
|
||||
for (const item of hooks) {
|
||||
const name = getDisplayName(item.path, "hook");
|
||||
const description =
|
||||
item.frontmatter?.description || item.frontmatter?.name || name;
|
||||
const event = item.frontmatter?.event || "N/A";
|
||||
lines.push(`| \`${name}\` | ${description} | ${event} |`);
|
||||
}
|
||||
lines.push("");
|
||||
}
|
||||
|
||||
// Skills
|
||||
const skills = items.filter((item) => item.kind === "skill");
|
||||
if (skills.length > 0) {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import path from "path";
|
||||
import path, { dirname } from "path";
|
||||
import { fileURLToPath } from "url";
|
||||
import { dirname } from "path";
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
@@ -100,6 +99,34 @@ Skills differ from other primitives by supporting bundled assets (scripts, code
|
||||
- Browse the skills table below to find relevant capabilities
|
||||
- Copy the skill folder to your local skills directory
|
||||
- Reference skills in your prompts or let the agent discover them automatically`,
|
||||
|
||||
hooksSection: `## 🪝 Hooks
|
||||
|
||||
Hooks enable automated workflows triggered by specific events during GitHub Copilot coding agent sessions, such as session start, session end, user prompts, and tool usage.`,
|
||||
|
||||
hooksUsage: `### How to Use Hooks
|
||||
|
||||
**What's Included:**
|
||||
- Each hook is a folder containing a \`README.md\` file and a \`hooks.json\` configuration
|
||||
- Hooks may include helper scripts, utilities, or other bundled assets
|
||||
- Hooks follow the [GitHub Copilot hooks specification](https://docs.github.com/en/copilot/how-tos/use-copilot-agents/coding-agent/use-hooks)
|
||||
|
||||
**To Install:**
|
||||
- Copy the hook folder to your repository's \`.github/hooks/\` directory
|
||||
- Ensure any bundled scripts are executable (\`chmod +x script.sh\`)
|
||||
- Commit the hook to your repository's default branch
|
||||
|
||||
**To Activate/Use:**
|
||||
- Hooks automatically execute during Copilot coding agent sessions
|
||||
- Configure hook events in the \`hooks.json\` file
|
||||
- Available events: \`sessionStart\`, \`sessionEnd\`, \`userPromptSubmitted\`, \`preToolUse\`, \`postToolUse\`, \`errorOccurred\`
|
||||
|
||||
**When to Use:**
|
||||
- Automate session logging and audit trails
|
||||
- Auto-commit changes at session end
|
||||
- Track usage analytics
|
||||
- Integrate with external tools and services
|
||||
- Custom session workflows`,
|
||||
};
|
||||
|
||||
const vscodeInstallImage =
|
||||
@@ -115,6 +142,7 @@ const AKA_INSTALL_URLS = {
|
||||
instructions: "https://aka.ms/awesome-copilot/install/instructions",
|
||||
prompt: "https://aka.ms/awesome-copilot/install/prompt",
|
||||
agent: "https://aka.ms/awesome-copilot/install/agent",
|
||||
hook: "https://aka.ms/awesome-copilot/install/hook",
|
||||
};
|
||||
|
||||
const ROOT_FOLDER = path.join(__dirname, "..");
|
||||
@@ -122,6 +150,7 @@ const INSTRUCTIONS_DIR = path.join(ROOT_FOLDER, "instructions");
|
||||
const PROMPTS_DIR = path.join(ROOT_FOLDER, "prompts");
|
||||
const AGENTS_DIR = path.join(ROOT_FOLDER, "agents");
|
||||
const SKILLS_DIR = path.join(ROOT_FOLDER, "skills");
|
||||
const HOOKS_DIR = path.join(ROOT_FOLDER, "hooks");
|
||||
const COLLECTIONS_DIR = path.join(ROOT_FOLDER, "collections");
|
||||
const COOKBOOK_DIR = path.join(ROOT_FOLDER, "cookbook");
|
||||
const MAX_COLLECTION_ITEMS = 50;
|
||||
@@ -135,23 +164,7 @@ const SKILL_DESCRIPTION_MAX_LENGTH = 1024;
|
||||
const DOCS_DIR = path.join(ROOT_FOLDER, "docs");
|
||||
|
||||
export {
|
||||
TEMPLATES,
|
||||
vscodeInstallImage,
|
||||
vscodeInsidersInstallImage,
|
||||
repoBaseUrl,
|
||||
AKA_INSTALL_URLS,
|
||||
ROOT_FOLDER,
|
||||
INSTRUCTIONS_DIR,
|
||||
PROMPTS_DIR,
|
||||
AGENTS_DIR,
|
||||
SKILLS_DIR,
|
||||
COLLECTIONS_DIR,
|
||||
COOKBOOK_DIR,
|
||||
MAX_COLLECTION_ITEMS,
|
||||
SKILL_NAME_MIN_LENGTH,
|
||||
SKILL_NAME_MAX_LENGTH,
|
||||
SKILL_DESCRIPTION_MIN_LENGTH,
|
||||
SKILL_DESCRIPTION_MAX_LENGTH,
|
||||
DOCS_DIR,
|
||||
AGENTS_DIR, AKA_INSTALL_URLS, COLLECTIONS_DIR,
|
||||
COOKBOOK_DIR, DOCS_DIR, HOOKS_DIR, INSTRUCTIONS_DIR, MAX_COLLECTION_ITEMS, PROMPTS_DIR, repoBaseUrl, ROOT_FOLDER, SKILL_DESCRIPTION_MAX_LENGTH, SKILL_DESCRIPTION_MIN_LENGTH, SKILL_NAME_MAX_LENGTH, SKILL_NAME_MIN_LENGTH, SKILLS_DIR, TEMPLATES, vscodeInsidersInstallImage, vscodeInstallImage
|
||||
};
|
||||
|
||||
|
||||
@@ -7,24 +7,26 @@
|
||||
*/
|
||||
|
||||
import fs from "fs";
|
||||
import path, { dirname } from "path";
|
||||
import path from "path";
|
||||
import { fileURLToPath } from "url";
|
||||
import {
|
||||
AGENTS_DIR,
|
||||
COLLECTIONS_DIR,
|
||||
COOKBOOK_DIR,
|
||||
HOOKS_DIR,
|
||||
INSTRUCTIONS_DIR,
|
||||
PROMPTS_DIR,
|
||||
ROOT_FOLDER,
|
||||
SKILLS_DIR,
|
||||
SKILLS_DIR
|
||||
} from "./constants.mjs";
|
||||
import { getGitFileDates } from "./utils/git-dates.mjs";
|
||||
import {
|
||||
parseCollectionYaml,
|
||||
parseFrontmatter,
|
||||
parseSkillMetadata,
|
||||
parseHookMetadata,
|
||||
parseYamlFile,
|
||||
} from "./yaml-parser.mjs";
|
||||
import { getGitFileDates } from "./utils/git-dates.mjs";
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
|
||||
@@ -122,6 +124,75 @@ function generateAgentsData(gitDates) {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate hooks metadata
|
||||
*/
|
||||
/**
|
||||
* Generate hooks metadata (similar to skills - folder-based)
|
||||
*/
|
||||
function generateHooksData(gitDates) {
|
||||
const hooks = [];
|
||||
|
||||
// Check if hooks directory exists
|
||||
if (!fs.existsSync(HOOKS_DIR)) {
|
||||
return {
|
||||
items: hooks,
|
||||
filters: {
|
||||
hooks: [],
|
||||
tags: [],
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// Get all hook folders (directories)
|
||||
const hookFolders = fs.readdirSync(HOOKS_DIR).filter((file) => {
|
||||
const filePath = path.join(HOOKS_DIR, file);
|
||||
return fs.statSync(filePath).isDirectory();
|
||||
});
|
||||
|
||||
// Track all unique values for filters
|
||||
const allHookTypes = new Set();
|
||||
const allTags = new Set();
|
||||
|
||||
for (const folder of hookFolders) {
|
||||
const hookPath = path.join(HOOKS_DIR, folder);
|
||||
const metadata = parseHookMetadata(hookPath);
|
||||
if (!metadata) continue;
|
||||
|
||||
const relativePath = path
|
||||
.relative(ROOT_FOLDER, hookPath)
|
||||
.replace(/\\/g, "/");
|
||||
const readmeRelativePath = `${relativePath}/README.md`;
|
||||
|
||||
// Track unique values
|
||||
(metadata.hooks || []).forEach((h) => allHookTypes.add(h));
|
||||
(metadata.tags || []).forEach((t) => allTags.add(t));
|
||||
|
||||
hooks.push({
|
||||
id: folder,
|
||||
title: metadata.name,
|
||||
description: metadata.description,
|
||||
hooks: metadata.hooks || [],
|
||||
tags: metadata.tags || [],
|
||||
assets: metadata.assets || [],
|
||||
path: relativePath,
|
||||
readmeFile: readmeRelativePath,
|
||||
lastUpdated: gitDates.get(readmeRelativePath) || null,
|
||||
});
|
||||
}
|
||||
|
||||
// Sort and return with filter metadata
|
||||
const sortedHooks = hooks.sort((a, b) => a.title.localeCompare(b.title));
|
||||
|
||||
return {
|
||||
items: sortedHooks,
|
||||
filters: {
|
||||
hooks: Array.from(allHookTypes).sort(),
|
||||
tags: Array.from(allTags).sort(),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate prompts metadata
|
||||
*/
|
||||
@@ -539,6 +610,7 @@ function generateSearchIndex(
|
||||
agents,
|
||||
prompts,
|
||||
instructions,
|
||||
hooks,
|
||||
skills,
|
||||
collections
|
||||
) {
|
||||
@@ -584,6 +656,20 @@ function generateSearchIndex(
|
||||
});
|
||||
}
|
||||
|
||||
for (const hook of hooks) {
|
||||
index.push({
|
||||
type: "hook",
|
||||
id: hook.id,
|
||||
title: hook.title,
|
||||
description: hook.description,
|
||||
path: hook.readmeFile,
|
||||
lastUpdated: hook.lastUpdated,
|
||||
searchText: `${hook.title} ${hook.description} ${hook.hooks.join(
|
||||
" "
|
||||
)} ${hook.tags.join(" ")}`.toLowerCase(),
|
||||
});
|
||||
}
|
||||
|
||||
for (const skill of skills) {
|
||||
index.push({
|
||||
type: "skill",
|
||||
@@ -720,7 +806,7 @@ async function main() {
|
||||
// Load git dates for all resource files (single efficient git command)
|
||||
console.log("Loading git history for last updated dates...");
|
||||
const gitDates = getGitFileDates(
|
||||
["agents/", "prompts/", "instructions/", "skills/", "collections/"],
|
||||
["agents/", "prompts/", "instructions/", "hooks/", "skills/", "collections/"],
|
||||
ROOT_FOLDER
|
||||
);
|
||||
console.log(`✓ Loaded dates for ${gitDates.size} files\n`);
|
||||
@@ -732,6 +818,12 @@ async function main() {
|
||||
`✓ Generated ${agents.length} agents (${agentsData.filters.models.length} models, ${agentsData.filters.tools.length} tools)`
|
||||
);
|
||||
|
||||
const hooksData = generateHooksData(gitDates);
|
||||
const hooks = hooksData.items;
|
||||
console.log(
|
||||
`✓ Generated ${hooks.length} hooks (${hooksData.filters.hooks.length} hook types, ${hooksData.filters.tags.length} tags)`
|
||||
);
|
||||
|
||||
const promptsData = generatePromptsData(gitDates);
|
||||
const prompts = promptsData.items;
|
||||
console.log(
|
||||
@@ -771,6 +863,7 @@ async function main() {
|
||||
agents,
|
||||
prompts,
|
||||
instructions,
|
||||
hooks,
|
||||
skills,
|
||||
collections
|
||||
);
|
||||
@@ -782,6 +875,11 @@ async function main() {
|
||||
JSON.stringify(agentsData, null, 2)
|
||||
);
|
||||
|
||||
fs.writeFileSync(
|
||||
path.join(WEBSITE_DATA_DIR, "hooks.json"),
|
||||
JSON.stringify(hooksData, null, 2)
|
||||
);
|
||||
|
||||
fs.writeFileSync(
|
||||
path.join(WEBSITE_DATA_DIR, "prompts.json"),
|
||||
JSON.stringify(promptsData, null, 2)
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
AKA_INSTALL_URLS,
|
||||
COLLECTIONS_DIR,
|
||||
DOCS_DIR,
|
||||
HOOKS_DIR,
|
||||
INSTRUCTIONS_DIR,
|
||||
PROMPTS_DIR,
|
||||
repoBaseUrl,
|
||||
@@ -22,6 +23,7 @@ import {
|
||||
parseCollectionYaml,
|
||||
parseFrontmatter,
|
||||
parseSkillMetadata,
|
||||
parseHookMetadata,
|
||||
} from "./yaml-parser.mjs";
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
@@ -515,6 +517,67 @@ function generateAgentsSection(agentsDir, registryNames = []) {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the hooks section with a table of all hooks
|
||||
*/
|
||||
function generateHooksSection(hooksDir) {
|
||||
if (!fs.existsSync(hooksDir)) {
|
||||
console.log(`Hooks directory does not exist: ${hooksDir}`);
|
||||
return "";
|
||||
}
|
||||
|
||||
// Get all hook folders (directories)
|
||||
const hookFolders = fs.readdirSync(hooksDir).filter((file) => {
|
||||
const filePath = path.join(hooksDir, file);
|
||||
return fs.statSync(filePath).isDirectory();
|
||||
});
|
||||
|
||||
// Parse each hook folder
|
||||
const hookEntries = hookFolders
|
||||
.map((folder) => {
|
||||
const hookPath = path.join(hooksDir, folder);
|
||||
const metadata = parseHookMetadata(hookPath);
|
||||
if (!metadata) return null;
|
||||
|
||||
return {
|
||||
folder,
|
||||
name: metadata.name,
|
||||
description: metadata.description,
|
||||
hooks: metadata.hooks,
|
||||
tags: metadata.tags,
|
||||
assets: metadata.assets,
|
||||
};
|
||||
})
|
||||
.filter((entry) => entry !== null)
|
||||
.sort((a, b) => a.name.localeCompare(b.name));
|
||||
|
||||
console.log(`Found ${hookEntries.length} hook(s)`);
|
||||
|
||||
if (hookEntries.length === 0) {
|
||||
return "";
|
||||
}
|
||||
|
||||
// Create table header
|
||||
let content =
|
||||
"| Name | Description | Events | Bundled Assets |\n| ---- | ----------- | ------ | -------------- |\n";
|
||||
|
||||
// Generate table rows for each hook
|
||||
for (const hook of hookEntries) {
|
||||
const link = `../hooks/${hook.folder}/README.md`;
|
||||
const events = hook.hooks.length > 0 ? hook.hooks.join(", ") : "N/A";
|
||||
const assetsList =
|
||||
hook.assets.length > 0
|
||||
? hook.assets.map((a) => `\`${a}\``).join("<br />")
|
||||
: "None";
|
||||
|
||||
content += `| [${hook.name}](${link}) | ${formatTableCell(
|
||||
hook.description
|
||||
)} | ${events} | ${assetsList} |\n`;
|
||||
}
|
||||
|
||||
return `${TEMPLATES.hooksSection}\n${TEMPLATES.hooksUsage}\n\n${content}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the skills section with a table of all skills
|
||||
*/
|
||||
@@ -1002,6 +1065,7 @@ async function main() {
|
||||
);
|
||||
const promptsHeader = TEMPLATES.promptsSection.replace(/^##\s/m, "# ");
|
||||
const agentsHeader = TEMPLATES.agentsSection.replace(/^##\s/m, "# ");
|
||||
const hooksHeader = TEMPLATES.hooksSection.replace(/^##\s/m, "# ");
|
||||
const skillsHeader = TEMPLATES.skillsSection.replace(/^##\s/m, "# ");
|
||||
const collectionsHeader = TEMPLATES.collectionsSection.replace(
|
||||
/^##\s/m,
|
||||
@@ -1031,6 +1095,15 @@ async function main() {
|
||||
registryNames
|
||||
);
|
||||
|
||||
// Generate hooks README
|
||||
const hooksReadme = buildCategoryReadme(
|
||||
generateHooksSection,
|
||||
HOOKS_DIR,
|
||||
hooksHeader,
|
||||
TEMPLATES.hooksUsage,
|
||||
registryNames
|
||||
);
|
||||
|
||||
// Generate skills README
|
||||
const skillsReadme = buildCategoryReadme(
|
||||
generateSkillsSection,
|
||||
@@ -1061,6 +1134,7 @@ async function main() {
|
||||
);
|
||||
writeFileIfChanged(path.join(DOCS_DIR, "README.prompts.md"), promptsReadme);
|
||||
writeFileIfChanged(path.join(DOCS_DIR, "README.agents.md"), agentsReadme);
|
||||
writeFileIfChanged(path.join(DOCS_DIR, "README.hooks.md"), hooksReadme);
|
||||
writeFileIfChanged(path.join(DOCS_DIR, "README.skills.md"), skillsReadme);
|
||||
writeFileIfChanged(
|
||||
path.join(DOCS_DIR, "README.collections.md"),
|
||||
|
||||
@@ -155,6 +155,41 @@ function validateAgentFile(filePath) {
|
||||
}
|
||||
}
|
||||
|
||||
function validateHookFile(filePath) {
|
||||
try {
|
||||
const hook = parseFrontmatter(filePath);
|
||||
|
||||
if (!hook) {
|
||||
return `Item ${filePath} hook file could not be parsed`;
|
||||
}
|
||||
|
||||
// Validate name field
|
||||
if (!hook.name || typeof hook.name !== "string") {
|
||||
return `Item ${filePath} hook must have a 'name' field`;
|
||||
}
|
||||
if (hook.name.length < 1 || hook.name.length > 50) {
|
||||
return `Item ${filePath} hook name must be between 1 and 50 characters`;
|
||||
}
|
||||
|
||||
// Validate description field
|
||||
if (!hook.description || typeof hook.description !== "string") {
|
||||
return `Item ${filePath} hook must have a 'description' field`;
|
||||
}
|
||||
if (hook.description.length < 1 || hook.description.length > 500) {
|
||||
return `Item ${filePath} hook description must be between 1 and 500 characters`;
|
||||
}
|
||||
|
||||
// Validate event field (optional but recommended)
|
||||
if (hook.event !== undefined && typeof hook.event !== "string") {
|
||||
return `Item ${filePath} hook 'event' must be a string`;
|
||||
}
|
||||
|
||||
return null; // All validations passed
|
||||
} catch (error) {
|
||||
return `Item ${filePath} hook file validation failed: ${error.message}`;
|
||||
}
|
||||
}
|
||||
|
||||
function validateCollectionItems(items) {
|
||||
if (!items || !Array.isArray(items)) {
|
||||
return "Items is required and must be an array";
|
||||
@@ -177,10 +212,10 @@ function validateCollectionItems(items) {
|
||||
if (!item.kind || typeof item.kind !== "string") {
|
||||
return `Item ${i + 1} must have a kind string`;
|
||||
}
|
||||
if (!["prompt", "instruction", "agent", "skill"].includes(item.kind)) {
|
||||
if (!["prompt", "instruction", "agent", "skill", "hook"].includes(item.kind)) {
|
||||
return `Item ${
|
||||
i + 1
|
||||
} kind must be one of: prompt, instruction, agent, skill`;
|
||||
} kind must be one of: prompt, instruction, agent, skill, hook`;
|
||||
}
|
||||
|
||||
// Validate file path exists
|
||||
@@ -208,6 +243,11 @@ function validateCollectionItems(items) {
|
||||
i + 1
|
||||
} kind is "agent" but path doesn't end with .agent.md`;
|
||||
}
|
||||
if (item.kind === "hook" && !item.path.endsWith(".hook.md")) {
|
||||
return `Item ${
|
||||
i + 1
|
||||
} kind is "hook" but path doesn't end with .hook.md`;
|
||||
}
|
||||
|
||||
// Validate agent-specific frontmatter
|
||||
if (item.kind === "agent") {
|
||||
@@ -216,6 +256,14 @@ function validateCollectionItems(items) {
|
||||
return agentValidation;
|
||||
}
|
||||
}
|
||||
|
||||
// Validate hook-specific frontmatter
|
||||
if (item.kind === "hook") {
|
||||
const hookValidation = validateHookFile(filePath, i + 1);
|
||||
if (hookValidation) {
|
||||
return hookValidation;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// YAML parser for collection files and frontmatter parsing using vfile-matter
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import yaml from "js-yaml";
|
||||
import path from "path";
|
||||
import { VFile } from "vfile";
|
||||
import { matter } from "vfile-matter";
|
||||
|
||||
@@ -173,7 +173,7 @@ function parseSkillMetadata(skillPath) {
|
||||
const relativePath = path.relative(skillPath, filePath);
|
||||
if (relativePath !== "SKILL.md") {
|
||||
// Normalize path separators to forward slashes for cross-platform consistency
|
||||
arrayOfFiles.push(relativePath.replace(/\\/g, '/'));
|
||||
arrayOfFiles.push(relativePath.replace(/\\/g, "/"));
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -195,6 +195,83 @@ function parseSkillMetadata(skillPath) {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse hook metadata from a hook folder (similar to skills)
|
||||
* @param {string} hookPath - Path to the hook folder
|
||||
* @returns {object|null} Hook metadata or null on error
|
||||
*/
|
||||
function parseHookMetadata(hookPath) {
|
||||
return safeFileOperation(
|
||||
() => {
|
||||
const readmeFile = path.join(hookPath, "README.md");
|
||||
if (!fs.existsSync(readmeFile)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const frontmatter = parseFrontmatter(readmeFile);
|
||||
|
||||
// Validate required fields
|
||||
if (!frontmatter?.name || !frontmatter?.description) {
|
||||
console.warn(
|
||||
`Invalid hook at ${hookPath}: missing name or description in frontmatter`
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
// Extract hook events from hooks.json if it exists
|
||||
let hookEvents = [];
|
||||
const hooksJsonPath = path.join(hookPath, "hooks.json");
|
||||
if (fs.existsSync(hooksJsonPath)) {
|
||||
try {
|
||||
const hooksJsonContent = fs.readFileSync(hooksJsonPath, "utf8");
|
||||
const hooksConfig = JSON.parse(hooksJsonContent);
|
||||
// Extract all hook event names from the hooks object
|
||||
if (hooksConfig.hooks && typeof hooksConfig.hooks === "object") {
|
||||
hookEvents = Object.keys(hooksConfig.hooks);
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn(
|
||||
`Failed to parse hooks.json at ${hookPath}: ${error.message}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// List bundled assets (all files except README.md), recursing through subdirectories
|
||||
const getAllFiles = (dirPath, arrayOfFiles = []) => {
|
||||
const files = fs.readdirSync(dirPath);
|
||||
|
||||
files.forEach((file) => {
|
||||
const filePath = path.join(dirPath, file);
|
||||
if (fs.statSync(filePath).isDirectory()) {
|
||||
arrayOfFiles = getAllFiles(filePath, arrayOfFiles);
|
||||
} else {
|
||||
const relativePath = path.relative(hookPath, filePath);
|
||||
if (relativePath !== "README.md") {
|
||||
// Normalize path separators to forward slashes for cross-platform consistency
|
||||
arrayOfFiles.push(relativePath.replace(/\\/g, "/"));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return arrayOfFiles;
|
||||
};
|
||||
|
||||
const assets = getAllFiles(hookPath).sort();
|
||||
|
||||
return {
|
||||
name: frontmatter.name,
|
||||
description: frontmatter.description,
|
||||
hooks: hookEvents,
|
||||
tags: frontmatter.tags || [],
|
||||
assets,
|
||||
path: hookPath,
|
||||
};
|
||||
},
|
||||
hookPath,
|
||||
null
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a generic YAML file (used for tools.yml and other config files)
|
||||
* @param {string} filePath - Path to the YAML file
|
||||
@@ -212,12 +289,13 @@ function parseYamlFile(filePath) {
|
||||
}
|
||||
|
||||
export {
|
||||
extractAgentMetadata,
|
||||
extractMcpServerConfigs,
|
||||
extractMcpServers,
|
||||
parseCollectionYaml,
|
||||
parseFrontmatter,
|
||||
extractAgentMetadata,
|
||||
extractMcpServers,
|
||||
extractMcpServerConfigs,
|
||||
parseSkillMetadata,
|
||||
parseHookMetadata,
|
||||
parseYamlFile,
|
||||
safeFileOperation,
|
||||
};
|
||||
|
||||
90
hooks/session-auto-commit/README.md
Normal file
90
hooks/session-auto-commit/README.md
Normal file
@@ -0,0 +1,90 @@
|
||||
---
|
||||
name: 'Session Auto-Commit'
|
||||
description: 'Automatically commits and pushes changes when a Copilot coding agent session ends'
|
||||
tags: ['automation', 'git', 'productivity']
|
||||
---
|
||||
|
||||
# Session Auto-Commit Hook
|
||||
|
||||
Automatically commits and pushes changes when a GitHub Copilot coding agent session ends, ensuring your work is always saved and backed up.
|
||||
|
||||
## Overview
|
||||
|
||||
This hook runs at the end of each Copilot coding agent session and automatically:
|
||||
- Detects if there are uncommitted changes
|
||||
- Stages all changes
|
||||
- Creates a timestamped commit
|
||||
- Pushes to the remote repository
|
||||
|
||||
## Features
|
||||
|
||||
- **Automatic Backup**: Never lose work from a Copilot session
|
||||
- **Timestamped Commits**: Each auto-commit includes the session end time
|
||||
- **Safe Execution**: Only commits when there are actual changes
|
||||
- **Error Handling**: Gracefully handles push failures
|
||||
|
||||
## Installation
|
||||
|
||||
1. Copy this hook folder to your repository's `.github/hooks/` directory:
|
||||
```bash
|
||||
cp -r hooks/session-auto-commit .github/hooks/
|
||||
```
|
||||
|
||||
2. Ensure the script is executable:
|
||||
```bash
|
||||
chmod +x .github/hooks/session-auto-commit/auto-commit.sh
|
||||
```
|
||||
|
||||
3. Commit the hook configuration to your repository's default branch
|
||||
|
||||
## Configuration
|
||||
|
||||
The hook is configured in `hooks.json` to run on the `sessionEnd` event:
|
||||
|
||||
```json
|
||||
{
|
||||
"version": 1,
|
||||
"hooks": {
|
||||
"sessionEnd": [
|
||||
{
|
||||
"type": "command",
|
||||
"bash": ".github/hooks/session-auto-commit/auto-commit.sh",
|
||||
"timeoutSec": 30
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## How It Works
|
||||
|
||||
1. When a Copilot coding agent session ends, the hook executes
|
||||
2. Checks if inside a Git repository
|
||||
3. Detects uncommitted changes using `git status`
|
||||
4. Stages all changes with `git add -A`
|
||||
5. Creates a commit with format: `auto-commit: YYYY-MM-DD HH:MM:SS`
|
||||
6. Attempts to push to remote
|
||||
7. Reports success or failure
|
||||
|
||||
## Customization
|
||||
|
||||
You can customize the hook by modifying `auto-commit.sh`:
|
||||
|
||||
- **Commit Message Format**: Change the timestamp format or message prefix
|
||||
- **Selective Staging**: Use specific git add patterns instead of `-A`
|
||||
- **Branch Selection**: Push to specific branches only
|
||||
- **Notifications**: Add desktop notifications or Slack messages
|
||||
|
||||
## Disabling
|
||||
|
||||
To temporarily disable auto-commits:
|
||||
|
||||
1. Remove or comment out the `sessionEnd` hook in `hooks.json`
|
||||
2. Or set an environment variable: `export SKIP_AUTO_COMMIT=true`
|
||||
|
||||
## Notes
|
||||
|
||||
- The hook uses `--no-verify` to avoid triggering pre-commit hooks
|
||||
- Failed pushes won't block session termination
|
||||
- Requires appropriate git credentials configured
|
||||
- Works with both Copilot coding agent and GitHub Copilot CLI
|
||||
45
hooks/session-auto-commit/auto-commit.sh
Executable file
45
hooks/session-auto-commit/auto-commit.sh
Executable file
@@ -0,0 +1,45 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Session Auto-Commit Hook
|
||||
# Automatically commits and pushes changes when a Copilot session ends
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Check if SKIP_AUTO_COMMIT is set
|
||||
if [[ "${SKIP_AUTO_COMMIT:-}" == "true" ]]; then
|
||||
echo "⏭️ Auto-commit skipped (SKIP_AUTO_COMMIT=true)"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Check if we're in a git repository
|
||||
if ! git rev-parse --is-inside-work-tree &>/dev/null; then
|
||||
echo "⚠️ Not in a git repository"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Check for uncommitted changes
|
||||
if [[ -z "$(git status --porcelain)" ]]; then
|
||||
echo "✨ No changes to commit"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "📦 Auto-committing changes from Copilot session..."
|
||||
|
||||
# Stage all changes
|
||||
git add -A
|
||||
|
||||
# Create timestamped commit
|
||||
TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S')
|
||||
git commit -m "auto-commit: $TIMESTAMP" --no-verify 2>/dev/null || {
|
||||
echo "⚠️ Commit failed"
|
||||
exit 0
|
||||
}
|
||||
|
||||
# Attempt to push
|
||||
if git push 2>/dev/null; then
|
||||
echo "✅ Changes committed and pushed successfully"
|
||||
else
|
||||
echo "⚠️ Push failed - changes committed locally"
|
||||
fi
|
||||
|
||||
exit 0
|
||||
12
hooks/session-auto-commit/hooks.json
Normal file
12
hooks/session-auto-commit/hooks.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"version": 1,
|
||||
"hooks": {
|
||||
"sessionEnd": [
|
||||
{
|
||||
"type": "command",
|
||||
"bash": ".github/hooks/session-auto-commit/auto-commit.sh",
|
||||
"timeoutSec": 30
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
64
hooks/session-logger/README.md
Normal file
64
hooks/session-logger/README.md
Normal file
@@ -0,0 +1,64 @@
|
||||
---
|
||||
name: 'Session Logger'
|
||||
description: 'Logs all Copilot coding agent session activity for audit and analysis'
|
||||
tags: ['logging', 'audit', 'analytics']
|
||||
---
|
||||
|
||||
# Session Logger Hook
|
||||
|
||||
Comprehensive logging for GitHub Copilot coding agent sessions, tracking session starts, ends, and user prompts for audit trails and usage analytics.
|
||||
|
||||
## Overview
|
||||
|
||||
This hook provides detailed logging of Copilot coding agent activity:
|
||||
- Session start/end times
|
||||
- User prompts and questions
|
||||
- Session duration
|
||||
- Working directory context
|
||||
|
||||
## Features
|
||||
|
||||
- **Complete Audit Trail**: Track all Copilot interactions
|
||||
- **Structured Logging**: JSON format for easy parsing
|
||||
- **Searchable History**: Review past sessions and prompts
|
||||
- **Analytics Ready**: Export data for usage analysis
|
||||
- **Privacy Aware**: Configurable to exclude sensitive data
|
||||
|
||||
## Installation
|
||||
|
||||
1. Copy this hook folder to your repository's `.github/hooks/` directory:
|
||||
```bash
|
||||
cp -r hooks/session-logger .github/hooks/
|
||||
```
|
||||
|
||||
2. Create the logs directory:
|
||||
```bash
|
||||
mkdir -p logs/copilot
|
||||
```
|
||||
|
||||
3. Ensure scripts are executable:
|
||||
```bash
|
||||
chmod +x .github/hooks/session-logger/*.sh
|
||||
```
|
||||
|
||||
4. Commit the hook configuration to your repository's default branch
|
||||
|
||||
## Log Format
|
||||
|
||||
Logs are written to `logs/copilot/session.log` in JSON format:
|
||||
|
||||
```json
|
||||
{
|
||||
"timestamp": "2024-01-15T10:30:00Z",
|
||||
"event": "sessionStart",
|
||||
"sessionId": "abc123",
|
||||
"cwd": "/workspace/project"
|
||||
}
|
||||
```
|
||||
|
||||
## Privacy & Security
|
||||
|
||||
- Add `logs/` to `.gitignore` to avoid committing session data
|
||||
- Use `LOG_LEVEL=ERROR` to only log errors
|
||||
- Set `SKIP_LOGGING=true` environment variable to disable
|
||||
- Logs are stored locally only
|
||||
32
hooks/session-logger/hooks.json
Normal file
32
hooks/session-logger/hooks.json
Normal file
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"version": 1,
|
||||
"hooks": {
|
||||
"sessionStart": [
|
||||
{
|
||||
"type": "command",
|
||||
"bash": ".github/hooks/session-logger/log-session-start.sh",
|
||||
"cwd": ".",
|
||||
"timeoutSec": 5
|
||||
}
|
||||
],
|
||||
"sessionEnd": [
|
||||
{
|
||||
"type": "command",
|
||||
"bash": ".github/hooks/session-logger/log-session-end.sh",
|
||||
"cwd": ".",
|
||||
"timeoutSec": 5
|
||||
}
|
||||
],
|
||||
"userPromptSubmitted": [
|
||||
{
|
||||
"type": "command",
|
||||
"bash": ".github/hooks/session-logger/log-prompt.sh",
|
||||
"cwd": ".",
|
||||
"env": {
|
||||
"LOG_LEVEL": "INFO"
|
||||
},
|
||||
"timeoutSec": 5
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
24
hooks/session-logger/log-prompt.sh
Executable file
24
hooks/session-logger/log-prompt.sh
Executable file
@@ -0,0 +1,24 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Log user prompt submission
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Skip if logging disabled
|
||||
if [[ "${SKIP_LOGGING:-}" == "true" ]]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Read input from Copilot (contains prompt info)
|
||||
INPUT=$(cat)
|
||||
|
||||
# Create logs directory if it doesn't exist
|
||||
mkdir -p logs/copilot
|
||||
|
||||
# Extract timestamp
|
||||
TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
||||
|
||||
# Log prompt (you can parse INPUT for more details)
|
||||
echo "{\"timestamp\":\"$TIMESTAMP\",\"event\":\"userPromptSubmitted\",\"level\":\"${LOG_LEVEL:-INFO}\"}" >> logs/copilot/prompts.log
|
||||
|
||||
exit 0
|
||||
25
hooks/session-logger/log-session-end.sh
Executable file
25
hooks/session-logger/log-session-end.sh
Executable file
@@ -0,0 +1,25 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Log session end event
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Skip if logging disabled
|
||||
if [[ "${SKIP_LOGGING:-}" == "true" ]]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Read input from Copilot
|
||||
INPUT=$(cat)
|
||||
|
||||
# Create logs directory if it doesn't exist
|
||||
mkdir -p logs/copilot
|
||||
|
||||
# Extract timestamp
|
||||
TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
||||
|
||||
# Log session end
|
||||
echo "{\"timestamp\":\"$TIMESTAMP\",\"event\":\"sessionEnd\"}" >> logs/copilot/session.log
|
||||
|
||||
echo "📝 Session end logged"
|
||||
exit 0
|
||||
26
hooks/session-logger/log-session-start.sh
Executable file
26
hooks/session-logger/log-session-start.sh
Executable file
@@ -0,0 +1,26 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Log session start event
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Skip if logging disabled
|
||||
if [[ "${SKIP_LOGGING:-}" == "true" ]]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Read input from Copilot
|
||||
INPUT=$(cat)
|
||||
|
||||
# Create logs directory if it doesn't exist
|
||||
mkdir -p logs/copilot
|
||||
|
||||
# Extract timestamp and session info
|
||||
TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
||||
CWD=$(pwd)
|
||||
|
||||
# Log session start
|
||||
echo "{\"timestamp\":\"$TIMESTAMP\",\"event\":\"sessionStart\",\"cwd\":\"$CWD\"}" >> logs/copilot/session.log
|
||||
|
||||
echo "📝 Session logged"
|
||||
exit 0
|
||||
Reference in New Issue
Block a user