@jagit/hook-copilot 0.0.0 → 0.0.2
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 +42 -25
- package/dist/index.d.ts +42 -0
- package/dist/index.js +108 -2
- package/package.json +7 -7
package/README.md
CHANGED
|
@@ -1,40 +1,57 @@
|
|
|
1
1
|
# @jagit/hook-copilot
|
|
2
2
|
|
|
3
|
-
Reports
|
|
3
|
+
Reports GitHub Copilot agent session usage to JaGit. Supports two modes:
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
1. **VS Code agent hook** (recommended) — hooks into the VS Code Copilot agent `Stop` lifecycle event, receiving a structured JSON payload from stdin including `session_id`, `cwd`, and `transcript_path`. Token counts and model name are parsed from the session transcript.
|
|
6
|
+
2. **Legacy shell wrapper** — wraps the Copilot CLI binary and fires after each invocation. No per-call telemetry is available under seat-based billing, so token counts are always zero.
|
|
6
7
|
|
|
7
|
-
|
|
8
|
-
install a shell function that wraps the real `copilot` binary and reports
|
|
9
|
-
after each invocation ends:
|
|
8
|
+
## Setup — VS Code Agent Hook (Recommended)
|
|
10
9
|
|
|
11
|
-
|
|
12
|
-
command copilot "$@"
|
|
13
|
-
local status=$?
|
|
14
|
-
npx -y @jagit/hook-copilot >/dev/null 2>&1 || true
|
|
15
|
-
return $status
|
|
16
|
-
}
|
|
10
|
+
Add the hook to your workspace's `.github/hooks/jagit.json` (or any [supported hook location](https://code.visualstudio.com/docs/agent-customization/hooks#_hook-file-locations)):
|
|
17
11
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
12
|
+
```json
|
|
13
|
+
{
|
|
14
|
+
"hooks": {
|
|
15
|
+
"Stop": [
|
|
16
|
+
{
|
|
17
|
+
"type": "command",
|
|
18
|
+
"command": "npx -y @jagit/hook-copilot"
|
|
19
|
+
}
|
|
20
|
+
]
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
```
|
|
22
24
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
+
VS Code automatically loads this file. When the Copilot agent session ends, JaGit receives the session report (session ID, model, aggregated token counts, tool call count).
|
|
26
|
+
|
|
27
|
+
For a permanent binary instead of `npx -y`: `npm i -g @jagit/hook-copilot`, then use `jagit-hook-copilot` as the command.
|
|
28
|
+
|
|
29
|
+
## Setup — Legacy Shell Wrapper (Copilot CLI)
|
|
30
|
+
|
|
31
|
+
If you are using the Copilot CLI directly (not the VS Code agent), install a shell function that wraps the real `copilot` binary and reports after each invocation ends:
|
|
32
|
+
|
|
33
|
+
```sh
|
|
34
|
+
copilot() {
|
|
35
|
+
command copilot "$@"
|
|
36
|
+
local status=$?
|
|
37
|
+
npx -y @jagit/hook-copilot >/dev/null 2>&1 || true
|
|
38
|
+
return $status
|
|
39
|
+
}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Add that function to your shell rc (`~/.zshrc`, `~/.bashrc`, etc.). Uninstall by removing the shell function.
|
|
25
43
|
|
|
26
44
|
## Environment
|
|
27
45
|
|
|
28
|
-
|
|
29
|
-
|
|
46
|
+
```sh
|
|
47
|
+
export JAGIT_BASE_URL="https://your-jagit-host"
|
|
48
|
+
export JAGIT_API_KEY="<your DASHBOARD_API_TOKEN>"
|
|
49
|
+
```
|
|
30
50
|
|
|
31
51
|
Identity defaults to `git config user.email`; override with `JAGIT_GIT_USERNAME`.
|
|
32
52
|
|
|
33
53
|
## Notes
|
|
34
54
|
|
|
35
|
-
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
surfaces real usage data.
|
|
39
|
-
- `costUsd` is always `null` and will remain so — there is no per-invocation
|
|
40
|
-
cost to report under seat-based billing.
|
|
55
|
+
- In **VS Code agent hook mode**, tokens and model are read from the session transcript at `transcript_path`. Both snake_case (`input_tokens`) and camelCase (`inputTokens`) transcript formats are supported.
|
|
56
|
+
- In **legacy shell wrapper mode**, token counts are always zero (`costUsd: null`) since the Copilot CLI exposes no per-invocation telemetry under seat-based billing.
|
|
57
|
+
- `costUsd` is always `null` — there is no per-session USD cost to report.
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,37 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { type AgentSessionPayload } from "@jagit/agent-reporter";
|
|
3
|
+
export interface CopilotStopStdin {
|
|
4
|
+
/** Optional per VS Code spec — common hook field */
|
|
5
|
+
session_id?: string;
|
|
6
|
+
cwd?: string;
|
|
7
|
+
hook_event_name?: string;
|
|
8
|
+
/** Absolute path to session transcript. Format is unstable — may change in future VS Code releases. */
|
|
9
|
+
transcript_path?: string;
|
|
10
|
+
timestamp?: string;
|
|
11
|
+
/**
|
|
12
|
+
* true when the agent is already continuing as a result of a previous Stop hook.
|
|
13
|
+
* Check this to prevent the hook from reporting duplicate sessions.
|
|
14
|
+
*/
|
|
15
|
+
stop_hook_active?: boolean;
|
|
16
|
+
}
|
|
17
|
+
export interface CopilotTranscriptEntry {
|
|
18
|
+
type?: string;
|
|
19
|
+
timestamp?: string;
|
|
20
|
+
message?: {
|
|
21
|
+
role?: string;
|
|
22
|
+
model?: string;
|
|
23
|
+
content?: unknown;
|
|
24
|
+
usage?: {
|
|
25
|
+
input_tokens?: number;
|
|
26
|
+
cache_read_input_tokens?: number;
|
|
27
|
+
cache_creation_input_tokens?: number;
|
|
28
|
+
output_tokens?: number;
|
|
29
|
+
inputTokens?: number;
|
|
30
|
+
cachedInputTokens?: number;
|
|
31
|
+
outputTokens?: number;
|
|
32
|
+
};
|
|
33
|
+
};
|
|
34
|
+
}
|
|
3
35
|
export interface CopilotInfo {
|
|
4
36
|
model?: string;
|
|
5
37
|
inputTokens?: number;
|
|
@@ -7,4 +39,14 @@ export interface CopilotInfo {
|
|
|
7
39
|
cachedInputTokens?: number;
|
|
8
40
|
toolCallCount?: number | null;
|
|
9
41
|
}
|
|
42
|
+
/**
|
|
43
|
+
* Build payload from a real VS Code Copilot agent Stop-hook stdin.
|
|
44
|
+
* Reads the transcript to aggregate token usage and detect the model.
|
|
45
|
+
*/
|
|
46
|
+
export declare function buildPayloadFromStdin(stdin: CopilotStopStdin, read?: (path: string) => CopilotTranscriptEntry[]): AgentSessionPayload;
|
|
47
|
+
/**
|
|
48
|
+
* Build payload in legacy mode (no stdin) — used when hook-copilot is invoked
|
|
49
|
+
* via the old shell-wrapper pattern around the Copilot CLI. Token counts are
|
|
50
|
+
* not available in this mode (seat-based billing has no per-call telemetry).
|
|
51
|
+
*/
|
|
10
52
|
export declare function buildPayload(cwd: string | undefined, info?: CopilotInfo): AgentSessionPayload;
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,81 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { realpathSync } from "node:fs";
|
|
2
|
+
import { readFileSync, realpathSync } from "node:fs";
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
4
|
import { resolveGitUsername, reportSession } from "@jagit/agent-reporter";
|
|
5
|
+
// ─── Helpers ─────────────────────────────────────────────────────────────────
|
|
6
|
+
function readTranscript(path) {
|
|
7
|
+
return readFileSync(path, "utf-8")
|
|
8
|
+
.split("\n")
|
|
9
|
+
.filter((l) => l.trim().length > 0)
|
|
10
|
+
.map((l) => {
|
|
11
|
+
try {
|
|
12
|
+
return JSON.parse(l);
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
return {};
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
function hasToolUse(content) {
|
|
20
|
+
return Array.isArray(content) && content.some((b) => b?.type === "tool_use");
|
|
21
|
+
}
|
|
22
|
+
// ─── Payload builders ─────────────────────────────────────────────────────────
|
|
23
|
+
/**
|
|
24
|
+
* Build payload from a real VS Code Copilot agent Stop-hook stdin.
|
|
25
|
+
* Reads the transcript to aggregate token usage and detect the model.
|
|
26
|
+
*/
|
|
27
|
+
export function buildPayloadFromStdin(stdin, read = readTranscript) {
|
|
28
|
+
let inputTokens = 0;
|
|
29
|
+
let cachedInputTokens = 0;
|
|
30
|
+
let cacheCreationInputTokens = 0;
|
|
31
|
+
let outputTokens = 0;
|
|
32
|
+
let toolCallCount = 0;
|
|
33
|
+
let model = "copilot";
|
|
34
|
+
const entries = stdin.transcript_path ? (() => {
|
|
35
|
+
try {
|
|
36
|
+
return read(stdin.transcript_path);
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
return [];
|
|
40
|
+
}
|
|
41
|
+
})() : [];
|
|
42
|
+
for (const e of entries) {
|
|
43
|
+
if (e.message?.role !== "assistant")
|
|
44
|
+
continue;
|
|
45
|
+
if (e.message.model)
|
|
46
|
+
model = e.message.model;
|
|
47
|
+
if (hasToolUse(e.message.content))
|
|
48
|
+
toolCallCount += 1;
|
|
49
|
+
const u = e.message.usage;
|
|
50
|
+
if (u) {
|
|
51
|
+
// Prefer snake_case (Claude-compatible), fall back to camelCase (OpenAI-style)
|
|
52
|
+
inputTokens += u.input_tokens ?? u.inputTokens ?? 0;
|
|
53
|
+
cachedInputTokens += u.cache_read_input_tokens ?? u.cachedInputTokens ?? 0;
|
|
54
|
+
cacheCreationInputTokens += u.cache_creation_input_tokens ?? 0;
|
|
55
|
+
outputTokens += u.output_tokens ?? u.outputTokens ?? 0;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
const startedAt = entries.find((e) => e.timestamp)?.timestamp ?? stdin.timestamp ?? new Date().toISOString();
|
|
59
|
+
return {
|
|
60
|
+
tool: "copilot",
|
|
61
|
+
// session_id is optional per VS Code spec; synthesize a fallback if absent
|
|
62
|
+
sessionId: stdin.session_id ?? `copilot-${Date.now()}-${process.pid}`,
|
|
63
|
+
gitUsername: resolveGitUsername(stdin.cwd),
|
|
64
|
+
model,
|
|
65
|
+
inputTokens,
|
|
66
|
+
cachedInputTokens,
|
|
67
|
+
cacheCreationInputTokens,
|
|
68
|
+
outputTokens,
|
|
69
|
+
costUsd: null,
|
|
70
|
+
toolCallCount,
|
|
71
|
+
startedAt,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Build payload in legacy mode (no stdin) — used when hook-copilot is invoked
|
|
76
|
+
* via the old shell-wrapper pattern around the Copilot CLI. Token counts are
|
|
77
|
+
* not available in this mode (seat-based billing has no per-call telemetry).
|
|
78
|
+
*/
|
|
5
79
|
export function buildPayload(cwd, info) {
|
|
6
80
|
return {
|
|
7
81
|
tool: "copilot",
|
|
@@ -10,15 +84,47 @@ export function buildPayload(cwd, info) {
|
|
|
10
84
|
model: info?.model ?? "copilot",
|
|
11
85
|
inputTokens: info?.inputTokens ?? 0,
|
|
12
86
|
cachedInputTokens: info?.cachedInputTokens ?? 0,
|
|
87
|
+
cacheCreationInputTokens: 0,
|
|
13
88
|
outputTokens: info?.outputTokens ?? 0,
|
|
14
89
|
costUsd: null,
|
|
15
90
|
toolCallCount: info?.toolCallCount ?? null,
|
|
16
91
|
startedAt: new Date().toISOString(),
|
|
17
92
|
};
|
|
18
93
|
}
|
|
94
|
+
// ─── Try to read a JSON object from stdin (fd 0) ─────────────────────────────
|
|
95
|
+
function tryReadStdin() {
|
|
96
|
+
try {
|
|
97
|
+
const raw = readFileSync(0, "utf-8").trim();
|
|
98
|
+
if (!raw)
|
|
99
|
+
return undefined;
|
|
100
|
+
const parsed = JSON.parse(raw);
|
|
101
|
+
// Accept any object that looks like a VS Code hook payload (session_id is optional per spec)
|
|
102
|
+
if (typeof parsed === "object" && parsed !== null && "hook_event_name" in parsed) {
|
|
103
|
+
return parsed;
|
|
104
|
+
}
|
|
105
|
+
// Also accept if session_id is present (legacy / Claude Code compat)
|
|
106
|
+
if (typeof parsed.session_id === "string") {
|
|
107
|
+
return parsed;
|
|
108
|
+
}
|
|
109
|
+
return undefined;
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
return undefined;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
// ─── Entry point ─────────────────────────────────────────────────────────────
|
|
19
116
|
async function main() {
|
|
20
117
|
try {
|
|
21
|
-
|
|
118
|
+
const stdin = tryReadStdin();
|
|
119
|
+
// stop_hook_active=true means the agent is re-running because a previous Stop hook
|
|
120
|
+
// blocked it. Skip reporting to avoid duplicate session entries.
|
|
121
|
+
if (stdin?.stop_hook_active === true) {
|
|
122
|
+
process.exit(0);
|
|
123
|
+
}
|
|
124
|
+
const payload = stdin
|
|
125
|
+
? buildPayloadFromStdin(stdin)
|
|
126
|
+
: buildPayload(process.cwd());
|
|
127
|
+
await reportSession(payload);
|
|
22
128
|
}
|
|
23
129
|
catch (err) {
|
|
24
130
|
console.error("[hook-copilot]", err instanceof Error ? err.message : err);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jagit/hook-copilot",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"jagit-hook-copilot": "dist/index.js"
|
|
@@ -8,16 +8,16 @@
|
|
|
8
8
|
"files": [
|
|
9
9
|
"dist"
|
|
10
10
|
],
|
|
11
|
-
"scripts": {
|
|
12
|
-
"build": "tsc -p tsconfig.json",
|
|
13
|
-
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
14
|
-
"test": "vitest run"
|
|
15
|
-
},
|
|
16
11
|
"dependencies": {
|
|
17
|
-
"@jagit/agent-reporter": "
|
|
12
|
+
"@jagit/agent-reporter": "0.0.2"
|
|
18
13
|
},
|
|
19
14
|
"devDependencies": {
|
|
20
15
|
"@types/node": "^25.9.3",
|
|
21
16
|
"vitest": "^2.1.9"
|
|
17
|
+
},
|
|
18
|
+
"scripts": {
|
|
19
|
+
"build": "tsc -p tsconfig.json",
|
|
20
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
21
|
+
"test": "vitest run"
|
|
22
22
|
}
|
|
23
23
|
}
|