@blogic-cz/agent-tools 1.11.1 → 1.12.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 +17 -43
- package/dist/credential-guard/bash.d.ts +3 -0
- package/dist/credential-guard/bash.d.ts.map +1 -0
- package/dist/credential-guard/claude-hook.d.ts.map +1 -1
- package/dist/credential-guard/engine.test.d.ts +5 -0
- package/dist/credential-guard/engine.test.d.ts.map +1 -0
- package/dist/credential-guard/index.d.ts +8 -35
- package/dist/credential-guard/index.d.ts.map +1 -1
- package/dist/credential-guard/ir.d.ts +150 -0
- package/dist/credential-guard/ir.d.ts.map +1 -0
- package/dist/credential-guard/literal-output.d.ts +2 -0
- package/dist/credential-guard/literal-output.d.ts.map +1 -0
- package/dist/credential-guard/passive-awk.d.ts +2 -0
- package/dist/credential-guard/passive-awk.d.ts.map +1 -0
- package/dist/credential-guard/passive-jq.d.ts.map +1 -1
- package/dist/credential-guard/passive-sed.d.ts +2 -0
- package/dist/credential-guard/passive-sed.d.ts.map +1 -0
- package/dist/credential-guard/passive.test.d.ts +2 -0
- package/dist/credential-guard/passive.test.d.ts.map +1 -0
- package/dist/credential-guard/paths.d.ts +14 -0
- package/dist/credential-guard/paths.d.ts.map +1 -0
- package/dist/credential-guard/policy.d.ts +47 -0
- package/dist/credential-guard/policy.d.ts.map +1 -0
- package/dist/credential-guard/powershell.d.ts +3 -0
- package/dist/credential-guard/powershell.d.ts.map +1 -0
- package/dist/credential-guard/registry.d.ts +89 -0
- package/dist/credential-guard/registry.d.ts.map +1 -0
- package/dist/credential-guard/review4.test.d.ts +2 -0
- package/dist/credential-guard/review4.test.d.ts.map +1 -0
- package/dist/credential-guard/review7.test.d.ts +2 -0
- package/dist/credential-guard/review7.test.d.ts.map +1 -0
- package/dist/credential-guard/shell-oracle.d.ts +2 -0
- package/dist/credential-guard/shell-oracle.d.ts.map +1 -0
- package/dist/credential-guard/xargs.d.ts +7 -0
- package/dist/credential-guard/xargs.d.ts.map +1 -0
- package/dist/gh-tool/pr/commands.d.ts.map +1 -1
- package/dist/gh-tool/read/api.d.ts +8 -0
- package/dist/gh-tool/read/api.d.ts.map +1 -1
- package/dist/gh-tool/read/cache.d.ts.map +1 -1
- package/dist/gh-tool/read/commands.d.ts +5 -0
- package/dist/gh-tool/read/commands.d.ts.map +1 -1
- package/dist/gh-tool/read/reads.d.ts +38 -0
- package/dist/gh-tool/read/reads.d.ts.map +1 -1
- package/dist/gh-tool/read/telemetry-command.d.ts +7 -0
- package/dist/gh-tool/read/telemetry-command.d.ts.map +1 -0
- package/dist/gh-tool/read/telemetry-store.d.ts +3 -0
- package/dist/gh-tool/read/telemetry-store.d.ts.map +1 -0
- package/dist/gh-tool/read/telemetry.d.ts +59 -0
- package/dist/gh-tool/read/telemetry.d.ts.map +1 -0
- package/dist/gh-tool/read/transport.d.ts.map +1 -1
- package/package.json +2 -1
- package/schemas/agent-tools.schema.json +1 -1
- package/src/credential-guard/bash.ts +769 -0
- package/src/credential-guard/claude-hook.ts +14 -14
- package/src/credential-guard/engine.test.ts +2050 -0
- package/src/credential-guard/index.ts +285 -4268
- package/src/credential-guard/ir.ts +139 -0
- package/src/credential-guard/literal-output.ts +8 -0
- package/src/credential-guard/passive-awk.ts +239 -0
- package/src/credential-guard/passive-jq.ts +183 -24
- package/src/credential-guard/passive-sed.ts +78 -0
- package/src/credential-guard/passive.test.ts +141 -0
- package/src/credential-guard/paths.ts +108 -0
- package/src/credential-guard/policy.ts +1576 -0
- package/src/credential-guard/powershell.ts +220 -0
- package/src/credential-guard/registry.ts +4720 -0
- package/src/credential-guard/review4.test.ts +83 -0
- package/src/credential-guard/review7.test.ts +51 -0
- package/src/credential-guard/rg-types.json +389 -0
- package/src/credential-guard/shell-oracle.ts +187 -0
- package/src/credential-guard/xargs.ts +65 -0
- package/src/gh-tool/main.ts +30 -6
- package/src/gh-tool/pr/commands.ts +9 -2
- package/src/gh-tool/pr/core.ts +2 -2
- package/src/gh-tool/read/api.ts +73 -6
- package/src/gh-tool/read/cache.ts +13 -4
- package/src/gh-tool/read/commands.ts +17 -1
- package/src/gh-tool/read/reads.ts +84 -1
- package/src/gh-tool/read/telemetry-command.ts +45 -0
- package/src/gh-tool/read/telemetry-store.ts +85 -0
- package/src/gh-tool/read/telemetry.ts +191 -0
- package/src/gh-tool/read/transport.ts +10 -5
- package/src/gh-tool/workflow.ts +2 -2
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { Effect } from "effect";
|
|
2
|
+
import { Command, Flag } from "effect/cli";
|
|
3
|
+
import { formatOption, logFormatted } from "#shared";
|
|
4
|
+
import { GitHubCommandError } from "#gh/errors";
|
|
5
|
+
import { getRateLimit } from "./reads";
|
|
6
|
+
import { summarizeRequests, Telemetry } from "./telemetry";
|
|
7
|
+
|
|
8
|
+
export const telemetryCommand = Command.make(
|
|
9
|
+
"telemetry",
|
|
10
|
+
{
|
|
11
|
+
hours: Flag.Int("hours").pipe(
|
|
12
|
+
Flag.withDefault(1),
|
|
13
|
+
Flag.withDescription("Report window in hours, up to 168"),
|
|
14
|
+
),
|
|
15
|
+
format: formatOption,
|
|
16
|
+
},
|
|
17
|
+
({ hours, format }) =>
|
|
18
|
+
Effect.gen(function* () {
|
|
19
|
+
const store = yield* Telemetry;
|
|
20
|
+
const report = yield* Effect.try({
|
|
21
|
+
try: () => {
|
|
22
|
+
if (!Number.isFinite(hours) || hours <= 0 || hours > 168)
|
|
23
|
+
throw new Error("hours must be greater than 0 and at most 168");
|
|
24
|
+
if (store === null) throw new Error("Telemetry database unavailable");
|
|
25
|
+
const since = new Date(Date.now() - hours * 3600000).toISOString();
|
|
26
|
+
return { since, ...summarizeRequests(store.read(since)) };
|
|
27
|
+
},
|
|
28
|
+
catch: (error) =>
|
|
29
|
+
new GitHubCommandError({
|
|
30
|
+
message: String(error),
|
|
31
|
+
command: "gh-tool telemetry",
|
|
32
|
+
exitCode: 1,
|
|
33
|
+
stderr: String(error),
|
|
34
|
+
}),
|
|
35
|
+
});
|
|
36
|
+
const current = yield* getRateLimit().pipe(
|
|
37
|
+
Effect.catch((error) => Effect.succeed({ error: error.message })),
|
|
38
|
+
);
|
|
39
|
+
yield* logFormatted({ ...report, current }, format);
|
|
40
|
+
}),
|
|
41
|
+
).pipe(
|
|
42
|
+
Command.withDescription(
|
|
43
|
+
"Report direct HTTP requests, gh subprocess calls, ReadCache hits and estimated external quota use",
|
|
44
|
+
),
|
|
45
|
+
);
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { Database } from "bun:sqlite";
|
|
2
|
+
|
|
3
|
+
import { redactSensitiveText } from "#shared/content-security";
|
|
4
|
+
import type { RequestEvent, RequestRow, TelemetryStore } from "./telemetry";
|
|
5
|
+
|
|
6
|
+
const telemetryCommand = (args: readonly string[]): string => {
|
|
7
|
+
const commandGroups = new Set([
|
|
8
|
+
"pr",
|
|
9
|
+
"issue",
|
|
10
|
+
"repo",
|
|
11
|
+
"label",
|
|
12
|
+
"branch",
|
|
13
|
+
"workflow",
|
|
14
|
+
"release",
|
|
15
|
+
"gist",
|
|
16
|
+
"cache",
|
|
17
|
+
]);
|
|
18
|
+
const pathLength = commandGroups.has(args[0]) ? 2 : 1;
|
|
19
|
+
const path = args.slice(0, pathLength).filter((arg) => !arg.startsWith("-"));
|
|
20
|
+
if (path.join(" ") === "pr stack" && args[2] && !args[2].startsWith("-")) {
|
|
21
|
+
path.push(args[2]);
|
|
22
|
+
}
|
|
23
|
+
const flags: string[] = [];
|
|
24
|
+
|
|
25
|
+
for (const arg of args.slice(path.length)) {
|
|
26
|
+
if (arg === "--") break;
|
|
27
|
+
if (arg.startsWith("-")) flags.push(arg.split("=", 1)[0]);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return [...path, ...flags.toSorted()].join(" ");
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export const openRequestTelemetry = (dbPath: string): TelemetryStore => {
|
|
34
|
+
const db = new Database(dbPath, { create: true, strict: true });
|
|
35
|
+
try {
|
|
36
|
+
db.run("PRAGMA busy_timeout=0");
|
|
37
|
+
db.run(`CREATE TABLE IF NOT EXISTS github_requests (
|
|
38
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT, ts TEXT NOT NULL, invocation TEXT NOT NULL,
|
|
39
|
+
command TEXT NOT NULL, method TEXT NOT NULL, endpoint TEXT NOT NULL,
|
|
40
|
+
status INTEGER, outcome TEXT NOT NULL, resource TEXT, used INTEGER,
|
|
41
|
+
remaining INTEGER, reset INTEGER
|
|
42
|
+
) STRICT`);
|
|
43
|
+
db.run("CREATE INDEX IF NOT EXISTS idx_github_requests_ts ON github_requests(ts)");
|
|
44
|
+
db.run(
|
|
45
|
+
"DELETE FROM github_requests WHERE ts < strftime('%Y-%m-%dT%H:%M:%fZ', 'now', '-7 days')",
|
|
46
|
+
);
|
|
47
|
+
} catch (error) {
|
|
48
|
+
db.close(false);
|
|
49
|
+
throw error;
|
|
50
|
+
}
|
|
51
|
+
const invocation = crypto.randomUUID();
|
|
52
|
+
const command = telemetryCommand(process.argv.slice(2));
|
|
53
|
+
return {
|
|
54
|
+
record: (event: RequestEvent) => {
|
|
55
|
+
try {
|
|
56
|
+
db.run(
|
|
57
|
+
`INSERT INTO github_requests
|
|
58
|
+
(ts, invocation, command, method, endpoint, status, outcome, resource, used, remaining, reset)
|
|
59
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
60
|
+
[
|
|
61
|
+
event.ts,
|
|
62
|
+
invocation,
|
|
63
|
+
redactSensitiveText(command),
|
|
64
|
+
event.method,
|
|
65
|
+
event.endpoint,
|
|
66
|
+
event.status,
|
|
67
|
+
event.outcome,
|
|
68
|
+
event.resource,
|
|
69
|
+
event.used,
|
|
70
|
+
event.remaining,
|
|
71
|
+
event.reset,
|
|
72
|
+
],
|
|
73
|
+
);
|
|
74
|
+
} catch {
|
|
75
|
+
/* Never wait for a writer or change command behavior. */
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
read: (since) =>
|
|
79
|
+
db
|
|
80
|
+
.query<RequestRow, [string]>("SELECT * FROM github_requests WHERE ts >= ? ORDER BY ts, id")
|
|
81
|
+
.all(since)
|
|
82
|
+
.map((row) => Object.assign(row, { command: redactSensitiveText(row.command) })),
|
|
83
|
+
close: () => db.close(false),
|
|
84
|
+
};
|
|
85
|
+
};
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import { Context, Effect } from "effect";
|
|
2
|
+
import { redactSensitiveText } from "#shared/content-security";
|
|
3
|
+
|
|
4
|
+
export type RequestEvent = {
|
|
5
|
+
ts: string;
|
|
6
|
+
method: string;
|
|
7
|
+
endpoint: string;
|
|
8
|
+
status: number | null;
|
|
9
|
+
outcome: "cache" | "304" | "200" | "bypass" | "error" | "subprocess";
|
|
10
|
+
resource: string | null;
|
|
11
|
+
used: number | null;
|
|
12
|
+
remaining: number | null;
|
|
13
|
+
reset: number | null;
|
|
14
|
+
};
|
|
15
|
+
export type RequestRow = RequestEvent & { id: number; invocation: string; command: string };
|
|
16
|
+
export type TelemetryStore = {
|
|
17
|
+
record: (event: RequestEvent) => void;
|
|
18
|
+
read: (since: string) => readonly RequestRow[];
|
|
19
|
+
close: () => void;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export const Telemetry = Context.Reference<TelemetryStore | null>("gh/Telemetry", {
|
|
23
|
+
defaultValue: () => null,
|
|
24
|
+
});
|
|
25
|
+
export const CacheRequest = Context.Reference<boolean>("gh/CacheRequest", {
|
|
26
|
+
defaultValue: () => false,
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
export const templateEndpoint = (path: string): string => {
|
|
30
|
+
const pathname = path.replace(/^https?:\/\/[^/]+/, "").split(/[?#]/, 1)[0] ?? "";
|
|
31
|
+
return redactSensitiveText(
|
|
32
|
+
`/${pathname.replace(/^\//, "")}`
|
|
33
|
+
.replace(/\b[0-9a-f]{7,64}\b/gi, (part) => (/^\d+$/.test(part) ? ":id" : ":sha"))
|
|
34
|
+
.replace(/\/\d+(?=\/|$)/g, "/:id"),
|
|
35
|
+
);
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export const recordRequest = (event: RequestEvent): Effect.Effect<void> =>
|
|
39
|
+
Effect.gen(function* () {
|
|
40
|
+
const store = yield* Telemetry;
|
|
41
|
+
try {
|
|
42
|
+
store?.record({
|
|
43
|
+
...event,
|
|
44
|
+
endpoint:
|
|
45
|
+
event.outcome === "subprocess" ? event.endpoint : templateEndpoint(event.endpoint),
|
|
46
|
+
});
|
|
47
|
+
} catch {
|
|
48
|
+
/* Best effort. */
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
export const cacheEvent = (operation: string): RequestEvent => ({
|
|
53
|
+
ts: new Date().toISOString(),
|
|
54
|
+
method: "CACHE",
|
|
55
|
+
endpoint: `cache/${operation}`,
|
|
56
|
+
status: null,
|
|
57
|
+
outcome: "cache",
|
|
58
|
+
resource: null,
|
|
59
|
+
used: null,
|
|
60
|
+
remaining: null,
|
|
61
|
+
reset: null,
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
const API_VALUE_FLAGS = new Set([
|
|
65
|
+
"-X",
|
|
66
|
+
"--method",
|
|
67
|
+
"-H",
|
|
68
|
+
"--header",
|
|
69
|
+
"-f",
|
|
70
|
+
"-F",
|
|
71
|
+
"--field",
|
|
72
|
+
"--raw-field",
|
|
73
|
+
"--jq",
|
|
74
|
+
"-q",
|
|
75
|
+
"--template",
|
|
76
|
+
"-t",
|
|
77
|
+
"--input",
|
|
78
|
+
"--cache",
|
|
79
|
+
"-p",
|
|
80
|
+
"--preview",
|
|
81
|
+
"--hostname",
|
|
82
|
+
]);
|
|
83
|
+
|
|
84
|
+
export const ghSubprocessEvent = (args: readonly string[], exitCode: number): RequestEvent => {
|
|
85
|
+
const api = args[0] === "api";
|
|
86
|
+
let endpoint: string | undefined;
|
|
87
|
+
let method: string | undefined;
|
|
88
|
+
let hasFields = false;
|
|
89
|
+
if (api) {
|
|
90
|
+
for (let index = 1; index < args.length; index++) {
|
|
91
|
+
const arg = args[index];
|
|
92
|
+
if (arg === undefined) continue;
|
|
93
|
+
if (arg === "--") {
|
|
94
|
+
endpoint ??= args[index + 1];
|
|
95
|
+
break;
|
|
96
|
+
}
|
|
97
|
+
if (!arg.startsWith("-")) {
|
|
98
|
+
endpoint ??= arg;
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
const equals = arg.indexOf("=");
|
|
102
|
+
const flag = equals < 0 ? arg : arg.slice(0, equals);
|
|
103
|
+
if (!API_VALUE_FLAGS.has(flag)) continue;
|
|
104
|
+
const value = equals < 0 ? args[++index] : arg.slice(equals + 1);
|
|
105
|
+
if (flag === "-X" || flag === "--method") method = value;
|
|
106
|
+
if (flag === "-f" || flag === "-F" || flag === "--field" || flag === "--raw-field")
|
|
107
|
+
hasFields = true;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return {
|
|
111
|
+
...cacheEvent(""),
|
|
112
|
+
method: api ? (method ?? (hasFields ? "POST" : "GET")) : "GH",
|
|
113
|
+
endpoint: api ? templateEndpoint(endpoint ?? "") : args.slice(0, 2).join(" "),
|
|
114
|
+
status: exitCode >= 100 && exitCode <= 599 ? exitCode : null,
|
|
115
|
+
outcome: "subprocess",
|
|
116
|
+
};
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
const counts = (values: readonly string[]) => {
|
|
120
|
+
const groups = new Map<string, number>();
|
|
121
|
+
for (const value of values) groups.set(value, (groups.get(value) ?? 0) + 1);
|
|
122
|
+
return [...groups]
|
|
123
|
+
.map(([name, requests]) => ({ name, requests }))
|
|
124
|
+
.toSorted((a, b) => b.requests - a.requests || a.name.localeCompare(b.name));
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
const subprocessSpendsResource = (row: RequestRow, resource: string): boolean => {
|
|
128
|
+
if (row.outcome !== "subprocess") return false;
|
|
129
|
+
if (row.method === "GH") return true;
|
|
130
|
+
if (row.method === "GET" && row.endpoint === "/rate_limit") return false;
|
|
131
|
+
return row.endpoint === "/graphql" ? resource === "graphql" : resource !== "graphql";
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
export const summarizeRequests = (rows: readonly RequestRow[]) => {
|
|
135
|
+
const requests = rows.filter((row) => row.outcome !== "cache" && row.outcome !== "subprocess");
|
|
136
|
+
const subprocesses = rows.filter((row) => row.outcome === "subprocess");
|
|
137
|
+
const previous = new Map<string, { used: number; index: number }>();
|
|
138
|
+
let externalUsageEstimate = 0;
|
|
139
|
+
let intervals = 0;
|
|
140
|
+
for (const [index, row] of rows.entries()) {
|
|
141
|
+
if (row.outcome === "cache" || row.outcome === "subprocess") continue;
|
|
142
|
+
if (row.resource === null || row.used === null || row.reset === null) continue;
|
|
143
|
+
const resource = row.resource;
|
|
144
|
+
const key = `${resource}:${row.reset}`;
|
|
145
|
+
const before = previous.get(key);
|
|
146
|
+
if (before !== undefined && row.used >= before.used) {
|
|
147
|
+
const interval = rows.slice(before.index + 1, index + 1);
|
|
148
|
+
const own = interval.filter(
|
|
149
|
+
(candidate) =>
|
|
150
|
+
candidate.resource === resource &&
|
|
151
|
+
(candidate.reset === null || candidate.reset === row.reset) &&
|
|
152
|
+
candidate.status !== 304 &&
|
|
153
|
+
candidate.endpoint !== "/rate_limit",
|
|
154
|
+
).length;
|
|
155
|
+
const subprocessCalls = interval.filter((candidate) =>
|
|
156
|
+
subprocessSpendsResource(candidate, resource),
|
|
157
|
+
).length;
|
|
158
|
+
externalUsageEstimate += Math.max(0, row.used - before.used - own - subprocessCalls);
|
|
159
|
+
intervals++;
|
|
160
|
+
}
|
|
161
|
+
if (before === undefined || row.used >= before.used)
|
|
162
|
+
previous.set(key, { used: row.used, index });
|
|
163
|
+
}
|
|
164
|
+
const notModified = requests.filter((row) => row.status === 304).length;
|
|
165
|
+
return {
|
|
166
|
+
directHttpRequests: requests.length,
|
|
167
|
+
ghSubprocessCalls: subprocesses.length,
|
|
168
|
+
cacheHits: rows.filter((row) => row.outcome === "cache").length,
|
|
169
|
+
subprocessTopCommands: counts(subprocesses.map((row) => redactSensitiveText(row.command)))
|
|
170
|
+
.slice(0, 20)
|
|
171
|
+
.map(({ name, requests: calls }) => ({ name, calls })),
|
|
172
|
+
subprocessTopEndpoints: counts(subprocesses.map((row) => `${row.method} ${row.endpoint}`))
|
|
173
|
+
.slice(0, 20)
|
|
174
|
+
.map(({ name, requests: calls }) => ({ name, calls })),
|
|
175
|
+
directHttpByResource: counts(requests.map((row) => row.resource ?? "unknown")),
|
|
176
|
+
directHttpTopCommands: counts(requests.map((row) => redactSensitiveText(row.command))).slice(
|
|
177
|
+
0,
|
|
178
|
+
20,
|
|
179
|
+
),
|
|
180
|
+
directHttpTopEndpoints: counts(requests.map((row) => `${row.method} ${row.endpoint}`)).slice(
|
|
181
|
+
0,
|
|
182
|
+
20,
|
|
183
|
+
),
|
|
184
|
+
notModified,
|
|
185
|
+
ratio304: requests.length === 0 ? null : notModified / requests.length,
|
|
186
|
+
externalUsageEstimate: intervals === 0 ? null : externalUsageEstimate,
|
|
187
|
+
observedIntervals: intervals,
|
|
188
|
+
caveat:
|
|
189
|
+
"Estimate within observed reset windows only. Missing headers, concurrent requests, different tokens and dropped telemetry reduce accuracy. Direct HTTP counts are exact. Each gh subprocess call may make zero or more HTTP requests; its HTTP status and quota headers are unavailable. For this estimate, subprocess calls count as one request each. Cache hits count ReadCache only.",
|
|
190
|
+
};
|
|
191
|
+
};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ghSubprocessEvent, recordRequest } from "./telemetry";
|
|
1
2
|
import { ChildProcess, ChildProcessSpawner } from "effect/process";
|
|
2
3
|
import { Context, Effect, Layer, Stream } from "effect";
|
|
3
4
|
|
|
@@ -182,13 +183,17 @@ export class GitHubService extends Context.Service<
|
|
|
182
183
|
|
|
183
184
|
const proc = yield* executor.spawn(command);
|
|
184
185
|
|
|
185
|
-
const stdoutChunk = yield*
|
|
186
|
+
const [stdoutChunk, stderrChunk] = yield* Effect.all(
|
|
187
|
+
[
|
|
188
|
+
proc.stdout.pipe(Stream.decodeText(), Stream.runCollect),
|
|
189
|
+
proc.stderr.pipe(Stream.decodeText(), Stream.runCollect),
|
|
190
|
+
],
|
|
191
|
+
{ concurrency: "unbounded" },
|
|
192
|
+
);
|
|
186
193
|
const stdout = stdoutChunk.join("");
|
|
187
|
-
|
|
188
|
-
const stderrChunk = yield* proc.stderr.pipe(Stream.decodeText(), Stream.runCollect);
|
|
189
194
|
const stderr = stderrChunk.join("");
|
|
190
|
-
|
|
191
195
|
const exitCode = yield* proc.exitCode;
|
|
196
|
+
yield* recordRequest(ghSubprocessEvent(args, Number(exitCode)));
|
|
192
197
|
|
|
193
198
|
return {
|
|
194
199
|
stdout,
|
|
@@ -246,7 +251,7 @@ export class GitHubService extends Context.Service<
|
|
|
246
251
|
const quotaFilter =
|
|
247
252
|
resource === undefined
|
|
248
253
|
? '.resources | to_entries[] | "\\(.key): reset time: \\(.value.reset | todateiso8601); remaining quota: \\(.value.remaining)"'
|
|
249
|
-
: `.resources.${resource} | "reset time: \\(.reset | todateiso8601); remaining quota: \\(.remaining)"`;
|
|
254
|
+
: `.resources.${resource} | "resource: ${resource}; reset time: \\(.reset | todateiso8601); remaining quota: \\(.remaining)"`;
|
|
250
255
|
const quota = yield* executeGh(["api", "rate_limit", "--jq", quotaFilter]).pipe(
|
|
251
256
|
Effect.orElseSucceed(() => null),
|
|
252
257
|
);
|
package/src/gh-tool/workflow.ts
CHANGED
|
@@ -298,7 +298,7 @@ const fetchAnnotations = Effect.fn("workflow.fetchAnnotations")(function* (opts:
|
|
|
298
298
|
const parts = opts.repo.split("/");
|
|
299
299
|
if (parts.length !== 2 || !parts[0] || !parts[1]) {
|
|
300
300
|
return yield* new GitHubCommandError({
|
|
301
|
-
message: `Invalid --repo format: "${opts.repo}". Expected "owner/name" (e.g. "
|
|
301
|
+
message: `Invalid --repo format: "${opts.repo}". Expected "owner/name" (e.g. "example-org/example-repo").`,
|
|
302
302
|
command: "workflow annotations",
|
|
303
303
|
exitCode: 1,
|
|
304
304
|
stderr: "",
|
|
@@ -574,7 +574,7 @@ export const fetchJobLogs = Effect.fn("workflow.fetchJobLogs")(function* (
|
|
|
574
574
|
const parts = opts.repo.split("/");
|
|
575
575
|
if (parts.length !== 2 || !parts[0] || !parts[1]) {
|
|
576
576
|
return yield* new GitHubCommandError({
|
|
577
|
-
message: `Invalid --repo format: "${opts.repo}". Expected "owner/name" (e.g. "
|
|
577
|
+
message: `Invalid --repo format: "${opts.repo}". Expected "owner/name" (e.g. "example-org/example-repo").`,
|
|
578
578
|
command: "workflow job-logs",
|
|
579
579
|
exitCode: 1,
|
|
580
580
|
stderr: "",
|