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 +39 -0
- package/dist/index.js +277 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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.
|
|
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
|