@blogic-cz/agent-tools 1.4.1 → 1.5.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 +16 -2
- package/dist/az-tool/config.d.ts.map +1 -1
- package/dist/az-tool/profile.d.ts.map +1 -1
- package/dist/az-tool/service.d.ts +2 -2
- package/dist/azdo-tool/build.d.ts +8 -8
- package/dist/azdo-tool/config.d.ts.map +1 -1
- package/dist/azdo-tool/service.d.ts +2 -2
- package/dist/config/loader.d.ts.map +1 -1
- package/dist/config/types.d.ts +2 -0
- package/dist/config/types.d.ts.map +1 -1
- package/dist/credential-guard/index.d.ts.map +1 -1
- package/dist/db-tool/schema.d.ts.map +1 -1
- package/dist/db-tool/service.d.ts +1 -1
- package/dist/db-tool/sql-client.d.ts.map +1 -1
- package/dist/gh-tool/api.d.ts.map +1 -1
- package/dist/gh-tool/config.d.ts.map +1 -1
- package/dist/gh-tool/gist.d.ts.map +1 -1
- package/dist/gh-tool/issue/commands.d.ts +1 -1
- package/dist/gh-tool/issue/triage.d.ts +1 -1
- package/dist/gh-tool/issue/triage.d.ts.map +1 -1
- package/dist/gh-tool/pr/commands.d.ts +10 -10
- package/dist/gh-tool/pr/commands.d.ts.map +1 -1
- package/dist/gh-tool/pr/core.d.ts +19 -19
- package/dist/gh-tool/pr/core.d.ts.map +1 -1
- package/dist/gh-tool/pr/stack.d.ts +13 -13
- package/dist/gh-tool/repo.d.ts +1 -1
- package/dist/gh-tool/service.d.ts +1 -1
- package/dist/gh-tool/text-input.d.ts.map +1 -1
- package/dist/gh-tool/workflow.d.ts +7 -7
- package/dist/gh-tool/workflow.d.ts.map +1 -1
- package/dist/k8s-tool/context.d.ts.map +1 -1
- package/dist/k8s-tool/security.d.ts.map +1 -1
- package/dist/k8s-tool/service.d.ts +2 -2
- package/dist/logs-tool/index.d.ts +1 -1
- package/dist/logs-tool/index.d.ts.map +1 -1
- package/dist/logs-tool/service.d.ts.map +1 -1
- package/dist/observability-tool/shared.d.ts.map +1 -1
- package/dist/session-tool/claude-code.d.ts.map +1 -1
- package/dist/session-tool/codex.d.ts +3 -1
- package/dist/session-tool/codex.d.ts.map +1 -1
- package/dist/session-tool/index.d.ts +1 -1
- package/dist/session-tool/index.d.ts.map +1 -1
- package/dist/session-tool/pi.d.ts.map +1 -1
- package/dist/session-tool/service.d.ts.map +1 -1
- package/dist/session-tool/summaries.d.ts.map +1 -1
- package/dist/session-tool/types.d.ts.map +1 -1
- package/dist/shared/audit.d.ts.map +1 -1
- package/dist/shared/azure-credentials.d.ts.map +1 -1
- package/dist/shared/binary-preflight.d.ts.map +1 -1
- package/dist/shared/cli.d.ts.map +1 -1
- package/dist/shared/error-renderer.d.ts.map +1 -1
- package/dist/shared/exec.d.ts.map +1 -1
- package/dist/shared/format.d.ts.map +1 -1
- package/dist/shared/path.d.ts.map +1 -1
- package/dist/shared/poll-until-resolved.d.ts.map +1 -1
- package/dist/shared/prerequisites/driver-commands.d.ts.map +1 -1
- package/dist/shared/prerequisites/errors.d.ts.map +1 -1
- package/dist/shared/prerequisites/guardian.d.ts.map +1 -1
- package/dist/shared/prerequisites/runtime.d.ts.map +1 -1
- package/dist/shared/prerequisites/store.d.ts.map +1 -1
- package/dist/shared/retry-transient.d.ts.map +1 -1
- package/dist/shared/schema-dump.d.ts.map +1 -1
- package/dist/shared/throttle.d.ts.map +1 -1
- package/package.json +13 -13
- package/schemas/agent-tools.schema.json +8 -0
- package/src/audit-tool/index.ts +4 -4
- package/src/az-tool/index.ts +4 -4
- package/src/azdo-tool/index.ts +11 -11
- package/src/config/loader.ts +1 -0
- package/src/config/types.ts +2 -0
- package/src/credential-guard/claude-hook.ts +8 -2
- package/src/credential-guard/index.ts +212 -3
- package/src/db-tool/index.ts +12 -12
- package/src/gh-tool/branch.ts +4 -4
- package/src/gh-tool/gist.ts +20 -20
- package/src/gh-tool/issue/commands.ts +33 -33
- package/src/gh-tool/issue/triage.ts +5 -5
- package/src/gh-tool/pr/commands.ts +108 -112
- package/src/gh-tool/pr/core.ts +4 -6
- package/src/gh-tool/release.ts +30 -30
- package/src/gh-tool/repo.ts +7 -7
- package/src/gh-tool/workflow.ts +24 -24
- package/src/k8s-tool/index.ts +21 -21
- package/src/logs-tool/index.ts +7 -10
- package/src/logs-tool/transformers.ts +1 -1
- package/src/observability-tool/logs.ts +4 -4
- package/src/observability-tool/metrics.ts +4 -4
- package/src/observability-tool/shared.ts +2 -2
- package/src/observability-tool/trace.ts +10 -10
- package/src/session-tool/index.ts +8 -14
- package/src/shared/format.ts +1 -1
- package/src/shared/log-transform.ts +1 -1
package/src/config/loader.ts
CHANGED
|
@@ -11,6 +11,7 @@ const CliToolOverrideSchema = Schema.Struct({
|
|
|
11
11
|
});
|
|
12
12
|
|
|
13
13
|
const CredentialGuardConfigSchema = Schema.Struct({
|
|
14
|
+
allowedEnvironmentVariables: Schema.optionalKey(Schema.Array(Schema.String)),
|
|
14
15
|
additionalBlockedPaths: Schema.optionalKey(Schema.Array(Schema.String)),
|
|
15
16
|
additionalAllowedPaths: Schema.optionalKey(Schema.Array(Schema.String)),
|
|
16
17
|
additionalBlockedCliTools: Schema.optionalKey(Schema.Array(CliToolOverrideSchema)),
|
package/src/config/types.ts
CHANGED
|
@@ -185,6 +185,8 @@ export type CliToolOverride = {
|
|
|
185
185
|
|
|
186
186
|
/** Credential guard config - merged with built-in defaults */
|
|
187
187
|
export type CredentialGuardConfig = {
|
|
188
|
+
/** Exact, case-sensitive names approved for static printenv reads. Empty by default. */
|
|
189
|
+
allowedEnvironmentVariables?: string[];
|
|
188
190
|
additionalBlockedPaths?: string[];
|
|
189
191
|
additionalAllowedPaths?: string[];
|
|
190
192
|
additionalBlockedCliTools?: CliToolOverride[];
|
|
@@ -10,7 +10,9 @@
|
|
|
10
10
|
* "command": "bun node_modules/@blogic-cz/agent-tools/src/credential-guard/claude-hook.ts" }] }] } }
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
import {
|
|
13
|
+
import { loadConfig } from "#config/loader";
|
|
14
|
+
|
|
15
|
+
import { createCredentialGuard } from "./index";
|
|
14
16
|
|
|
15
17
|
const stdin = await Bun.stdin.text();
|
|
16
18
|
|
|
@@ -20,7 +22,11 @@ try {
|
|
|
20
22
|
tool_input?: Record<string, unknown>;
|
|
21
23
|
} = JSON.parse(stdin);
|
|
22
24
|
|
|
23
|
-
|
|
25
|
+
const config = await loadConfig();
|
|
26
|
+
createCredentialGuard(config?.credentialGuard).handleToolExecuteBefore(
|
|
27
|
+
{ tool: data.tool_name },
|
|
28
|
+
{ args: data.tool_input ?? {} },
|
|
29
|
+
);
|
|
24
30
|
} catch (error: unknown) {
|
|
25
31
|
const message = error instanceof Error ? error.message : String(error);
|
|
26
32
|
process.stderr.write(message);
|
|
@@ -143,8 +143,6 @@ const SECRET_PATTERNS = [
|
|
|
143
143
|
* Dangerous bash patterns that might expose secrets.
|
|
144
144
|
*/
|
|
145
145
|
const DEFAULT_DANGEROUS_BASH_PATTERNS: RegExp[] = [
|
|
146
|
-
/printenv/i,
|
|
147
|
-
/(?:^|&&|\||;)\s*env(?:\s|$)/i,
|
|
148
146
|
/\bcat\s+\S*\.env/i,
|
|
149
147
|
/\bcat\s+\S*\.pem/i,
|
|
150
148
|
/\bcat\s+\S*\.key/i,
|
|
@@ -245,6 +243,201 @@ function escapeRegex(s: string): string {
|
|
|
245
243
|
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
246
244
|
}
|
|
247
245
|
|
|
246
|
+
/** Static shell words only. Never expand variables or execute a command. */
|
|
247
|
+
function parseStaticShellCommands(
|
|
248
|
+
command: string,
|
|
249
|
+
): { pipelines: string[][][] } | "brace-expansion" | undefined {
|
|
250
|
+
const pipelines: string[][][] = [];
|
|
251
|
+
let commands: string[][] = [];
|
|
252
|
+
let argv: string[] = [];
|
|
253
|
+
let word = "";
|
|
254
|
+
let started = false;
|
|
255
|
+
let quote: "'" | '"' | undefined;
|
|
256
|
+
|
|
257
|
+
const finishWord = () => {
|
|
258
|
+
if (started) argv.push(word);
|
|
259
|
+
word = "";
|
|
260
|
+
started = false;
|
|
261
|
+
};
|
|
262
|
+
const finishCommand = () => {
|
|
263
|
+
finishWord();
|
|
264
|
+
if (argv.length) commands.push(argv);
|
|
265
|
+
argv = [];
|
|
266
|
+
};
|
|
267
|
+
const finishPipeline = () => {
|
|
268
|
+
finishCommand();
|
|
269
|
+
if (commands.length) pipelines.push(commands);
|
|
270
|
+
commands = [];
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
for (let i = 0; i < command.length; i++) {
|
|
274
|
+
const char = command.charAt(i);
|
|
275
|
+
if (quote === "'") {
|
|
276
|
+
if (char === quote) quote = undefined;
|
|
277
|
+
else word += char;
|
|
278
|
+
} else if (char === "\\") {
|
|
279
|
+
const next = command[++i];
|
|
280
|
+
if (next === undefined || next === "\n" || next === "\r") return undefined;
|
|
281
|
+
if (quote === '"' && !/[\\$`"]/.test(next)) word += "\\";
|
|
282
|
+
word += next;
|
|
283
|
+
started = true;
|
|
284
|
+
} else if (char === "$" || char === "`") {
|
|
285
|
+
return undefined;
|
|
286
|
+
} else if (quote === '"') {
|
|
287
|
+
if (char === quote) quote = undefined;
|
|
288
|
+
else word += char;
|
|
289
|
+
} else if (char === "'" || char === '"') {
|
|
290
|
+
quote = char;
|
|
291
|
+
started = true;
|
|
292
|
+
} else if (char === "{" && /^\{[^{}]*(?:,|\.\.)[^{}]*\}/.test(command.slice(i))) {
|
|
293
|
+
return "brace-expansion";
|
|
294
|
+
} else if (/[<>()]/.test(char) || (char === "#" && !started)) {
|
|
295
|
+
return undefined;
|
|
296
|
+
} else if (char === "|" && command[i - 1] !== "|" && command[i + 1] !== "|") {
|
|
297
|
+
finishCommand();
|
|
298
|
+
if (command[i + 1] === "&") i++;
|
|
299
|
+
} else if (/[;&|\r\n]/.test(char)) {
|
|
300
|
+
finishPipeline();
|
|
301
|
+
} else if (/\s/.test(char)) {
|
|
302
|
+
finishWord();
|
|
303
|
+
} else {
|
|
304
|
+
word += char;
|
|
305
|
+
started = true;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
if (quote) return undefined;
|
|
310
|
+
finishPipeline();
|
|
311
|
+
return { pipelines };
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
function mentionsEnvironmentRead(text: string): boolean {
|
|
315
|
+
return /printenv|\benv\b/i.test(text.replace(/['"\\]/g, ""));
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
function hasBraceExpansion(text: string): boolean {
|
|
319
|
+
return /\{[^{}]*(?:,|\.\.)[^{}]*\}/.test(text);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
function unwrapStaticCommand(words: string[]): string[] {
|
|
323
|
+
const argv = [...words];
|
|
324
|
+
const executable = () => argv[0]?.split("/").at(-1);
|
|
325
|
+
while (executable() === "rtk" || executable() === "command") {
|
|
326
|
+
const wrapper = executable();
|
|
327
|
+
argv.shift();
|
|
328
|
+
if (wrapper === "rtk" && argv[0] === "proxy") argv.shift();
|
|
329
|
+
if (wrapper === "command" && argv[0] === "--") argv.shift();
|
|
330
|
+
}
|
|
331
|
+
return argv;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
function isAllowedEnvironmentRead(argv: string[], allowedNames: Set<string>): boolean {
|
|
335
|
+
if (argv[0]?.split("/").at(-1) !== "printenv") return false;
|
|
336
|
+
const args = argv.slice(1);
|
|
337
|
+
if (args[0] === "--") args.shift();
|
|
338
|
+
return args.length > 0 && args.every((name) => allowedNames.has(name));
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
function hasExecutionOption(args: string[], longOptions: string[], shortOption?: string): boolean {
|
|
342
|
+
return args.some((arg) => {
|
|
343
|
+
const flag = arg.split("=", 1)[0] ?? "";
|
|
344
|
+
if (flag.startsWith("--") && flag.length > 2) {
|
|
345
|
+
// Git supports long-option abbreviations. Refuse ambiguous prefixes too.
|
|
346
|
+
return longOptions.some((option) => option.startsWith(flag.slice(2)));
|
|
347
|
+
}
|
|
348
|
+
return shortOption !== undefined && /^-[^-]/.test(flag) && flag.includes(shortOption);
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
function isPassiveTextCommand(argv: string[]): boolean {
|
|
353
|
+
const name = argv[0]?.split("/").at(-1) ?? "";
|
|
354
|
+
const args = argv.slice(1);
|
|
355
|
+
// These commands consume literal text. Execution options are not exceptions.
|
|
356
|
+
if (["echo", "printf", "grep", "head"].includes(name)) {
|
|
357
|
+
return true;
|
|
358
|
+
}
|
|
359
|
+
if (name === "rg") return !hasExecutionOption(args, ["pre", "hostname-bin"]);
|
|
360
|
+
return (
|
|
361
|
+
name === "git" &&
|
|
362
|
+
args[0] === "grep" &&
|
|
363
|
+
!hasExecutionOption(args, ["open-files-in-pager", "textconv"], "O")
|
|
364
|
+
);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
function hasStaticEnvironmentRead(argv: string[], allowedNames: Set<string>): boolean {
|
|
368
|
+
const name = argv[0]?.split("/").at(-1);
|
|
369
|
+
if (name === "printenv") return !isAllowedEnvironmentRead(argv, allowedNames);
|
|
370
|
+
if (name === "env") return true;
|
|
371
|
+
if (isPassiveTextCommand(argv)) return false;
|
|
372
|
+
if (
|
|
373
|
+
argv.some((arg) =>
|
|
374
|
+
["sh", "bash", "zsh", "dash", "ksh", "fish", "csh", "tcsh", "eval"].includes(
|
|
375
|
+
arg.split("/").at(-1) ?? "",
|
|
376
|
+
),
|
|
377
|
+
) &&
|
|
378
|
+
hasBraceExpansion(argv.slice(1).join(" "))
|
|
379
|
+
) {
|
|
380
|
+
return true;
|
|
381
|
+
}
|
|
382
|
+
return mentionsEnvironmentRead(argv.join(" "));
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
function hasEnvironmentRead(command: string, allowedNames: Set<string>): boolean {
|
|
386
|
+
if (isLiteralTextWrite(command)) return false;
|
|
387
|
+
// ponytail: complex shell syntax stays conservative; use a shell AST if more exceptions are needed.
|
|
388
|
+
const parsed = parseStaticShellCommands(command);
|
|
389
|
+
if (parsed === "brace-expansion") return true;
|
|
390
|
+
if (!parsed) return mentionsEnvironmentRead(command) || hasBraceExpansion(command);
|
|
391
|
+
const pipelines = parsed.pipelines.map((commands) => commands.map(unwrapStaticCommand));
|
|
392
|
+
const unwrapped = pipelines.flat();
|
|
393
|
+
if (unwrapped.some((argv) => hasStaticEnvironmentRead(argv, allowedNames))) return true;
|
|
394
|
+
|
|
395
|
+
// A literal producer can feed executable text to a shell, xargs, or an unknown consumer.
|
|
396
|
+
return pipelines.some(
|
|
397
|
+
(pipeline) =>
|
|
398
|
+
pipeline.length > 1 &&
|
|
399
|
+
pipeline.some(
|
|
400
|
+
(argv) =>
|
|
401
|
+
isAllowedEnvironmentRead(argv, allowedNames) ||
|
|
402
|
+
(isPassiveTextCommand(argv) &&
|
|
403
|
+
(mentionsEnvironmentRead(argv.join(" ")) || hasBraceExpansion(argv.join(" ")))),
|
|
404
|
+
) &&
|
|
405
|
+
pipeline.some(
|
|
406
|
+
(argv) => !isPassiveTextCommand(argv) && !isAllowedEnvironmentRead(argv, allowedNames),
|
|
407
|
+
),
|
|
408
|
+
);
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
/** A single literal writer cannot execute its body or feed another command. */
|
|
412
|
+
function isLiteralTextWrite(command: string): boolean {
|
|
413
|
+
const lines = command.trimEnd().split(/\r?\n/);
|
|
414
|
+
const heredoc = /^(.*?)<<(-?)[ \t]*(['"]?)([A-Za-z_][A-Za-z0-9_]*)\3[ \t]*$/.exec(lines[0] ?? "");
|
|
415
|
+
let header = command;
|
|
416
|
+
let writers = ["echo", "printf"];
|
|
417
|
+
if (heredoc) {
|
|
418
|
+
const [, prefix, stripTabs, quote, delimiter] = heredoc;
|
|
419
|
+
const end = lines.findIndex(
|
|
420
|
+
(line, index) => index > 0 && (stripTabs ? line.replace(/^\t+/, "") : line) === delimiter,
|
|
421
|
+
);
|
|
422
|
+
// Only the first delimiter closes the body. No following commands are exceptions.
|
|
423
|
+
if (end < 0 || end !== lines.length - 1) return false;
|
|
424
|
+
const body = lines.slice(1, end).join("\n");
|
|
425
|
+
if (!quote && /[$`\\]/.test(body)) return false;
|
|
426
|
+
header = prefix ?? "";
|
|
427
|
+
writers = ["cat", "tee"];
|
|
428
|
+
} else if (!command.includes(">")) {
|
|
429
|
+
return false;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
// Output redirection does not execute text. Other unsupported syntax still fails parsing.
|
|
433
|
+
const parsed = parseStaticShellCommands(header.replace(/>/g, " "));
|
|
434
|
+
if (!parsed || parsed === "brace-expansion" || parsed.pipelines.length !== 1) return false;
|
|
435
|
+
const pipeline = parsed.pipelines[0];
|
|
436
|
+
if (pipeline?.length !== 1) return false;
|
|
437
|
+
const argv = unwrapStaticCommand(pipeline[0] ?? []);
|
|
438
|
+
return writers.includes(argv[0]?.split("/").at(-1) ?? "");
|
|
439
|
+
}
|
|
440
|
+
|
|
248
441
|
/** Extract file path from hook arguments. */
|
|
249
442
|
export function extractFilePath(args: Record<string, unknown>): string {
|
|
250
443
|
return (args.filePath as string) || (args.file_path as string) || (args.path as string) || "";
|
|
@@ -271,6 +464,19 @@ export function extractCommand(args: Record<string, unknown>): string {
|
|
|
271
464
|
* @returns Object with all guard functions bound to the merged pattern sets.
|
|
272
465
|
*/
|
|
273
466
|
export function createCredentialGuard(config?: CredentialGuardConfig): CredentialGuard {
|
|
467
|
+
const names = config?.allowedEnvironmentVariables;
|
|
468
|
+
if (
|
|
469
|
+
names !== undefined &&
|
|
470
|
+
(!Array.isArray(names) || names.some((name) => typeof name !== "string"))
|
|
471
|
+
) {
|
|
472
|
+
throw new Error("allowedEnvironmentVariables must be an array of strings");
|
|
473
|
+
}
|
|
474
|
+
const allowedEnvironmentVariables = new Set(names ?? []);
|
|
475
|
+
for (const name of allowedEnvironmentVariables) {
|
|
476
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name)) {
|
|
477
|
+
throw new Error(`Invalid allowed environment variable name: ${JSON.stringify(name)}`);
|
|
478
|
+
}
|
|
479
|
+
}
|
|
274
480
|
const blockedPathPatterns = [
|
|
275
481
|
...DEFAULT_BLOCKED_PATH_PATTERNS,
|
|
276
482
|
...(config?.additionalBlockedPaths ?? []).map((p) => new RegExp(p)),
|
|
@@ -332,7 +538,10 @@ export function createCredentialGuard(config?: CredentialGuardConfig): Credentia
|
|
|
332
538
|
}
|
|
333
539
|
|
|
334
540
|
function isDangerousBashCommand(command: string): boolean {
|
|
335
|
-
return
|
|
541
|
+
return (
|
|
542
|
+
dangerousBashPatterns.some((pattern) => pattern.test(command)) ||
|
|
543
|
+
hasEnvironmentRead(command, allowedEnvironmentVariables)
|
|
544
|
+
);
|
|
336
545
|
}
|
|
337
546
|
|
|
338
547
|
function isGhCommandAllowed(command: string): boolean {
|
package/src/db-tool/index.ts
CHANGED
|
@@ -60,19 +60,19 @@ const resolveEnv = (envOption: Option.Option<string>) =>
|
|
|
60
60
|
const sqlCommand = Command.make(
|
|
61
61
|
"sql",
|
|
62
62
|
{
|
|
63
|
-
env: Flag.optional(Flag.
|
|
63
|
+
env: Flag.optional(Flag.String("env")).pipe(
|
|
64
64
|
Flag.withDescription(
|
|
65
65
|
"Target database environment name (e.g. local, test, prod). Falls back to defaultEnvironment in config.",
|
|
66
66
|
),
|
|
67
67
|
),
|
|
68
|
-
sql: Flag.
|
|
69
|
-
limit: Flag.optional(Flag.
|
|
68
|
+
sql: Flag.String("sql").pipe(Flag.withDescription("SQL query to execute")),
|
|
69
|
+
limit: Flag.optional(Flag.Int("limit")).pipe(
|
|
70
70
|
Flag.withDescription(
|
|
71
71
|
"Max rows to return (default 50). Use 0 for no cap. Prefer a SQL LIMIT for large tables.",
|
|
72
72
|
),
|
|
73
73
|
),
|
|
74
74
|
format: formatOption,
|
|
75
|
-
profile: Flag.optional(Flag.
|
|
75
|
+
profile: Flag.optional(Flag.String("profile")).pipe(
|
|
76
76
|
Flag.withDescription("Database profile name from agent-tools.json5 (if multiple configured)"),
|
|
77
77
|
),
|
|
78
78
|
},
|
|
@@ -88,22 +88,22 @@ const sqlCommand = Command.make(
|
|
|
88
88
|
const schemaCommand = Command.make(
|
|
89
89
|
"schema",
|
|
90
90
|
{
|
|
91
|
-
env: Flag.optional(Flag.
|
|
91
|
+
env: Flag.optional(Flag.String("env")).pipe(
|
|
92
92
|
Flag.withDescription(
|
|
93
93
|
"Target database environment name (e.g. local, test, prod). Falls back to defaultEnvironment in config.",
|
|
94
94
|
),
|
|
95
95
|
),
|
|
96
|
-
mode: Flag.
|
|
96
|
+
mode: Flag.Literals("mode", ["tables", "columns", "full", "relationships"]).pipe(
|
|
97
97
|
Flag.withDescription(
|
|
98
98
|
"Schema introspection mode: tables (list all), columns (show columns for --table), full (all tables with columns), relationships (foreign keys)",
|
|
99
99
|
),
|
|
100
100
|
),
|
|
101
|
-
table: Flag.
|
|
101
|
+
table: Flag.String("table").pipe(
|
|
102
102
|
Flag.withDescription("Table name (required for --mode columns)"),
|
|
103
103
|
Flag.optional,
|
|
104
104
|
),
|
|
105
105
|
format: formatOption,
|
|
106
|
-
profile: Flag.optional(Flag.
|
|
106
|
+
profile: Flag.optional(Flag.String("profile")).pipe(
|
|
107
107
|
Flag.withDescription("Database profile name from agent-tools.json5 (if multiple configured)"),
|
|
108
108
|
),
|
|
109
109
|
},
|
|
@@ -124,7 +124,7 @@ const envsCommand = Command.make(
|
|
|
124
124
|
"envs",
|
|
125
125
|
{
|
|
126
126
|
format: formatOption,
|
|
127
|
-
profile: Flag.optional(Flag.
|
|
127
|
+
profile: Flag.optional(Flag.String("profile")).pipe(
|
|
128
128
|
Flag.withDescription("Database profile name from agent-tools.json5 (if multiple configured)"),
|
|
129
129
|
),
|
|
130
130
|
},
|
|
@@ -179,16 +179,16 @@ const makeDiagnosticCommand = (name: string, description: string, sql: string) =
|
|
|
179
179
|
Command.make(
|
|
180
180
|
name,
|
|
181
181
|
{
|
|
182
|
-
env: Flag.optional(Flag.
|
|
182
|
+
env: Flag.optional(Flag.String("env")).pipe(
|
|
183
183
|
Flag.withDescription(
|
|
184
184
|
"Target database environment name (e.g. local, test, prod). Falls back to defaultEnvironment in config.",
|
|
185
185
|
),
|
|
186
186
|
),
|
|
187
|
-
limit: Flag.optional(Flag.
|
|
187
|
+
limit: Flag.optional(Flag.Int("limit")).pipe(
|
|
188
188
|
Flag.withDescription("Max rows to return (default 50). Use 0 for no cap."),
|
|
189
189
|
),
|
|
190
190
|
format: formatOption,
|
|
191
|
-
profile: Flag.optional(Flag.
|
|
191
|
+
profile: Flag.optional(Flag.String("profile")).pipe(
|
|
192
192
|
Flag.withDescription(
|
|
193
193
|
"Database profile name from agent-tools.json5 (if multiple configured)",
|
|
194
194
|
),
|
package/src/gh-tool/branch.ts
CHANGED
|
@@ -63,14 +63,14 @@ export const renameBranch = Effect.fn("branch.renameBranch")(function* (opts: {
|
|
|
63
63
|
export const branchRenameCommand = Command.make(
|
|
64
64
|
"rename",
|
|
65
65
|
{
|
|
66
|
-
confirm: Flag.
|
|
66
|
+
confirm: Flag.Boolean("confirm").pipe(
|
|
67
67
|
Flag.withDescription("Actually rename (without this flag, only shows dry-run)"),
|
|
68
68
|
Flag.withDefault(false),
|
|
69
69
|
),
|
|
70
70
|
format: formatOption,
|
|
71
|
-
newName: Flag.
|
|
72
|
-
oldName: Flag.
|
|
73
|
-
repo: Flag.
|
|
71
|
+
newName: Flag.String("new-name").pipe(Flag.withDescription("New branch name")),
|
|
72
|
+
oldName: Flag.String("old-name").pipe(Flag.withDescription("Current branch name to rename")),
|
|
73
|
+
repo: Flag.String("repo").pipe(
|
|
74
74
|
Flag.withDescription("Target repository (owner/name). Defaults to current repo"),
|
|
75
75
|
Flag.optional,
|
|
76
76
|
),
|
package/src/gh-tool/gist.ts
CHANGED
|
@@ -402,11 +402,11 @@ export const gistListCommand = Command.make(
|
|
|
402
402
|
"list",
|
|
403
403
|
{
|
|
404
404
|
format: formatOption,
|
|
405
|
-
limit: Flag.
|
|
405
|
+
limit: Flag.Int("limit").pipe(
|
|
406
406
|
Flag.withDescription("Maximum number of gists to return"),
|
|
407
407
|
Flag.withDefault(10),
|
|
408
408
|
),
|
|
409
|
-
visibility: Flag.
|
|
409
|
+
visibility: Flag.Literals("visibility", ["public", "secret"]).pipe(
|
|
410
410
|
Flag.withDescription("Filter by visibility: public or secret"),
|
|
411
411
|
Flag.optional,
|
|
412
412
|
),
|
|
@@ -424,13 +424,13 @@ export const gistListCommand = Command.make(
|
|
|
424
424
|
export const gistViewCommand = Command.make(
|
|
425
425
|
"view",
|
|
426
426
|
{
|
|
427
|
-
filename: Flag.
|
|
427
|
+
filename: Flag.String("filename").pipe(
|
|
428
428
|
Flag.withDescription("Return only this file from the gist"),
|
|
429
429
|
Flag.optional,
|
|
430
430
|
),
|
|
431
431
|
format: formatOption,
|
|
432
|
-
id: Flag.
|
|
433
|
-
metadataOnly: Flag.
|
|
432
|
+
id: Flag.String("id").pipe(Flag.withDescription("Gist id or URL")),
|
|
433
|
+
metadataOnly: Flag.Boolean("metadata-only").pipe(
|
|
434
434
|
Flag.withDescription("Omit file contents"),
|
|
435
435
|
Flag.withDefault(false),
|
|
436
436
|
),
|
|
@@ -450,27 +450,27 @@ export const gistViewCommand = Command.make(
|
|
|
450
450
|
export const gistCreateCommand = Command.make(
|
|
451
451
|
"create",
|
|
452
452
|
{
|
|
453
|
-
body: Flag.
|
|
453
|
+
body: Flag.String("body").pipe(
|
|
454
454
|
Flag.withDescription("Inline gist content (requires --filename)"),
|
|
455
455
|
Flag.optional,
|
|
456
456
|
),
|
|
457
|
-
bodyFile: Flag.
|
|
457
|
+
bodyFile: Flag.String("body-file").pipe(
|
|
458
458
|
Flag.withDescription(
|
|
459
459
|
"Read gist content from a file path or '-' for stdin (requires --filename)",
|
|
460
460
|
),
|
|
461
461
|
Flag.optional,
|
|
462
462
|
),
|
|
463
|
-
desc: Flag.
|
|
464
|
-
filename: Flag.
|
|
463
|
+
desc: Flag.String("desc").pipe(Flag.withDescription("Gist description"), Flag.optional),
|
|
464
|
+
filename: Flag.String("filename").pipe(
|
|
465
465
|
Flag.withDescription("File name used for --body/--body-file content"),
|
|
466
466
|
Flag.optional,
|
|
467
467
|
),
|
|
468
|
-
files: Flag.
|
|
468
|
+
files: Flag.String("files").pipe(
|
|
469
469
|
Flag.withDescription("Comma-separated paths of existing files to upload"),
|
|
470
470
|
Flag.optional,
|
|
471
471
|
),
|
|
472
472
|
format: formatOption,
|
|
473
|
-
public: Flag.
|
|
473
|
+
public: Flag.Boolean("public").pipe(
|
|
474
474
|
Flag.withDescription("Publish as a public gist (gists are secret by default)"),
|
|
475
475
|
Flag.withDefault(false),
|
|
476
476
|
),
|
|
@@ -526,26 +526,26 @@ export const gistCreateCommand = Command.make(
|
|
|
526
526
|
export const gistEditCommand = Command.make(
|
|
527
527
|
"edit",
|
|
528
528
|
{
|
|
529
|
-
add: Flag.
|
|
529
|
+
add: Flag.String("add").pipe(
|
|
530
530
|
Flag.withDescription("Path of a new file to add to the gist"),
|
|
531
531
|
Flag.optional,
|
|
532
532
|
),
|
|
533
|
-
body: Flag.
|
|
533
|
+
body: Flag.String("body").pipe(
|
|
534
534
|
Flag.withDescription("Replacement content for --filename"),
|
|
535
535
|
Flag.optional,
|
|
536
536
|
),
|
|
537
|
-
bodyFile: Flag.
|
|
537
|
+
bodyFile: Flag.String("body-file").pipe(
|
|
538
538
|
Flag.withDescription("Read replacement content from a file path or '-' for stdin"),
|
|
539
539
|
Flag.optional,
|
|
540
540
|
),
|
|
541
|
-
desc: Flag.
|
|
542
|
-
filename: Flag.
|
|
541
|
+
desc: Flag.String("desc").pipe(Flag.withDescription("New gist description"), Flag.optional),
|
|
542
|
+
filename: Flag.String("filename").pipe(
|
|
543
543
|
Flag.withDescription("Gist file to replace with --body/--body-file"),
|
|
544
544
|
Flag.optional,
|
|
545
545
|
),
|
|
546
546
|
format: formatOption,
|
|
547
|
-
id: Flag.
|
|
548
|
-
remove: Flag.
|
|
547
|
+
id: Flag.String("id").pipe(Flag.withDescription("Gist id or URL")),
|
|
548
|
+
remove: Flag.String("remove").pipe(
|
|
549
549
|
Flag.withDescription("File name to remove from the gist"),
|
|
550
550
|
Flag.optional,
|
|
551
551
|
),
|
|
@@ -609,12 +609,12 @@ export const gistEditCommand = Command.make(
|
|
|
609
609
|
export const gistDeleteCommand = Command.make(
|
|
610
610
|
"delete",
|
|
611
611
|
{
|
|
612
|
-
confirm: Flag.
|
|
612
|
+
confirm: Flag.Boolean("confirm").pipe(
|
|
613
613
|
Flag.withDescription("Actually delete the gist (without this flag, only shows dry-run)"),
|
|
614
614
|
Flag.withDefault(false),
|
|
615
615
|
),
|
|
616
616
|
format: formatOption,
|
|
617
|
-
id: Flag.
|
|
617
|
+
id: Flag.String("id").pipe(Flag.withDescription("Gist id or URL")),
|
|
618
618
|
},
|
|
619
619
|
({ confirm, format, id }) =>
|
|
620
620
|
Effect.gen(function* () {
|