agent-yes 1.46.6 → 1.48.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/agent-yes.config.schema.json +163 -0
- package/dist/{SUPPORTED_CLIS-BwukLRmP.js → SUPPORTED_CLIS-OBl9bioJ.js} +2 -2
- package/dist/{agent-yes.config-CrlZMQo-.js → agent-yes.config-CUuciqYW.js} +98 -3
- package/dist/{agent-yes.config-DQLH9OgO.js → agent-yes.config-DgkhZ7eQ.js} +1 -1
- package/dist/cli.js +3 -3
- package/dist/index.js +1 -1
- package/examples/.agent-yes.config.json +16 -0
- package/examples/.agent-yes.config.yaml +55 -0
- package/examples/README.md +71 -0
- package/examples/docker.sh +2 -0
- package/examples/gcp.sh +75 -0
- package/package.json +6 -3
- package/ts/configLoader.spec.ts +127 -0
- package/ts/configLoader.ts +148 -0
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"$id": "https://github.com/snomiao/agent-yes/blob/main/agent-yes.config.schema.json",
|
|
4
|
+
"title": "agent-yes configuration",
|
|
5
|
+
"description": "Configuration schema for agent-yes - automated interaction wrapper for AI coding assistants",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"properties": {
|
|
8
|
+
"$schema": {
|
|
9
|
+
"type": "string",
|
|
10
|
+
"description": "JSON Schema reference for IDE support"
|
|
11
|
+
},
|
|
12
|
+
"configDir": {
|
|
13
|
+
"type": "string",
|
|
14
|
+
"description": "Directory to store agent-yes config files (e.g., session store)"
|
|
15
|
+
},
|
|
16
|
+
"logsDir": {
|
|
17
|
+
"type": "string",
|
|
18
|
+
"description": "Directory to store agent-yes log files"
|
|
19
|
+
},
|
|
20
|
+
"clis": {
|
|
21
|
+
"type": "object",
|
|
22
|
+
"description": "CLI-specific configurations",
|
|
23
|
+
"additionalProperties": {
|
|
24
|
+
"$ref": "#/definitions/AgentCliConfig"
|
|
25
|
+
},
|
|
26
|
+
"properties": {
|
|
27
|
+
"claude": { "$ref": "#/definitions/AgentCliConfig" },
|
|
28
|
+
"gemini": { "$ref": "#/definitions/AgentCliConfig" },
|
|
29
|
+
"codex": { "$ref": "#/definitions/AgentCliConfig" },
|
|
30
|
+
"copilot": { "$ref": "#/definitions/AgentCliConfig" },
|
|
31
|
+
"cursor": { "$ref": "#/definitions/AgentCliConfig" },
|
|
32
|
+
"grok": { "$ref": "#/definitions/AgentCliConfig" },
|
|
33
|
+
"qwen": { "$ref": "#/definitions/AgentCliConfig" },
|
|
34
|
+
"auggie": { "$ref": "#/definitions/AgentCliConfig" },
|
|
35
|
+
"amp": { "$ref": "#/definitions/AgentCliConfig" },
|
|
36
|
+
"opencode": { "$ref": "#/definitions/AgentCliConfig" }
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
"definitions": {
|
|
41
|
+
"AgentCliConfig": {
|
|
42
|
+
"type": "object",
|
|
43
|
+
"description": "Configuration for a specific CLI tool",
|
|
44
|
+
"properties": {
|
|
45
|
+
"install": {
|
|
46
|
+
"description": "Install command(s) for the CLI tool",
|
|
47
|
+
"oneOf": [
|
|
48
|
+
{ "type": "string" },
|
|
49
|
+
{
|
|
50
|
+
"type": "object",
|
|
51
|
+
"properties": {
|
|
52
|
+
"powershell": { "type": "string", "description": "PowerShell install command (Windows)" },
|
|
53
|
+
"bash": { "type": "string", "description": "Bash install command (Unix/macOS)" },
|
|
54
|
+
"npm": { "type": "string", "description": "npm install command (fallback)" },
|
|
55
|
+
"unix": { "type": "string", "description": "Unix-specific install command" },
|
|
56
|
+
"windows": { "type": "string", "description": "Windows-specific install command" }
|
|
57
|
+
},
|
|
58
|
+
"additionalProperties": false
|
|
59
|
+
}
|
|
60
|
+
]
|
|
61
|
+
},
|
|
62
|
+
"version": {
|
|
63
|
+
"type": "string",
|
|
64
|
+
"description": "Command to check if CLI is installed (e.g., 'claude --version')"
|
|
65
|
+
},
|
|
66
|
+
"binary": {
|
|
67
|
+
"type": "string",
|
|
68
|
+
"description": "Actual binary name if different from CLI name (e.g., 'cursor-agent' for cursor)"
|
|
69
|
+
},
|
|
70
|
+
"defaultArgs": {
|
|
71
|
+
"type": "array",
|
|
72
|
+
"items": { "type": "string" },
|
|
73
|
+
"description": "Default arguments to always pass to the CLI"
|
|
74
|
+
},
|
|
75
|
+
"ready": {
|
|
76
|
+
"type": "array",
|
|
77
|
+
"items": { "type": "string" },
|
|
78
|
+
"description": "Regex patterns to detect when CLI is ready for input. Set to [] to disable ready check."
|
|
79
|
+
},
|
|
80
|
+
"fatal": {
|
|
81
|
+
"type": "array",
|
|
82
|
+
"items": { "type": "string" },
|
|
83
|
+
"description": "Regex patterns to detect fatal errors that should stop execution"
|
|
84
|
+
},
|
|
85
|
+
"working": {
|
|
86
|
+
"type": "array",
|
|
87
|
+
"items": { "type": "string" },
|
|
88
|
+
"description": "Regex patterns to detect when CLI is currently processing"
|
|
89
|
+
},
|
|
90
|
+
"exitCommands": {
|
|
91
|
+
"type": "array",
|
|
92
|
+
"items": { "type": "string" },
|
|
93
|
+
"description": "Commands to exit the CLI gracefully (e.g., '/exit', '/quit')"
|
|
94
|
+
},
|
|
95
|
+
"promptArg": {
|
|
96
|
+
"type": "string",
|
|
97
|
+
"description": "How to pass the prompt: 'first-arg', 'last-arg', or a flag like '--prompt'",
|
|
98
|
+
"examples": ["first-arg", "last-arg", "--prompt", "-p"]
|
|
99
|
+
},
|
|
100
|
+
"noEOL": {
|
|
101
|
+
"type": "boolean",
|
|
102
|
+
"description": "If true, don't split lines by newline (for CLIs using cursor movement like codex)"
|
|
103
|
+
},
|
|
104
|
+
"enter": {
|
|
105
|
+
"type": "array",
|
|
106
|
+
"items": { "type": "string" },
|
|
107
|
+
"description": "Regex patterns that trigger automatic Enter key press"
|
|
108
|
+
},
|
|
109
|
+
"enterExclude": {
|
|
110
|
+
"type": "array",
|
|
111
|
+
"items": { "type": "string" },
|
|
112
|
+
"description": "Regex patterns to exclude from auto-enter (even if 'enter' matches)"
|
|
113
|
+
},
|
|
114
|
+
"typingRespond": {
|
|
115
|
+
"type": "object",
|
|
116
|
+
"description": "Map of responses to type when specific patterns are matched",
|
|
117
|
+
"additionalProperties": {
|
|
118
|
+
"type": "array",
|
|
119
|
+
"items": { "type": "string" },
|
|
120
|
+
"description": "Regex patterns that trigger this response"
|
|
121
|
+
},
|
|
122
|
+
"examples": [
|
|
123
|
+
{ "y\\n": ["Do you want to continue\\?"] },
|
|
124
|
+
{ "1\\n": ["Select an option:"] }
|
|
125
|
+
]
|
|
126
|
+
},
|
|
127
|
+
"restoreArgs": {
|
|
128
|
+
"type": "array",
|
|
129
|
+
"items": { "type": "string" },
|
|
130
|
+
"description": "Arguments to add when restarting after a crash (e.g., ['--continue'])"
|
|
131
|
+
},
|
|
132
|
+
"restartWithoutContinueArg": {
|
|
133
|
+
"type": "array",
|
|
134
|
+
"items": { "type": "string" },
|
|
135
|
+
"description": "Regex patterns for errors that require restart WITHOUT continue args"
|
|
136
|
+
},
|
|
137
|
+
"bunx": {
|
|
138
|
+
"type": "boolean",
|
|
139
|
+
"description": "Use bunx instead of npx to run the CLI (faster startup)"
|
|
140
|
+
},
|
|
141
|
+
"help": {
|
|
142
|
+
"type": "string",
|
|
143
|
+
"description": "URL to documentation or help page for this CLI"
|
|
144
|
+
},
|
|
145
|
+
"systemPrompt": {
|
|
146
|
+
"type": "string",
|
|
147
|
+
"description": "Flag to pass system prompt (e.g., '--append-system-prompt')"
|
|
148
|
+
},
|
|
149
|
+
"system": {
|
|
150
|
+
"type": "string",
|
|
151
|
+
"description": "System prompt content to inject"
|
|
152
|
+
},
|
|
153
|
+
"updateAvailable": {
|
|
154
|
+
"type": "array",
|
|
155
|
+
"items": { "type": "string" },
|
|
156
|
+
"description": "Regex patterns to detect update available messages"
|
|
157
|
+
}
|
|
158
|
+
},
|
|
159
|
+
"additionalProperties": false
|
|
160
|
+
}
|
|
161
|
+
},
|
|
162
|
+
"additionalProperties": false
|
|
163
|
+
}
|
|
@@ -10118,7 +10118,7 @@ const globalAgentRegistry = new AgentRegistry();
|
|
|
10118
10118
|
|
|
10119
10119
|
//#endregion
|
|
10120
10120
|
//#region ts/index.ts
|
|
10121
|
-
const config = await import("./agent-yes.config-
|
|
10121
|
+
const config = await import("./agent-yes.config-DgkhZ7eQ.js").then((mod) => mod.default || mod);
|
|
10122
10122
|
const CLIS_CONFIG = config.clis;
|
|
10123
10123
|
/**
|
|
10124
10124
|
* Main function to run agent-cli with automatic yes/no responses
|
|
@@ -10735,4 +10735,4 @@ const SUPPORTED_CLIS = Object.keys(CLIS_CONFIG);
|
|
|
10735
10735
|
|
|
10736
10736
|
//#endregion
|
|
10737
10737
|
export { AgentContext as a, config as i, CLIS_CONFIG as n, PidStore as o, agentYes as r, removeControlCharacters as s, SUPPORTED_CLIS as t };
|
|
10738
|
-
//# sourceMappingURL=SUPPORTED_CLIS-
|
|
10738
|
+
//# sourceMappingURL=SUPPORTED_CLIS-OBl9bioJ.js.map
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { t as logger } from "./logger-DH1Rx9HI.js";
|
|
2
|
-
import { mkdir } from "node:fs/promises";
|
|
2
|
+
import { access, mkdir, readFile } from "node:fs/promises";
|
|
3
3
|
import os from "node:os";
|
|
4
4
|
import path from "node:path";
|
|
5
|
+
import { parse } from "yaml";
|
|
5
6
|
|
|
6
7
|
//#region ts/defineConfig.ts
|
|
7
8
|
async function defineCliYesConfig(cfg) {
|
|
@@ -20,6 +21,94 @@ function deepMixin(target, source, ...more) {
|
|
|
20
21
|
return target;
|
|
21
22
|
}
|
|
22
23
|
|
|
24
|
+
//#endregion
|
|
25
|
+
//#region ts/configLoader.ts
|
|
26
|
+
//! Config file loader with cascading support
|
|
27
|
+
//! Supports JSON, YAML, YML formats
|
|
28
|
+
//! Priority: project-dir > home-dir > package-dir
|
|
29
|
+
const CONFIG_FILENAME = ".agent-yes.config";
|
|
30
|
+
const CONFIG_EXTENSIONS = [
|
|
31
|
+
".json",
|
|
32
|
+
".yml",
|
|
33
|
+
".yaml"
|
|
34
|
+
];
|
|
35
|
+
/**
|
|
36
|
+
* Check if a file exists
|
|
37
|
+
*/
|
|
38
|
+
async function fileExists(filepath) {
|
|
39
|
+
try {
|
|
40
|
+
await access(filepath);
|
|
41
|
+
return true;
|
|
42
|
+
} catch {
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Parse config file based on extension
|
|
48
|
+
*/
|
|
49
|
+
async function parseConfigFile(filepath) {
|
|
50
|
+
const content = await readFile(filepath, "utf-8");
|
|
51
|
+
const ext = path.extname(filepath).toLowerCase();
|
|
52
|
+
switch (ext) {
|
|
53
|
+
case ".json": return JSON.parse(content);
|
|
54
|
+
case ".yml":
|
|
55
|
+
case ".yaml": return parse(content) ?? {};
|
|
56
|
+
default: throw new Error(`Unsupported config file extension: ${ext}`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Find config file in a directory (checks all supported extensions)
|
|
61
|
+
*/
|
|
62
|
+
async function findConfigInDir(dir) {
|
|
63
|
+
for (const ext of CONFIG_EXTENSIONS) {
|
|
64
|
+
const filepath = path.join(dir, `${CONFIG_FILENAME}${ext}`);
|
|
65
|
+
if (await fileExists(filepath)) return filepath;
|
|
66
|
+
}
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Load config from a directory if it exists
|
|
71
|
+
*/
|
|
72
|
+
async function loadConfigFromDir(dir) {
|
|
73
|
+
const filepath = await findConfigInDir(dir);
|
|
74
|
+
if (!filepath) return {};
|
|
75
|
+
try {
|
|
76
|
+
logger.debug(`[config] Loading config from: ${filepath}`);
|
|
77
|
+
return await parseConfigFile(filepath);
|
|
78
|
+
} catch (error) {
|
|
79
|
+
logger.warn(`[config] Failed to parse config file ${filepath}:`, error);
|
|
80
|
+
return {};
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Get the package directory (where agent-yes is installed)
|
|
85
|
+
*/
|
|
86
|
+
function getPackageDir() {
|
|
87
|
+
return path.dirname(new URL(import.meta.url).pathname);
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Load configs from cascading locations and merge them
|
|
91
|
+
* Priority (highest to lowest): project-dir > home-dir > package-dir
|
|
92
|
+
* Higher priority configs override lower priority ones
|
|
93
|
+
*/
|
|
94
|
+
async function loadCascadingConfig(options = {}) {
|
|
95
|
+
const projectDir = options.projectDir ?? process.cwd();
|
|
96
|
+
const homeDir = options.homeDir ?? os.homedir();
|
|
97
|
+
const packageDir = getPackageDir();
|
|
98
|
+
const nonEmptyConfigs = (await Promise.all([
|
|
99
|
+
loadConfigFromDir(packageDir),
|
|
100
|
+
loadConfigFromDir(homeDir),
|
|
101
|
+
loadConfigFromDir(projectDir)
|
|
102
|
+
])).filter((c) => c && Object.keys(c).length > 0);
|
|
103
|
+
if (nonEmptyConfigs.length === 0) {
|
|
104
|
+
logger.debug("[config] No config files found in any location");
|
|
105
|
+
return {};
|
|
106
|
+
}
|
|
107
|
+
const merged = deepMixin({}, ...nonEmptyConfigs);
|
|
108
|
+
logger.debug("[config] Merged config from", nonEmptyConfigs.length, "sources");
|
|
109
|
+
return merged;
|
|
110
|
+
}
|
|
111
|
+
|
|
23
112
|
//#endregion
|
|
24
113
|
//#region agent-yes.config.ts
|
|
25
114
|
logger.debug("loading cli-yes.config.ts from " + import.meta.url);
|
|
@@ -35,7 +124,13 @@ const configDir = await (async () => {
|
|
|
35
124
|
return tmpConfigDir;
|
|
36
125
|
}
|
|
37
126
|
})();
|
|
38
|
-
|
|
127
|
+
const cascadingConfig = await loadCascadingConfig();
|
|
128
|
+
const legacyConfigs = await Promise.all([
|
|
129
|
+
import(path.resolve(os.homedir(), ".agent-yes/config.ts")).catch(() => ({ default: {} })).then((mod) => mod.default),
|
|
130
|
+
import(path.resolve(process.cwd(), "node_modules/.agent-yes/config.ts")).catch(() => ({ default: {} })).then((mod) => mod.default),
|
|
131
|
+
import(path.resolve(process.cwd(), ".agent-yes/config.ts")).catch(() => ({ default: {} })).then((mod) => mod.default)
|
|
132
|
+
]);
|
|
133
|
+
var agent_yes_config_default = deepMixin(await getDefaultConfig(), cascadingConfig, ...legacyConfigs);
|
|
39
134
|
function getDefaultConfig() {
|
|
40
135
|
return defineCliYesConfig({
|
|
41
136
|
configDir,
|
|
@@ -157,4 +252,4 @@ function getDefaultConfig() {
|
|
|
157
252
|
|
|
158
253
|
//#endregion
|
|
159
254
|
export { agent_yes_config_default as t };
|
|
160
|
-
//# sourceMappingURL=agent-yes.config-
|
|
255
|
+
//# sourceMappingURL=agent-yes.config-CUuciqYW.js.map
|
package/dist/cli.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
import { c as __toESM, i as __commonJSMin, r as require_ms, t as logger } from "./logger-DH1Rx9HI.js";
|
|
3
|
-
import "./agent-yes.config-
|
|
4
|
-
import { o as PidStore, t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-
|
|
3
|
+
import "./agent-yes.config-CUuciqYW.js";
|
|
4
|
+
import { o as PidStore, t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-OBl9bioJ.js";
|
|
5
5
|
import { createRequire } from "node:module";
|
|
6
6
|
import { argv } from "process";
|
|
7
7
|
import { spawn } from "child_process";
|
|
@@ -4505,7 +4505,7 @@ const Yargs = YargsFactory(esm_default);
|
|
|
4505
4505
|
//#endregion
|
|
4506
4506
|
//#region package.json
|
|
4507
4507
|
var name = "agent-yes";
|
|
4508
|
-
var version = "1.
|
|
4508
|
+
var version = "1.48.0";
|
|
4509
4509
|
|
|
4510
4510
|
//#endregion
|
|
4511
4511
|
//#region ts/parseCliArgs.ts
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import "./logger-DH1Rx9HI.js";
|
|
2
|
-
import { a as AgentContext, i as config, n as CLIS_CONFIG, r as agentYes, s as removeControlCharacters } from "./SUPPORTED_CLIS-
|
|
2
|
+
import { a as AgentContext, i as config, n as CLIS_CONFIG, r as agentYes, s as removeControlCharacters } from "./SUPPORTED_CLIS-OBl9bioJ.js";
|
|
3
3
|
|
|
4
4
|
export { AgentContext, CLIS_CONFIG, config, agentYes as default, removeControlCharacters };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://raw.githubusercontent.com/snomiao/agent-yes/main/agent-yes.config.schema.json",
|
|
3
|
+
"configDir": "~/.agent-yes",
|
|
4
|
+
"logsDir": "~/.agent-yes/logs",
|
|
5
|
+
"clis": {
|
|
6
|
+
"claude": {
|
|
7
|
+
"defaultArgs": ["--verbose"],
|
|
8
|
+
"ready": ["\\? for shortcuts", "^>[ \\u00A0]"],
|
|
9
|
+
"enter": ["> 1\\. Yes", "Press Enter to continue"]
|
|
10
|
+
},
|
|
11
|
+
"codex": {
|
|
12
|
+
"defaultArgs": ["--search"],
|
|
13
|
+
"ready": ["\\? for shortcuts"]
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# yaml-language-server: $schema=https://raw.githubusercontent.com/snomiao/agent-yes/main/agent-yes.config.schema.json
|
|
2
|
+
|
|
3
|
+
# agent-yes configuration
|
|
4
|
+
# Place this file in your project root or home directory
|
|
5
|
+
|
|
6
|
+
configDir: ~/.agent-yes
|
|
7
|
+
logsDir: ~/.agent-yes/logs
|
|
8
|
+
|
|
9
|
+
clis:
|
|
10
|
+
# Claude Code configuration
|
|
11
|
+
claude:
|
|
12
|
+
defaultArgs:
|
|
13
|
+
- --verbose
|
|
14
|
+
# Patterns to detect when Claude is ready for input
|
|
15
|
+
ready:
|
|
16
|
+
- "\\? for shortcuts"
|
|
17
|
+
- "^>[ \\u00A0]"
|
|
18
|
+
# Patterns that trigger automatic Enter
|
|
19
|
+
enter:
|
|
20
|
+
- "> 1\\. Yes"
|
|
21
|
+
- "Press Enter to continue"
|
|
22
|
+
# Auto-type responses
|
|
23
|
+
typingRespond:
|
|
24
|
+
"1\n":
|
|
25
|
+
- "Do you want to use this API key\\?"
|
|
26
|
+
|
|
27
|
+
# Codex configuration
|
|
28
|
+
codex:
|
|
29
|
+
defaultArgs:
|
|
30
|
+
- --search
|
|
31
|
+
ready:
|
|
32
|
+
- "\\? for shortcuts"
|
|
33
|
+
enter:
|
|
34
|
+
- "> 1\\. Yes,"
|
|
35
|
+
- "> 1\\. Approve and run now"
|
|
36
|
+
|
|
37
|
+
# Gemini configuration
|
|
38
|
+
gemini:
|
|
39
|
+
ready:
|
|
40
|
+
- "Type your message"
|
|
41
|
+
enter:
|
|
42
|
+
- "│ ● 1\\. Yes, allow once"
|
|
43
|
+
restoreArgs:
|
|
44
|
+
- --resume
|
|
45
|
+
|
|
46
|
+
# Custom CLI example
|
|
47
|
+
# my-custom-cli:
|
|
48
|
+
# binary: my-cli-binary
|
|
49
|
+
# install:
|
|
50
|
+
# npm: "npm install -g my-cli"
|
|
51
|
+
# bash: "curl -fsSL https://example.com/install.sh | bash"
|
|
52
|
+
# ready:
|
|
53
|
+
# - "Ready>"
|
|
54
|
+
# enter:
|
|
55
|
+
# - "Confirm\\? \\[y/N\\]"
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# GCP Cloud Run Demo
|
|
2
|
+
|
|
3
|
+
## Quick Start
|
|
4
|
+
|
|
5
|
+
### 1. Set your API key
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
export ANTHROPIC_API_KEY='your-api-key-here'
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
### 2. Run the deployment script
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
cd demo
|
|
15
|
+
./gcp.sh "hello"
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Usage
|
|
19
|
+
|
|
20
|
+
The script accepts a command as the first argument:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
# Run a hello command
|
|
24
|
+
./gcp.sh "hello"
|
|
25
|
+
|
|
26
|
+
# Run a custom command
|
|
27
|
+
./gcp.sh "list all files in the current directory"
|
|
28
|
+
|
|
29
|
+
# Default command (if no argument provided)
|
|
30
|
+
./gcp.sh
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Configuration
|
|
34
|
+
|
|
35
|
+
You can customize the deployment with environment variables:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
export GCP_PROJECT_ID="my-project"
|
|
39
|
+
export GCP_REGION="asia-northeast1"
|
|
40
|
+
export GCP_JOB_NAME="my-custom-job"
|
|
41
|
+
export GCP_IMAGE="ghcr.io/snomiao/agent-yes:latest"
|
|
42
|
+
|
|
43
|
+
./gcp.sh "your command here"
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## How it works
|
|
47
|
+
|
|
48
|
+
1. **No TTY needed**: The script configures Cloud Run Jobs to run non-interactively
|
|
49
|
+
2. **API Key authentication**: Uses `ANTHROPIC_API_KEY` environment variable
|
|
50
|
+
3. **Auto-execution**: Creates/updates the job and executes it immediately
|
|
51
|
+
4. **Log streaming**: Automatically streams logs from the execution
|
|
52
|
+
|
|
53
|
+
## Why Cloud Run Jobs instead of Cloud Run Services?
|
|
54
|
+
|
|
55
|
+
- **Cloud Run Services**: Require an HTTP server listening on `$PORT` - not suitable for CLI tools
|
|
56
|
+
- **Cloud Run Jobs**: Run to completion and exit - perfect for CLI tools like `agent-yes`
|
|
57
|
+
|
|
58
|
+
## Alternative: Quick test on Compute Engine
|
|
59
|
+
|
|
60
|
+
If you need TTY for local testing:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
# Create a VM
|
|
64
|
+
gcloud compute instances create agent-yes-vm \
|
|
65
|
+
--machine-type=e2-medium \
|
|
66
|
+
--zone=us-central1-a
|
|
67
|
+
|
|
68
|
+
# SSH and run
|
|
69
|
+
gcloud compute ssh agent-yes-vm -- \
|
|
70
|
+
'docker run -it --rm -e ANTHROPIC_API_KEY=xxx ghcr.io/snomiao/agent-yes:latest claude -- hello'
|
|
71
|
+
```
|
package/examples/gcp.sh
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
set -e
|
|
4
|
+
|
|
5
|
+
# Configuration
|
|
6
|
+
PROJECT_ID="${GCP_PROJECT_ID:-$(gcloud config get-value project)}"
|
|
7
|
+
REGION="${GCP_REGION:-us-central1}"
|
|
8
|
+
JOB_NAME="${GCP_JOB_NAME:-agent-yes-job}"
|
|
9
|
+
IMAGE="${GCP_IMAGE:-ghcr.io/snomiao/agent-yes:latest}"
|
|
10
|
+
COMMAND="${1:-hello}"
|
|
11
|
+
|
|
12
|
+
# Check for ANTHROPIC_API_KEY
|
|
13
|
+
if [ -z "$ANTHROPIC_API_KEY" ]; then
|
|
14
|
+
echo "Error: ANTHROPIC_API_KEY environment variable is not set"
|
|
15
|
+
echo ""
|
|
16
|
+
echo "IMPORTANT: You need an API key from Anthropic Console (NOT OAuth token)"
|
|
17
|
+
echo "1. Go to: https://console.anthropic.com/"
|
|
18
|
+
echo "2. Create an API key (starts with sk-ant-api03-...)"
|
|
19
|
+
echo "3. Export it: export ANTHROPIC_API_KEY='sk-ant-api03-...'"
|
|
20
|
+
echo ""
|
|
21
|
+
echo "Note: The OAuth token from ~/.claude/.credentials.json won't work for Cloud Run"
|
|
22
|
+
echo ""
|
|
23
|
+
exit 1
|
|
24
|
+
fi
|
|
25
|
+
|
|
26
|
+
echo "========================================="
|
|
27
|
+
echo "Google Cloud Run Jobs Deployment"
|
|
28
|
+
echo "========================================="
|
|
29
|
+
echo "Project ID: $PROJECT_ID"
|
|
30
|
+
echo "Region: $REGION"
|
|
31
|
+
echo "Job Name: $JOB_NAME"
|
|
32
|
+
echo "Image: $IMAGE"
|
|
33
|
+
echo "Command: $COMMAND"
|
|
34
|
+
echo "========================================="
|
|
35
|
+
|
|
36
|
+
# Deploy or update Cloud Run Job
|
|
37
|
+
echo ""
|
|
38
|
+
echo "Deploying to Cloud Run Jobs..."
|
|
39
|
+
if gcloud run jobs describe "$JOB_NAME" --region="$REGION" &>/dev/null; then
|
|
40
|
+
echo "Job exists, updating..."
|
|
41
|
+
gcloud run jobs update "$JOB_NAME" \
|
|
42
|
+
--image="$IMAGE" \
|
|
43
|
+
--region="$REGION" \
|
|
44
|
+
--task-timeout=3600 \
|
|
45
|
+
--max-retries=0 \
|
|
46
|
+
--set-env-vars="ANTHROPIC_API_KEY=$ANTHROPIC_API_KEY" \
|
|
47
|
+
--args="claude","--","$COMMAND"
|
|
48
|
+
else
|
|
49
|
+
echo "Creating new job..."
|
|
50
|
+
gcloud run jobs create "$JOB_NAME" \
|
|
51
|
+
--image="$IMAGE" \
|
|
52
|
+
--region="$REGION" \
|
|
53
|
+
--task-timeout=3600 \
|
|
54
|
+
--max-retries=0 \
|
|
55
|
+
--set-env-vars="ANTHROPIC_API_KEY=$ANTHROPIC_API_KEY" \
|
|
56
|
+
--args="claude","--","$COMMAND"
|
|
57
|
+
fi
|
|
58
|
+
|
|
59
|
+
# Execute the job
|
|
60
|
+
echo ""
|
|
61
|
+
echo "Executing the job..."
|
|
62
|
+
EXECUTION_NAME=$(gcloud run jobs execute "$JOB_NAME" --region="$REGION" --format="value(metadata.name)")
|
|
63
|
+
|
|
64
|
+
echo ""
|
|
65
|
+
echo "Job execution started: $EXECUTION_NAME"
|
|
66
|
+
echo ""
|
|
67
|
+
echo "Streaming logs..."
|
|
68
|
+
echo "========================================="
|
|
69
|
+
gcloud run jobs executions logs tail "$EXECUTION_NAME" --region="$REGION"
|
|
70
|
+
|
|
71
|
+
echo ""
|
|
72
|
+
echo "========================================="
|
|
73
|
+
echo "View in console:"
|
|
74
|
+
echo " https://console.cloud.google.com/run/jobs/details/$REGION/$JOB_NAME?project=$PROJECT_ID"
|
|
75
|
+
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-yes",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.48.0",
|
|
4
4
|
"description": "A wrapper tool that automates interactions with various AI CLI tools by automatically handling common prompts and responses.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai",
|
|
@@ -50,7 +50,9 @@
|
|
|
50
50
|
"ts/*.ts",
|
|
51
51
|
"!dist/**/*.map",
|
|
52
52
|
"dist/**/*.js",
|
|
53
|
-
"bin"
|
|
53
|
+
"bin",
|
|
54
|
+
"agent-yes.config.schema.json",
|
|
55
|
+
"examples"
|
|
54
56
|
],
|
|
55
57
|
"type": "module",
|
|
56
58
|
"module": "ts/index.ts",
|
|
@@ -82,7 +84,8 @@
|
|
|
82
84
|
"@snomiao/bun-pty": "^0.3.4",
|
|
83
85
|
"@snomiao/keyv-sqlite": "^5.0.4",
|
|
84
86
|
"bun-pty": "^0.4.8",
|
|
85
|
-
"from-node-stream": "^0.1.2"
|
|
87
|
+
"from-node-stream": "^0.1.2",
|
|
88
|
+
"yaml": "^2.8.2"
|
|
86
89
|
},
|
|
87
90
|
"devDependencies": {
|
|
88
91
|
"@semantic-release/exec": "^7.1.0",
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from "bun:test";
|
|
2
|
+
import { loadCascadingConfig, getConfigPaths } from "./configLoader.ts";
|
|
3
|
+
import { mkdir, writeFile, rm } from "node:fs/promises";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import os from "node:os";
|
|
6
|
+
|
|
7
|
+
describe("configLoader", () => {
|
|
8
|
+
const testDir = path.join(os.tmpdir(), "agent-yes-config-test");
|
|
9
|
+
|
|
10
|
+
beforeEach(async () => {
|
|
11
|
+
await mkdir(testDir, { recursive: true });
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
afterEach(async () => {
|
|
15
|
+
await rm(testDir, { recursive: true, force: true });
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it("should load JSON config", async () => {
|
|
19
|
+
const configPath = path.join(testDir, ".agent-yes.config.json");
|
|
20
|
+
await writeFile(
|
|
21
|
+
configPath,
|
|
22
|
+
JSON.stringify({
|
|
23
|
+
configDir: "/custom/config",
|
|
24
|
+
clis: {
|
|
25
|
+
claude: {
|
|
26
|
+
defaultArgs: ["--verbose"],
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
})
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
const config = await loadCascadingConfig({ projectDir: testDir });
|
|
33
|
+
expect(config.configDir).toBe("/custom/config");
|
|
34
|
+
expect(config.clis?.claude?.defaultArgs).toEqual(["--verbose"]);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("should load YAML config", async () => {
|
|
38
|
+
const configPath = path.join(testDir, ".agent-yes.config.yaml");
|
|
39
|
+
await writeFile(
|
|
40
|
+
configPath,
|
|
41
|
+
`
|
|
42
|
+
configDir: /custom/yaml/config
|
|
43
|
+
clis:
|
|
44
|
+
gemini:
|
|
45
|
+
defaultArgs:
|
|
46
|
+
- --resume
|
|
47
|
+
`
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
const config = await loadCascadingConfig({ projectDir: testDir });
|
|
51
|
+
expect(config.configDir).toBe("/custom/yaml/config");
|
|
52
|
+
expect(config.clis?.gemini?.defaultArgs).toEqual(["--resume"]);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("should load YML config", async () => {
|
|
56
|
+
const configPath = path.join(testDir, ".agent-yes.config.yml");
|
|
57
|
+
await writeFile(
|
|
58
|
+
configPath,
|
|
59
|
+
`
|
|
60
|
+
logsDir: /custom/logs
|
|
61
|
+
`
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
const config = await loadCascadingConfig({ projectDir: testDir });
|
|
65
|
+
expect(config.logsDir).toBe("/custom/logs");
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it("should prefer JSON over YAML when both exist", async () => {
|
|
69
|
+
await writeFile(
|
|
70
|
+
path.join(testDir, ".agent-yes.config.json"),
|
|
71
|
+
JSON.stringify({ configDir: "/json/config" })
|
|
72
|
+
);
|
|
73
|
+
await writeFile(
|
|
74
|
+
path.join(testDir, ".agent-yes.config.yaml"),
|
|
75
|
+
`configDir: /yaml/config`
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
const config = await loadCascadingConfig({ projectDir: testDir });
|
|
79
|
+
expect(config.configDir).toBe("/json/config");
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it("should return config paths", () => {
|
|
83
|
+
const paths = getConfigPaths({ projectDir: testDir });
|
|
84
|
+
expect(paths.length).toBeGreaterThan(0);
|
|
85
|
+
expect(paths.some((p) => p.includes(".agent-yes.config.json"))).toBe(true);
|
|
86
|
+
expect(paths.some((p) => p.includes(".agent-yes.config.yml"))).toBe(true);
|
|
87
|
+
expect(paths.some((p) => p.includes(".agent-yes.config.yaml"))).toBe(true);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it("should return empty config when no config files exist", async () => {
|
|
91
|
+
const emptyDir = path.join(testDir, "empty");
|
|
92
|
+
await mkdir(emptyDir, { recursive: true });
|
|
93
|
+
|
|
94
|
+
const config = await loadCascadingConfig({
|
|
95
|
+
projectDir: emptyDir,
|
|
96
|
+
homeDir: emptyDir, // Use same empty dir to avoid loading actual home config
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
expect(config).toEqual({});
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it("should merge configs with project taking precedence", async () => {
|
|
103
|
+
const homeDir = path.join(testDir, "home");
|
|
104
|
+
const projectDir = path.join(testDir, "project");
|
|
105
|
+
await mkdir(homeDir, { recursive: true });
|
|
106
|
+
await mkdir(projectDir, { recursive: true });
|
|
107
|
+
|
|
108
|
+
await writeFile(
|
|
109
|
+
path.join(homeDir, ".agent-yes.config.json"),
|
|
110
|
+
JSON.stringify({
|
|
111
|
+
configDir: "/home/config",
|
|
112
|
+
logsDir: "/home/logs",
|
|
113
|
+
})
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
await writeFile(
|
|
117
|
+
path.join(projectDir, ".agent-yes.config.json"),
|
|
118
|
+
JSON.stringify({
|
|
119
|
+
configDir: "/project/config",
|
|
120
|
+
})
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
const config = await loadCascadingConfig({ projectDir, homeDir });
|
|
124
|
+
expect(config.configDir).toBe("/project/config"); // Project takes precedence
|
|
125
|
+
expect(config.logsDir).toBe("/home/logs"); // Home is used when not overridden
|
|
126
|
+
});
|
|
127
|
+
});
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
//! Config file loader with cascading support
|
|
2
|
+
//! Supports JSON, YAML, YML formats
|
|
3
|
+
//! Priority: project-dir > home-dir > package-dir
|
|
4
|
+
|
|
5
|
+
import { readFile, access } from "node:fs/promises";
|
|
6
|
+
import path from "node:path";
|
|
7
|
+
import os from "node:os";
|
|
8
|
+
import { parse as parseYaml } from "yaml";
|
|
9
|
+
import { logger } from "./logger.ts";
|
|
10
|
+
import type { AgentYesConfig } from "./index.ts";
|
|
11
|
+
import { deepMixin } from "./utils.ts";
|
|
12
|
+
|
|
13
|
+
const CONFIG_FILENAME = ".agent-yes.config";
|
|
14
|
+
const CONFIG_EXTENSIONS = [".json", ".yml", ".yaml"] as const;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Check if a file exists
|
|
18
|
+
*/
|
|
19
|
+
async function fileExists(filepath: string): Promise<boolean> {
|
|
20
|
+
try {
|
|
21
|
+
await access(filepath);
|
|
22
|
+
return true;
|
|
23
|
+
} catch {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Parse config file based on extension
|
|
30
|
+
*/
|
|
31
|
+
async function parseConfigFile(filepath: string): Promise<Partial<AgentYesConfig>> {
|
|
32
|
+
const content = await readFile(filepath, "utf-8");
|
|
33
|
+
const ext = path.extname(filepath).toLowerCase();
|
|
34
|
+
|
|
35
|
+
switch (ext) {
|
|
36
|
+
case ".json":
|
|
37
|
+
return JSON.parse(content);
|
|
38
|
+
case ".yml":
|
|
39
|
+
case ".yaml":
|
|
40
|
+
return parseYaml(content) ?? {};
|
|
41
|
+
default:
|
|
42
|
+
throw new Error(`Unsupported config file extension: ${ext}`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Find config file in a directory (checks all supported extensions)
|
|
48
|
+
*/
|
|
49
|
+
async function findConfigInDir(dir: string): Promise<string | null> {
|
|
50
|
+
for (const ext of CONFIG_EXTENSIONS) {
|
|
51
|
+
const filepath = path.join(dir, `${CONFIG_FILENAME}${ext}`);
|
|
52
|
+
if (await fileExists(filepath)) {
|
|
53
|
+
return filepath;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Load config from a directory if it exists
|
|
61
|
+
*/
|
|
62
|
+
async function loadConfigFromDir(dir: string): Promise<Partial<AgentYesConfig>> {
|
|
63
|
+
const filepath = await findConfigInDir(dir);
|
|
64
|
+
if (!filepath) {
|
|
65
|
+
return {};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
try {
|
|
69
|
+
logger.debug(`[config] Loading config from: ${filepath}`);
|
|
70
|
+
return await parseConfigFile(filepath);
|
|
71
|
+
} catch (error) {
|
|
72
|
+
logger.warn(`[config] Failed to parse config file ${filepath}:`, error);
|
|
73
|
+
return {};
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Get the package directory (where agent-yes is installed)
|
|
79
|
+
*/
|
|
80
|
+
function getPackageDir(): string {
|
|
81
|
+
// __dirname equivalent for ESM
|
|
82
|
+
return path.dirname(new URL(import.meta.url).pathname);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export interface ConfigLoadOptions {
|
|
86
|
+
/** Override the project directory (defaults to process.cwd()) */
|
|
87
|
+
projectDir?: string;
|
|
88
|
+
/** Override the home directory (defaults to os.homedir()) */
|
|
89
|
+
homeDir?: string;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Load configs from cascading locations and merge them
|
|
94
|
+
* Priority (highest to lowest): project-dir > home-dir > package-dir
|
|
95
|
+
* Higher priority configs override lower priority ones
|
|
96
|
+
*/
|
|
97
|
+
export async function loadCascadingConfig(
|
|
98
|
+
options: ConfigLoadOptions = {}
|
|
99
|
+
): Promise<Partial<AgentYesConfig>> {
|
|
100
|
+
const projectDir = options.projectDir ?? process.cwd();
|
|
101
|
+
const homeDir = options.homeDir ?? os.homedir();
|
|
102
|
+
const packageDir = getPackageDir();
|
|
103
|
+
|
|
104
|
+
// Load configs from each location (lowest to highest priority)
|
|
105
|
+
const configs = await Promise.all([
|
|
106
|
+
// Package directory (lowest priority - defaults from package)
|
|
107
|
+
loadConfigFromDir(packageDir),
|
|
108
|
+
// Home directory (middle priority - user defaults)
|
|
109
|
+
loadConfigFromDir(homeDir),
|
|
110
|
+
// Project directory (highest priority - project-specific)
|
|
111
|
+
loadConfigFromDir(projectDir),
|
|
112
|
+
]);
|
|
113
|
+
|
|
114
|
+
// Filter out empty configs and merge
|
|
115
|
+
const nonEmptyConfigs = configs.filter(
|
|
116
|
+
(c) => c && Object.keys(c).length > 0
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
if (nonEmptyConfigs.length === 0) {
|
|
120
|
+
logger.debug("[config] No config files found in any location");
|
|
121
|
+
return {};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Merge configs with deepMixin (later configs override earlier ones)
|
|
125
|
+
const merged = deepMixin({}, ...nonEmptyConfigs);
|
|
126
|
+
logger.debug("[config] Merged config from", nonEmptyConfigs.length, "sources");
|
|
127
|
+
|
|
128
|
+
return merged;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Get all possible config file paths (for debugging/user info)
|
|
133
|
+
*/
|
|
134
|
+
export function getConfigPaths(options: ConfigLoadOptions = {}): string[] {
|
|
135
|
+
const projectDir = options.projectDir ?? process.cwd();
|
|
136
|
+
const homeDir = options.homeDir ?? os.homedir();
|
|
137
|
+
const packageDir = getPackageDir();
|
|
138
|
+
|
|
139
|
+
const paths: string[] = [];
|
|
140
|
+
|
|
141
|
+
for (const dir of [packageDir, homeDir, projectDir]) {
|
|
142
|
+
for (const ext of CONFIG_EXTENSIONS) {
|
|
143
|
+
paths.push(path.join(dir, `${CONFIG_FILENAME}${ext}`));
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return paths;
|
|
148
|
+
}
|