opencode-rules-md 0.6.5
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/LICENSE +21 -0
- package/README.md +650 -0
- package/dist/cli.mjs +374 -0
- package/dist/src/active-rules-state.d.ts +12 -0
- package/dist/src/active-rules-state.d.ts.map +1 -0
- package/dist/src/active-rules-state.js +118 -0
- package/dist/src/active-rules-state.js.map +1 -0
- package/dist/src/cli/config.d.ts +62 -0
- package/dist/src/cli/config.d.ts.map +1 -0
- package/dist/src/cli/config.js +246 -0
- package/dist/src/cli/config.js.map +1 -0
- package/dist/src/cli/install.d.ts +21 -0
- package/dist/src/cli/install.d.ts.map +1 -0
- package/dist/src/cli/install.js +63 -0
- package/dist/src/cli/install.js.map +1 -0
- package/dist/src/cli/main.d.ts +19 -0
- package/dist/src/cli/main.d.ts.map +1 -0
- package/dist/src/cli/main.js +135 -0
- package/dist/src/cli/main.js.map +1 -0
- package/dist/src/cli/real-fs.d.ts +26 -0
- package/dist/src/cli/real-fs.d.ts.map +1 -0
- package/dist/src/cli/real-fs.js +42 -0
- package/dist/src/cli/real-fs.js.map +1 -0
- package/dist/src/cli/status.d.ts +19 -0
- package/dist/src/cli/status.d.ts.map +1 -0
- package/dist/src/cli/status.js +58 -0
- package/dist/src/cli/status.js.map +1 -0
- package/dist/src/debug.d.ts +3 -0
- package/dist/src/debug.d.ts.map +1 -0
- package/dist/src/debug.js +8 -0
- package/dist/src/debug.js.map +1 -0
- package/dist/src/git-branch.d.ts +2 -0
- package/dist/src/git-branch.d.ts.map +1 -0
- package/dist/src/git-branch.js +30 -0
- package/dist/src/git-branch.js.map +1 -0
- package/dist/src/index.d.ts +26 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +55 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/mcp-tools.d.ts +8 -0
- package/dist/src/mcp-tools.d.ts.map +1 -0
- package/dist/src/mcp-tools.js +18 -0
- package/dist/src/mcp-tools.js.map +1 -0
- package/dist/src/message-context.d.ts +55 -0
- package/dist/src/message-context.d.ts.map +1 -0
- package/dist/src/message-context.js +110 -0
- package/dist/src/message-context.js.map +1 -0
- package/dist/src/message-paths.d.ts +34 -0
- package/dist/src/message-paths.d.ts.map +1 -0
- package/dist/src/message-paths.js +112 -0
- package/dist/src/message-paths.js.map +1 -0
- package/dist/src/project-fingerprint.d.ts +2 -0
- package/dist/src/project-fingerprint.d.ts.map +1 -0
- package/dist/src/project-fingerprint.js +61 -0
- package/dist/src/project-fingerprint.js.map +1 -0
- package/dist/src/rule-discovery.d.ts +49 -0
- package/dist/src/rule-discovery.d.ts.map +1 -0
- package/dist/src/rule-discovery.js +143 -0
- package/dist/src/rule-discovery.js.map +1 -0
- package/dist/src/rule-filter.d.ts +61 -0
- package/dist/src/rule-filter.d.ts.map +1 -0
- package/dist/src/rule-filter.js +180 -0
- package/dist/src/rule-filter.js.map +1 -0
- package/dist/src/rule-metadata.d.ts +29 -0
- package/dist/src/rule-metadata.d.ts.map +1 -0
- package/dist/src/rule-metadata.js +100 -0
- package/dist/src/rule-metadata.js.map +1 -0
- package/dist/src/runtime-chat.d.ts +34 -0
- package/dist/src/runtime-chat.d.ts.map +1 -0
- package/dist/src/runtime-chat.js +39 -0
- package/dist/src/runtime-chat.js.map +1 -0
- package/dist/src/runtime-context.d.ts +25 -0
- package/dist/src/runtime-context.d.ts.map +1 -0
- package/dist/src/runtime-context.js +109 -0
- package/dist/src/runtime-context.js.map +1 -0
- package/dist/src/runtime.d.ts +31 -0
- package/dist/src/runtime.d.ts.map +1 -0
- package/dist/src/runtime.js +238 -0
- package/dist/src/runtime.js.map +1 -0
- package/dist/src/session-store.d.ts +34 -0
- package/dist/src/session-store.d.ts.map +1 -0
- package/dist/src/session-store.js +90 -0
- package/dist/src/session-store.js.map +1 -0
- package/dist/src/utils.d.ts +15 -0
- package/dist/src/utils.d.ts.map +1 -0
- package/dist/src/utils.js +19 -0
- package/dist/src/utils.js.map +1 -0
- package/dist/tui/data/rules.d.ts +63 -0
- package/dist/tui/data/rules.d.ts.map +1 -0
- package/dist/tui/data/rules.js +173 -0
- package/dist/tui/data/rules.js.map +1 -0
- package/dist/tui/index.d.ts +8 -0
- package/dist/tui/index.d.ts.map +1 -0
- package/dist/tui/index.js +13 -0
- package/dist/tui/index.js.map +1 -0
- package/dist/tui/slots/sidebar-content.d.ts +11 -0
- package/dist/tui/slots/sidebar-content.d.ts.map +1 -0
- package/dist/tui/slots/sidebar-content.js +150 -0
- package/dist/tui/slots/sidebar-content.js.map +1 -0
- package/package.json +101 -0
- package/src/active-rules-state.ts +166 -0
- package/src/cli/config.ts +312 -0
- package/src/cli/install.ts +94 -0
- package/src/cli/main.ts +167 -0
- package/src/cli/real-fs.ts +65 -0
- package/src/cli/status.ts +80 -0
- package/src/debug.ts +9 -0
- package/src/git-branch.ts +37 -0
- package/src/index.ts +68 -0
- package/src/mcp-tools.ts +24 -0
- package/src/message-context.ts +139 -0
- package/src/message-paths.ts +153 -0
- package/src/project-fingerprint.ts +72 -0
- package/src/rule-discovery.ts +205 -0
- package/src/rule-filter.ts +269 -0
- package/src/rule-metadata.ts +161 -0
- package/src/runtime-chat.ts +68 -0
- package/src/runtime-context.ts +136 -0
- package/src/runtime.ts +369 -0
- package/src/session-store.ts +129 -0
- package/src/utils.ts +36 -0
- package/tui/.atl/.skill-registry.cache.json +3 -0
- package/tui/.atl/skill-registry.md +57 -0
- package/tui/data/rules.ts +230 -0
- package/tui/index.tsx +23 -0
- package/tui/slots/sidebar-content.tsx +364 -0
- package/tui/types/opencode-plugin-tui.d.ts +78 -0
package/dist/cli.mjs
ADDED
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/cli/main.ts
|
|
4
|
+
import { parseArgs } from "node:util";
|
|
5
|
+
|
|
6
|
+
// src/cli/config.ts
|
|
7
|
+
import * as os from "node:os";
|
|
8
|
+
import * as path from "node:path";
|
|
9
|
+
function stripJsoncComments(content) {
|
|
10
|
+
let result = "";
|
|
11
|
+
let i = 0;
|
|
12
|
+
while (i < content.length) {
|
|
13
|
+
const char = content[i];
|
|
14
|
+
if (char === '"') {
|
|
15
|
+
result += char;
|
|
16
|
+
i++;
|
|
17
|
+
while (i < content.length) {
|
|
18
|
+
const c = content[i];
|
|
19
|
+
if (c === "\\" && i + 1 < content.length) {
|
|
20
|
+
result += c + content[i + 1];
|
|
21
|
+
i += 2;
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
if (c === '"') {
|
|
25
|
+
result += c;
|
|
26
|
+
i++;
|
|
27
|
+
break;
|
|
28
|
+
}
|
|
29
|
+
result += c;
|
|
30
|
+
i++;
|
|
31
|
+
}
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
if (char === "/" && content[i + 1] === "/") {
|
|
35
|
+
while (i < content.length && content[i] !== `
|
|
36
|
+
`) {
|
|
37
|
+
i++;
|
|
38
|
+
}
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
if (char === "/" && content[i + 1] === "*") {
|
|
42
|
+
i += 2;
|
|
43
|
+
while (i + 1 < content.length && !(content[i] === "*" && content[i + 1] === "/")) {
|
|
44
|
+
i++;
|
|
45
|
+
}
|
|
46
|
+
if (i + 1 < content.length) {
|
|
47
|
+
i += 2;
|
|
48
|
+
}
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
if (char === "," && (content[i + 1] === "}" || content[i + 1] === "]")) {
|
|
52
|
+
i++;
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
result += char;
|
|
56
|
+
i++;
|
|
57
|
+
}
|
|
58
|
+
return result;
|
|
59
|
+
}
|
|
60
|
+
function resolveGlobalConfigPath(fs, opts = {}) {
|
|
61
|
+
const dir = resolveConfigDir();
|
|
62
|
+
if (opts.ensureDir && !fs.existsSync(dir)) {
|
|
63
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
64
|
+
}
|
|
65
|
+
return path.join(dir, "opencode.json");
|
|
66
|
+
}
|
|
67
|
+
function resolveConfigDir() {
|
|
68
|
+
if (process.env.OPENCODE_CONFIG_DIR) {
|
|
69
|
+
return process.env.OPENCODE_CONFIG_DIR;
|
|
70
|
+
}
|
|
71
|
+
if (process.env.XDG_CONFIG_HOME) {
|
|
72
|
+
return path.join(process.env.XDG_CONFIG_HOME, "opencode");
|
|
73
|
+
}
|
|
74
|
+
const home = process.env.HOME || os.homedir();
|
|
75
|
+
if (home) {
|
|
76
|
+
return path.join(home, ".config", "opencode");
|
|
77
|
+
}
|
|
78
|
+
const appdata = process.env.APPDATA;
|
|
79
|
+
if (appdata) {
|
|
80
|
+
return path.join(appdata, "opencode");
|
|
81
|
+
}
|
|
82
|
+
throw new Error("Cannot resolve config dir: none of OPENCODE_CONFIG_DIR, XDG_CONFIG_HOME, HOME, or APPDATA is set");
|
|
83
|
+
}
|
|
84
|
+
function loadGlobalConfig(fs) {
|
|
85
|
+
const configPath = resolveGlobalConfigPath(fs);
|
|
86
|
+
if (!fs.existsSync(configPath)) {
|
|
87
|
+
return { path: configPath, existed: false, config: {} };
|
|
88
|
+
}
|
|
89
|
+
try {
|
|
90
|
+
const raw = fs.readFileSync(configPath);
|
|
91
|
+
const stripped = stripJsoncComments(raw);
|
|
92
|
+
if (stripped.trim() === "") {
|
|
93
|
+
return { path: configPath, existed: true, config: {} };
|
|
94
|
+
}
|
|
95
|
+
const parsed = JSON.parse(stripped);
|
|
96
|
+
return { path: configPath, existed: true, config: parsed };
|
|
97
|
+
} catch (err) {
|
|
98
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
99
|
+
return { path: configPath, existed: true, config: {}, parseError: error };
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
function normalizePlugin(config) {
|
|
103
|
+
const raw = Array.isArray(config["plugin"]) ? config["plugin"] : [];
|
|
104
|
+
const seen = new Map;
|
|
105
|
+
const result = [];
|
|
106
|
+
for (const item of raw) {
|
|
107
|
+
if (typeof item !== "string")
|
|
108
|
+
continue;
|
|
109
|
+
const prefix = getPluginPrefix(item);
|
|
110
|
+
seen.set(prefix, result.length);
|
|
111
|
+
result.push(item);
|
|
112
|
+
}
|
|
113
|
+
return collapseByPrefix(result, seen);
|
|
114
|
+
}
|
|
115
|
+
function removePlugin(config, prefix) {
|
|
116
|
+
if (!Array.isArray(config["plugin"]))
|
|
117
|
+
return;
|
|
118
|
+
const raw = config["plugin"];
|
|
119
|
+
config["plugin"] = raw.filter((item) => typeof item === "string" && !item.startsWith(prefix));
|
|
120
|
+
}
|
|
121
|
+
function addPlugin(config, specifier) {
|
|
122
|
+
if (!Array.isArray(config["plugin"])) {
|
|
123
|
+
config["plugin"] = [];
|
|
124
|
+
}
|
|
125
|
+
const plugin = config["plugin"];
|
|
126
|
+
const prefix = getPluginPrefix(specifier);
|
|
127
|
+
const removed = plugin.filter((p) => getPluginPrefix(p) !== prefix);
|
|
128
|
+
removed.push(specifier);
|
|
129
|
+
config["plugin"] = removed;
|
|
130
|
+
}
|
|
131
|
+
function rotateBackups(fs, configPath, _pluginName) {
|
|
132
|
+
const dir = path.dirname(configPath);
|
|
133
|
+
const base = path.basename(configPath);
|
|
134
|
+
const timestamp = Date.now();
|
|
135
|
+
const newBak = path.join(dir, `${base}.bak.${timestamp}`);
|
|
136
|
+
if (fs.existsSync(configPath)) {
|
|
137
|
+
fs.copyFileSync(configPath, newBak);
|
|
138
|
+
} else {
|
|
139
|
+
fs.writeFileSync(newBak, "", "utf-8");
|
|
140
|
+
}
|
|
141
|
+
const entries = fs.readdirSync(dir);
|
|
142
|
+
const bakFiles = entries.filter((e) => e.startsWith(base + ".bak.")).sort();
|
|
143
|
+
while (bakFiles.length > 3) {
|
|
144
|
+
const oldest = bakFiles.shift();
|
|
145
|
+
fs.unlinkSync(path.join(dir, oldest));
|
|
146
|
+
}
|
|
147
|
+
return newBak;
|
|
148
|
+
}
|
|
149
|
+
function writeAtomically(fs, configPath, content) {
|
|
150
|
+
const dir = path.dirname(configPath);
|
|
151
|
+
const base = path.basename(configPath);
|
|
152
|
+
const tmpPath = path.join(dir, `.${base}.tmp.${Date.now()}`);
|
|
153
|
+
fs.writeFileSync(tmpPath, content, "utf-8");
|
|
154
|
+
fs.renameSync(tmpPath, configPath);
|
|
155
|
+
}
|
|
156
|
+
function getPluginPrefix(specifier) {
|
|
157
|
+
const atIdx = specifier.indexOf("@");
|
|
158
|
+
return atIdx === -1 ? specifier : specifier.substring(0, atIdx);
|
|
159
|
+
}
|
|
160
|
+
function collapseByPrefix(plugins, lastSeen) {
|
|
161
|
+
const keptIndices = new Set([...lastSeen.values()]);
|
|
162
|
+
return plugins.filter((_, idx) => keptIndices.has(idx));
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// src/cli/real-fs.ts
|
|
166
|
+
import * as fs from "node:fs";
|
|
167
|
+
import * as nodePath from "node:path";
|
|
168
|
+
var realFs = {
|
|
169
|
+
readFileSync(filePath) {
|
|
170
|
+
return fs.readFileSync(filePath, "utf-8");
|
|
171
|
+
},
|
|
172
|
+
writeFileSync(filePath, content, _encoding) {
|
|
173
|
+
const dir = nodePath.dirname(filePath);
|
|
174
|
+
if (!fs.existsSync(dir)) {
|
|
175
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
176
|
+
}
|
|
177
|
+
fs.writeFileSync(filePath, content, "utf-8");
|
|
178
|
+
},
|
|
179
|
+
renameSync(from, to) {
|
|
180
|
+
fs.renameSync(from, to);
|
|
181
|
+
},
|
|
182
|
+
copyFileSync(from, to) {
|
|
183
|
+
fs.copyFileSync(from, to);
|
|
184
|
+
},
|
|
185
|
+
unlinkSync(filePath) {
|
|
186
|
+
fs.unlinkSync(filePath);
|
|
187
|
+
},
|
|
188
|
+
mkdirSync(filePath, opts) {
|
|
189
|
+
fs.mkdirSync(filePath, opts);
|
|
190
|
+
},
|
|
191
|
+
readdirSync(filePath) {
|
|
192
|
+
return fs.readdirSync(filePath);
|
|
193
|
+
},
|
|
194
|
+
existsSync(filePath) {
|
|
195
|
+
return fs.existsSync(filePath);
|
|
196
|
+
}
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
// src/cli/install.ts
|
|
200
|
+
function runInstall(opts, fs2 = realFs) {
|
|
201
|
+
const loadResult = loadGlobalConfig(fs2);
|
|
202
|
+
if (loadResult.parseError) {
|
|
203
|
+
return {
|
|
204
|
+
status: "error",
|
|
205
|
+
path: loadResult.path,
|
|
206
|
+
specifier: buildSpecifier(opts.version),
|
|
207
|
+
parseError: loadResult.parseError
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
const configPath = loadResult.path;
|
|
211
|
+
const config = loadResult.config;
|
|
212
|
+
config["plugin"] = normalizePlugin(config);
|
|
213
|
+
const specifier = buildSpecifier(opts.version);
|
|
214
|
+
const existing = config["plugin"] ?? [];
|
|
215
|
+
if (existing.includes(specifier)) {
|
|
216
|
+
return { status: "noop", path: configPath, specifier };
|
|
217
|
+
}
|
|
218
|
+
removePlugin(config, "opencode-rules-md");
|
|
219
|
+
addPlugin(config, specifier);
|
|
220
|
+
if (opts.dryRun) {
|
|
221
|
+
const planned = JSON.stringify(config, null, 2);
|
|
222
|
+
console.log(`Planned config:
|
|
223
|
+
` + planned);
|
|
224
|
+
return { status: "planned", path: configPath, specifier };
|
|
225
|
+
}
|
|
226
|
+
const backup = rotateBackups(fs2, configPath, "opencode-rules-md");
|
|
227
|
+
const serialized = JSON.stringify(config, null, 2);
|
|
228
|
+
writeAtomically(fs2, configPath, serialized);
|
|
229
|
+
console.log(`Installed ${specifier} to ${configPath}`);
|
|
230
|
+
if (backup) {
|
|
231
|
+
console.log(`Backup written to ${backup}`);
|
|
232
|
+
}
|
|
233
|
+
return { status: "wrote", path: configPath, specifier, backup };
|
|
234
|
+
}
|
|
235
|
+
function buildSpecifier(version) {
|
|
236
|
+
return version ? `opencode-rules-md@${version}` : "opencode-rules-md";
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// src/cli/status.ts
|
|
240
|
+
import * as fs2 from "node:fs";
|
|
241
|
+
import * as path2 from "node:path";
|
|
242
|
+
function runStatus(fs3 = realFs) {
|
|
243
|
+
const loadResult = loadGlobalConfig(fs3);
|
|
244
|
+
const configPath = loadResult.path;
|
|
245
|
+
if (loadResult.parseError) {
|
|
246
|
+
return {
|
|
247
|
+
installed: false,
|
|
248
|
+
path: configPath,
|
|
249
|
+
version: getVersion(),
|
|
250
|
+
parseError: loadResult.parseError
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
const config = loadResult.config;
|
|
254
|
+
const pluginList = Array.isArray(config["plugin"]) ? config["plugin"] : [];
|
|
255
|
+
const specifier = pluginList.find((p) => p.startsWith("opencode-rules-md"));
|
|
256
|
+
const result = {
|
|
257
|
+
installed: specifier !== undefined,
|
|
258
|
+
path: configPath,
|
|
259
|
+
version: getVersion()
|
|
260
|
+
};
|
|
261
|
+
if (specifier) {
|
|
262
|
+
result.specifier = specifier;
|
|
263
|
+
}
|
|
264
|
+
return result;
|
|
265
|
+
}
|
|
266
|
+
function getVersion() {
|
|
267
|
+
try {
|
|
268
|
+
const pkgPath = path2.resolve(process.cwd(), "package.json");
|
|
269
|
+
const content = fs2.readFileSync(pkgPath, "utf-8");
|
|
270
|
+
const pkg = JSON.parse(content);
|
|
271
|
+
return pkg.version ?? "unknown";
|
|
272
|
+
} catch {
|
|
273
|
+
return "unknown";
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// src/cli/main.ts
|
|
278
|
+
var HELP_TEXT = `opencode-rules-md CLI
|
|
279
|
+
|
|
280
|
+
Usage: opencode-rules-md <command> [options]
|
|
281
|
+
|
|
282
|
+
Commands:
|
|
283
|
+
install Add opencode-rules-md to the global opencode config
|
|
284
|
+
status Report whether opencode-rules-md is installed
|
|
285
|
+
|
|
286
|
+
Options:
|
|
287
|
+
--version <v> Specify a plugin version to install
|
|
288
|
+
--latest Install the latest version (default)
|
|
289
|
+
--dry-run Show what would be written, without changing files
|
|
290
|
+
--yes Skip confirmation prompts (future use)
|
|
291
|
+
-h, --help Show this help text
|
|
292
|
+
`.trim();
|
|
293
|
+
var USAGE_ERROR_TEXT = `Error: unknown command. Run 'opencode-rules-md --help' for usage.
|
|
294
|
+
`.trim();
|
|
295
|
+
function parseCliArgs(argv) {
|
|
296
|
+
const { positionals, values } = parseArgs({
|
|
297
|
+
args: argv,
|
|
298
|
+
allowPositionals: true,
|
|
299
|
+
options: {
|
|
300
|
+
version: { type: "string" },
|
|
301
|
+
latest: { type: "boolean" },
|
|
302
|
+
"dry-run": { type: "boolean" },
|
|
303
|
+
yes: { type: "boolean" },
|
|
304
|
+
h: { type: "boolean" },
|
|
305
|
+
help: { type: "boolean" }
|
|
306
|
+
}
|
|
307
|
+
});
|
|
308
|
+
const command = positionals[0] ?? null;
|
|
309
|
+
const options = {
|
|
310
|
+
...values.version !== undefined && { version: values.version },
|
|
311
|
+
...values.latest && { latest: true },
|
|
312
|
+
...values["dry-run"] && { dryRun: true },
|
|
313
|
+
...values.yes && { yes: true },
|
|
314
|
+
...(values.help || values.h) && { help: true }
|
|
315
|
+
};
|
|
316
|
+
const unknownFlags = [];
|
|
317
|
+
return { command, options, unknownFlags };
|
|
318
|
+
}
|
|
319
|
+
async function runMain(argv, fs3 = realFs) {
|
|
320
|
+
try {
|
|
321
|
+
const { command, options, unknownFlags } = parseCliArgs(argv);
|
|
322
|
+
if (options.help) {
|
|
323
|
+
console.log(HELP_TEXT);
|
|
324
|
+
return 0;
|
|
325
|
+
}
|
|
326
|
+
if (unknownFlags.length > 0) {
|
|
327
|
+
console.error(`Error: unknown option(s): ${unknownFlags.join(", ")}`);
|
|
328
|
+
console.error(USAGE_ERROR_TEXT);
|
|
329
|
+
return 2;
|
|
330
|
+
}
|
|
331
|
+
if (!command) {
|
|
332
|
+
console.error(USAGE_ERROR_TEXT);
|
|
333
|
+
return 2;
|
|
334
|
+
}
|
|
335
|
+
switch (command) {
|
|
336
|
+
case "install": {
|
|
337
|
+
const installOpts = {};
|
|
338
|
+
if (options.version !== undefined)
|
|
339
|
+
installOpts.version = options.version;
|
|
340
|
+
if (options.dryRun)
|
|
341
|
+
installOpts.dryRun = true;
|
|
342
|
+
const installResult = runInstall(installOpts, fs3);
|
|
343
|
+
if (installResult.status === "error") {
|
|
344
|
+
return 1;
|
|
345
|
+
}
|
|
346
|
+
return 0;
|
|
347
|
+
}
|
|
348
|
+
case "status": {
|
|
349
|
+
const statusResult = runStatus(fs3);
|
|
350
|
+
if (statusResult.installed) {
|
|
351
|
+
console.log(`opencode-rules-md is installed (${statusResult.specifier})`);
|
|
352
|
+
} else {
|
|
353
|
+
console.log("opencode-rules-md is not installed");
|
|
354
|
+
}
|
|
355
|
+
return 0;
|
|
356
|
+
}
|
|
357
|
+
default: {
|
|
358
|
+
console.error(`Error: unknown command '${command}'`);
|
|
359
|
+
console.error(USAGE_ERROR_TEXT);
|
|
360
|
+
return 2;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
} catch (err) {
|
|
364
|
+
console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
365
|
+
return 1;
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
369
|
+
const exitCode = await runMain(process.argv.slice(2));
|
|
370
|
+
process.exit(exitCode);
|
|
371
|
+
}
|
|
372
|
+
export {
|
|
373
|
+
runMain
|
|
374
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export interface ActiveRulesState {
|
|
2
|
+
sessionId: string;
|
|
3
|
+
matchedRulePaths: string[];
|
|
4
|
+
evaluatedAt: number;
|
|
5
|
+
}
|
|
6
|
+
/** @internal Test-only: override the state directory */
|
|
7
|
+
export declare function _setStateDirForTesting(dir: string | null): void;
|
|
8
|
+
export declare function resolveStateDir(): string;
|
|
9
|
+
export declare function getStateFilePath(sessionId: string): string;
|
|
10
|
+
export declare function writeActiveRulesState(sessionId: string, matchedPaths: string[]): void;
|
|
11
|
+
export declare function readActiveRulesState(sessionId: string): Promise<ActiveRulesState | null>;
|
|
12
|
+
//# sourceMappingURL=active-rules-state.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"active-rules-state.d.ts","sourceRoot":"","sources":["../../src/active-rules-state.ts"],"names":[],"mappings":"AAQA,MAAM,WAAW,gBAAgB;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,WAAW,EAAE,MAAM,CAAC;CACrB;AAeD,wDAAwD;AACxD,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAE/D;AAED,wBAAgB,eAAe,IAAI,MAAM,CAKxC;AAED,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAK1D;AAED,wBAAgB,qBAAqB,CACnC,SAAS,EAAE,MAAM,EACjB,YAAY,EAAE,MAAM,EAAE,GACrB,IAAI,CAyBN;AAqCD,wBAAsB,oBAAoB,CACxC,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC,CAwBlC"}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import * as fs from 'node:fs/promises';
|
|
2
|
+
import * as os from 'node:os';
|
|
3
|
+
import * as path from 'node:path';
|
|
4
|
+
import * as crypto from 'node:crypto';
|
|
5
|
+
import { createDebugLog } from './debug';
|
|
6
|
+
const debugLog = createDebugLog();
|
|
7
|
+
// Per-session write queue to serialize concurrent writes
|
|
8
|
+
const writeQueues = new Map();
|
|
9
|
+
// Allows tests to override the state directory
|
|
10
|
+
let stateDirOverride = null;
|
|
11
|
+
// Strict pattern for safe sessionId: alphanumeric, underscore, hyphen only
|
|
12
|
+
const SAFE_SESSION_ID_PATTERN = /^[A-Za-z0-9_-]+$/;
|
|
13
|
+
function isValidSessionId(sessionId) {
|
|
14
|
+
return SAFE_SESSION_ID_PATTERN.test(sessionId);
|
|
15
|
+
}
|
|
16
|
+
/** @internal Test-only: override the state directory */
|
|
17
|
+
export function _setStateDirForTesting(dir) {
|
|
18
|
+
stateDirOverride = dir;
|
|
19
|
+
}
|
|
20
|
+
export function resolveStateDir() {
|
|
21
|
+
if (stateDirOverride !== null) {
|
|
22
|
+
return stateDirOverride;
|
|
23
|
+
}
|
|
24
|
+
return path.join(os.homedir(), '.opencode', 'state', 'opencode-rules-md');
|
|
25
|
+
}
|
|
26
|
+
export function getStateFilePath(sessionId) {
|
|
27
|
+
if (!isValidSessionId(sessionId)) {
|
|
28
|
+
throw new Error(`Invalid sessionId: ${sessionId}`);
|
|
29
|
+
}
|
|
30
|
+
return path.join(resolveStateDir(), `${sessionId}.json`);
|
|
31
|
+
}
|
|
32
|
+
export function writeActiveRulesState(sessionId, matchedPaths) {
|
|
33
|
+
if (!isValidSessionId(sessionId)) {
|
|
34
|
+
debugLog(`Invalid sessionId rejected: ${sessionId}`);
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
const state = {
|
|
38
|
+
sessionId,
|
|
39
|
+
matchedRulePaths: matchedPaths,
|
|
40
|
+
evaluatedAt: Date.now(),
|
|
41
|
+
};
|
|
42
|
+
// Chain onto existing queue for this session, or start fresh
|
|
43
|
+
const previousWrite = writeQueues.get(sessionId) ?? Promise.resolve();
|
|
44
|
+
const currentWrite = previousWrite.then(async () => {
|
|
45
|
+
await doAtomicWrite(sessionId, state);
|
|
46
|
+
});
|
|
47
|
+
writeQueues.set(sessionId, currentWrite);
|
|
48
|
+
// Fire-and-forget: catch errors to prevent unhandled rejection
|
|
49
|
+
currentWrite.catch(() => {
|
|
50
|
+
// Errors already logged in doAtomicWrite
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
async function doAtomicWrite(sessionId, state) {
|
|
54
|
+
const stateDir = resolveStateDir();
|
|
55
|
+
const finalPath = getStateFilePath(sessionId);
|
|
56
|
+
const tempPath = path.join(stateDir, `.${sessionId}-${crypto.randomBytes(8).toString('hex')}.tmp`);
|
|
57
|
+
try {
|
|
58
|
+
// Ensure directory exists
|
|
59
|
+
await fs.mkdir(stateDir, { recursive: true });
|
|
60
|
+
// Write to temp file
|
|
61
|
+
const content = JSON.stringify(state);
|
|
62
|
+
await fs.writeFile(tempPath, content, 'utf-8');
|
|
63
|
+
// Atomic rename
|
|
64
|
+
await fs.rename(tempPath, finalPath);
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
debugLog(`Failed to write active rules state for session ${sessionId}: ${error}`);
|
|
68
|
+
// Clean up temp file if it exists
|
|
69
|
+
try {
|
|
70
|
+
await fs.unlink(tempPath);
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
// Ignore cleanup errors
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
export async function readActiveRulesState(sessionId) {
|
|
78
|
+
if (!isValidSessionId(sessionId)) {
|
|
79
|
+
debugLog(`Invalid sessionId rejected: ${sessionId}`);
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
const filePath = getStateFilePath(sessionId);
|
|
83
|
+
try {
|
|
84
|
+
const content = await fs.readFile(filePath, 'utf-8');
|
|
85
|
+
const parsed = JSON.parse(content);
|
|
86
|
+
if (!isValidActiveRulesState(parsed)) {
|
|
87
|
+
debugLog(`Invalid active rules state format for session ${sessionId}`);
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
return parsed;
|
|
91
|
+
}
|
|
92
|
+
catch (error) {
|
|
93
|
+
debugLog(`Failed to read active rules state for session ${sessionId}: ${error}`);
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
function isValidActiveRulesState(value) {
|
|
98
|
+
if (typeof value !== 'object' || value === null) {
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
const obj = value;
|
|
102
|
+
if (typeof obj['sessionId'] !== 'string') {
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
if (typeof obj['evaluatedAt'] !== 'number') {
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
if (!Array.isArray(obj['matchedRulePaths'])) {
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
for (const item of obj['matchedRulePaths']) {
|
|
112
|
+
if (typeof item !== 'string') {
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return true;
|
|
117
|
+
}
|
|
118
|
+
//# sourceMappingURL=active-rules-state.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"active-rules-state.js","sourceRoot":"","sources":["../../src/active-rules-state.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,kBAAkB,CAAC;AACvC,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAClC,OAAO,KAAK,MAAM,MAAM,aAAa,CAAC;AACtC,OAAO,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAEzC,MAAM,QAAQ,GAAG,cAAc,EAAE,CAAC;AAQlC,yDAAyD;AACzD,MAAM,WAAW,GAAG,IAAI,GAAG,EAAyB,CAAC;AAErD,+CAA+C;AAC/C,IAAI,gBAAgB,GAAkB,IAAI,CAAC;AAE3C,2EAA2E;AAC3E,MAAM,uBAAuB,GAAG,kBAAkB,CAAC;AAEnD,SAAS,gBAAgB,CAAC,SAAiB;IACzC,OAAO,uBAAuB,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;AACjD,CAAC;AAED,wDAAwD;AACxD,MAAM,UAAU,sBAAsB,CAAC,GAAkB;IACvD,gBAAgB,GAAG,GAAG,CAAC;AACzB,CAAC;AAED,MAAM,UAAU,eAAe;IAC7B,IAAI,gBAAgB,KAAK,IAAI,EAAE,CAAC;QAC9B,OAAO,gBAAgB,CAAC;IAC1B,CAAC;IACD,OAAO,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,WAAW,EAAE,OAAO,EAAE,mBAAmB,CAAC,CAAC;AAC5E,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,SAAiB;IAChD,IAAI,CAAC,gBAAgB,CAAC,SAAS,CAAC,EAAE,CAAC;QACjC,MAAM,IAAI,KAAK,CAAC,sBAAsB,SAAS,EAAE,CAAC,CAAC;IACrD,CAAC;IACD,OAAO,IAAI,CAAC,IAAI,CAAC,eAAe,EAAE,EAAE,GAAG,SAAS,OAAO,CAAC,CAAC;AAC3D,CAAC;AAED,MAAM,UAAU,qBAAqB,CACnC,SAAiB,EACjB,YAAsB;IAEtB,IAAI,CAAC,gBAAgB,CAAC,SAAS,CAAC,EAAE,CAAC;QACjC,QAAQ,CAAC,+BAA+B,SAAS,EAAE,CAAC,CAAC;QACrD,OAAO;IACT,CAAC;IAED,MAAM,KAAK,GAAqB;QAC9B,SAAS;QACT,gBAAgB,EAAE,YAAY;QAC9B,WAAW,EAAE,IAAI,CAAC,GAAG,EAAE;KACxB,CAAC;IAEF,6DAA6D;IAC7D,MAAM,aAAa,GAAG,WAAW,CAAC,GAAG,CAAC,SAAS,CAAC,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;IAEtE,MAAM,YAAY,GAAG,aAAa,CAAC,IAAI,CAAC,KAAK,IAAI,EAAE;QACjD,MAAM,aAAa,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;IACxC,CAAC,CAAC,CAAC;IAEH,WAAW,CAAC,GAAG,CAAC,SAAS,EAAE,YAAY,CAAC,CAAC;IAEzC,+DAA+D;IAC/D,YAAY,CAAC,KAAK,CAAC,GAAG,EAAE;QACtB,yCAAyC;IAC3C,CAAC,CAAC,CAAC;AACL,CAAC;AAED,KAAK,UAAU,aAAa,CAC1B,SAAiB,EACjB,KAAuB;IAEvB,MAAM,QAAQ,GAAG,eAAe,EAAE,CAAC;IACnC,MAAM,SAAS,GAAG,gBAAgB,CAAC,SAAS,CAAC,CAAC;IAC9C,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CACxB,QAAQ,EACR,IAAI,SAAS,IAAI,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,MAAM,CAC7D,CAAC;IAEF,IAAI,CAAC;QACH,0BAA0B;QAC1B,MAAM,EAAE,CAAC,KAAK,CAAC,QAAQ,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAE9C,qBAAqB;QACrB,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;QACtC,MAAM,EAAE,CAAC,SAAS,CAAC,QAAQ,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;QAE/C,gBAAgB;QAChB,MAAM,EAAE,CAAC,MAAM,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC;IACvC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,QAAQ,CACN,kDAAkD,SAAS,KAAK,KAAK,EAAE,CACxE,CAAC;QAEF,kCAAkC;QAClC,IAAI,CAAC;YACH,MAAM,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;QAC5B,CAAC;QAAC,MAAM,CAAC;YACP,wBAAwB;QAC1B,CAAC;IACH,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,oBAAoB,CACxC,SAAiB;IAEjB,IAAI,CAAC,gBAAgB,CAAC,SAAS,CAAC,EAAE,CAAC;QACjC,QAAQ,CAAC,+BAA+B,SAAS,EAAE,CAAC,CAAC;QACrD,OAAO,IAAI,CAAC;IACd,CAAC;IAED,MAAM,QAAQ,GAAG,gBAAgB,CAAC,SAAS,CAAC,CAAC;IAE7C,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;QACrD,MAAM,MAAM,GAAY,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QAE5C,IAAI,CAAC,uBAAuB,CAAC,MAAM,CAAC,EAAE,CAAC;YACrC,QAAQ,CAAC,iDAAiD,SAAS,EAAE,CAAC,CAAC;YACvE,OAAO,IAAI,CAAC;QACd,CAAC;QAED,OAAO,MAAM,CAAC;IAChB,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,QAAQ,CACN,iDAAiD,SAAS,KAAK,KAAK,EAAE,CACvE,CAAC;QACF,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,SAAS,uBAAuB,CAAC,KAAc;IAC7C,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;QAChD,OAAO,KAAK,CAAC;IACf,CAAC;IAED,MAAM,GAAG,GAAG,KAAgC,CAAC;IAE7C,IAAI,OAAO,GAAG,CAAC,WAAW,CAAC,KAAK,QAAQ,EAAE,CAAC;QACzC,OAAO,KAAK,CAAC;IACf,CAAC;IAED,IAAI,OAAO,GAAG,CAAC,aAAa,CAAC,KAAK,QAAQ,EAAE,CAAC;QAC3C,OAAO,KAAK,CAAC;IACf,CAAC;IAED,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAC,EAAE,CAAC;QAC5C,OAAO,KAAK,CAAC;IACf,CAAC;IAED,KAAK,MAAM,IAAI,IAAI,GAAG,CAAC,kBAAkB,CAAC,EAAE,CAAC;QAC3C,IAAI,OAAO,IAAI,KAAK,QAAQ,EAAE,CAAC;YAC7B,OAAO,KAAK,CAAC;QACf,CAAC;IACH,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI config helpers: discovery, JSONC-safe parsing, plugin normalization,
|
|
3
|
+
* backup rotation, and atomic write.
|
|
4
|
+
*
|
|
5
|
+
* All filesystem access goes through the CliFs interface so tests can inject
|
|
6
|
+
* an in-memory implementation without touching real disk.
|
|
7
|
+
*/
|
|
8
|
+
import type { CliFs } from './real-fs.js';
|
|
9
|
+
export interface LoadResult {
|
|
10
|
+
path: string;
|
|
11
|
+
existed: boolean;
|
|
12
|
+
config: Record<string, unknown>;
|
|
13
|
+
parseError?: Error;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Strip JSONC comments and trailing commas while preserving
|
|
17
|
+
* comment-like sequences inside string literals.
|
|
18
|
+
*/
|
|
19
|
+
export declare function stripJsoncComments(content: string): string;
|
|
20
|
+
/**
|
|
21
|
+
* Resolve the global opencode config path using the same precedence as
|
|
22
|
+
* opencode itself: OPENCODE_CONFIG_DIR → XDG_CONFIG_HOME → HOME fallback.
|
|
23
|
+
*
|
|
24
|
+
* On Windows, APPDATA is tried as a last resort when XDG_CONFIG_HOME and
|
|
25
|
+
* HOME are both absent.
|
|
26
|
+
*/
|
|
27
|
+
export declare function resolveGlobalConfigPath(fs: CliFs, opts?: {
|
|
28
|
+
ensureDir?: boolean;
|
|
29
|
+
}): string;
|
|
30
|
+
/**
|
|
31
|
+
* Load and parse the global opencode config.
|
|
32
|
+
* Treats a missing file as an empty config object (not an error).
|
|
33
|
+
* Surfaces parse errors so callers can exit non-zero without corrupting data.
|
|
34
|
+
*/
|
|
35
|
+
export declare function loadGlobalConfig(fs: CliFs): LoadResult;
|
|
36
|
+
/**
|
|
37
|
+
* Return the plugin array with opencode-rules-md* entries deduplicated by
|
|
38
|
+
* name prefix, keeping the last occurrence of each unique prefix.
|
|
39
|
+
*/
|
|
40
|
+
export declare function normalizePlugin(config: Record<string, unknown>): string[];
|
|
41
|
+
/**
|
|
42
|
+
* Remove all plugins matching the given prefix (used to dedupe before append).
|
|
43
|
+
*/
|
|
44
|
+
export declare function removePlugin(config: Record<string, unknown>, prefix: string): void;
|
|
45
|
+
/**
|
|
46
|
+
* Add a plugin specifier, removing any existing entry with the same prefix first
|
|
47
|
+
* so it ends up at the specified position (default: end). This makes the operation
|
|
48
|
+
* idempotent — re-adding the same specifier just moves it to the end.
|
|
49
|
+
*/
|
|
50
|
+
export declare function addPlugin(config: Record<string, unknown>, specifier: string): void;
|
|
51
|
+
/**
|
|
52
|
+
* Write a timestamped backup of the given config path and rotate so at most
|
|
53
|
+
* 3 backups are retained (oldest deleted first).
|
|
54
|
+
* Returns the path of the newly created backup.
|
|
55
|
+
*/
|
|
56
|
+
export declare function rotateBackups(fs: CliFs, configPath: string, _pluginName: string): string;
|
|
57
|
+
/**
|
|
58
|
+
* Write content to a temporary sibling file then rename it into place.
|
|
59
|
+
* The final path is never in a partially-written state.
|
|
60
|
+
*/
|
|
61
|
+
export declare function writeAtomically(fs: CliFs, configPath: string, content: string): void;
|
|
62
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../../src/cli/config.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,cAAc,CAAC;AAM1C,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,UAAU,CAAC,EAAE,KAAK,CAAC;CACpB;AAMD;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAgE1D;AAMD;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CACrC,EAAE,EAAE,KAAK,EACT,IAAI,GAAE;IAAE,SAAS,CAAC,EAAE,OAAO,CAAA;CAAO,GACjC,MAAM,CAMR;AA+BD;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,KAAK,GAAG,UAAU,CAoBtD;AAMD;;;GAGG;AACH,wBAAgB,eAAe,CAC7B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC9B,MAAM,EAAE,CAcV;AAED;;GAEG;AACH,wBAAgB,YAAY,CAC1B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,MAAM,EAAE,MAAM,GACb,IAAI,CAMN;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CACvB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,SAAS,EAAE,MAAM,GAChB,IAAI,CAUN;AAMD;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,EAAE,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM,CAyBxF;AAMD;;;GAGG;AACH,wBAAgB,eAAe,CAAC,EAAE,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAOpF"}
|