@burmese/opencode 3.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/LICENSE +21 -0
- package/dist/check-opencode-foundation.d.ts +2 -0
- package/dist/check-opencode-foundation.d.ts.map +1 -0
- package/dist/check-opencode-foundation.js +245 -0
- package/dist/check-opencode-foundation.js.map +1 -0
- package/dist/check-opencode-plugin.d.ts +2 -0
- package/dist/check-opencode-plugin.d.ts.map +1 -0
- package/dist/check-opencode-plugin.js +107 -0
- package/dist/check-opencode-plugin.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -0
- package/dist/opencode-config.d.ts +44 -0
- package/dist/opencode-config.d.ts.map +1 -0
- package/dist/opencode-config.js +213 -0
- package/dist/opencode-config.js.map +1 -0
- package/dist/opencode-global-setup.d.ts +42 -0
- package/dist/opencode-global-setup.d.ts.map +1 -0
- package/dist/opencode-global-setup.js +378 -0
- package/dist/opencode-global-setup.js.map +1 -0
- package/dist/opencode-plugin-runtime.d.ts +38 -0
- package/dist/opencode-plugin-runtime.d.ts.map +1 -0
- package/dist/opencode-plugin-runtime.js +213 -0
- package/dist/opencode-plugin-runtime.js.map +1 -0
- package/dist/opencode-previews.d.ts +25 -0
- package/dist/opencode-previews.d.ts.map +1 -0
- package/dist/opencode-previews.js +37 -0
- package/dist/opencode-previews.js.map +1 -0
- package/dist/opencode-project-setup.d.ts +27 -0
- package/dist/opencode-project-setup.d.ts.map +1 -0
- package/dist/opencode-project-setup.js +180 -0
- package/dist/opencode-project-setup.js.map +1 -0
- package/dist/opencode-status.d.ts +14 -0
- package/dist/opencode-status.d.ts.map +1 -0
- package/dist/opencode-status.js +145 -0
- package/dist/opencode-status.js.map +1 -0
- package/dist/plugin.d.ts +8 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/plugin.js +8 -0
- package/dist/plugin.js.map +1 -0
- package/package.json +44 -0
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { chmodSync, closeSync, existsSync, lstatSync, mkdirSync, openSync, readFileSync, renameSync, statSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { dirname, isAbsolute, join, relative } from "node:path";
|
|
4
|
+
import { homedir } from "node:os";
|
|
5
|
+
import { applyEdits, modify, parse } from "jsonc-parser";
|
|
6
|
+
export const maxOpenCodeConfigBytes = 1024 * 1024;
|
|
7
|
+
export function getProjectOpenCodeConfigPaths(projectDir) {
|
|
8
|
+
const root = assertSafeProjectRoot(projectDir);
|
|
9
|
+
return {
|
|
10
|
+
candidates: [join(root, "opencode.json"), join(root, "opencode.jsonc"), join(root, ".opencode", "opencode.json"), join(root, ".opencode", "opencode.jsonc")],
|
|
11
|
+
defaultCreatePath: join(root, ".opencode", "opencode.jsonc"),
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
export function selectProjectOpenCodeConfigPath(projectDir) {
|
|
15
|
+
const paths = getProjectOpenCodeConfigPaths(projectDir);
|
|
16
|
+
return paths.candidates.find((candidate) => existsSync(candidate)) ?? paths.defaultCreatePath;
|
|
17
|
+
}
|
|
18
|
+
export function getGlobalOpenCodeConfigDir(env = process.env, homeDir = homedir(), platform = process.platform) {
|
|
19
|
+
if (env.OPENCODE_CONFIG_DIR)
|
|
20
|
+
return env.OPENCODE_CONFIG_DIR;
|
|
21
|
+
if (platform === "win32")
|
|
22
|
+
return join(env.APPDATA || join(homeDir, "AppData", "Roaming"), "opencode");
|
|
23
|
+
return join(env.XDG_CONFIG_HOME || join(homeDir, ".config"), "opencode");
|
|
24
|
+
}
|
|
25
|
+
export function getGlobalOpenCodeConfigPaths(env = process.env, homeDir = homedir(), platform = process.platform) {
|
|
26
|
+
const configDir = getGlobalOpenCodeConfigDir(env, homeDir, platform);
|
|
27
|
+
return {
|
|
28
|
+
candidates: [join(configDir, "config.json"), join(configDir, "opencode.json"), join(configDir, "opencode.jsonc")],
|
|
29
|
+
defaultCreatePath: join(configDir, "opencode.jsonc"),
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
export function createOpenCodeExecutableDetection(input = {}) {
|
|
33
|
+
const platform = input.platform ?? process.platform;
|
|
34
|
+
return {
|
|
35
|
+
command: input.command ?? (platform === "win32" ? "opencode.cmd" : "opencode"),
|
|
36
|
+
platform,
|
|
37
|
+
available: input.available ?? false,
|
|
38
|
+
version: input.version,
|
|
39
|
+
error: input.error,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
export function readOpenCodeConfigFile(path) {
|
|
43
|
+
const safety = assertSafeExistingConfigFile(path);
|
|
44
|
+
if (!safety.ok)
|
|
45
|
+
return safety;
|
|
46
|
+
return parseOpenCodeConfig(readFileSync(path, "utf8"));
|
|
47
|
+
}
|
|
48
|
+
export function parseOpenCodeConfig(text) {
|
|
49
|
+
if (Buffer.byteLength(text, "utf8") > maxOpenCodeConfigBytes)
|
|
50
|
+
return { ok: false, message: "OpenCode config is too large." };
|
|
51
|
+
const errors = [];
|
|
52
|
+
const parsed = parse(text || "{}", errors, { allowTrailingComma: true, disallowComments: false });
|
|
53
|
+
if (errors.length > 0)
|
|
54
|
+
return { ok: false, message: "OpenCode config JSONC is invalid." };
|
|
55
|
+
if (!isRecord(parsed) || Array.isArray(parsed))
|
|
56
|
+
return { ok: false, message: "OpenCode config must be a JSON object." };
|
|
57
|
+
const fields = validateKnownFieldTypes(parsed);
|
|
58
|
+
if (!fields.ok)
|
|
59
|
+
return fields;
|
|
60
|
+
return { ok: true, value: parsed };
|
|
61
|
+
}
|
|
62
|
+
export function updateOpenCodeConfigText(text, updates) {
|
|
63
|
+
const parsed = parseOpenCodeConfig(text);
|
|
64
|
+
if (!parsed.ok)
|
|
65
|
+
return parsed;
|
|
66
|
+
let next = text.trim() ? text : "{}\n";
|
|
67
|
+
for (const update of updates) {
|
|
68
|
+
const edits = modify(next, [...update.path], update.value, { formattingOptions: { tabSize: 2, insertSpaces: true } });
|
|
69
|
+
next = applyEdits(next, edits);
|
|
70
|
+
}
|
|
71
|
+
return next.endsWith("\n") ? next : `${next}\n`;
|
|
72
|
+
}
|
|
73
|
+
export function planOpenCodeConfigWrite(rootPath, targetPath, content) {
|
|
74
|
+
const root = assertSafeProjectRoot(rootPath);
|
|
75
|
+
const rel = relative(root, targetPath);
|
|
76
|
+
if (rel.startsWith("..") || isAbsolute(rel))
|
|
77
|
+
return { ok: false, message: "OpenCode config target must stay inside the validated root." };
|
|
78
|
+
const parent = dirname(targetPath);
|
|
79
|
+
const parentSafety = assertSafeParentDirectory(parent);
|
|
80
|
+
if (!parentSafety.ok)
|
|
81
|
+
return parentSafety;
|
|
82
|
+
const existing = assertSafeExistingConfigFile(targetPath, true);
|
|
83
|
+
if (!existing.ok)
|
|
84
|
+
return existing;
|
|
85
|
+
const parsed = parseOpenCodeConfig(content);
|
|
86
|
+
if (!parsed.ok)
|
|
87
|
+
return parsed;
|
|
88
|
+
const stamp = `${process.pid}-${Date.now()}-${randomUUID()}`;
|
|
89
|
+
return {
|
|
90
|
+
rootPath: root,
|
|
91
|
+
targetPath,
|
|
92
|
+
backupPath: existsSync(targetPath) ? uniquePath(`${targetPath}.openpets-backup-${stamp}.json`) : undefined,
|
|
93
|
+
tempPath: uniquePath(join(parent, `.openpets-${stamp}.tmp`)),
|
|
94
|
+
content,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
export function executePlannedWrite(plan) {
|
|
98
|
+
const root = assertSafeProjectRoot(plan.rootPath);
|
|
99
|
+
const rel = relative(root, plan.targetPath);
|
|
100
|
+
if (rel.startsWith("..") || isAbsolute(rel))
|
|
101
|
+
throw new Error("OpenCode write target escaped validated root.");
|
|
102
|
+
for (const path of [plan.backupPath, plan.tempPath].filter((value) => typeof value === "string")) {
|
|
103
|
+
const pathRel = relative(root, path);
|
|
104
|
+
if (pathRel.startsWith("..") || isAbsolute(pathRel))
|
|
105
|
+
throw new Error("OpenCode write support path escaped validated root.");
|
|
106
|
+
if (dirname(path) !== dirname(plan.targetPath))
|
|
107
|
+
throw new Error("OpenCode write support path must stay next to target.");
|
|
108
|
+
}
|
|
109
|
+
const parentSafety = assertSafeParentDirectory(dirname(plan.targetPath));
|
|
110
|
+
if (!parentSafety.ok)
|
|
111
|
+
throw new Error(parentSafety.message);
|
|
112
|
+
const targetSafety = assertSafeExistingConfigFile(plan.targetPath, true);
|
|
113
|
+
if (!targetSafety.ok)
|
|
114
|
+
throw new Error(targetSafety.message);
|
|
115
|
+
const parsed = parseOpenCodeConfig(plan.content);
|
|
116
|
+
if (!parsed.ok)
|
|
117
|
+
throw new Error(parsed.message);
|
|
118
|
+
mkdirSync(dirname(plan.targetPath), { recursive: true, mode: 0o700 });
|
|
119
|
+
if (plan.backupPath && existsSync(plan.targetPath)) {
|
|
120
|
+
const backupFd = openSync(plan.backupPath, "wx", 0o600);
|
|
121
|
+
try {
|
|
122
|
+
writeFileSync(backupFd, readFileSync(plan.targetPath));
|
|
123
|
+
}
|
|
124
|
+
finally {
|
|
125
|
+
closeSync(backupFd);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
const fd = openSync(plan.tempPath, "wx", 0o600);
|
|
129
|
+
try {
|
|
130
|
+
writeFileSync(fd, plan.content, "utf8");
|
|
131
|
+
}
|
|
132
|
+
finally {
|
|
133
|
+
closeSync(fd);
|
|
134
|
+
}
|
|
135
|
+
renameSync(plan.tempPath, plan.targetPath);
|
|
136
|
+
try {
|
|
137
|
+
chmodSync(plan.targetPath, 0o600);
|
|
138
|
+
}
|
|
139
|
+
catch { /* best effort */ }
|
|
140
|
+
}
|
|
141
|
+
export function assertSafeProjectRoot(projectDir) {
|
|
142
|
+
if (!isAbsolute(projectDir))
|
|
143
|
+
throw new Error("OpenCode project path must be absolute.");
|
|
144
|
+
if (!existsSync(projectDir))
|
|
145
|
+
throw new Error("OpenCode project path does not exist.");
|
|
146
|
+
const stat = lstatSync(projectDir);
|
|
147
|
+
if (stat.isSymbolicLink() || !stat.isDirectory())
|
|
148
|
+
throw new Error("OpenCode project path must be a safe directory.");
|
|
149
|
+
return projectDir;
|
|
150
|
+
}
|
|
151
|
+
function assertSafeExistingConfigFile(path, allowMissing = false) {
|
|
152
|
+
if (!existsSync(path))
|
|
153
|
+
return allowMissing ? { ok: true } : { ok: false, message: "OpenCode config does not exist." };
|
|
154
|
+
const stat = lstatSync(path);
|
|
155
|
+
if (stat.isSymbolicLink() || !stat.isFile())
|
|
156
|
+
return { ok: false, message: "OpenCode config path must be a regular file." };
|
|
157
|
+
if (stat.size > maxOpenCodeConfigBytes)
|
|
158
|
+
return { ok: false, message: "OpenCode config is too large." };
|
|
159
|
+
return { ok: true };
|
|
160
|
+
}
|
|
161
|
+
function assertSafeParentDirectory(path) {
|
|
162
|
+
const existing = nearestExistingParent(path);
|
|
163
|
+
const rel = relative(existing, path);
|
|
164
|
+
if (rel.startsWith("..") || isAbsolute(rel))
|
|
165
|
+
return { ok: false, message: "OpenCode config parent escapes target directory." };
|
|
166
|
+
let current = existing;
|
|
167
|
+
while (current !== dirname(current)) {
|
|
168
|
+
if (existsSync(current) && lstatSync(current).isSymbolicLink())
|
|
169
|
+
return { ok: false, message: "OpenCode config parent must not be a symlink." };
|
|
170
|
+
if (current === path)
|
|
171
|
+
break;
|
|
172
|
+
current = dirname(current);
|
|
173
|
+
}
|
|
174
|
+
if (existsSync(path)) {
|
|
175
|
+
const stat = lstatSync(path);
|
|
176
|
+
if (stat.isSymbolicLink() || !stat.isDirectory())
|
|
177
|
+
return { ok: false, message: "OpenCode config parent must be a safe directory." };
|
|
178
|
+
}
|
|
179
|
+
return { ok: true };
|
|
180
|
+
}
|
|
181
|
+
function nearestExistingParent(path) {
|
|
182
|
+
let current = path;
|
|
183
|
+
while (!existsSync(current))
|
|
184
|
+
current = dirname(current);
|
|
185
|
+
if (!statSync(current).isDirectory())
|
|
186
|
+
current = dirname(current);
|
|
187
|
+
return current;
|
|
188
|
+
}
|
|
189
|
+
function validateKnownFieldTypes(config) {
|
|
190
|
+
if (config.mcp !== undefined && !isRecord(config.mcp))
|
|
191
|
+
return { ok: false, message: "OpenCode config mcp field must be an object." };
|
|
192
|
+
if (config.instructions !== undefined && !Array.isArray(config.instructions))
|
|
193
|
+
return { ok: false, message: "OpenCode config instructions field must be an array." };
|
|
194
|
+
if (Array.isArray(config.instructions) && !config.instructions.every((entry) => typeof entry === "string"))
|
|
195
|
+
return { ok: false, message: "OpenCode config instructions entries must be strings." };
|
|
196
|
+
if (config.plugin !== undefined && !Array.isArray(config.plugin))
|
|
197
|
+
return { ok: false, message: "OpenCode config plugin field must be an array." };
|
|
198
|
+
return { ok: true };
|
|
199
|
+
}
|
|
200
|
+
function uniquePath(path) {
|
|
201
|
+
if (!existsSync(path))
|
|
202
|
+
return path;
|
|
203
|
+
for (let index = 1; index < 1000; index += 1) {
|
|
204
|
+
const candidate = `${path}.${index}`;
|
|
205
|
+
if (!existsSync(candidate))
|
|
206
|
+
return candidate;
|
|
207
|
+
}
|
|
208
|
+
throw new Error("Unable to allocate unique OpenCode temp path.");
|
|
209
|
+
}
|
|
210
|
+
function isRecord(value) {
|
|
211
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
212
|
+
}
|
|
213
|
+
//# sourceMappingURL=opencode-config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"opencode-config.js","sourceRoot":"","sources":["../src/opencode-config.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { type OpenCodeConfigPaths, type PlannedWrite } from "./opencode-config.js";
|
|
2
|
+
import { type OpenCodeCommandMode } from "./opencode-previews.js";
|
|
3
|
+
export interface PrepareOpenCodeGlobalSetupOptions {
|
|
4
|
+
readonly configDir: string;
|
|
5
|
+
readonly petId?: string;
|
|
6
|
+
readonly cliVersion: string;
|
|
7
|
+
readonly pluginVersion?: string;
|
|
8
|
+
readonly commandMode?: OpenCodeCommandMode;
|
|
9
|
+
readonly cliEntryPath?: string;
|
|
10
|
+
}
|
|
11
|
+
export interface PreparedOpenCodeGlobalSetup {
|
|
12
|
+
readonly configDir: string;
|
|
13
|
+
readonly petId?: string;
|
|
14
|
+
readonly configPath: string;
|
|
15
|
+
readonly instructionPath: string;
|
|
16
|
+
readonly configWrite: PlannedWrite;
|
|
17
|
+
readonly cleanupConfigWrites: readonly PlannedWrite[];
|
|
18
|
+
readonly instructionWrite: GlobalPlannedTextWrite;
|
|
19
|
+
}
|
|
20
|
+
export interface GlobalPlannedTextWrite {
|
|
21
|
+
readonly targetPath: string;
|
|
22
|
+
readonly backupPath?: string;
|
|
23
|
+
readonly tempPath: string;
|
|
24
|
+
readonly content: string;
|
|
25
|
+
}
|
|
26
|
+
export interface OpenCodeGlobalState {
|
|
27
|
+
readonly status: "not_installed" | "installed" | "custom" | "conflict" | "error";
|
|
28
|
+
readonly message: string;
|
|
29
|
+
}
|
|
30
|
+
export declare function getExplicitGlobalOpenCodeConfigPaths(configDir: string): OpenCodeConfigPaths;
|
|
31
|
+
export declare function prepareOpenCodeGlobalSetup(options: PrepareOpenCodeGlobalSetupOptions): PreparedOpenCodeGlobalSetup;
|
|
32
|
+
export declare function writePreparedOpenCodeGlobalSetup(prepared: PreparedOpenCodeGlobalSetup): void;
|
|
33
|
+
export declare function prepareOpenCodeGlobalRemove(configDir: string): {
|
|
34
|
+
readonly configWrites: readonly PlannedWrite[];
|
|
35
|
+
readonly instructionWrite?: GlobalPlannedTextWrite;
|
|
36
|
+
};
|
|
37
|
+
export declare function writePreparedOpenCodeGlobalRemove(prepared: {
|
|
38
|
+
readonly configWrites: readonly PlannedWrite[];
|
|
39
|
+
readonly instructionWrite?: GlobalPlannedTextWrite;
|
|
40
|
+
}): void;
|
|
41
|
+
export declare function doctorOpenCodeGlobalSetup(configDir: string): OpenCodeGlobalState;
|
|
42
|
+
//# sourceMappingURL=opencode-global-setup.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"opencode-global-setup.d.ts","sourceRoot":"","sources":["../src/opencode-global-setup.ts"],"names":[],"mappings":"AAIA,OAAO,EAAyE,KAAK,mBAAmB,EAAE,KAAK,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAC1J,OAAO,EAA2G,KAAK,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AAI3K,MAAM,WAAW,iCAAiC;IAChD,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,WAAW,CAAC,EAAE,mBAAmB,CAAC;IAC3C,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;CAChC;AAED,MAAM,WAAW,2BAA2B;IAC1C,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,WAAW,EAAE,YAAY,CAAC;IACnC,QAAQ,CAAC,mBAAmB,EAAE,SAAS,YAAY,EAAE,CAAC;IACtD,QAAQ,CAAC,gBAAgB,EAAE,sBAAsB,CAAC;CACnD;AAED,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,MAAM,EAAE,eAAe,GAAG,WAAW,GAAG,QAAQ,GAAG,UAAU,GAAG,OAAO,CAAC;IACjF,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAMD,wBAAgB,oCAAoC,CAAC,SAAS,EAAE,MAAM,GAAG,mBAAmB,CAG3F;AAED,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,iCAAiC,GAAG,2BAA2B,CA6BlH;AAED,wBAAgB,gCAAgC,CAAC,QAAQ,EAAE,2BAA2B,GAAG,IAAI,CAI5F;AAED,wBAAgB,2BAA2B,CAAC,SAAS,EAAE,MAAM,GAAG;IAAE,QAAQ,CAAC,YAAY,EAAE,SAAS,YAAY,EAAE,CAAC;IAAC,QAAQ,CAAC,gBAAgB,CAAC,EAAE,sBAAsB,CAAA;CAAE,CAuBrK;AAED,wBAAgB,iCAAiC,CAAC,QAAQ,EAAE;IAAE,QAAQ,CAAC,YAAY,EAAE,SAAS,YAAY,EAAE,CAAC;IAAC,QAAQ,CAAC,gBAAgB,CAAC,EAAE,sBAAsB,CAAA;CAAE,GAAG,IAAI,CAGxK;AAED,wBAAgB,yBAAyB,CAAC,SAAS,EAAE,MAAM,GAAG,mBAAmB,CAOhF"}
|
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
import { chmodSync, closeSync, existsSync, lstatSync, mkdirSync, openSync, readFileSync, renameSync, statSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { dirname, isAbsolute, join, relative } from "node:path";
|
|
3
|
+
import { randomUUID } from "node:crypto";
|
|
4
|
+
import { parseOpenCodeConfig, readOpenCodeConfigFile, updateOpenCodeConfigText } from "./opencode-config.js";
|
|
5
|
+
import { buildOpenCodeInstructionPath, buildOpenCodeMcpEntry, buildOpenCodePluginPreview, validateOpenPetsPetArg } from "./opencode-previews.js";
|
|
6
|
+
import { classifyOpenCodeInstructionsStatus, classifyOpenCodeMcpStatus, classifyOpenCodePluginStatus, isManagedOpenPetsMcpEntry, isManagedOpenPetsPluginEntry, isOpenPetsLikePluginEntry } from "./opencode-status.js";
|
|
7
|
+
import { createOpenPetsInstructionBlock } from "./opencode-project-setup.js";
|
|
8
|
+
const maxInstructionBytes = 1024 * 1024;
|
|
9
|
+
const openPetsStart = "<!-- OPENPETS:START -->";
|
|
10
|
+
const openPetsEnd = "<!-- OPENPETS:END -->";
|
|
11
|
+
export function getExplicitGlobalOpenCodeConfigPaths(configDir) {
|
|
12
|
+
assertSafeDirectoryRoot(configDir, true);
|
|
13
|
+
return { candidates: [join(configDir, "config.json"), join(configDir, "opencode.json"), join(configDir, "opencode.jsonc")], defaultCreatePath: join(configDir, "opencode.jsonc") };
|
|
14
|
+
}
|
|
15
|
+
export function prepareOpenCodeGlobalSetup(options) {
|
|
16
|
+
const petId = options.petId === undefined ? undefined : validateOpenPetsPetArg(options.petId);
|
|
17
|
+
const paths = getExplicitGlobalOpenCodeConfigPaths(options.configDir);
|
|
18
|
+
const existingConfigs = readExistingGlobalConfigs(options.configDir, paths.candidates);
|
|
19
|
+
const configs = existingConfigs.map((entry) => entry.config);
|
|
20
|
+
const instructionPath = buildOpenCodeInstructionPath("global", options.configDir);
|
|
21
|
+
assertSafeGlobalPath(options.configDir, instructionPath, "OpenCode instruction");
|
|
22
|
+
const instructionContent = existsSync(instructionPath) ? readSafeInstructionFile(instructionPath) : "";
|
|
23
|
+
const mcpStatus = classifyOpenCodeMcpStatus(configs, { cliVersion: options.cliVersion, petId, commandMode: options.commandMode, cliEntryPath: options.cliEntryPath });
|
|
24
|
+
const instructionStatus = classifyOpenCodeInstructionsStatus(configs, "global", options.configDir, { [instructionPath]: instructionContent });
|
|
25
|
+
const pluginStatus = classifyOpenCodePluginStatus(configs, petId, options.pluginVersion ?? options.cliVersion);
|
|
26
|
+
for (const status of [mcpStatus, instructionStatus, pluginStatus]) {
|
|
27
|
+
if (status.status === "custom" || status.status === "conflict" || status.status === "error")
|
|
28
|
+
throw new Error(`${status.message} Edit or remove the custom OpenPets OpenCode entry, then rerun setup.`);
|
|
29
|
+
}
|
|
30
|
+
const selectedPath = selectWriteTarget(options.configDir, paths.candidates, existingConfigs, paths.defaultCreatePath);
|
|
31
|
+
const selectedText = existsSync(selectedPath) ? readFileSync(selectedPath, "utf8") : "{}\n";
|
|
32
|
+
const parsed = parseOpenCodeConfig(selectedText);
|
|
33
|
+
if (!parsed.ok)
|
|
34
|
+
throw new Error(parsed.message);
|
|
35
|
+
const nextConfig = buildNextGlobalConfig(parsed.value, petId, options);
|
|
36
|
+
const nextText = updateOpenCodeConfigText(selectedText, [
|
|
37
|
+
{ path: ["mcp"], value: nextConfig.mcp },
|
|
38
|
+
{ path: ["instructions"], value: nextConfig.instructions },
|
|
39
|
+
{ path: ["plugin"], value: nextConfig.plugin },
|
|
40
|
+
]);
|
|
41
|
+
if (typeof nextText !== "string")
|
|
42
|
+
throw new Error(nextText.message);
|
|
43
|
+
const configWrite = planGlobalConfigWrite(options.configDir, selectedPath, nextText);
|
|
44
|
+
const cleanupConfigWrites = planSetupCleanupWrites(options.configDir, selectedPath, existingConfigs);
|
|
45
|
+
const instructionWrite = planTextWrite(options.configDir, instructionPath, upsertOpenPetsBlock(instructionContent));
|
|
46
|
+
return { configDir: options.configDir, petId, configPath: selectedPath, instructionPath, configWrite, cleanupConfigWrites, instructionWrite };
|
|
47
|
+
}
|
|
48
|
+
export function writePreparedOpenCodeGlobalSetup(prepared) {
|
|
49
|
+
executeTextWrite(prepared.instructionWrite);
|
|
50
|
+
for (const write of prepared.cleanupConfigWrites)
|
|
51
|
+
executeGlobalConfigWrite(write);
|
|
52
|
+
executeGlobalConfigWrite(prepared.configWrite);
|
|
53
|
+
}
|
|
54
|
+
export function prepareOpenCodeGlobalRemove(configDir) {
|
|
55
|
+
const paths = getExplicitGlobalOpenCodeConfigPaths(configDir);
|
|
56
|
+
const existingConfigs = readExistingGlobalConfigs(configDir, paths.candidates);
|
|
57
|
+
const state = classifyGlobalState(configDir, existingConfigs);
|
|
58
|
+
if (state.status === "custom" || state.status === "conflict" || state.status === "error")
|
|
59
|
+
throw new Error(state.message);
|
|
60
|
+
const owners = existingConfigs.filter((entry) => hasManagedOpenPetsEntry(configDir, entry.config));
|
|
61
|
+
if (owners.length === 0)
|
|
62
|
+
return { configWrites: [] };
|
|
63
|
+
if (owners.length > 1)
|
|
64
|
+
throw new Error("OpenCode has OpenPets entries in multiple global config files. Remove duplicates manually.");
|
|
65
|
+
const owner = owners[0];
|
|
66
|
+
if (!owner)
|
|
67
|
+
return { configWrites: [] };
|
|
68
|
+
const text = readFileSync(owner.path, "utf8");
|
|
69
|
+
const next = removeManagedConfig(configDir, owner.config);
|
|
70
|
+
const nextText = updateOpenCodeConfigText(text, [
|
|
71
|
+
{ path: ["mcp"], value: next.mcp },
|
|
72
|
+
{ path: ["instructions"], value: next.instructions },
|
|
73
|
+
{ path: ["plugin"], value: next.plugin },
|
|
74
|
+
]);
|
|
75
|
+
if (typeof nextText !== "string")
|
|
76
|
+
throw new Error(nextText.message);
|
|
77
|
+
const configWrite = planGlobalConfigWrite(configDir, owner.path, nextText);
|
|
78
|
+
const instructionPath = buildOpenCodeInstructionPath("global", configDir);
|
|
79
|
+
const instructionContent = existsSync(instructionPath) ? readSafeInstructionFile(instructionPath) : "";
|
|
80
|
+
const instructionWrite = hasManagedInstructionBlock(instructionContent) ? planTextWrite(configDir, instructionPath, removeOpenPetsBlock(instructionContent)) : undefined;
|
|
81
|
+
return { configWrites: [configWrite], instructionWrite };
|
|
82
|
+
}
|
|
83
|
+
export function writePreparedOpenCodeGlobalRemove(prepared) {
|
|
84
|
+
for (const write of prepared.configWrites)
|
|
85
|
+
executeGlobalConfigWrite(write);
|
|
86
|
+
if (prepared.instructionWrite)
|
|
87
|
+
executeTextWrite(prepared.instructionWrite);
|
|
88
|
+
}
|
|
89
|
+
export function doctorOpenCodeGlobalSetup(configDir) {
|
|
90
|
+
try {
|
|
91
|
+
const paths = getExplicitGlobalOpenCodeConfigPaths(configDir);
|
|
92
|
+
return classifyGlobalState(configDir, readExistingGlobalConfigs(configDir, paths.candidates));
|
|
93
|
+
}
|
|
94
|
+
catch (error) {
|
|
95
|
+
return { status: "error", message: error instanceof Error ? error.message : "OpenCode global setup status is unavailable." };
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
function readExistingGlobalConfigs(configDir, candidates) {
|
|
99
|
+
return candidates.flatMap((path) => {
|
|
100
|
+
if (!existsSync(path))
|
|
101
|
+
return [];
|
|
102
|
+
assertSafeGlobalPath(configDir, path, "OpenCode config");
|
|
103
|
+
const parsed = readOpenCodeConfigFile(path);
|
|
104
|
+
if (!parsed.ok)
|
|
105
|
+
throw new Error(parsed.message);
|
|
106
|
+
return [{ path, config: parsed.value }];
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
function buildNextGlobalConfig(config, petId, options) {
|
|
110
|
+
const mcp = isRecord(config.mcp) ? { ...config.mcp } : {};
|
|
111
|
+
mcp.openpets = buildOpenCodeMcpEntry({ cliVersion: options.cliVersion, petId, commandMode: options.commandMode, cliEntryPath: options.cliEntryPath });
|
|
112
|
+
const instructionPath = buildOpenCodeInstructionPath("global", options.configDir);
|
|
113
|
+
const instructions = [...new Set([...(Array.isArray(config.instructions) ? config.instructions.filter((entry) => typeof entry === "string") : []), instructionPath])];
|
|
114
|
+
const plugin = [...(Array.isArray(config.plugin) ? config.plugin.filter((entry) => !isManagedOpenPetsPluginEntry(entry)) : []), buildOpenCodePluginPreview(petId, options.pluginVersion ?? options.cliVersion)];
|
|
115
|
+
return { mcp, instructions, plugin };
|
|
116
|
+
}
|
|
117
|
+
function removeManagedConfig(configDir, config) {
|
|
118
|
+
const mcp = isRecord(config.mcp) ? { ...config.mcp } : {};
|
|
119
|
+
if (isManagedOpenPetsMcpEntry(mcp.openpets))
|
|
120
|
+
delete mcp.openpets;
|
|
121
|
+
const instructionPath = buildOpenCodeInstructionPath("global", configDir);
|
|
122
|
+
const instructions = Array.isArray(config.instructions) ? config.instructions.filter((entry) => typeof entry === "string" && entry !== instructionPath) : [];
|
|
123
|
+
const plugin = Array.isArray(config.plugin) ? config.plugin.filter((entry) => !isManagedOpenPetsPluginEntry(entry)) : [];
|
|
124
|
+
return { mcp: Object.keys(mcp).length > 0 ? mcp : undefined, instructions: instructions.length > 0 ? instructions : undefined, plugin: plugin.length > 0 ? plugin : undefined };
|
|
125
|
+
}
|
|
126
|
+
function selectWriteTarget(configDir, candidates, existing, fallback) {
|
|
127
|
+
const owners = existing.filter((entry) => hasManagedOpenPetsEntry(configDir, entry.config)).map((entry) => entry.path);
|
|
128
|
+
const uniqueOwners = [...new Set(owners)];
|
|
129
|
+
if (uniqueOwners.length > 1)
|
|
130
|
+
throw new Error("OpenCode has OpenPets entries in multiple global config files. Remove duplicates manually.");
|
|
131
|
+
const arrayOwner = selectArrayFieldOwner(configDir, candidates, existing);
|
|
132
|
+
if (arrayOwner)
|
|
133
|
+
return arrayOwner;
|
|
134
|
+
if (uniqueOwners.length === 1)
|
|
135
|
+
return uniqueOwners[0] ?? fallback;
|
|
136
|
+
const highestPrecedenceExisting = [...candidates].reverse().find((candidate) => existing.some((entry) => entry.path === candidate));
|
|
137
|
+
if (highestPrecedenceExisting)
|
|
138
|
+
return highestPrecedenceExisting;
|
|
139
|
+
return fallback;
|
|
140
|
+
}
|
|
141
|
+
function planSetupCleanupWrites(configDir, selectedPath, existing) {
|
|
142
|
+
return existing.flatMap((entry) => {
|
|
143
|
+
if (entry.path === selectedPath || !hasManagedOpenPetsEntry(configDir, entry.config))
|
|
144
|
+
return [];
|
|
145
|
+
const source = readFileSync(entry.path, "utf8");
|
|
146
|
+
const next = removeManagedConfig(configDir, entry.config);
|
|
147
|
+
const nextText = updateOpenCodeConfigText(source, [
|
|
148
|
+
{ path: ["mcp"], value: next.mcp },
|
|
149
|
+
{ path: ["instructions"], value: next.instructions },
|
|
150
|
+
{ path: ["plugin"], value: next.plugin },
|
|
151
|
+
]);
|
|
152
|
+
if (typeof nextText !== "string")
|
|
153
|
+
throw new Error(nextText.message);
|
|
154
|
+
return [planGlobalConfigWrite(configDir, entry.path, nextText)];
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
function selectArrayFieldOwner(configDir, candidates, existing) {
|
|
158
|
+
const pluginOwner = findEffectiveArrayOwner(candidates, existing, "plugin", (entry) => !isManagedOpenPetsPluginEntry(entry), isManagedOpenPetsPluginEntry);
|
|
159
|
+
const instructionPath = buildOpenCodeInstructionPath("global", configDir);
|
|
160
|
+
const instructionOwner = findEffectiveArrayOwner(candidates, existing, "instructions", (entry) => typeof entry === "string" && entry !== instructionPath, (entry) => entry === instructionPath);
|
|
161
|
+
const owners = [...new Set([pluginOwner, instructionOwner].filter((value) => typeof value === "string"))];
|
|
162
|
+
if (owners.length > 1)
|
|
163
|
+
throw new Error("OpenCode global plugin and instruction arrays live in different config files. Consolidate them before installing OpenPets.");
|
|
164
|
+
return owners[0];
|
|
165
|
+
}
|
|
166
|
+
function findEffectiveArrayOwner(candidates, existing, field, isUserEntry, isManagedEntry) {
|
|
167
|
+
const entries = [...candidates].reverse().flatMap((candidate) => {
|
|
168
|
+
const entry = existing.find((item) => item.path === candidate);
|
|
169
|
+
const value = entry?.config[field];
|
|
170
|
+
return entry && Array.isArray(value) ? [{ path: entry.path, values: value }] : [];
|
|
171
|
+
});
|
|
172
|
+
for (let index = 0; index < entries.length; index += 1) {
|
|
173
|
+
const entry = entries[index];
|
|
174
|
+
if (!entry)
|
|
175
|
+
continue;
|
|
176
|
+
if (entry.values.some(isUserEntry))
|
|
177
|
+
return entry.path;
|
|
178
|
+
const lowerUserOwner = entries.slice(index + 1).find((item) => item.values.some(isUserEntry))?.path;
|
|
179
|
+
if (entry.values.some(isManagedEntry))
|
|
180
|
+
return lowerUserOwner ?? entry.path;
|
|
181
|
+
if (lowerUserOwner)
|
|
182
|
+
throw new Error(`OpenCode global ${field} array in a higher-precedence config shadows user ${field} entries in a lower-precedence config. Consolidate them before installing OpenPets.`);
|
|
183
|
+
return entry.path;
|
|
184
|
+
}
|
|
185
|
+
return undefined;
|
|
186
|
+
}
|
|
187
|
+
function hasManagedOpenPetsEntry(configDir, config) {
|
|
188
|
+
if (isRecord(config.mcp) && isManagedOpenPetsMcpEntry(config.mcp.openpets))
|
|
189
|
+
return true;
|
|
190
|
+
if (Array.isArray(config.instructions) && config.instructions.some((entry) => entry === buildOpenCodeInstructionPath("global", configDir)))
|
|
191
|
+
return true;
|
|
192
|
+
if (Array.isArray(config.plugin) && config.plugin.some(isManagedOpenPetsPluginEntry))
|
|
193
|
+
return true;
|
|
194
|
+
return false;
|
|
195
|
+
}
|
|
196
|
+
function hasCustomOpenPetsEntry(configDir, config) {
|
|
197
|
+
if (isRecord(config.mcp) && config.mcp.openpets !== undefined && !isManagedOpenPetsMcpEntry(config.mcp.openpets))
|
|
198
|
+
return true;
|
|
199
|
+
if (Array.isArray(config.instructions) && config.instructions.some((entry) => typeof entry === "string" && /openpets\.md$/i.test(entry) && entry !== buildOpenCodeInstructionPath("global", configDir)))
|
|
200
|
+
return true;
|
|
201
|
+
if (Array.isArray(config.plugin) && config.plugin.some((entry) => isOpenPetsLikePluginEntry(entry) && !isManagedOpenPetsPluginEntry(entry)))
|
|
202
|
+
return true;
|
|
203
|
+
return false;
|
|
204
|
+
}
|
|
205
|
+
function classifyGlobalState(configDir, existing) {
|
|
206
|
+
if (existing.some((entry) => hasCustomOpenPetsEntry(configDir, entry.config)))
|
|
207
|
+
return { status: "custom", message: "OpenCode has custom OpenPets-like global entries. Edit or remove them manually." };
|
|
208
|
+
const owners = existing.filter((entry) => hasManagedOpenPetsEntry(configDir, entry.config));
|
|
209
|
+
if (owners.length > 1)
|
|
210
|
+
return { status: "conflict", message: "OpenCode has OpenPets entries in multiple global config files. Remove duplicates manually." };
|
|
211
|
+
if (owners.length === 1)
|
|
212
|
+
return { status: "installed", message: "OpenCode global OpenPets setup is installed." };
|
|
213
|
+
return { status: "not_installed", message: "OpenCode global OpenPets setup is not installed." };
|
|
214
|
+
}
|
|
215
|
+
function planTextWrite(root, targetPath, content) {
|
|
216
|
+
assertSafeGlobalPath(root, targetPath, "OpenCode instruction");
|
|
217
|
+
if (existsSync(targetPath)) {
|
|
218
|
+
const stat = lstatSync(targetPath);
|
|
219
|
+
if (stat.isSymbolicLink() || !stat.isFile())
|
|
220
|
+
throw new Error("OpenCode instruction path must be a safe regular file.");
|
|
221
|
+
if (stat.size > maxInstructionBytes)
|
|
222
|
+
throw new Error("OpenCode instruction file is too large.");
|
|
223
|
+
}
|
|
224
|
+
const stamp = `${process.pid}-${Date.now()}-${randomUUID()}`;
|
|
225
|
+
return { targetPath, backupPath: existsSync(targetPath) ? `${targetPath}.openpets-backup-${stamp}.md` : undefined, tempPath: join(dirname(targetPath), `.openpets-${stamp}.tmp`), content };
|
|
226
|
+
}
|
|
227
|
+
function executeTextWrite(plan) {
|
|
228
|
+
const parent = dirname(plan.targetPath);
|
|
229
|
+
if (existsSync(parent)) {
|
|
230
|
+
const parentStat = lstatSync(parent);
|
|
231
|
+
if (parentStat.isSymbolicLink() || !parentStat.isDirectory())
|
|
232
|
+
throw new Error("OpenCode instruction directory is unsafe.");
|
|
233
|
+
}
|
|
234
|
+
if (existsSync(plan.targetPath)) {
|
|
235
|
+
const targetStat = lstatSync(plan.targetPath);
|
|
236
|
+
if (targetStat.isSymbolicLink() || !targetStat.isFile())
|
|
237
|
+
throw new Error("OpenCode instruction path must be a safe regular file.");
|
|
238
|
+
if (targetStat.size > maxInstructionBytes)
|
|
239
|
+
throw new Error("OpenCode instruction file is too large.");
|
|
240
|
+
}
|
|
241
|
+
if (plan.backupPath && dirname(plan.backupPath) !== parent)
|
|
242
|
+
throw new Error("OpenCode instruction backup path is unsafe.");
|
|
243
|
+
if (dirname(plan.tempPath) !== parent)
|
|
244
|
+
throw new Error("OpenCode instruction temp path is unsafe.");
|
|
245
|
+
mkdirSync(dirname(plan.targetPath), { recursive: true, mode: 0o700 });
|
|
246
|
+
if (plan.backupPath && existsSync(plan.targetPath)) {
|
|
247
|
+
const backup = openSync(plan.backupPath, "wx", 0o600);
|
|
248
|
+
try {
|
|
249
|
+
writeFileSync(backup, readFileSync(plan.targetPath));
|
|
250
|
+
}
|
|
251
|
+
finally {
|
|
252
|
+
closeSync(backup);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
const fd = openSync(plan.tempPath, "wx", 0o600);
|
|
256
|
+
try {
|
|
257
|
+
writeFileSync(fd, plan.content, "utf8");
|
|
258
|
+
}
|
|
259
|
+
finally {
|
|
260
|
+
closeSync(fd);
|
|
261
|
+
}
|
|
262
|
+
renameSync(plan.tempPath, plan.targetPath);
|
|
263
|
+
try {
|
|
264
|
+
chmodSync(plan.targetPath, 0o600);
|
|
265
|
+
}
|
|
266
|
+
catch { /* best effort */ }
|
|
267
|
+
}
|
|
268
|
+
function planGlobalConfigWrite(rootPath, targetPath, content) {
|
|
269
|
+
assertSafeGlobalPath(rootPath, targetPath, "OpenCode config");
|
|
270
|
+
const parsed = parseOpenCodeConfig(content);
|
|
271
|
+
if (!parsed.ok)
|
|
272
|
+
throw new Error(parsed.message);
|
|
273
|
+
const stamp = `${process.pid}-${Date.now()}-${randomUUID()}`;
|
|
274
|
+
return { rootPath, targetPath, backupPath: existsSync(targetPath) ? `${targetPath}.openpets-backup-${stamp}.json` : undefined, tempPath: join(dirname(targetPath), `.openpets-${stamp}.tmp`), content };
|
|
275
|
+
}
|
|
276
|
+
function executeGlobalConfigWrite(plan) {
|
|
277
|
+
const parent = dirname(plan.targetPath);
|
|
278
|
+
assertSafeGlobalPath(plan.rootPath, plan.targetPath, "OpenCode config");
|
|
279
|
+
if (existsSync(plan.targetPath)) {
|
|
280
|
+
const targetStat = lstatSync(plan.targetPath);
|
|
281
|
+
if (targetStat.isSymbolicLink() || !targetStat.isFile())
|
|
282
|
+
throw new Error("OpenCode config path must be a safe regular file.");
|
|
283
|
+
}
|
|
284
|
+
if (plan.backupPath && (dirname(plan.backupPath) !== parent || existsSync(plan.backupPath)))
|
|
285
|
+
throw new Error("OpenCode config backup path is unsafe.");
|
|
286
|
+
if (dirname(plan.tempPath) !== parent || existsSync(plan.tempPath))
|
|
287
|
+
throw new Error("OpenCode config temp path is unsafe.");
|
|
288
|
+
const parsed = parseOpenCodeConfig(plan.content);
|
|
289
|
+
if (!parsed.ok)
|
|
290
|
+
throw new Error(parsed.message);
|
|
291
|
+
mkdirSync(parent, { recursive: true, mode: 0o700 });
|
|
292
|
+
if (plan.backupPath && existsSync(plan.targetPath)) {
|
|
293
|
+
const backup = openSync(plan.backupPath, "wx", 0o600);
|
|
294
|
+
try {
|
|
295
|
+
writeFileSync(backup, readFileSync(plan.targetPath));
|
|
296
|
+
}
|
|
297
|
+
finally {
|
|
298
|
+
closeSync(backup);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
const fd = openSync(plan.tempPath, "wx", 0o600);
|
|
302
|
+
try {
|
|
303
|
+
writeFileSync(fd, plan.content, "utf8");
|
|
304
|
+
}
|
|
305
|
+
finally {
|
|
306
|
+
closeSync(fd);
|
|
307
|
+
}
|
|
308
|
+
renameSync(plan.tempPath, plan.targetPath);
|
|
309
|
+
try {
|
|
310
|
+
chmodSync(plan.targetPath, 0o600);
|
|
311
|
+
}
|
|
312
|
+
catch { /* best effort */ }
|
|
313
|
+
}
|
|
314
|
+
function readSafeInstructionFile(path) {
|
|
315
|
+
const stat = lstatSync(path);
|
|
316
|
+
if (stat.isSymbolicLink() || !stat.isFile())
|
|
317
|
+
throw new Error("OpenCode instruction path must be a safe regular file.");
|
|
318
|
+
if (stat.size > maxInstructionBytes)
|
|
319
|
+
throw new Error("OpenCode instruction file is too large.");
|
|
320
|
+
return readFileSync(path, "utf8");
|
|
321
|
+
}
|
|
322
|
+
function upsertOpenPetsBlock(source) {
|
|
323
|
+
const withoutBlock = source.replace(new RegExp(`${escapeRegExp(openPetsStart)}[\\s\\S]*?${escapeRegExp(openPetsEnd)}\\n?`, "g"), "").replace(/\n{3,}/g, "\n\n").replace(/\s*$/u, "");
|
|
324
|
+
const block = createOpenPetsInstructionBlock();
|
|
325
|
+
return withoutBlock ? `${withoutBlock}\n\n${block}` : block;
|
|
326
|
+
}
|
|
327
|
+
function removeOpenPetsBlock(source) {
|
|
328
|
+
return source.replace(new RegExp(`${escapeRegExp(openPetsStart)}[\\s\\S]*?${escapeRegExp(openPetsEnd)}\\n?`, "g"), "").replace(/\n{3,}/g, "\n\n").replace(/\s*$/u, (match) => (match.includes("\n") ? "\n" : ""));
|
|
329
|
+
}
|
|
330
|
+
function hasManagedInstructionBlock(value) {
|
|
331
|
+
return new RegExp(`${escapeRegExp(openPetsStart)}[\\s\\S]*?${escapeRegExp(openPetsEnd)}`).test(value);
|
|
332
|
+
}
|
|
333
|
+
function assertSafeDirectoryRoot(root, allowMissing) {
|
|
334
|
+
if (!isAbsolute(root))
|
|
335
|
+
throw new Error("OpenCode global config directory must be absolute.");
|
|
336
|
+
if (!existsSync(root)) {
|
|
337
|
+
if (allowMissing)
|
|
338
|
+
return;
|
|
339
|
+
throw new Error("OpenCode global config directory does not exist.");
|
|
340
|
+
}
|
|
341
|
+
const stat = lstatSync(root);
|
|
342
|
+
if (stat.isSymbolicLink() || !stat.isDirectory())
|
|
343
|
+
throw new Error("OpenCode global config directory is unsafe.");
|
|
344
|
+
}
|
|
345
|
+
function assertSafeGlobalPath(root, targetPath, label) {
|
|
346
|
+
assertSafeNearestExistingRoot(root);
|
|
347
|
+
const rel = relative(root, targetPath);
|
|
348
|
+
if (rel.startsWith("..") || isAbsolute(rel))
|
|
349
|
+
throw new Error(`${label} path escapes global config directory.`);
|
|
350
|
+
let current = root;
|
|
351
|
+
for (const part of rel.split(/[\\/]+/).filter(Boolean).slice(0, -1)) {
|
|
352
|
+
current = join(current, part);
|
|
353
|
+
if (!existsSync(current))
|
|
354
|
+
continue;
|
|
355
|
+
const stat = lstatSync(current);
|
|
356
|
+
if (stat.isSymbolicLink() || !stat.isDirectory())
|
|
357
|
+
throw new Error(`${label} parent directory is unsafe.`);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
function assertSafeNearestExistingRoot(root) {
|
|
361
|
+
if (!isAbsolute(root))
|
|
362
|
+
throw new Error("OpenCode global config directory must be absolute.");
|
|
363
|
+
let current = root;
|
|
364
|
+
while (!existsSync(current))
|
|
365
|
+
current = dirname(current);
|
|
366
|
+
const stat = statSync(current);
|
|
367
|
+
if (!stat.isDirectory())
|
|
368
|
+
throw new Error("OpenCode global config parent is unsafe.");
|
|
369
|
+
if (lstatSync(current).isSymbolicLink())
|
|
370
|
+
throw new Error("OpenCode global config parent must not be a symlink.");
|
|
371
|
+
}
|
|
372
|
+
function escapeRegExp(value) {
|
|
373
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
374
|
+
}
|
|
375
|
+
function isRecord(value) {
|
|
376
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
377
|
+
}
|
|
378
|
+
//# sourceMappingURL=opencode-global-setup.js.map
|