copilot-agent 0.9.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 +104 -24
- package/dist/index.js +583 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2830,9 +2830,587 @@ function showDiff(sessionId, opts) {
|
|
|
2830
2830
|
console.log();
|
|
2831
2831
|
}
|
|
2832
2832
|
|
|
2833
|
+
// src/commands/quota.ts
|
|
2834
|
+
import chalk4 from "chalk";
|
|
2835
|
+
|
|
2836
|
+
// src/lib/quota.ts
|
|
2837
|
+
import { join as join9 } from "path";
|
|
2838
|
+
import { homedir as homedir7 } from "os";
|
|
2839
|
+
var DATA_DIR = join9(homedir7(), ".copilot-agent");
|
|
2840
|
+
var USAGE_FILE = join9(DATA_DIR, "usage.jsonl");
|
|
2841
|
+
function buildUsageSummary(days) {
|
|
2842
|
+
const sessions = listAllSessions(500);
|
|
2843
|
+
const cutoff = days ? Date.now() - days * 864e5 : 0;
|
|
2844
|
+
const filtered = sessions.filter((s) => s.mtime >= cutoff);
|
|
2845
|
+
const summary = {
|
|
2846
|
+
total: { sessions: 0, premium: 0, tokens: 0, turns: 0, durationMs: 0 },
|
|
2847
|
+
copilot: { sessions: 0, premium: 0, tokens: 0 },
|
|
2848
|
+
claude: { sessions: 0, premium: 0, tokens: 0 },
|
|
2849
|
+
byDay: {}
|
|
2850
|
+
};
|
|
2851
|
+
for (const s of filtered) {
|
|
2852
|
+
const report = getAgentSessionReport(s.id, s.agent);
|
|
2853
|
+
if (!report) continue;
|
|
2854
|
+
summary.total.sessions++;
|
|
2855
|
+
summary.total.premium += report.premiumRequests;
|
|
2856
|
+
summary.total.tokens += report.outputTokens;
|
|
2857
|
+
summary.total.turns += report.assistantTurns;
|
|
2858
|
+
summary.total.durationMs += report.durationMs;
|
|
2859
|
+
const bucket = s.agent === "claude" ? summary.claude : summary.copilot;
|
|
2860
|
+
bucket.sessions++;
|
|
2861
|
+
bucket.premium += report.premiumRequests;
|
|
2862
|
+
bucket.tokens += report.outputTokens;
|
|
2863
|
+
const day = new Date(s.mtime).toISOString().slice(0, 10);
|
|
2864
|
+
if (!summary.byDay[day]) summary.byDay[day] = { premium: 0, tokens: 0, sessions: 0 };
|
|
2865
|
+
summary.byDay[day].premium += report.premiumRequests;
|
|
2866
|
+
summary.byDay[day].tokens += report.outputTokens;
|
|
2867
|
+
summary.byDay[day].sessions++;
|
|
2868
|
+
}
|
|
2869
|
+
return summary;
|
|
2870
|
+
}
|
|
2871
|
+
function formatTokens(n) {
|
|
2872
|
+
if (n >= 1e6) return `${(n / 1e6).toFixed(1)}M`;
|
|
2873
|
+
if (n >= 1e3) return `${(n / 1e3).toFixed(1)}k`;
|
|
2874
|
+
return String(n);
|
|
2875
|
+
}
|
|
2876
|
+
function formatDurationShort(ms) {
|
|
2877
|
+
if (ms < 6e4) return `${Math.round(ms / 1e3)}s`;
|
|
2878
|
+
if (ms < 36e5) return `${Math.round(ms / 6e4)}m`;
|
|
2879
|
+
const h = Math.floor(ms / 36e5);
|
|
2880
|
+
const m = Math.round(ms % 36e5 / 6e4);
|
|
2881
|
+
return `${h}h ${m}m`;
|
|
2882
|
+
}
|
|
2883
|
+
|
|
2884
|
+
// src/commands/quota.ts
|
|
2885
|
+
function registerQuotaCommand(program2) {
|
|
2886
|
+
program2.command("quota").description("Track premium requests, tokens, and usage over time").option("-d, --days <n>", "Number of days to show", "7").option("--all", "Show all-time usage").action((opts) => {
|
|
2887
|
+
const days = opts.all ? void 0 : parseInt(opts.days, 10);
|
|
2888
|
+
const label = days ? `Last ${days} days` : "All time";
|
|
2889
|
+
const summary = buildUsageSummary(days);
|
|
2890
|
+
console.log();
|
|
2891
|
+
console.log(chalk4.bold.cyan(` \u2B21 Usage Summary \u2014 ${label}`));
|
|
2892
|
+
console.log(chalk4.dim(` ${"\u2500".repeat(50)}`));
|
|
2893
|
+
console.log();
|
|
2894
|
+
const t = summary.total;
|
|
2895
|
+
console.log(` ${chalk4.bold("Sessions")} ${chalk4.white(String(t.sessions))}`);
|
|
2896
|
+
console.log(` ${chalk4.bold("Premium")} ${chalk4.yellow("\u2B21 " + String(t.premium))}`);
|
|
2897
|
+
console.log(` ${chalk4.bold("Tokens")} ${chalk4.green(formatTokens(t.tokens))}`);
|
|
2898
|
+
console.log(` ${chalk4.bold("Turns")} ${chalk4.white(String(t.turns))}`);
|
|
2899
|
+
console.log(` ${chalk4.bold("Total time")} ${chalk4.white(formatDurationShort(t.durationMs))}`);
|
|
2900
|
+
console.log();
|
|
2901
|
+
if (summary.copilot.sessions > 0 || summary.claude.sessions > 0) {
|
|
2902
|
+
console.log(chalk4.bold.cyan(" Per Agent"));
|
|
2903
|
+
console.log(chalk4.dim(` ${"\u2500".repeat(50)}`));
|
|
2904
|
+
if (summary.copilot.sessions > 0) {
|
|
2905
|
+
const c = summary.copilot;
|
|
2906
|
+
console.log(` ${chalk4.cyan("copilot")} ${String(c.sessions).padStart(4)} sessions ${chalk4.yellow("\u2B21" + String(c.premium).padStart(5))} ${chalk4.green(formatTokens(c.tokens).padStart(7))} tokens`);
|
|
2907
|
+
}
|
|
2908
|
+
if (summary.claude.sessions > 0) {
|
|
2909
|
+
const c = summary.claude;
|
|
2910
|
+
console.log(` ${chalk4.yellow("claude ")} ${String(c.sessions).padStart(4)} sessions ${chalk4.yellow("\u2B21" + String(c.premium).padStart(5))} ${chalk4.green(formatTokens(c.tokens).padStart(7))} tokens`);
|
|
2911
|
+
}
|
|
2912
|
+
console.log();
|
|
2913
|
+
}
|
|
2914
|
+
const dayEntries = Object.entries(summary.byDay).sort((a, b) => a[0].localeCompare(b[0]));
|
|
2915
|
+
if (dayEntries.length > 0) {
|
|
2916
|
+
console.log(chalk4.bold.cyan(" Daily Usage"));
|
|
2917
|
+
console.log(chalk4.dim(` ${"\u2500".repeat(50)}`));
|
|
2918
|
+
const maxPremium = Math.max(...dayEntries.map(([, d]) => d.premium), 1);
|
|
2919
|
+
const barWidth = 24;
|
|
2920
|
+
for (const [day, data] of dayEntries.slice(-14)) {
|
|
2921
|
+
const shortDay = day.slice(5);
|
|
2922
|
+
const filled = Math.round(data.premium / maxPremium * barWidth);
|
|
2923
|
+
const bar2 = chalk4.cyan("\u2588".repeat(filled)) + chalk4.dim("\u2591".repeat(barWidth - filled));
|
|
2924
|
+
console.log(` ${chalk4.dim(shortDay)} ${bar2} ${chalk4.yellow("\u2B21" + String(data.premium).padStart(4))} ${chalk4.dim(String(data.sessions) + " sess")}`);
|
|
2925
|
+
}
|
|
2926
|
+
console.log();
|
|
2927
|
+
}
|
|
2928
|
+
});
|
|
2929
|
+
}
|
|
2930
|
+
|
|
2931
|
+
// src/commands/compact.ts
|
|
2932
|
+
import chalk5 from "chalk";
|
|
2933
|
+
|
|
2934
|
+
// src/lib/compact.ts
|
|
2935
|
+
import { writeFileSync as writeFileSync4, existsSync as existsSync8, mkdirSync as mkdirSync6 } from "fs";
|
|
2936
|
+
import { join as join10 } from "path";
|
|
2937
|
+
import { homedir as homedir8 } from "os";
|
|
2938
|
+
var COMPACT_DIR = join10(homedir8(), ".copilot-agent", "compacts");
|
|
2939
|
+
function ensureDir2() {
|
|
2940
|
+
if (!existsSync8(COMPACT_DIR)) mkdirSync6(COMPACT_DIR, { recursive: true });
|
|
2941
|
+
}
|
|
2942
|
+
function compactSession(sessionId, agent) {
|
|
2943
|
+
const report = getAgentSessionReport(sessionId, agent);
|
|
2944
|
+
if (!report) return null;
|
|
2945
|
+
const project = report.cwd?.split("/").pop() || "unknown";
|
|
2946
|
+
const done = [];
|
|
2947
|
+
for (const task of report.taskCompletions) {
|
|
2948
|
+
done.push(task.split("\n")[0].slice(0, 100));
|
|
2949
|
+
}
|
|
2950
|
+
for (const file of report.filesCreated) {
|
|
2951
|
+
done.push(`Created ${file}`);
|
|
2952
|
+
}
|
|
2953
|
+
for (const file of report.filesEdited) {
|
|
2954
|
+
done.push(`Edited ${file}`);
|
|
2955
|
+
}
|
|
2956
|
+
const remaining = [];
|
|
2957
|
+
if (!report.complete) {
|
|
2958
|
+
remaining.push("Session was interrupted before completion");
|
|
2959
|
+
}
|
|
2960
|
+
for (const err of report.errors) {
|
|
2961
|
+
remaining.push(`Fix: ${err.split("\n")[0].slice(0, 100)}`);
|
|
2962
|
+
}
|
|
2963
|
+
const filesChanged = [
|
|
2964
|
+
...report.filesCreated.map((f) => `+ ${f}`),
|
|
2965
|
+
...report.filesEdited.map((f) => `~ ${f}`)
|
|
2966
|
+
];
|
|
2967
|
+
const commits = report.gitCommits.map((c) => c.split("\n")[0].slice(0, 100));
|
|
2968
|
+
const durationMs = report.durationMs;
|
|
2969
|
+
const durationStr = durationMs < 6e4 ? `${Math.round(durationMs / 1e3)}s` : durationMs < 36e5 ? `${Math.round(durationMs / 6e4)}m` : `${Math.floor(durationMs / 36e5)}h ${Math.round(durationMs % 36e5 / 6e4)}m`;
|
|
2970
|
+
const lines = [];
|
|
2971
|
+
lines.push(`## Session Context (auto-generated)`);
|
|
2972
|
+
lines.push(`**Project:** ${project} | **Agent:** ${report.agent} | **Duration:** ${durationStr}`);
|
|
2973
|
+
lines.push(`**Turns:** ${report.assistantTurns} | **Tokens:** ${report.outputTokens.toLocaleString()} | **Premium:** ${report.premiumRequests}`);
|
|
2974
|
+
lines.push("");
|
|
2975
|
+
if (report.summary) {
|
|
2976
|
+
lines.push(`**Task:** ${report.summary}`);
|
|
2977
|
+
lines.push("");
|
|
2978
|
+
}
|
|
2979
|
+
if (done.length > 0) {
|
|
2980
|
+
lines.push("### \u2705 Completed");
|
|
2981
|
+
for (const d of done) lines.push(`- ${d}`);
|
|
2982
|
+
lines.push("");
|
|
2983
|
+
}
|
|
2984
|
+
if (remaining.length > 0) {
|
|
2985
|
+
lines.push("### \u23F3 Remaining");
|
|
2986
|
+
for (const r of remaining) lines.push(`- ${r}`);
|
|
2987
|
+
lines.push("");
|
|
2988
|
+
}
|
|
2989
|
+
if (commits.length > 0) {
|
|
2990
|
+
lines.push("### Git Commits");
|
|
2991
|
+
for (const c of commits) lines.push(`- ${c}`);
|
|
2992
|
+
lines.push("");
|
|
2993
|
+
}
|
|
2994
|
+
if (filesChanged.length > 0) {
|
|
2995
|
+
lines.push("### Files Changed");
|
|
2996
|
+
for (const f of filesChanged) lines.push(`- ${f}`);
|
|
2997
|
+
lines.push("");
|
|
2998
|
+
}
|
|
2999
|
+
if (report.errors.length > 0) {
|
|
3000
|
+
lines.push("### \u26A0\uFE0F Errors Encountered");
|
|
3001
|
+
for (const e of report.errors.slice(0, 5)) lines.push(`- ${e.split("\n")[0]}`);
|
|
3002
|
+
lines.push("");
|
|
3003
|
+
}
|
|
3004
|
+
const topTools = Object.entries(report.toolUsage).sort((a, b) => b[1] - a[1]).slice(0, 5);
|
|
3005
|
+
if (topTools.length > 0) {
|
|
3006
|
+
lines.push("### Tools Used");
|
|
3007
|
+
for (const [tool, count] of topTools) lines.push(`- ${tool}: ${count}x`);
|
|
3008
|
+
lines.push("");
|
|
3009
|
+
}
|
|
3010
|
+
const markdown = lines.join("\n");
|
|
3011
|
+
return {
|
|
3012
|
+
sessionId,
|
|
3013
|
+
agent: report.agent,
|
|
3014
|
+
project,
|
|
3015
|
+
summary: report.summary || "",
|
|
3016
|
+
done,
|
|
3017
|
+
remaining,
|
|
3018
|
+
filesChanged,
|
|
3019
|
+
commits,
|
|
3020
|
+
errors: report.errors.map((e) => e.split("\n")[0]),
|
|
3021
|
+
stats: {
|
|
3022
|
+
turns: report.assistantTurns,
|
|
3023
|
+
tokens: report.outputTokens,
|
|
3024
|
+
premium: report.premiumRequests,
|
|
3025
|
+
duration: durationStr
|
|
3026
|
+
},
|
|
3027
|
+
markdown
|
|
3028
|
+
};
|
|
3029
|
+
}
|
|
3030
|
+
function saveCompact(compact) {
|
|
3031
|
+
ensureDir2();
|
|
3032
|
+
const filename = `${compact.sessionId.slice(0, 12)}.md`;
|
|
3033
|
+
const filepath = join10(COMPACT_DIR, filename);
|
|
3034
|
+
writeFileSync4(filepath, compact.markdown, "utf-8");
|
|
3035
|
+
return filepath;
|
|
3036
|
+
}
|
|
3037
|
+
function buildResumePrompt(compact) {
|
|
3038
|
+
const parts = [];
|
|
3039
|
+
parts.push("Continue the previous task. Here is the context from the interrupted session:");
|
|
3040
|
+
parts.push("");
|
|
3041
|
+
if (compact.summary) parts.push(`Task: ${compact.summary}`);
|
|
3042
|
+
if (compact.done.length > 0) {
|
|
3043
|
+
parts.push("Already completed:");
|
|
3044
|
+
for (const d of compact.done.slice(0, 10)) parts.push(`- ${d}`);
|
|
3045
|
+
}
|
|
3046
|
+
if (compact.remaining.length > 0) {
|
|
3047
|
+
parts.push("Still needs to be done:");
|
|
3048
|
+
for (const r of compact.remaining) parts.push(`- ${r}`);
|
|
3049
|
+
}
|
|
3050
|
+
if (compact.errors.length > 0) {
|
|
3051
|
+
parts.push("Errors to address:");
|
|
3052
|
+
for (const e of compact.errors.slice(0, 3)) parts.push(`- ${e}`);
|
|
3053
|
+
}
|
|
3054
|
+
parts.push("");
|
|
3055
|
+
parts.push("Please continue where the previous session left off.");
|
|
3056
|
+
return parts.join("\n");
|
|
3057
|
+
}
|
|
3058
|
+
|
|
3059
|
+
// src/commands/compact.ts
|
|
3060
|
+
function registerCompactCommand(program2) {
|
|
3061
|
+
program2.command("compact [session-id]").description("Generate context summary from a session for handoff/resume").option("--save", "Save compact to ~/.copilot-agent/compacts/").option("--resume-prompt", "Output a resume prompt for the next session").option("-a, --agent <type>", "Agent type: copilot | claude").action((sessionId, opts) => {
|
|
3062
|
+
try {
|
|
3063
|
+
showCompact(sessionId, opts);
|
|
3064
|
+
} catch (err) {
|
|
3065
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
3066
|
+
console.error(chalk5.red(` \u2717 ${msg}`));
|
|
3067
|
+
}
|
|
3068
|
+
});
|
|
3069
|
+
}
|
|
3070
|
+
function showCompact(sessionId, opts) {
|
|
3071
|
+
if (!sessionId) {
|
|
3072
|
+
const sessions = listAllSessions(10);
|
|
3073
|
+
if (sessions.length === 0) {
|
|
3074
|
+
console.log(chalk5.dim(" No sessions found"));
|
|
3075
|
+
return;
|
|
3076
|
+
}
|
|
3077
|
+
sessionId = sessions[0].id;
|
|
3078
|
+
console.log(chalk5.dim(` Using latest session: ${sessionId.slice(0, 12)}\u2026
|
|
3079
|
+
`));
|
|
3080
|
+
}
|
|
3081
|
+
const compact = compactSession(sessionId, opts.agent);
|
|
3082
|
+
if (!compact) {
|
|
3083
|
+
console.log(chalk5.red(` \u2717 Session not found: ${sessionId}`));
|
|
3084
|
+
return;
|
|
3085
|
+
}
|
|
3086
|
+
if (opts.resumePrompt) {
|
|
3087
|
+
console.log(buildResumePrompt(compact));
|
|
3088
|
+
return;
|
|
3089
|
+
}
|
|
3090
|
+
const agentTag = compact.agent === "claude" ? chalk5.yellow("[claude]") : chalk5.cyan("[copilot]");
|
|
3091
|
+
console.log(chalk5.bold.cyan(` \u{1F4CB} Session Compact \u2014 ${compact.project}`) + ` ${agentTag}`);
|
|
3092
|
+
console.log(chalk5.dim(` ${compact.sessionId}`));
|
|
3093
|
+
console.log(chalk5.dim(` ${"\u2500".repeat(50)}`));
|
|
3094
|
+
console.log();
|
|
3095
|
+
console.log(` ${chalk5.bold("Duration")} ${compact.stats.duration} ${chalk5.bold("Turns")} ${compact.stats.turns} ${chalk5.bold("Tokens")} ${compact.stats.tokens.toLocaleString()} ${chalk5.bold("Premium")} ${chalk5.yellow("\u2B21" + compact.stats.premium)}`);
|
|
3096
|
+
console.log();
|
|
3097
|
+
if (compact.summary) {
|
|
3098
|
+
console.log(chalk5.bold(" Task:") + ` ${compact.summary}`);
|
|
3099
|
+
console.log();
|
|
3100
|
+
}
|
|
3101
|
+
if (compact.done.length > 0) {
|
|
3102
|
+
console.log(chalk5.bold.green(" \u2705 Completed:"));
|
|
3103
|
+
for (const d of compact.done.slice(0, 15)) {
|
|
3104
|
+
console.log(chalk5.green(` \u25CF ${d}`));
|
|
3105
|
+
}
|
|
3106
|
+
if (compact.done.length > 15) console.log(chalk5.dim(` ... +${compact.done.length - 15} more`));
|
|
3107
|
+
console.log();
|
|
3108
|
+
}
|
|
3109
|
+
if (compact.remaining.length > 0) {
|
|
3110
|
+
console.log(chalk5.bold.yellow(" \u23F3 Remaining:"));
|
|
3111
|
+
for (const r of compact.remaining) {
|
|
3112
|
+
console.log(chalk5.yellow(` \u25CB ${r}`));
|
|
3113
|
+
}
|
|
3114
|
+
console.log();
|
|
3115
|
+
}
|
|
3116
|
+
if (compact.commits.length > 0) {
|
|
3117
|
+
console.log(chalk5.bold.cyan(" Commits:"));
|
|
3118
|
+
for (const c of compact.commits.slice(0, 8)) {
|
|
3119
|
+
console.log(chalk5.cyan(` \u25CF ${c}`));
|
|
3120
|
+
}
|
|
3121
|
+
console.log();
|
|
3122
|
+
}
|
|
3123
|
+
if (compact.errors.length > 0) {
|
|
3124
|
+
console.log(chalk5.bold.red(" \u26A0\uFE0F Errors:"));
|
|
3125
|
+
for (const e of compact.errors.slice(0, 5)) {
|
|
3126
|
+
console.log(chalk5.red(` \u2717 ${e}`));
|
|
3127
|
+
}
|
|
3128
|
+
console.log();
|
|
3129
|
+
}
|
|
3130
|
+
if (opts.save) {
|
|
3131
|
+
const path = saveCompact(compact);
|
|
3132
|
+
console.log(chalk5.green(` \u2714 Saved to ${path}`));
|
|
3133
|
+
console.log();
|
|
3134
|
+
}
|
|
3135
|
+
}
|
|
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
|
+
|
|
2833
3411
|
// src/index.ts
|
|
2834
3412
|
var program = new Command();
|
|
2835
|
-
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.");
|
|
2836
3414
|
registerStatusCommand(program);
|
|
2837
3415
|
registerWatchCommand(program);
|
|
2838
3416
|
registerRunCommand(program);
|
|
@@ -2844,5 +3422,9 @@ registerWebCommand(program);
|
|
|
2844
3422
|
registerConfigCommand(program);
|
|
2845
3423
|
registerProxyCommand(program);
|
|
2846
3424
|
registerDiffCommand(program);
|
|
3425
|
+
registerQuotaCommand(program);
|
|
3426
|
+
registerCompactCommand(program);
|
|
3427
|
+
registerHooksCommand(program);
|
|
3428
|
+
registerPrCommand(program);
|
|
2847
3429
|
program.parse();
|
|
2848
3430
|
//# sourceMappingURL=index.js.map
|