dsh-agy-cli 0.0.0-stage → 0.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.
Files changed (91) hide show
  1. package/LICENSE +30 -0
  2. package/README.en.md +27 -0
  3. package/README.md +94 -2
  4. package/agents/dsh-agy-image-view.md +17 -0
  5. package/agents/dsh-agy-mcp-executor.md +32 -0
  6. package/agents/dsh-agy-read-only.md +20 -0
  7. package/agents/dsh-agy-tool-free.md +16 -0
  8. package/agents/dsh-agy-workspace-write.md +23 -0
  9. package/bin/dsh-agy-cli.js +9 -0
  10. package/bin/win32-x64/agy-launcher.exe +0 -0
  11. package/cordis.patch.yml +16 -0
  12. package/dist/client.js +4259 -0
  13. package/lib/agent-installer.d.ts +66 -0
  14. package/lib/agent-installer.js +147 -0
  15. package/lib/agent-presets.d.ts +42 -0
  16. package/lib/agent-presets.js +130 -0
  17. package/lib/agents-cli.d.ts +3 -0
  18. package/lib/agents-cli.js +110 -0
  19. package/lib/agy/diagnostics.d.ts +29 -0
  20. package/lib/agy/diagnostics.js +109 -0
  21. package/lib/agy/errors.d.ts +20 -0
  22. package/lib/agy/errors.js +77 -0
  23. package/lib/agy/experimental-transport.d.ts +79 -0
  24. package/lib/agy/experimental-transport.js +612 -0
  25. package/lib/agy/keepalive.d.ts +140 -0
  26. package/lib/agy/keepalive.js +424 -0
  27. package/lib/agy/limiter.d.ts +32 -0
  28. package/lib/agy/limiter.js +111 -0
  29. package/lib/agy/log.d.ts +214 -0
  30. package/lib/agy/log.js +503 -0
  31. package/lib/agy/models.d.ts +49 -0
  32. package/lib/agy/models.js +203 -0
  33. package/lib/agy/overrides.d.ts +17 -0
  34. package/lib/agy/overrides.js +73 -0
  35. package/lib/agy/parser.d.ts +102 -0
  36. package/lib/agy/parser.js +492 -0
  37. package/lib/agy/persistent-transport.d.ts +101 -0
  38. package/lib/agy/persistent-transport.js +753 -0
  39. package/lib/agy/process.d.ts +144 -0
  40. package/lib/agy/process.js +452 -0
  41. package/lib/agy/redact.d.ts +3 -0
  42. package/lib/agy/redact.js +24 -0
  43. package/lib/agy/stream-protocol.d.ts +50 -0
  44. package/lib/agy/stream-protocol.js +156 -0
  45. package/lib/agy/windows-launcher.d.ts +21 -0
  46. package/lib/agy/windows-launcher.js +76 -0
  47. package/lib/diagnostics.d.ts +78 -0
  48. package/lib/diagnostics.js +162 -0
  49. package/lib/doctor.d.ts +138 -0
  50. package/lib/doctor.js +923 -0
  51. package/lib/dsh/context.d.ts +133 -0
  52. package/lib/dsh/context.js +352 -0
  53. package/lib/dsh-home.d.ts +6 -0
  54. package/lib/dsh-home.js +11 -0
  55. package/lib/index.d.ts +43 -0
  56. package/lib/index.js +256 -0
  57. package/lib/mcp/agy-registration.d.ts +70 -0
  58. package/lib/mcp/agy-registration.js +245 -0
  59. package/lib/mcp/bridge.d.ts +73 -0
  60. package/lib/mcp/bridge.js +295 -0
  61. package/lib/mcp/endpoint.d.ts +41 -0
  62. package/lib/mcp/endpoint.js +142 -0
  63. package/lib/mcp/protocol.d.ts +107 -0
  64. package/lib/mcp/protocol.js +197 -0
  65. package/lib/mcp/tool-projection.d.ts +64 -0
  66. package/lib/mcp/tool-projection.js +168 -0
  67. package/lib/provider/agy.d.ts +153 -0
  68. package/lib/provider/agy.js +2159 -0
  69. package/lib/provider/config.d.ts +226 -0
  70. package/lib/provider/config.js +294 -0
  71. package/lib/provider/error-codes.d.ts +22 -0
  72. package/lib/provider/error-codes.js +29 -0
  73. package/lib/provider/image-bridge.d.ts +60 -0
  74. package/lib/provider/image-bridge.js +290 -0
  75. package/lib/provider/mock.d.ts +15 -0
  76. package/lib/provider/mock.js +78 -0
  77. package/lib/provider/prompt-budget.d.ts +45 -0
  78. package/lib/provider/prompt-budget.js +596 -0
  79. package/lib/provider/serialize.d.ts +15 -0
  80. package/lib/provider/serialize.js +82 -0
  81. package/lib/provider/tool-protocol.d.ts +80 -0
  82. package/lib/provider/tool-protocol.js +848 -0
  83. package/lib/provider/tool-schema-file.d.ts +11 -0
  84. package/lib/provider/tool-schema-file.js +27 -0
  85. package/lib/session/file-store.d.ts +56 -0
  86. package/lib/session/file-store.js +186 -0
  87. package/lib/session/store.d.ts +49 -0
  88. package/lib/session/store.js +62 -0
  89. package/lib/web/router.d.ts +19 -0
  90. package/lib/web/router.js +906 -0
  91. package/package.json +112 -4
@@ -0,0 +1,66 @@
1
+ import { type AgentPreset, type AgentPresetId } from './agent-presets.js';
2
+ /**
3
+ * Where the AGY CLI discovers agent templates.
4
+ *
5
+ * This is `gemini-root-config/agents`, not `~/.gemini/config/agents`: the CLI
6
+ * keeps a second, differently-populated `agents` directory under its data root
7
+ * and only scans this one (`agy agents` lists a template added here and ignores
8
+ * one removed from the other). `DSH_AGY_CONFIG_ROOT` mirrors
9
+ * {@link agyConfigRoot} in `mcp/agy-registration.ts`, so pointing AGY at
10
+ * another root moves both the MCP table and the templates together.
11
+ */
12
+ export declare const DEFAULT_AGENT_DIRECTORY: string;
13
+ export type AgentInstallAction = 'create' | 'unchanged' | 'conflict' | 'backup-and-create';
14
+ export declare class AgentInstallError extends Error {
15
+ readonly code: 'AGENT_TARGET_EXISTS' | 'AGENT_TARGET_NOT_FILE' | 'AGENT_INSTALL_FAILED';
16
+ constructor(message: string, code: 'AGENT_TARGET_EXISTS' | 'AGENT_TARGET_NOT_FILE' | 'AGENT_INSTALL_FAILED', options?: ErrorOptions);
17
+ }
18
+ export interface AgentInstallOptions {
19
+ preset: AgentPresetId | string;
20
+ directory?: string;
21
+ apply?: boolean;
22
+ backup?: boolean;
23
+ }
24
+ export interface AgentInstallResult {
25
+ preset: AgentPresetId;
26
+ agentName: string;
27
+ targetPath: string;
28
+ applied: boolean;
29
+ action: AgentInstallAction;
30
+ backupPath?: string;
31
+ }
32
+ /**
33
+ * Preview or explicitly install one bundled Agent template.
34
+ * Existing files are never overwritten unless both apply and backup are set.
35
+ */
36
+ export declare function installAgentPreset(options: AgentInstallOptions): Promise<AgentInstallResult>;
37
+ export interface AgentDirectoryEntry {
38
+ id: AgentPresetId;
39
+ agentName: string;
40
+ fileName: string;
41
+ description: string;
42
+ tools: readonly string[];
43
+ writeAccess: boolean;
44
+ }
45
+ /** One bundled template that is not present in the directory AGY scans. */
46
+ export interface MissingAgentTemplate {
47
+ readonly preset: AgentPresetId;
48
+ readonly agentName: string;
49
+ readonly targetPath: string;
50
+ }
51
+ /**
52
+ * Which bundled templates are absent from the AGY agent directory.
53
+ *
54
+ * AGY refuses an unknown profile before it accepts any input, exiting with
55
+ * `error: unknown agent "<name>" (available: ...)`; the adapter then sees a
56
+ * child that died while its stdin was being written and reports
57
+ * `AGY_INPUT_FAILED`, which names the wrong component. Checking the directory up
58
+ * front turns that into one actionable line.
59
+ *
60
+ * Only presence is checked, never the contents: a user who edited their copy of
61
+ * a template meant to, and overwriting it is not this function's business.
62
+ */
63
+ export declare function findMissingAgentTemplates(presets: readonly AgentPreset[], directory?: string): Promise<readonly MissingAgentTemplate[]>;
64
+ export declare function describeAgentPresets(): readonly AgentDirectoryEntry[];
65
+ export declare function isAgentPreset(value: unknown): value is AgentPresetId;
66
+ //# sourceMappingURL=agent-installer.d.ts.map
@@ -0,0 +1,147 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { lstat, mkdir, readFile, rename, unlink, writeFile } from 'node:fs/promises';
3
+ import { homedir } from 'node:os';
4
+ import { basename, join, resolve } from 'node:path';
5
+ import { getAgentPreset, listAgentPresets, readAgentPresetTemplate, requireAgentPreset, } from './agent-presets.js';
6
+ /**
7
+ * Where the AGY CLI discovers agent templates.
8
+ *
9
+ * This is `gemini-root-config/agents`, not `~/.gemini/config/agents`: the CLI
10
+ * keeps a second, differently-populated `agents` directory under its data root
11
+ * and only scans this one (`agy agents` lists a template added here and ignores
12
+ * one removed from the other). `DSH_AGY_CONFIG_ROOT` mirrors
13
+ * {@link agyConfigRoot} in `mcp/agy-registration.ts`, so pointing AGY at
14
+ * another root moves both the MCP table and the templates together.
15
+ */
16
+ export const DEFAULT_AGENT_DIRECTORY = join(process.env.DSH_AGY_CONFIG_ROOT?.trim() || join(homedir(), '.gemini', 'antigravity-cli', 'gemini-root-config'), 'agents');
17
+ export class AgentInstallError extends Error {
18
+ code;
19
+ constructor(message, code, options) {
20
+ super(message, options);
21
+ this.code = code;
22
+ this.name = 'AgentInstallError';
23
+ }
24
+ }
25
+ function configuredAgentDirectory(directory) {
26
+ const selected = directory?.trim() || process.env.AGY_AGENT_DIR?.trim() || DEFAULT_AGENT_DIRECTORY;
27
+ return resolve(selected);
28
+ }
29
+ async function targetState(targetPath) {
30
+ try {
31
+ const stat = await lstat(targetPath);
32
+ if (!stat.isFile())
33
+ return { exists: true, file: false };
34
+ return { exists: true, file: true, content: await readFile(targetPath, 'utf8') };
35
+ }
36
+ catch (error) {
37
+ if (error instanceof Error && 'code' in error && error.code === 'ENOENT')
38
+ return { exists: false };
39
+ throw error;
40
+ }
41
+ }
42
+ function backupPathFor(targetPath) {
43
+ return `${targetPath}.backup-${new Date().toISOString().replace(/[:.]/g, '-')}-${randomUUID()}`;
44
+ }
45
+ async function writeAtomic(targetPath, content) {
46
+ const directory = resolve(targetPath, '..');
47
+ const tempPath = join(directory, `.${basename(targetPath)}.${randomUUID()}.tmp`);
48
+ try {
49
+ await writeFile(tempPath, content, { encoding: 'utf8', flag: 'wx', mode: 0o600 });
50
+ await rename(tempPath, targetPath);
51
+ }
52
+ catch (error) {
53
+ await unlink(tempPath).catch(() => undefined);
54
+ throw new AgentInstallError(`Could not install Agent template: ${error instanceof Error ? error.message : String(error)}`, 'AGENT_INSTALL_FAILED', { cause: error });
55
+ }
56
+ }
57
+ function resultFor(preset, targetPath, applied, action, backupPath) {
58
+ return {
59
+ preset: preset.id,
60
+ agentName: preset.agentName,
61
+ targetPath,
62
+ applied,
63
+ action,
64
+ ...(backupPath === undefined ? {} : { backupPath }),
65
+ };
66
+ }
67
+ /**
68
+ * Preview or explicitly install one bundled Agent template.
69
+ * Existing files are never overwritten unless both apply and backup are set.
70
+ */
71
+ export async function installAgentPreset(options) {
72
+ const preset = requireAgentPreset(options.preset);
73
+ const directory = configuredAgentDirectory(options.directory);
74
+ const targetPath = join(directory, preset.fileName);
75
+ const content = await readAgentPresetTemplate(preset);
76
+ const state = await targetState(targetPath);
77
+ if (state.exists && !state.file) {
78
+ throw new AgentInstallError(`Agent target is not a regular file: ${targetPath}`, 'AGENT_TARGET_NOT_FILE');
79
+ }
80
+ if (state.exists && state.content === content) {
81
+ // Nothing was written, so `applied` must stay false even under --apply:
82
+ // the CLI would otherwise report "applied" for a no-op.
83
+ return resultFor(preset, targetPath, false, 'unchanged');
84
+ }
85
+ if (state.exists && options.apply !== true) {
86
+ return resultFor(preset, targetPath, false, 'conflict');
87
+ }
88
+ if (state.exists && options.backup !== true) {
89
+ throw new AgentInstallError(`Agent target already exists; preview first, then use --backup to preserve it: ${targetPath}`, 'AGENT_TARGET_EXISTS');
90
+ }
91
+ if (options.apply !== true) {
92
+ return resultFor(preset, targetPath, false, 'create');
93
+ }
94
+ await mkdir(directory, { recursive: true });
95
+ let backupPath;
96
+ if (state.exists) {
97
+ backupPath = backupPathFor(targetPath);
98
+ await rename(targetPath, backupPath);
99
+ }
100
+ try {
101
+ await writeAtomic(targetPath, content);
102
+ }
103
+ catch (error) {
104
+ if (backupPath !== undefined) {
105
+ await rename(backupPath, targetPath).catch(() => undefined);
106
+ }
107
+ throw error;
108
+ }
109
+ return resultFor(preset, targetPath, true, backupPath === undefined ? 'create' : 'backup-and-create', backupPath);
110
+ }
111
+ /**
112
+ * Which bundled templates are absent from the AGY agent directory.
113
+ *
114
+ * AGY refuses an unknown profile before it accepts any input, exiting with
115
+ * `error: unknown agent "<name>" (available: ...)`; the adapter then sees a
116
+ * child that died while its stdin was being written and reports
117
+ * `AGY_INPUT_FAILED`, which names the wrong component. Checking the directory up
118
+ * front turns that into one actionable line.
119
+ *
120
+ * Only presence is checked, never the contents: a user who edited their copy of
121
+ * a template meant to, and overwriting it is not this function's business.
122
+ */
123
+ export async function findMissingAgentTemplates(presets, directory) {
124
+ const root = configuredAgentDirectory(directory);
125
+ const missing = [];
126
+ for (const preset of presets) {
127
+ const targetPath = join(root, preset.fileName);
128
+ const state = await targetState(targetPath);
129
+ if (!state.exists || !state.file)
130
+ missing.push({ preset: preset.id, agentName: preset.agentName, targetPath });
131
+ }
132
+ return missing;
133
+ }
134
+ export function describeAgentPresets() {
135
+ return listAgentPresets().map(preset => ({
136
+ id: preset.id,
137
+ agentName: preset.agentName,
138
+ fileName: preset.fileName,
139
+ description: preset.description,
140
+ tools: preset.tools,
141
+ writeAccess: preset.writeAccess,
142
+ }));
143
+ }
144
+ export function isAgentPreset(value) {
145
+ return getAgentPreset(value) !== undefined;
146
+ }
147
+ //# sourceMappingURL=agent-installer.js.map
@@ -0,0 +1,42 @@
1
+ export declare const AGENT_PRESET_IDS: readonly ["tool-free", "mcp-executor", "image-view", "read-only", "workspace-write"];
2
+ export type AgentPresetId = typeof AGENT_PRESET_IDS[number];
3
+ export type AgentExecutionMode = 'plan' | 'accept-edits';
4
+ export interface AgentPreset {
5
+ readonly id: AgentPresetId;
6
+ readonly agentName: string;
7
+ readonly fileName: string;
8
+ readonly description: string;
9
+ readonly tools: readonly string[];
10
+ readonly writeAccess: boolean;
11
+ readonly mode: AgentExecutionMode | undefined;
12
+ }
13
+ export declare function listAgentPresets(): readonly AgentPreset[];
14
+ export declare function getAgentPreset(value: unknown): AgentPreset | undefined;
15
+ export declare function requireAgentPreset(value: string): AgentPreset;
16
+ /**
17
+ * The Agent templates one request can reach, given the effective settings.
18
+ *
19
+ * AGY resolves `--agent` itself and exits with `unknown agent` when the named
20
+ * profile has no template on disk, so the set that must exist is derivable from
21
+ * the config without starting anything:
22
+ *
23
+ * - `dsh-owned` pins the executor preset, but an image-bearing request swaps in
24
+ * the image preset, so an enabled `imageInput: experimental` adds it;
25
+ * - every other tool policy uses the configured `agent` string verbatim. That
26
+ * string is matched against the bundled names first, because a user who typed
27
+ * `dsh-agy-read-only` is naming a bundled template and deserves the same
28
+ * presence check; an unmatched name is returned with no preset so callers can
29
+ * see it is the user's own template to keep in place.
30
+ */
31
+ export declare function agentTemplatesForRequest(options: {
32
+ readonly toolPolicy: 'reject' | 'agy-owned' | 'dsh-owned';
33
+ readonly agentPreset?: AgentPresetId | undefined;
34
+ readonly agent?: string | undefined;
35
+ readonly imageInput?: 'off' | 'experimental' | undefined;
36
+ }): readonly {
37
+ readonly preset: AgentPreset | undefined;
38
+ readonly agentName: string;
39
+ }[];
40
+ /** Read a bundled template; the template file is included in the npm tarball. */
41
+ export declare function readAgentPresetTemplate(value: AgentPresetId | AgentPreset): Promise<string>;
42
+ //# sourceMappingURL=agent-presets.d.ts.map
@@ -0,0 +1,130 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ export const AGENT_PRESET_IDS = ['tool-free', 'mcp-executor', 'image-view', 'read-only', 'workspace-write'];
3
+ const PRESETS = Object.freeze({
4
+ 'tool-free': Object.freeze({
5
+ id: 'tool-free',
6
+ agentName: 'dsh-agy-tool-free',
7
+ fileName: 'dsh-agy-tool-free.md',
8
+ description: 'Text-only reasoning with no AGY tools.',
9
+ tools: Object.freeze([]),
10
+ writeAccess: false,
11
+ mode: undefined,
12
+ }),
13
+ 'mcp-executor': Object.freeze({
14
+ id: 'mcp-executor',
15
+ agentName: 'dsh-agy-mcp-executor',
16
+ fileName: 'dsh-agy-mcp-executor.md',
17
+ // `excludeDefaultComponents: true` in the template is what removes the
18
+ // built-in tools; `tools: []` alone does not. Measured against AGY 1.3.2:
19
+ // an otherwise identical template carrying only `tools: []` still exposed
20
+ // all 60 built-ins, and the model called `run_command`, which headless mode
21
+ // then soft-denied, producing an empty response. A tool that does not exist
22
+ // cannot be called, which is the only reliable way to keep AGY's own
23
+ // executors out of the loop — an agent template that merely asks the model
24
+ // not to use them is bypassed probabilistically.
25
+ description: 'All actions through the DSH MCP server; no AGY built-in tools.',
26
+ tools: Object.freeze([]),
27
+ writeAccess: false,
28
+ mode: undefined,
29
+ }),
30
+ 'image-view': Object.freeze({
31
+ id: 'image-view',
32
+ agentName: 'dsh-agy-image-view',
33
+ fileName: 'dsh-agy-image-view.md',
34
+ description: 'Per-request image inspection with only view_file enabled.',
35
+ tools: Object.freeze(['view_file']),
36
+ writeAccess: false,
37
+ mode: 'plan',
38
+ }),
39
+ 'read-only': Object.freeze({
40
+ id: 'read-only',
41
+ agentName: 'dsh-agy-read-only',
42
+ fileName: 'dsh-agy-read-only.md',
43
+ description: 'Read-only workspace inspection and search.',
44
+ tools: Object.freeze(['find_by_name', 'grep_search', 'view_file', 'list_dir']),
45
+ writeAccess: false,
46
+ mode: 'plan',
47
+ }),
48
+ 'workspace-write': Object.freeze({
49
+ id: 'workspace-write',
50
+ agentName: 'dsh-agy-workspace-write',
51
+ fileName: 'dsh-agy-workspace-write.md',
52
+ description: 'Bounded file edits inside an explicitly configured workspace.',
53
+ tools: Object.freeze([
54
+ 'find_by_name',
55
+ 'grep_search',
56
+ 'view_file',
57
+ 'list_dir',
58
+ 'multi_replace_file_content',
59
+ 'replace_file_content',
60
+ 'write_to_file',
61
+ ]),
62
+ writeAccess: true,
63
+ mode: 'accept-edits',
64
+ }),
65
+ });
66
+ export function listAgentPresets() {
67
+ return AGENT_PRESET_IDS.map(id => PRESETS[id]);
68
+ }
69
+ export function getAgentPreset(value) {
70
+ if (typeof value !== 'string')
71
+ return undefined;
72
+ return AGENT_PRESET_IDS.includes(value)
73
+ ? PRESETS[value]
74
+ : undefined;
75
+ }
76
+ export function requireAgentPreset(value) {
77
+ const preset = getAgentPreset(value);
78
+ if (preset === undefined) {
79
+ throw new TypeError(`Unknown Agent preset: ${value}. Expected one of: ${AGENT_PRESET_IDS.join(', ')}`);
80
+ }
81
+ return preset;
82
+ }
83
+ /**
84
+ * The Agent templates one request can reach, given the effective settings.
85
+ *
86
+ * AGY resolves `--agent` itself and exits with `unknown agent` when the named
87
+ * profile has no template on disk, so the set that must exist is derivable from
88
+ * the config without starting anything:
89
+ *
90
+ * - `dsh-owned` pins the executor preset, but an image-bearing request swaps in
91
+ * the image preset, so an enabled `imageInput: experimental` adds it;
92
+ * - every other tool policy uses the configured `agent` string verbatim. That
93
+ * string is matched against the bundled names first, because a user who typed
94
+ * `dsh-agy-read-only` is naming a bundled template and deserves the same
95
+ * presence check; an unmatched name is returned with no preset so callers can
96
+ * see it is the user's own template to keep in place.
97
+ */
98
+ export function agentTemplatesForRequest(options) {
99
+ const names = [];
100
+ const push = (preset, agentName) => {
101
+ if (agentName === undefined || names.some(entry => entry.agentName === agentName))
102
+ return;
103
+ names.push({ preset, agentName });
104
+ };
105
+ if (options.toolPolicy === 'dsh-owned') {
106
+ const executor = getAgentPreset('mcp-executor');
107
+ push(executor, executor?.agentName);
108
+ if (options.imageInput === 'experimental') {
109
+ const image = getAgentPreset('image-view');
110
+ push(image, image?.agentName);
111
+ }
112
+ return names;
113
+ }
114
+ const byPresetId = options.agentPreset === undefined ? undefined : getAgentPreset(options.agentPreset);
115
+ if (byPresetId !== undefined) {
116
+ push(byPresetId, byPresetId.agentName);
117
+ return names;
118
+ }
119
+ const byName = options.agent === undefined
120
+ ? undefined
121
+ : listAgentPresets().find(preset => preset.agentName === options.agent || preset.id === options.agent);
122
+ push(byName, options.agent);
123
+ return names;
124
+ }
125
+ /** Read a bundled template; the template file is included in the npm tarball. */
126
+ export async function readAgentPresetTemplate(value) {
127
+ const preset = typeof value === 'string' ? requireAgentPreset(value) : value;
128
+ return readFile(new URL(`../agents/${preset.fileName}`, import.meta.url), 'utf8');
129
+ }
130
+ //# sourceMappingURL=agent-presets.js.map
@@ -0,0 +1,3 @@
1
+ export declare function formatAgentsHelp(): string;
2
+ export declare function runAgentsCli(argv: readonly string[], stdout?: (text: string) => void, stderr?: (text: string) => void): Promise<number>;
3
+ //# sourceMappingURL=agents-cli.d.ts.map
@@ -0,0 +1,110 @@
1
+ import { AgentInstallError, describeAgentPresets, installAgentPreset, } from './agent-installer.js';
2
+ import { AGENT_PRESET_IDS } from './agent-presets.js';
3
+ function optionValue(args, name) {
4
+ const index = args.indexOf(name);
5
+ if (index === -1)
6
+ return undefined;
7
+ const value = args[index + 1];
8
+ if (value === undefined || value.startsWith('--'))
9
+ return undefined;
10
+ return value;
11
+ }
12
+ export function formatAgentsHelp() {
13
+ return `Usage: dsh-agy-cli agents <command> [options]
14
+
15
+ Quota-free Agent template management. Preview is the default; no files are changed without --apply.
16
+
17
+ Commands:
18
+ list List bundled Agent presets
19
+ install <preset> Preview or install one preset
20
+
21
+ Options:
22
+ --dir <path> Agent directory (default: $AGY_AGENT_DIR or the AGY config root's agents/)
23
+ --apply Write the template; omitted means preview only
24
+ --backup Move an existing file aside before --apply
25
+ --json Output structured JSON
26
+ -h, --help Show help information
27
+
28
+ Presets: ${AGENT_PRESET_IDS.join(', ')}
29
+
30
+ Examples:
31
+ npx dsh-agy-cli agents list
32
+ npx dsh-agy-cli agents install read-only --dir ~/.gemini/antigravity-cli/gemini-root-config/agents
33
+ npx dsh-agy-cli agents install workspace-write --apply --backup
34
+ `;
35
+ }
36
+ function formatInstallResult(result) {
37
+ // "unchanged" is neither applied nor a preview: the file already holds the
38
+ // target content, so saying either would mislead the operator.
39
+ const mode = result.action === 'unchanged' ? 'unchanged' : result.applied ? 'applied' : 'preview';
40
+ const lines = [
41
+ `mode: ${mode}`,
42
+ `preset: ${result.preset}`,
43
+ `agent: ${result.agentName}`,
44
+ `target: ${result.targetPath}`,
45
+ `action: ${result.action}`,
46
+ ];
47
+ if (result.backupPath !== undefined)
48
+ lines.push(`backup: ${result.backupPath}`);
49
+ if (!result.applied && result.action === 'create')
50
+ lines.push('next: rerun with --apply to write the file');
51
+ if (!result.applied && result.action === 'conflict')
52
+ lines.push('next: rerun with --apply --backup to preserve and replace the file');
53
+ return lines.join('\n');
54
+ }
55
+ export async function runAgentsCli(argv, stdout = text => process.stdout.write(text), stderr = text => process.stderr.write(text)) {
56
+ const args = [...argv];
57
+ if (args[0] === 'agents' || args[0] === 'agent')
58
+ args.shift();
59
+ if (args.includes('-h') || args.includes('--help')) {
60
+ stdout(`${formatAgentsHelp()}\n`);
61
+ return 0;
62
+ }
63
+ const command = args.shift() ?? 'list';
64
+ const jsonOutput = args.includes('--json');
65
+ if (command === 'list') {
66
+ const entries = describeAgentPresets();
67
+ if (jsonOutput)
68
+ stdout(`${JSON.stringify({ quotaUsed: false, presets: entries }, null, 2)}\n`);
69
+ else {
70
+ stdout(`${entries.map(entry => `${entry.id}: ${entry.agentName} [${entry.tools.join(', ') || 'no tools'}]`).join('\n')}\n`);
71
+ }
72
+ return 0;
73
+ }
74
+ if (command !== 'install') {
75
+ stderr(`Unknown agents command: ${command}. Use --help for usage.\n`);
76
+ return 2;
77
+ }
78
+ const preset = args.shift();
79
+ if (preset === undefined) {
80
+ stderr(`Missing Agent preset. Expected one of: ${AGENT_PRESET_IDS.join(', ')}\n`);
81
+ return 2;
82
+ }
83
+ try {
84
+ const directory = optionValue(args, '--dir');
85
+ const result = await installAgentPreset({
86
+ preset,
87
+ ...(directory === undefined ? {} : { directory }),
88
+ apply: args.includes('--apply'),
89
+ backup: args.includes('--backup'),
90
+ });
91
+ if (jsonOutput)
92
+ stdout(`${JSON.stringify({ quotaUsed: false, ...result }, null, 2)}\n`);
93
+ else
94
+ stdout(`${formatInstallResult(result)}\n`);
95
+ return 0;
96
+ }
97
+ catch (error) {
98
+ const message = error instanceof Error ? error.message : String(error);
99
+ if (jsonOutput) {
100
+ stdout(`${JSON.stringify({ quotaUsed: false, ok: false, error: {
101
+ code: error instanceof AgentInstallError ? error.code : 'AGENT_INSTALL_FAILED',
102
+ message,
103
+ } }, null, 2)}\n`);
104
+ }
105
+ else
106
+ stderr(`Error installing Agent template: ${message}\n`);
107
+ return 1;
108
+ }
109
+ }
110
+ //# sourceMappingURL=agents-cli.js.map
@@ -0,0 +1,29 @@
1
+ import { type ProcessRequest, type ProcessResult } from './process.js';
2
+ export interface AgyDiagnosticOptions {
3
+ executable?: string;
4
+ expectedAgent?: string;
5
+ minimumVersion?: string;
6
+ timeoutMs?: number;
7
+ runCommand?: AgyDiagnosticCommand;
8
+ }
9
+ export type AgyDiagnosticCommand = (request: ProcessRequest) => Promise<ProcessResult>;
10
+ export interface AgyDiagnosticResult {
11
+ ok: boolean;
12
+ /** Safe source label; never contains the resolved user filesystem path. */
13
+ executable: string;
14
+ executableSource: 'explicit' | 'environment' | 'path';
15
+ version: string | undefined;
16
+ agents: readonly string[];
17
+ expectedAgent: string | undefined;
18
+ minimumVersion: string | undefined;
19
+ versionSupported: boolean;
20
+ agentAvailable: boolean;
21
+ errors: readonly string[];
22
+ }
23
+ export declare function parseAgyVersion(output: string): string | undefined;
24
+ export declare function isAgyVersionAtLeast(actual: string, minimum: string): boolean;
25
+ /** Parse the plain one-agent-per-line output of `agy agents`. */
26
+ export declare function parseAgyAgents(output: string): string[];
27
+ /** Check the local AGY executable without spending model quota or invoking tools. */
28
+ export declare function diagnoseAgy(options?: AgyDiagnosticOptions): Promise<AgyDiagnosticResult>;
29
+ //# sourceMappingURL=diagnostics.d.ts.map
@@ -0,0 +1,109 @@
1
+ import { AgyProcessError, resolveAgyExecutable, runProcess } from './process.js';
2
+ import { redactText } from './redact.js';
3
+ function parseVersionValue(value) {
4
+ const match = /(?:^|\D)(\d+)\.(\d+)\.(\d+)(?:$|\D)/.exec(value);
5
+ if (match === null)
6
+ return undefined;
7
+ const major = Number(match[1]);
8
+ const minor = Number(match[2]);
9
+ const patch = Number(match[3]);
10
+ if (![major, minor, patch].every(Number.isSafeInteger))
11
+ return undefined;
12
+ return { major, minor, patch, text: `${major}.${minor}.${patch}` };
13
+ }
14
+ export function parseAgyVersion(output) {
15
+ return parseVersionValue(output)?.text;
16
+ }
17
+ export function isAgyVersionAtLeast(actual, minimum) {
18
+ const actualVersion = parseVersionValue(actual);
19
+ const minimumVersion = parseVersionValue(minimum);
20
+ if (actualVersion === undefined || minimumVersion === undefined)
21
+ return false;
22
+ if (actualVersion.major !== minimumVersion.major)
23
+ return actualVersion.major > minimumVersion.major;
24
+ if (actualVersion.minor !== minimumVersion.minor)
25
+ return actualVersion.minor > minimumVersion.minor;
26
+ return actualVersion.patch >= minimumVersion.patch;
27
+ }
28
+ /** Parse the plain one-agent-per-line output of `agy agents`. */
29
+ export function parseAgyAgents(output) {
30
+ return [...new Set(output
31
+ .split(/\r?\n/)
32
+ .map(line => line.trim())
33
+ .filter(line => /^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(line)))];
34
+ }
35
+ function commandRequest(executable, args, timeoutMs) {
36
+ return timeoutMs === undefined
37
+ ? { executable, args, windowsNoConsole: true }
38
+ : { executable, args, timeoutMs, windowsNoConsole: true };
39
+ }
40
+ function commandFailure(label, result) {
41
+ if (result.termination === 'completed' && result.exitCode === 0)
42
+ return undefined;
43
+ return `${label} failed (${result.termination}, exit code ${result.exitCode ?? 'unknown'})`;
44
+ }
45
+ function executableSource(options) {
46
+ if (options.executable?.trim())
47
+ return 'explicit';
48
+ if (process.env.AGY_PATH?.trim() || process.env.AGY_EXECUTABLE?.trim())
49
+ return 'environment';
50
+ return 'path';
51
+ }
52
+ async function runDiagnosticCommand(label, executable, args, timeoutMs, runCommand) {
53
+ try {
54
+ const result = await runCommand(commandRequest(executable, args, timeoutMs));
55
+ return { result, error: commandFailure(label, result) };
56
+ }
57
+ catch (error) {
58
+ if (error instanceof AgyProcessError) {
59
+ return { result: undefined, error: redactText(error.message, 512) };
60
+ }
61
+ return {
62
+ result: undefined,
63
+ error: `${label} failed: ${redactText(error instanceof Error ? error.message : String(error), 512)}`,
64
+ };
65
+ }
66
+ }
67
+ /** Check the local AGY executable without spending model quota or invoking tools. */
68
+ export async function diagnoseAgy(options = {}) {
69
+ const executable = resolveAgyExecutable(options.executable);
70
+ const source = executableSource(options);
71
+ const runCommand = options.runCommand ?? runProcess;
72
+ const versionCheck = await runDiagnosticCommand('agy --version', executable, ['--version'], options.timeoutMs, runCommand);
73
+ const agentsCheck = await runDiagnosticCommand('agy agents', executable, ['agents'], options.timeoutMs, runCommand);
74
+ const versionOutput = versionCheck.result === undefined
75
+ ? ''
76
+ : versionCheck.result.stdoutLines.join('\n');
77
+ const version = parseAgyVersion(versionOutput);
78
+ const agentsOutput = agentsCheck.result === undefined
79
+ ? ''
80
+ : agentsCheck.result.stdoutLines.join('\n');
81
+ const agents = parseAgyAgents(agentsOutput);
82
+ const versionSupported = options.minimumVersion === undefined
83
+ ? version !== undefined
84
+ : version !== undefined && isAgyVersionAtLeast(version, options.minimumVersion);
85
+ const agentAvailable = options.expectedAgent === undefined || agents.includes(options.expectedAgent);
86
+ const errors = [
87
+ ...(versionCheck.error === undefined ? [] : [versionCheck.error]),
88
+ ...(agentsCheck.error === undefined ? [] : [agentsCheck.error]),
89
+ ...(version === undefined ? ['Unable to parse AGY version from --version output'] : []),
90
+ ...(options.minimumVersion !== undefined && !versionSupported
91
+ ? [`AGY version ${version ?? 'unknown'} is below the required minimum ${options.minimumVersion}`]
92
+ : []),
93
+ ...(options.expectedAgent !== undefined && !agentAvailable
94
+ ? [`AGY Agent "${redactText(options.expectedAgent, 128)}" was not found`] : []),
95
+ ];
96
+ return {
97
+ ok: errors.length === 0,
98
+ executable: source,
99
+ executableSource: source,
100
+ version,
101
+ agents,
102
+ expectedAgent: options.expectedAgent,
103
+ minimumVersion: options.minimumVersion,
104
+ versionSupported,
105
+ agentAvailable,
106
+ errors,
107
+ };
108
+ }
109
+ //# sourceMappingURL=diagnostics.js.map
@@ -0,0 +1,20 @@
1
+ import { CONTEXT_WINDOW_EXCEEDED_CODE, QUOTA_EXCEEDED_CODE } from '@deepseek-ai/dsh-llm';
2
+ /** Stable provider-neutral codes emitted after classifying AGY text/status details. */
3
+ export type AgyClassifiedErrorCode = typeof CONTEXT_WINDOW_EXCEEDED_CODE | typeof QUOTA_EXCEEDED_CODE | 'AUTH' | 'RATE_LIMIT' | 'MODEL_NOT_FOUND' | 'AGY_AGENT_MISSING' | 'AGY_STATUS' | 'AGY_EXIT' | 'TRANSPORT';
4
+ /**
5
+ * Classify observed AGY status/stderr/error-event detail. This is deliberately
6
+ * conservative: unknown text keeps the caller-supplied fallback code.
7
+ *
8
+ * Note the ordering is load-bearing for 429s. AGY emits
9
+ * `RESOURCE_EXHAUSTED (code 429)` for both a drained 5h/weekly bucket and a
10
+ * transient burst limit; only the drained form names a reset far in the future.
11
+ * The drained form is reported as RATE_LIMIT so the bounded retry in
12
+ * `dsh-llm-retry` can re-issue the turn — live probes showed the bucket sits on
13
+ * a boundary value where 1 in 5 attempts succeeds (probe_retry_efficacy.mjs:
14
+ * 5/5 rounds recovered, 2-3 attempts each), while a 92-96h reset means retrying
15
+ * beyond a few attempts is pure token burn.
16
+ */
17
+ export declare function classifyAgyFailure(detail: string, fallback: string): string;
18
+ /** Keep user-facing failure text bounded and redacted; structured logs use only the code. */
19
+ export declare function safeAgyFailureMessage(prefix: string, detail: string | undefined): string;
20
+ //# sourceMappingURL=errors.d.ts.map