opencode-pilot 0.1.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/.devcontainer/devcontainer.json +16 -0
- package/.github/workflows/ci.yml +67 -0
- package/.releaserc.cjs +28 -0
- package/AGENTS.md +71 -0
- package/CONTRIBUTING.md +102 -0
- package/LICENSE +21 -0
- package/README.md +72 -0
- package/bin/opencode-pilot +809 -0
- package/dist/opencode-ntfy.tar.gz +0 -0
- package/examples/config.yaml +73 -0
- package/examples/templates/default.md +7 -0
- package/examples/templates/devcontainer.md +7 -0
- package/examples/templates/review-feedback.md +7 -0
- package/examples/templates/review.md +15 -0
- package/install.sh +246 -0
- package/package.json +40 -0
- package/plugin/config.js +76 -0
- package/plugin/index.js +260 -0
- package/plugin/logger.js +125 -0
- package/plugin/notifier.js +110 -0
- package/service/actions.js +334 -0
- package/service/io.opencode.ntfy.plist +29 -0
- package/service/logger.js +82 -0
- package/service/poll-service.js +246 -0
- package/service/poller.js +339 -0
- package/service/readiness.js +234 -0
- package/service/repo-config.js +222 -0
- package/service/server.js +1523 -0
- package/service/utils.js +21 -0
- package/test/run_tests.bash +34 -0
- package/test/test_actions.bash +263 -0
- package/test/test_cli.bash +161 -0
- package/test/test_config.bash +438 -0
- package/test/test_helper.bash +140 -0
- package/test/test_logger.bash +401 -0
- package/test/test_notifier.bash +310 -0
- package/test/test_plist.bash +125 -0
- package/test/test_plugin.bash +952 -0
- package/test/test_poll_service.bash +179 -0
- package/test/test_poller.bash +120 -0
- package/test/test_readiness.bash +313 -0
- package/test/test_repo_config.bash +406 -0
- package/test/test_service.bash +1342 -0
- package/test/unit/actions.test.js +235 -0
- package/test/unit/config.test.js +86 -0
- package/test/unit/paths.test.js +77 -0
- package/test/unit/poll-service.test.js +142 -0
- package/test/unit/poller.test.js +347 -0
- package/test/unit/repo-config.test.js +441 -0
- package/test/unit/utils.test.js +53 -0
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* repo-config.js - Configuration management
|
|
3
|
+
*
|
|
4
|
+
* Manages configuration stored in ~/.config/opencode-pilot/config.yaml
|
|
5
|
+
* Supports:
|
|
6
|
+
* - repos: per-repository settings (use YAML anchors for sharing)
|
|
7
|
+
* - sources: polling sources with generic tool references
|
|
8
|
+
* - tools: field mappings for normalizing MCP responses
|
|
9
|
+
* - templates: prompt templates stored as markdown files
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import fs from "fs";
|
|
13
|
+
import path from "path";
|
|
14
|
+
import os from "os";
|
|
15
|
+
import YAML from "yaml";
|
|
16
|
+
import { getNestedValue } from "./utils.js";
|
|
17
|
+
|
|
18
|
+
// Default config path
|
|
19
|
+
const DEFAULT_CONFIG_PATH = path.join(
|
|
20
|
+
os.homedir(),
|
|
21
|
+
".config/opencode-pilot/config.yaml"
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
// Default templates directory
|
|
25
|
+
const DEFAULT_TEMPLATES_DIR = path.join(
|
|
26
|
+
os.homedir(),
|
|
27
|
+
".config/opencode-pilot/templates"
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
// In-memory config cache (for testing and runtime)
|
|
31
|
+
let configCache = null;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Expand template string with item fields
|
|
35
|
+
* Supports {field} and {field.nested} syntax
|
|
36
|
+
*/
|
|
37
|
+
function expandTemplate(template, item) {
|
|
38
|
+
return template.replace(/\{([^}]+)\}/g, (match, fieldPath) => {
|
|
39
|
+
const value = getNestedValue(item, fieldPath);
|
|
40
|
+
return value !== undefined ? String(value) : match;
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Load configuration from YAML file or object
|
|
46
|
+
* @param {string|object} [configOrPath] - Path to YAML file or config object
|
|
47
|
+
*/
|
|
48
|
+
export function loadRepoConfig(configOrPath) {
|
|
49
|
+
const emptyConfig = { repos: {}, sources: [] };
|
|
50
|
+
|
|
51
|
+
if (typeof configOrPath === "object") {
|
|
52
|
+
// Direct config object (for testing)
|
|
53
|
+
configCache = configOrPath;
|
|
54
|
+
return configCache;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const configPath = configOrPath || DEFAULT_CONFIG_PATH;
|
|
58
|
+
|
|
59
|
+
if (!fs.existsSync(configPath)) {
|
|
60
|
+
configCache = emptyConfig;
|
|
61
|
+
return configCache;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
try {
|
|
65
|
+
const content = fs.readFileSync(configPath, "utf-8");
|
|
66
|
+
configCache = YAML.parse(content, { merge: true }) || emptyConfig;
|
|
67
|
+
} catch (err) {
|
|
68
|
+
// Log error but continue with empty config to allow graceful degradation
|
|
69
|
+
console.error(`Warning: Failed to parse config at ${configPath}: ${err.message}`);
|
|
70
|
+
configCache = emptyConfig;
|
|
71
|
+
}
|
|
72
|
+
return configCache;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Get raw config (loads if not cached)
|
|
77
|
+
*/
|
|
78
|
+
function getRawConfig() {
|
|
79
|
+
if (!configCache) {
|
|
80
|
+
loadRepoConfig();
|
|
81
|
+
}
|
|
82
|
+
return configCache;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Get configuration for a specific repo
|
|
87
|
+
* @param {string} repoKey - Repository identifier (e.g., "myorg/backend")
|
|
88
|
+
* @returns {object} Repository configuration or empty object
|
|
89
|
+
*/
|
|
90
|
+
export function getRepoConfig(repoKey) {
|
|
91
|
+
const config = getRawConfig();
|
|
92
|
+
const repos = config.repos || {};
|
|
93
|
+
const repoConfig = repos[repoKey] || {};
|
|
94
|
+
|
|
95
|
+
// Normalize: support both 'path' and 'repo_path' keys
|
|
96
|
+
if (repoConfig.path && !repoConfig.repo_path) {
|
|
97
|
+
return { ...repoConfig, repo_path: repoConfig.path };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return repoConfig;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Get all top-level sources (for polling)
|
|
105
|
+
* @returns {Array} Array of source configurations
|
|
106
|
+
*/
|
|
107
|
+
export function getSources() {
|
|
108
|
+
const config = getRawConfig();
|
|
109
|
+
return config.sources || [];
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Get all sources (alias for getSources)
|
|
114
|
+
* @returns {Array} Array of source configurations
|
|
115
|
+
*/
|
|
116
|
+
export function getAllSources() {
|
|
117
|
+
return getSources();
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Get field mappings for a tool provider
|
|
122
|
+
* @param {string} provider - Tool provider name (e.g., "github", "linear")
|
|
123
|
+
* @returns {object|null} Field mappings or null if not configured
|
|
124
|
+
*/
|
|
125
|
+
export function getToolMappings(provider) {
|
|
126
|
+
const config = getRawConfig();
|
|
127
|
+
const tools = config.tools || {};
|
|
128
|
+
const toolConfig = tools[provider];
|
|
129
|
+
|
|
130
|
+
if (!toolConfig || !toolConfig.mappings) {
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return toolConfig.mappings;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Load a template from the templates directory
|
|
139
|
+
* @param {string} templateName - Template name (without .md extension)
|
|
140
|
+
* @param {string} [templatesDir] - Templates directory path (for testing)
|
|
141
|
+
* @returns {string|null} Template content or null if not found
|
|
142
|
+
*/
|
|
143
|
+
export function getTemplate(templateName, templatesDir) {
|
|
144
|
+
const dir = templatesDir || DEFAULT_TEMPLATES_DIR;
|
|
145
|
+
const templatePath = path.join(dir, `${templateName}.md`);
|
|
146
|
+
|
|
147
|
+
if (!fs.existsSync(templatePath)) {
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return fs.readFileSync(templatePath, "utf-8");
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Resolve repos for an item based on source configuration
|
|
156
|
+
* @param {object} source - Source configuration
|
|
157
|
+
* @param {object} item - Item from the source
|
|
158
|
+
* @returns {Array<string>} Array of repo keys
|
|
159
|
+
*/
|
|
160
|
+
export function resolveRepoForItem(source, item) {
|
|
161
|
+
// Multi-repo: explicit repos array
|
|
162
|
+
if (Array.isArray(source.repos)) {
|
|
163
|
+
return source.repos;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Single repo from field reference (e.g., "{repository.full_name}")
|
|
167
|
+
if (typeof source.repo === "string") {
|
|
168
|
+
const resolved = expandTemplate(source.repo, item);
|
|
169
|
+
// Only return if actually resolved (not still a template)
|
|
170
|
+
if (resolved && !resolved.includes("{")) {
|
|
171
|
+
return [resolved];
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// No repo configuration - repo-agnostic source
|
|
176
|
+
return [];
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* List all configured repo keys
|
|
181
|
+
* @returns {Array<string>} List of repo keys
|
|
182
|
+
*/
|
|
183
|
+
export function listRepos() {
|
|
184
|
+
const config = getRawConfig();
|
|
185
|
+
const repos = config.repos || {};
|
|
186
|
+
return Object.keys(repos);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Find repo key by local filesystem path
|
|
191
|
+
* @param {string} searchPath - Local path to search for
|
|
192
|
+
* @returns {string|null} Repo key or null if not found
|
|
193
|
+
*/
|
|
194
|
+
export function findRepoByPath(searchPath) {
|
|
195
|
+
const config = getRawConfig();
|
|
196
|
+
const repos = config.repos || {};
|
|
197
|
+
|
|
198
|
+
// Normalize search path
|
|
199
|
+
const normalizedSearch = path.resolve(searchPath.replace(/^~/, os.homedir()));
|
|
200
|
+
|
|
201
|
+
for (const repoKey of Object.keys(repos)) {
|
|
202
|
+
const repoConfig = repos[repoKey];
|
|
203
|
+
const repoPath = repoConfig.repo_path || repoConfig.path;
|
|
204
|
+
if (!repoPath) continue;
|
|
205
|
+
|
|
206
|
+
const normalizedRepoPath = path.resolve(
|
|
207
|
+
repoPath.replace(/^~/, os.homedir())
|
|
208
|
+
);
|
|
209
|
+
if (normalizedSearch === normalizedRepoPath) {
|
|
210
|
+
return repoKey;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
return null;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Clear config cache (for testing)
|
|
219
|
+
*/
|
|
220
|
+
export function clearConfigCache() {
|
|
221
|
+
configCache = null;
|
|
222
|
+
}
|