copilot-agent 0.10.0 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -19,6 +19,8 @@ Autonomous AI agent manager — auto-resume sessions, discover tasks, run overni
19
19
  | **`diff`** | Show git changes made by an agent session |
20
20
  | **`quota`** | Track premium requests, tokens, and usage over time |
21
21
  | **`compact`** | Generate context summary for session handoff/resume |
22
+ | **`hooks`** | Event-driven automation (on_task_complete, on_error, etc.) |
23
+ | **`pr`** | Auto-create GitHub Pull Request from session changes |
22
24
 
23
25
  All commands support `--agent copilot` or `--agent claude` (auto-detects if omitted).
24
26
 
@@ -165,6 +167,43 @@ copilot-agent compact --save
165
167
  copilot-agent compact --resume-prompt
166
168
  ```
167
169
 
170
+ ### Hooks (event-driven automation)
171
+
172
+ ```bash
173
+ # Show configured hooks
174
+ copilot-agent hooks list
175
+
176
+ # Test-run hooks for an event
177
+ copilot-agent hooks test on_task_complete
178
+ ```
179
+
180
+ Create `~/.copilot-agent/hooks.yaml` or `.copilot-agent/hooks.yaml`:
181
+
182
+ ```yaml
183
+ on_task_complete:
184
+ - command: "npm test"
185
+ name: "Run tests"
186
+ on_session_end:
187
+ - command: "git push origin HEAD"
188
+ name: "Auto-push"
189
+ on_error:
190
+ - command: "curl -X POST $SLACK_WEBHOOK -d '{\"text\":\"Agent error!\"}'"
191
+ name: "Notify Slack"
192
+ ```
193
+
194
+ ### Auto-create Pull Request
195
+
196
+ ```bash
197
+ # Create PR from latest session
198
+ copilot-agent pr
199
+
200
+ # Dry-run (preview without creating)
201
+ copilot-agent pr --dry-run
202
+
203
+ # Create ready (non-draft) PR
204
+ copilot-agent pr --no-draft
205
+ ```
206
+
168
207
  ## How it works
169
208
 
170
209
  1. **Agent abstraction** — Unified interface for both Copilot CLI and Claude Code
package/dist/index.js CHANGED
@@ -3134,9 +3134,283 @@ function showCompact(sessionId, opts) {
3134
3134
  }
3135
3135
  }
3136
3136
 
3137
+ // src/commands/hooks.ts
3138
+ import chalk6 from "chalk";
3139
+
3140
+ // src/lib/hooks.ts
3141
+ import { readFileSync as readFileSync6, existsSync as existsSync9 } from "fs";
3142
+ import { join as join11 } from "path";
3143
+ import { homedir as homedir9 } from "os";
3144
+ import { parse as parseYaml2 } from "yaml";
3145
+ import { findUpSync as findUpSync2 } from "find-up";
3146
+ import { execaCommand } from "execa";
3147
+ var GLOBAL_HOOKS = join11(homedir9(), ".copilot-agent", "hooks.yaml");
3148
+ var PROJECT_HOOKS = ".copilot-agent/hooks.yaml";
3149
+ function loadHooksConfig(cwd) {
3150
+ const configs = [];
3151
+ if (existsSync9(GLOBAL_HOOKS)) {
3152
+ try {
3153
+ const parsed = parseYaml2(readFileSync6(GLOBAL_HOOKS, "utf-8"));
3154
+ if (parsed) configs.push(parsed);
3155
+ } catch {
3156
+ }
3157
+ }
3158
+ const projectPath = findUpSync2(PROJECT_HOOKS, { cwd: cwd || process.cwd(), type: "file" });
3159
+ if (projectPath) {
3160
+ try {
3161
+ const parsed = parseYaml2(readFileSync6(projectPath, "utf-8"));
3162
+ if (parsed) configs.push(parsed);
3163
+ } catch {
3164
+ }
3165
+ }
3166
+ const merged = {};
3167
+ for (const cfg of configs) {
3168
+ for (const event of ["on_session_start", "on_task_complete", "on_session_end", "on_error", "on_resume"]) {
3169
+ const hooks = cfg[event];
3170
+ if (hooks && Array.isArray(hooks)) {
3171
+ if (!merged[event]) merged[event] = [];
3172
+ merged[event].push(...hooks);
3173
+ }
3174
+ }
3175
+ }
3176
+ return merged;
3177
+ }
3178
+ async function runHooks(event, cwd, env) {
3179
+ const config = loadHooksConfig(cwd);
3180
+ const hooks = config[event];
3181
+ if (!hooks || hooks.length === 0) return [];
3182
+ const results = [];
3183
+ for (const hook of hooks) {
3184
+ const start = Date.now();
3185
+ try {
3186
+ const result = await execaCommand(hook.command, {
3187
+ cwd: cwd || process.cwd(),
3188
+ timeout: (hook.timeout || 30) * 1e3,
3189
+ env: { ...process.env, ...env },
3190
+ reject: false
3191
+ });
3192
+ results.push({
3193
+ hook,
3194
+ event,
3195
+ success: result.exitCode === 0,
3196
+ output: result.stdout?.slice(0, 500),
3197
+ error: result.exitCode !== 0 ? result.stderr?.slice(0, 500) : void 0,
3198
+ durationMs: Date.now() - start
3199
+ });
3200
+ } catch (err) {
3201
+ results.push({
3202
+ hook,
3203
+ event,
3204
+ success: false,
3205
+ error: err instanceof Error ? err.message : String(err),
3206
+ durationMs: Date.now() - start
3207
+ });
3208
+ }
3209
+ }
3210
+ return results;
3211
+ }
3212
+ function getHooksSummary(config) {
3213
+ const events = ["on_session_start", "on_task_complete", "on_session_end", "on_error", "on_resume"];
3214
+ return events.map((e) => ({ event: e, count: config[e]?.length || 0 })).filter((e) => e.count > 0);
3215
+ }
3216
+
3217
+ // src/commands/hooks.ts
3218
+ function registerHooksCommand(program2) {
3219
+ const cmd = program2.command("hooks").description("Manage event-driven automation hooks");
3220
+ cmd.command("list").description("Show all configured hooks").action(() => {
3221
+ const config = loadHooksConfig();
3222
+ const summary = getHooksSummary(config);
3223
+ console.log(chalk6.bold.cyan("\n \u26A1 Hooks Configuration\n"));
3224
+ if (summary.length === 0) {
3225
+ console.log(chalk6.dim(" No hooks configured"));
3226
+ console.log(chalk6.dim("\n Create ~/.copilot-agent/hooks.yaml or .copilot-agent/hooks.yaml:"));
3227
+ console.log(chalk6.dim(" on_task_complete:"));
3228
+ console.log(chalk6.dim(' - command: "npm test"'));
3229
+ console.log(chalk6.dim(' name: "Run tests"'));
3230
+ console.log();
3231
+ return;
3232
+ }
3233
+ const events = ["on_session_start", "on_task_complete", "on_session_end", "on_error", "on_resume"];
3234
+ for (const event of events) {
3235
+ const hooks = config[event];
3236
+ if (!hooks || hooks.length === 0) continue;
3237
+ console.log(chalk6.bold(` ${event}`) + chalk6.dim(` (${hooks.length})`));
3238
+ for (const h of hooks) {
3239
+ const name = h.name ? chalk6.white(h.name) : chalk6.dim("unnamed");
3240
+ const timeout = h.timeout ? chalk6.dim(` (${h.timeout}s)`) : "";
3241
+ console.log(` ${chalk6.green("\u25CF")} ${name}: ${chalk6.cyan(h.command)}${timeout}`);
3242
+ }
3243
+ console.log();
3244
+ }
3245
+ });
3246
+ cmd.command("test <event>").description("Test-run hooks for a specific event").action(async (event) => {
3247
+ const validEvents = ["on_session_start", "on_task_complete", "on_session_end", "on_error", "on_resume"];
3248
+ if (!validEvents.includes(event)) {
3249
+ console.log(chalk6.red(` \u2717 Invalid event: ${event}`));
3250
+ console.log(chalk6.dim(` Valid events: ${validEvents.join(", ")}`));
3251
+ return;
3252
+ }
3253
+ console.log(chalk6.cyan(`
3254
+ Running hooks for ${chalk6.bold(event)}...
3255
+ `));
3256
+ const results = await runHooks(event);
3257
+ if (results.length === 0) {
3258
+ console.log(chalk6.dim(` No hooks configured for ${event}`));
3259
+ return;
3260
+ }
3261
+ for (const r of results) {
3262
+ const icon = r.success ? chalk6.green("\u2714") : chalk6.red("\u2717");
3263
+ const name = r.hook.name || r.hook.command;
3264
+ const time = chalk6.dim(`${r.durationMs}ms`);
3265
+ console.log(` ${icon} ${name} ${time}`);
3266
+ if (r.output) console.log(chalk6.dim(` ${r.output.split("\n")[0]}`));
3267
+ if (r.error) console.log(chalk6.red(` ${r.error.split("\n")[0]}`));
3268
+ }
3269
+ console.log();
3270
+ });
3271
+ }
3272
+
3273
+ // src/commands/pr.ts
3274
+ import chalk7 from "chalk";
3275
+ import { execaCommandSync as execaCommandSync2 } from "execa";
3276
+ function registerPrCommand(program2) {
3277
+ program2.command("pr [session-id]").description("Create a GitHub Pull Request from agent session changes").option("--draft", "Create as draft PR (default: true)", true).option("--no-draft", "Create as ready PR").option("-b, --base <branch>", "Base branch (default: main)").option("-a, --agent <type>", "Agent type: copilot | claude").option("--dry-run", "Show PR details without creating").action((sessionId, opts) => {
3278
+ try {
3279
+ createPr(sessionId, opts);
3280
+ } catch (err) {
3281
+ const msg = err instanceof Error ? err.message : String(err);
3282
+ console.error(chalk7.red(` \u2717 ${msg}`));
3283
+ }
3284
+ });
3285
+ }
3286
+ function createPr(sessionId, opts) {
3287
+ const sessions = listAllSessions(50);
3288
+ let targetSession;
3289
+ if (sessionId) {
3290
+ targetSession = sessions.find((s) => s.id.startsWith(sessionId));
3291
+ } else {
3292
+ targetSession = sessions[0];
3293
+ if (targetSession) {
3294
+ console.log(chalk7.dim(` Using latest session: ${targetSession.id.slice(0, 12)}\u2026
3295
+ `));
3296
+ }
3297
+ }
3298
+ if (!targetSession) {
3299
+ console.log(chalk7.red(" \u2717 No session found"));
3300
+ return;
3301
+ }
3302
+ const agentType = opts.agent || targetSession.agent;
3303
+ const report = getAgentSessionReport(targetSession.id, agentType);
3304
+ if (!report) {
3305
+ console.log(chalk7.red(" \u2717 Could not load session report"));
3306
+ return;
3307
+ }
3308
+ const cwd = report.cwd;
3309
+ if (!cwd || !isGitRepo(cwd)) {
3310
+ console.log(chalk7.red(" \u2717 Not a git repository"));
3311
+ return;
3312
+ }
3313
+ const currentBranch = gitCurrentBranch(cwd);
3314
+ const baseBranch = opts.base || "main";
3315
+ if (!currentBranch || currentBranch === baseBranch) {
3316
+ console.log(chalk7.yellow(` \u26A0 Currently on ${baseBranch} \u2014 switch to a feature branch first`));
3317
+ return;
3318
+ }
3319
+ const agentName = report.agent === "claude" ? "Claude" : "Copilot";
3320
+ const title = report.summary ? report.summary.slice(0, 72) : `[${agentName}] ${currentBranch.replace("agent/", "").replace(/-/g, " ").slice(0, 60)}`;
3321
+ const bodyParts = [];
3322
+ bodyParts.push(`## \u{1F916} Auto-generated by copilot-agent (${report.agent})
3323
+ `);
3324
+ if (report.summary) {
3325
+ bodyParts.push(`**Task:** ${report.summary}
3326
+ `);
3327
+ }
3328
+ bodyParts.push("### Stats");
3329
+ bodyParts.push(`- **Duration:** ${formatDur(report.durationMs)}`);
3330
+ bodyParts.push(`- **Turns:** ${report.assistantTurns}`);
3331
+ bodyParts.push(`- **Tokens:** ${report.outputTokens.toLocaleString()}`);
3332
+ bodyParts.push(`- **Premium requests:** ${report.premiumRequests}`);
3333
+ bodyParts.push("");
3334
+ if (report.gitCommits.length > 0) {
3335
+ bodyParts.push("### Commits");
3336
+ for (const c of report.gitCommits.slice(0, 20)) {
3337
+ bodyParts.push(`- ${c.split("\n")[0]}`);
3338
+ }
3339
+ bodyParts.push("");
3340
+ }
3341
+ if (report.filesCreated.length > 0 || report.filesEdited.length > 0) {
3342
+ bodyParts.push("### Files Changed");
3343
+ for (const f of report.filesCreated.slice(0, 15)) bodyParts.push(`- \u2795 ${f}`);
3344
+ for (const f of report.filesEdited.slice(0, 15)) bodyParts.push(`- \u270F\uFE0F ${f}`);
3345
+ bodyParts.push("");
3346
+ }
3347
+ if (report.taskCompletions.length > 0) {
3348
+ bodyParts.push("### Tasks Completed");
3349
+ for (const t of report.taskCompletions.slice(0, 10)) {
3350
+ bodyParts.push(`- \u2705 ${t.split("\n")[0]}`);
3351
+ }
3352
+ bodyParts.push("");
3353
+ }
3354
+ bodyParts.push(`---
3355
+ *Session: \`${report.id.slice(0, 12)}\u2026\`*`);
3356
+ const body = bodyParts.join("\n");
3357
+ console.log(chalk7.bold.cyan(" \u{1F4DD} Pull Request Preview\n"));
3358
+ console.log(` ${chalk7.bold("Title:")} ${title}`);
3359
+ console.log(` ${chalk7.bold("Branch:")} ${chalk7.cyan(currentBranch)} \u2192 ${chalk7.green(baseBranch)}`);
3360
+ console.log(` ${chalk7.bold("Draft:")} ${opts.draft ? "yes" : "no"}`);
3361
+ console.log(` ${chalk7.bold("Agent:")} ${report.agent}`);
3362
+ console.log(` ${chalk7.bold("Files:")} ${chalk7.green(`+${report.filesCreated.length}`)} created, ${chalk7.yellow(`~${report.filesEdited.length}`)} edited`);
3363
+ console.log(` ${chalk7.bold("Commits:")} ${report.gitCommits.length}`);
3364
+ console.log();
3365
+ if (opts.dryRun) {
3366
+ console.log(chalk7.dim(" (dry run \u2014 PR not created)"));
3367
+ console.log(chalk7.dim("\n Body preview:"));
3368
+ console.log(chalk7.dim(body.split("\n").map((l) => ` ${l}`).join("\n")));
3369
+ return;
3370
+ }
3371
+ try {
3372
+ console.log(chalk7.dim(" Pushing branch..."));
3373
+ execaCommandSync2(`git push -u origin ${currentBranch}`, { cwd });
3374
+ } catch {
3375
+ }
3376
+ try {
3377
+ const ghArgs = [
3378
+ "gh",
3379
+ "pr",
3380
+ "create",
3381
+ "--title",
3382
+ title,
3383
+ "--body",
3384
+ body,
3385
+ "--base",
3386
+ baseBranch,
3387
+ "--label",
3388
+ "automated"
3389
+ ];
3390
+ if (opts.draft) ghArgs.push("--draft");
3391
+ const result = execaCommandSync2(
3392
+ ghArgs.map((a) => a.includes(" ") ? `"${a}"` : a).join(" "),
3393
+ { cwd }
3394
+ );
3395
+ const prUrl = result.stdout.trim();
3396
+ console.log(chalk7.green(` \u2714 PR created: ${prUrl}`));
3397
+ } catch (err) {
3398
+ const msg = err instanceof Error ? err.stderr || err.message : String(err);
3399
+ console.log(chalk7.red(` \u2717 Failed to create PR: ${msg}`));
3400
+ console.log(chalk7.dim(" Make sure gh CLI is installed and authenticated"));
3401
+ }
3402
+ }
3403
+ function formatDur(ms) {
3404
+ if (ms < 6e4) return `${Math.round(ms / 1e3)}s`;
3405
+ if (ms < 36e5) return `${Math.round(ms / 6e4)}m`;
3406
+ const h = Math.floor(ms / 36e5);
3407
+ const m = Math.round(ms % 36e5 / 6e4);
3408
+ return `${h}h ${m}m`;
3409
+ }
3410
+
3137
3411
  // src/index.ts
3138
3412
  var program = new Command();
3139
- program.name("copilot-agent").version("0.10.0").description("Autonomous AI agent manager \u2014 auto-resume, task discovery, overnight runs. Supports GitHub Copilot CLI + Claude Code.");
3413
+ program.name("copilot-agent").version("0.11.0").description("Autonomous AI agent manager \u2014 auto-resume, task discovery, overnight runs. Supports GitHub Copilot CLI + Claude Code.");
3140
3414
  registerStatusCommand(program);
3141
3415
  registerWatchCommand(program);
3142
3416
  registerRunCommand(program);
@@ -3150,5 +3424,7 @@ registerProxyCommand(program);
3150
3424
  registerDiffCommand(program);
3151
3425
  registerQuotaCommand(program);
3152
3426
  registerCompactCommand(program);
3427
+ registerHooksCommand(program);
3428
+ registerPrCommand(program);
3153
3429
  program.parse();
3154
3430
  //# sourceMappingURL=index.js.map