opencodekit 0.23.1 → 0.23.2
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/dist/index.js +354 -825
- package/dist/template/.opencode/AGENTS.md +12 -0
- package/dist/template/.opencode/command/init.md +198 -34
- package/dist/template/.opencode/context/fallow.md +137 -0
- package/dist/template/.opencode/opencode.json +12 -315
- package/dist/template/.opencode/plugin/memory/compile.ts +171 -186
- package/dist/template/.opencode/plugin/memory/index-generator.ts +118 -133
- package/dist/template/.opencode/plugin/memory/lint.ts +253 -275
- package/dist/template/.opencode/plugin/memory/tools.ts +224 -268
- package/dist/template/.opencode/plugin/memory/validate.ts +154 -164
- package/dist/template/.opencode/plugin/sdk/copilot/responses/tool/web-search-preview.ts +13 -30
- package/dist/template/.opencode/plugin/sdk/copilot/responses/tool/web-search-shared.ts +25 -0
- package/dist/template/.opencode/plugin/sdk/copilot/responses/tool/web-search.ts +17 -34
- package/dist/template/.opencode/plugin/srcwalk.ts +775 -661
- package/dist/template/.opencode/skill/condition-based-waiting/example.ts +15 -2
- package/dist/template/.opencode/skill/fallow/SKILL.md +409 -0
- package/dist/template/.opencode/skill/fallow/references/cli-reference.md +1905 -0
- package/dist/template/.opencode/skill/fallow/references/gotchas.md +644 -0
- package/dist/template/.opencode/skill/fallow/references/patterns.md +791 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -4,15 +4,15 @@ import * as p from "@clack/prompts";
|
|
|
4
4
|
import { cac } from "cac";
|
|
5
5
|
import color from "picocolors";
|
|
6
6
|
import { createHash, createHmac } from "node:crypto";
|
|
7
|
-
import { copyFileSync, existsSync, lstatSync, mkdirSync, readFileSync, readdirSync,
|
|
7
|
+
import { copyFileSync, existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, rmSync, unlinkSync, writeFileSync } from "node:fs";
|
|
8
8
|
import { homedir, hostname, platform, release } from "node:os";
|
|
9
9
|
import { basename, dirname, join, relative } from "node:path";
|
|
10
10
|
import envPaths from "env-paths";
|
|
11
11
|
import machineId from "node-machine-id";
|
|
12
12
|
import { z } from "zod";
|
|
13
|
-
import { execSync, spawn } from "node:child_process";
|
|
14
13
|
import { fileURLToPath } from "node:url";
|
|
15
14
|
import { applyPatch, createPatch } from "diff";
|
|
15
|
+
import { spawn } from "node:child_process";
|
|
16
16
|
import * as readline from "node:readline";
|
|
17
17
|
|
|
18
18
|
//#region \0rolldown/runtime.js
|
|
@@ -20,7 +20,7 @@ var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
|
20
20
|
|
|
21
21
|
//#endregion
|
|
22
22
|
//#region package.json
|
|
23
|
-
var version = "0.23.
|
|
23
|
+
var version = "0.23.2";
|
|
24
24
|
|
|
25
25
|
//#endregion
|
|
26
26
|
//#region src/utils/license.ts
|
|
@@ -264,16 +264,28 @@ async function activateCommand(keyArg) {
|
|
|
264
264
|
//#endregion
|
|
265
265
|
//#region src/utils/errors.ts
|
|
266
266
|
/**
|
|
267
|
+
* Get the global OpenCode config directory based on OS.
|
|
268
|
+
* - macOS/Linux: ~/.config/opencode/ (respects XDG_CONFIG_HOME)
|
|
269
|
+
* - Windows: %APPDATA%\opencode\ or %LOCALAPPDATA%\opencode\
|
|
270
|
+
*/
|
|
271
|
+
function getGlobalConfigDir() {
|
|
272
|
+
if (platform() === "win32") return join(process.env.APPDATA || process.env.LOCALAPPDATA || join(homedir(), "AppData", "Roaming"), "opencode");
|
|
273
|
+
return join(process.env.XDG_CONFIG_HOME || join(homedir(), ".config"), "opencode");
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
267
276
|
* Resolve the .opencode directory path.
|
|
268
|
-
* Handles
|
|
277
|
+
* Handles three cases:
|
|
269
278
|
* 1. Standard project: cwd has .opencode/ subdirectory
|
|
270
279
|
* 2. Global config dir: cwd IS the opencode config dir (has opencode.json directly)
|
|
271
|
-
*
|
|
280
|
+
* 3. Global config dir at ~/.config/opencode/ (fallback)
|
|
281
|
+
* Returns null if none apply.
|
|
272
282
|
*/
|
|
273
283
|
function resolveOpencodePath() {
|
|
274
284
|
const nested = join(process.cwd(), ".opencode");
|
|
275
285
|
if (existsSync(nested)) return nested;
|
|
276
286
|
if (existsSync(join(process.cwd(), "opencode.json"))) return process.cwd();
|
|
287
|
+
const globalDir = getGlobalConfigDir();
|
|
288
|
+
if (existsSync(join(globalDir, "opencode.json"))) return globalDir;
|
|
277
289
|
return null;
|
|
278
290
|
}
|
|
279
291
|
/**
|
|
@@ -321,9 +333,6 @@ function showWarning(message, suggestion) {
|
|
|
321
333
|
p.log.warn(`${color.yellow("!")} ${message}`);
|
|
322
334
|
if (suggestion) p.log.info(` ${color.dim(`→ ${suggestion}`)}`);
|
|
323
335
|
}
|
|
324
|
-
/**
|
|
325
|
-
* Display empty state with suggestion
|
|
326
|
-
*/
|
|
327
336
|
function showEmpty(resource, createCmd) {
|
|
328
337
|
p.log.info(color.dim(`No ${resource} found`));
|
|
329
338
|
if (createCmd) p.log.info(color.dim(`→ Run: ${color.cyan(createCmd)}`));
|
|
@@ -338,13 +347,7 @@ function showEmpty(resource, createCmd) {
|
|
|
338
347
|
const InitOptionsSchema = z.object({
|
|
339
348
|
force: z.boolean().optional().default(false),
|
|
340
349
|
global: z.boolean().optional().default(false),
|
|
341
|
-
|
|
342
|
-
recommend: z.boolean().optional().default(false),
|
|
343
|
-
yes: z.boolean().optional().default(false),
|
|
344
|
-
backup: z.boolean().optional().default(false),
|
|
345
|
-
prune: z.boolean().optional().default(false),
|
|
346
|
-
pruneAll: z.boolean().optional().default(false),
|
|
347
|
-
projectOnly: z.boolean().optional().default(false)
|
|
350
|
+
yes: z.boolean().optional().default(false)
|
|
348
351
|
});
|
|
349
352
|
const UpgradeOptionsSchema = z.object({
|
|
350
353
|
force: z.boolean().optional().default(false),
|
|
@@ -2752,825 +2755,116 @@ function fileModificationStatus(filePath, relativePath, manifest) {
|
|
|
2752
2755
|
}
|
|
2753
2756
|
|
|
2754
2757
|
//#endregion
|
|
2755
|
-
//#region src/
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
* e.g., "agent/build.md" -> "agent-build.md.patch"
|
|
2772
|
-
*/
|
|
2773
|
-
function pathToPatchFilename(relativePath) {
|
|
2774
|
-
return `${relativePath.replace(/\//g, "-")}.patch`;
|
|
2775
|
-
}
|
|
2776
|
-
/**
|
|
2777
|
-
* Get the patches directory path.
|
|
2778
|
-
*/
|
|
2779
|
-
function getPatchesDir(opencodeDir) {
|
|
2780
|
-
return join(opencodeDir, PATCHES_DIR);
|
|
2781
|
-
}
|
|
2782
|
-
/**
|
|
2783
|
-
* Get the template root directory (from dist/template or dev mode).
|
|
2784
|
-
*/
|
|
2758
|
+
//#region src/commands/init.ts
|
|
2759
|
+
const EXCLUDED_DIRS = [
|
|
2760
|
+
"node_modules",
|
|
2761
|
+
".git",
|
|
2762
|
+
"dist",
|
|
2763
|
+
".DS_Store",
|
|
2764
|
+
"coverage",
|
|
2765
|
+
".next",
|
|
2766
|
+
".turbo"
|
|
2767
|
+
];
|
|
2768
|
+
const EXCLUDED_FILES = [
|
|
2769
|
+
"bun.lock",
|
|
2770
|
+
"package-lock.json",
|
|
2771
|
+
"yarn.lock",
|
|
2772
|
+
"pnpm-lock.yaml"
|
|
2773
|
+
];
|
|
2785
2774
|
function getTemplateRoot$2() {
|
|
2786
2775
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
2787
|
-
const possiblePaths = [
|
|
2788
|
-
|
|
2789
|
-
join(__dirname, "..", "..", ".opencode"),
|
|
2790
|
-
join(__dirname, "..", "template")
|
|
2791
|
-
];
|
|
2792
|
-
for (const path of possiblePaths) {
|
|
2793
|
-
if (existsSync(join(path, ".opencode"))) return path;
|
|
2794
|
-
if (existsSync(join(path, "opencode.json"))) return dirname(path);
|
|
2795
|
-
}
|
|
2776
|
+
const possiblePaths = [join(__dirname, "template"), join(__dirname, "..", "..", ".opencode")];
|
|
2777
|
+
for (const path of possiblePaths) if (existsSync(join(path, ".opencode"))) return path;
|
|
2796
2778
|
return null;
|
|
2797
2779
|
}
|
|
2798
|
-
/**
|
|
2799
|
-
* Load patch metadata from .patches.json.
|
|
2800
|
-
*/
|
|
2801
|
-
function loadPatchMetadata(opencodeDir) {
|
|
2802
|
-
const metadataPath = join(getPatchesDir(opencodeDir), PATCHES_JSON);
|
|
2803
|
-
if (!existsSync(metadataPath)) return {
|
|
2804
|
-
version: METADATA_VERSION,
|
|
2805
|
-
patches: {}
|
|
2806
|
-
};
|
|
2807
|
-
try {
|
|
2808
|
-
const content = readFileSync(metadataPath, "utf-8");
|
|
2809
|
-
return JSON.parse(content);
|
|
2810
|
-
} catch {
|
|
2811
|
-
return {
|
|
2812
|
-
version: METADATA_VERSION,
|
|
2813
|
-
patches: {}
|
|
2814
|
-
};
|
|
2815
|
-
}
|
|
2816
|
-
}
|
|
2817
|
-
/**
|
|
2818
|
-
* Save patch metadata to .patches.json.
|
|
2819
|
-
*/
|
|
2820
|
-
function savePatchMetadata(opencodeDir, metadata) {
|
|
2821
|
-
const patchesDir = getPatchesDir(opencodeDir);
|
|
2822
|
-
if (!existsSync(patchesDir)) mkdirSync(patchesDir, { recursive: true });
|
|
2823
|
-
writeFileSync(join(patchesDir, PATCHES_JSON), JSON.stringify(metadata, null, 2));
|
|
2824
|
-
}
|
|
2825
|
-
/**
|
|
2826
|
-
* Get the current OpenCodeKit version from package.json.
|
|
2827
|
-
*/
|
|
2828
2780
|
function getPackageVersion$2() {
|
|
2829
2781
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
2830
2782
|
const pkgPaths = [join(__dirname, "..", "..", "package.json"), join(__dirname, "..", "package.json")];
|
|
2831
|
-
for (const pkgPath of pkgPaths)
|
|
2832
|
-
|
|
2833
|
-
|
|
2783
|
+
for (const pkgPath of pkgPaths) {
|
|
2784
|
+
if (!existsSync(pkgPath)) continue;
|
|
2785
|
+
return JSON.parse(readFileSync(pkgPath, "utf-8")).version;
|
|
2786
|
+
}
|
|
2834
2787
|
return "unknown";
|
|
2835
2788
|
}
|
|
2836
|
-
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
|
|
2840
|
-
|
|
2841
|
-
|
|
2842
|
-
|
|
2843
|
-
|
|
2844
|
-
|
|
2845
|
-
|
|
2846
|
-
function savePatch(opencodeDir, relativePath, templateContent, userContent) {
|
|
2847
|
-
const metadata = loadPatchMetadata(opencodeDir);
|
|
2848
|
-
const patchesDir = getPatchesDir(opencodeDir);
|
|
2849
|
-
if (!existsSync(patchesDir)) mkdirSync(patchesDir, { recursive: true });
|
|
2850
|
-
const patchContent = generatePatch(templateContent, userContent, relativePath);
|
|
2851
|
-
const patchFilename = pathToPatchFilename(relativePath);
|
|
2852
|
-
writeFileSync(join(patchesDir, patchFilename), patchContent);
|
|
2853
|
-
const entry = {
|
|
2854
|
-
originalHash: calculateHash(templateContent),
|
|
2855
|
-
currentHash: calculateHash(userContent),
|
|
2856
|
-
patchFile: patchFilename,
|
|
2857
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2858
|
-
templateVersion: getPackageVersion$2()
|
|
2859
|
-
};
|
|
2860
|
-
metadata.patches[relativePath] = entry;
|
|
2861
|
-
savePatchMetadata(opencodeDir, metadata);
|
|
2862
|
-
return entry;
|
|
2863
|
-
}
|
|
2864
|
-
/**
|
|
2865
|
-
* Remove a patch for a file.
|
|
2866
|
-
*/
|
|
2867
|
-
function removePatch(opencodeDir, relativePath) {
|
|
2868
|
-
const metadata = loadPatchMetadata(opencodeDir);
|
|
2869
|
-
const entry = metadata.patches[relativePath];
|
|
2870
|
-
if (!entry) return false;
|
|
2871
|
-
const patchPath = join(getPatchesDir(opencodeDir), entry.patchFile);
|
|
2872
|
-
if (existsSync(patchPath)) {
|
|
2873
|
-
const { rmSync } = __require("node:fs");
|
|
2874
|
-
rmSync(patchPath);
|
|
2789
|
+
async function copyDir(src, dest) {
|
|
2790
|
+
const { mkdir, readdir } = await import("node:fs/promises");
|
|
2791
|
+
await mkdir(dest, { recursive: true });
|
|
2792
|
+
for (const entry of await readdir(src, { withFileTypes: true })) {
|
|
2793
|
+
if (EXCLUDED_DIRS.includes(entry.name)) continue;
|
|
2794
|
+
if (!entry.isDirectory() && EXCLUDED_FILES.includes(entry.name)) continue;
|
|
2795
|
+
const srcPath = join(src, entry.name);
|
|
2796
|
+
const destPath = join(dest, entry.name);
|
|
2797
|
+
if (entry.isSymbolicLink()) {} else if (entry.isDirectory()) await copyDir(srcPath, destPath);
|
|
2798
|
+
else writeFileSync(destPath, readFileSync(srcPath, "utf-8"));
|
|
2875
2799
|
}
|
|
2876
|
-
delete metadata.patches[relativePath];
|
|
2877
|
-
savePatchMetadata(opencodeDir, metadata);
|
|
2878
|
-
return true;
|
|
2879
|
-
}
|
|
2880
|
-
/**
|
|
2881
|
-
* Apply a patch to file content.
|
|
2882
|
-
* @returns The patched content, or null if patch failed.
|
|
2883
|
-
*/
|
|
2884
|
-
function applyPatch$1(originalContent, patchContent) {
|
|
2885
|
-
return applyPatch(originalContent, patchContent);
|
|
2886
2800
|
}
|
|
2887
|
-
|
|
2888
|
-
|
|
2889
|
-
|
|
2890
|
-
|
|
2891
|
-
|
|
2892
|
-
|
|
2893
|
-
|
|
2894
|
-
|
|
2895
|
-
|
|
2896
|
-
|
|
2897
|
-
|
|
2898
|
-
|
|
2899
|
-
|
|
2900
|
-
|
|
2901
|
-
continue;
|
|
2902
|
-
}
|
|
2903
|
-
const filePath = join(opencodeDir, relativePath);
|
|
2904
|
-
const patchPath = join(patchesDir, entry.patchFile);
|
|
2905
|
-
if (!existsSync(filePath)) {
|
|
2906
|
-
results.push({
|
|
2907
|
-
success: false,
|
|
2908
|
-
file: relativePath,
|
|
2909
|
-
message: "File no longer exists"
|
|
2910
|
-
});
|
|
2911
|
-
continue;
|
|
2801
|
+
async function initCommand(rawOptions = {}) {
|
|
2802
|
+
const options = parseOptions(InitOptionsSchema, rawOptions);
|
|
2803
|
+
if (process.argv.includes("--quiet")) return;
|
|
2804
|
+
p.intro(color.bgCyan(color.black(" OpenCodeKit ")));
|
|
2805
|
+
if (options.global) {
|
|
2806
|
+
const globalDir = getGlobalConfigDir();
|
|
2807
|
+
const osName = process.platform === "win32" ? "Windows" : process.platform === "darwin" ? "macOS" : "Linux";
|
|
2808
|
+
p.log.info(`Installing to global config (${osName})`);
|
|
2809
|
+
p.log.info(`Target: ${color.cyan(globalDir)}`);
|
|
2810
|
+
if (existsSync(globalDir) && !options.force) {
|
|
2811
|
+
p.log.warn(`Global config already exists at ${globalDir}`);
|
|
2812
|
+
p.log.info(`Use ${color.cyan("--force")} to overwrite`);
|
|
2813
|
+
p.outro("Nothing to do");
|
|
2814
|
+
return;
|
|
2912
2815
|
}
|
|
2913
|
-
|
|
2914
|
-
|
|
2915
|
-
|
|
2916
|
-
|
|
2917
|
-
|
|
2918
|
-
});
|
|
2919
|
-
continue;
|
|
2816
|
+
const templateRoot = getTemplateRoot$2();
|
|
2817
|
+
if (!templateRoot) {
|
|
2818
|
+
p.log.error("Template not found. Please reinstall opencodekit.");
|
|
2819
|
+
p.outro(color.red("Failed"));
|
|
2820
|
+
process.exit(1);
|
|
2920
2821
|
}
|
|
2921
|
-
const
|
|
2922
|
-
|
|
2923
|
-
const
|
|
2924
|
-
if (
|
|
2925
|
-
|
|
2926
|
-
|
|
2927
|
-
|
|
2928
|
-
|
|
2929
|
-
message: "Patch conflict - saved to .rej file",
|
|
2930
|
-
conflict: true
|
|
2931
|
-
});
|
|
2932
|
-
} else {
|
|
2933
|
-
writeFileSync(filePath, patched);
|
|
2934
|
-
metadata.patches[relativePath].currentHash = calculateHash(patched);
|
|
2935
|
-
savePatchMetadata(opencodeDir, metadata);
|
|
2936
|
-
results.push({
|
|
2937
|
-
success: true,
|
|
2938
|
-
file: relativePath,
|
|
2939
|
-
message: "Patch applied successfully"
|
|
2940
|
-
});
|
|
2822
|
+
const s = p.spinner();
|
|
2823
|
+
s.start("Copying to global config");
|
|
2824
|
+
const opencodeSrc = join(templateRoot, ".opencode");
|
|
2825
|
+
if (!existsSync(opencodeSrc)) {
|
|
2826
|
+
s.stop("Failed");
|
|
2827
|
+
p.log.error("Template .opencode/ not found");
|
|
2828
|
+
p.outro(color.red("Failed"));
|
|
2829
|
+
process.exit(1);
|
|
2941
2830
|
}
|
|
2831
|
+
await copyDir(opencodeSrc, globalDir);
|
|
2832
|
+
s.stop("Done");
|
|
2833
|
+
writeFileSync(join(globalDir, ".version"), getPackageVersion$2());
|
|
2834
|
+
generateManifest(globalDir, getPackageVersion$2());
|
|
2835
|
+
p.note(`Global config installed at:\n${globalDir}\n\nThis provides default agents, skills, and tools\nfor all OpenCode projects on this machine.`, "Global Installation Complete");
|
|
2836
|
+
p.outro(color.green("Ready!"));
|
|
2837
|
+
return;
|
|
2942
2838
|
}
|
|
2943
|
-
|
|
2944
|
-
}
|
|
2945
|
-
|
|
2946
|
-
|
|
2947
|
-
|
|
2948
|
-
function checkPatchStatus(opencodeDir, templateRoot) {
|
|
2949
|
-
const metadata = loadPatchMetadata(opencodeDir);
|
|
2950
|
-
const statuses = [];
|
|
2951
|
-
for (const [relativePath, entry] of Object.entries(metadata.patches)) {
|
|
2952
|
-
if (!existsSync(join(opencodeDir, relativePath))) {
|
|
2953
|
-
statuses.push({
|
|
2954
|
-
relativePath,
|
|
2955
|
-
entry,
|
|
2956
|
-
status: "missing",
|
|
2957
|
-
message: "User file no longer exists"
|
|
2958
|
-
});
|
|
2959
|
-
continue;
|
|
2960
|
-
}
|
|
2961
|
-
if (templateRoot) {
|
|
2962
|
-
const templateFilePath = join(templateRoot, ".opencode", relativePath);
|
|
2963
|
-
if (existsSync(templateFilePath)) {
|
|
2964
|
-
const templateContent = readFileSync(templateFilePath, "utf-8");
|
|
2965
|
-
if (calculateHash(templateContent) !== entry.originalHash) {
|
|
2966
|
-
const patchPath = join(getPatchesDir(opencodeDir), entry.patchFile);
|
|
2967
|
-
if (existsSync(patchPath)) if (applyPatch$1(templateContent, readFileSync(patchPath, "utf-8")) === false) statuses.push({
|
|
2968
|
-
relativePath,
|
|
2969
|
-
entry,
|
|
2970
|
-
status: "conflict",
|
|
2971
|
-
message: "Template changed and patch cannot apply cleanly"
|
|
2972
|
-
});
|
|
2973
|
-
else statuses.push({
|
|
2974
|
-
relativePath,
|
|
2975
|
-
entry,
|
|
2976
|
-
status: "stale",
|
|
2977
|
-
message: "Template changed but patch can still apply"
|
|
2978
|
-
});
|
|
2979
|
-
else statuses.push({
|
|
2980
|
-
relativePath,
|
|
2981
|
-
entry,
|
|
2982
|
-
status: "missing",
|
|
2983
|
-
message: "Patch file missing"
|
|
2984
|
-
});
|
|
2985
|
-
continue;
|
|
2986
|
-
}
|
|
2987
|
-
}
|
|
2988
|
-
}
|
|
2989
|
-
statuses.push({
|
|
2990
|
-
relativePath,
|
|
2991
|
-
entry,
|
|
2992
|
-
status: "clean",
|
|
2993
|
-
message: "Patch is up to date"
|
|
2994
|
-
});
|
|
2995
|
-
}
|
|
2996
|
-
return statuses;
|
|
2997
|
-
}
|
|
2998
|
-
|
|
2999
|
-
//#endregion
|
|
3000
|
-
//#region src/commands/init.ts
|
|
3001
|
-
const EXCLUDED_DIRS = [
|
|
3002
|
-
"node_modules",
|
|
3003
|
-
".git",
|
|
3004
|
-
"dist",
|
|
3005
|
-
".DS_Store",
|
|
3006
|
-
"coverage",
|
|
3007
|
-
".next",
|
|
3008
|
-
".turbo"
|
|
3009
|
-
];
|
|
3010
|
-
const EXCLUDED_FILES = [
|
|
3011
|
-
"bun.lock",
|
|
3012
|
-
"package-lock.json",
|
|
3013
|
-
"yarn.lock",
|
|
3014
|
-
"pnpm-lock.yaml"
|
|
3015
|
-
];
|
|
3016
|
-
const PRESERVE_USER_DIRS = ["memory/project", "context"];
|
|
3017
|
-
const SHARED_CONFIG_DIRS = [
|
|
3018
|
-
"agent",
|
|
3019
|
-
"command",
|
|
3020
|
-
"skill",
|
|
3021
|
-
"tool"
|
|
3022
|
-
];
|
|
3023
|
-
/**
|
|
3024
|
-
* Detect if global config has any of the shared dirs populated.
|
|
3025
|
-
* Returns null if no global config or no shared dirs found.
|
|
3026
|
-
*/
|
|
3027
|
-
function detectGlobalConfig() {
|
|
3028
|
-
const globalDir = getGlobalConfigDir();
|
|
3029
|
-
if (!existsSync(globalDir)) return null;
|
|
3030
|
-
const coveredDirs = SHARED_CONFIG_DIRS.filter((d) => {
|
|
3031
|
-
const dirPath = join(globalDir, d);
|
|
3032
|
-
if (!existsSync(dirPath)) return false;
|
|
3033
|
-
try {
|
|
3034
|
-
return readdirSync(dirPath).filter((e) => !e.startsWith(".")).length > 0;
|
|
3035
|
-
} catch {
|
|
3036
|
-
return false;
|
|
3037
|
-
}
|
|
3038
|
-
});
|
|
3039
|
-
if (coveredDirs.length === 0) return null;
|
|
3040
|
-
return {
|
|
3041
|
-
dir: globalDir,
|
|
3042
|
-
coveredDirs
|
|
3043
|
-
};
|
|
3044
|
-
}
|
|
3045
|
-
/**
|
|
3046
|
-
* Get the global OpenCode config directory based on OS.
|
|
3047
|
-
* - macOS/Linux: ~/.config/opencode/ (respects XDG_CONFIG_HOME)
|
|
3048
|
-
* - Windows: %APPDATA%\opencode\ or %LOCALAPPDATA%\opencode\
|
|
3049
|
-
*/
|
|
3050
|
-
function getGlobalConfigDir() {
|
|
3051
|
-
if (platform() === "win32") return join(process.env.APPDATA || process.env.LOCALAPPDATA || join(homedir(), "AppData", "Roaming"), "opencode");
|
|
3052
|
-
return join(process.env.XDG_CONFIG_HOME || join(homedir(), ".config"), "opencode");
|
|
3053
|
-
}
|
|
3054
|
-
function detectMode(targetDir) {
|
|
3055
|
-
if (existsSync(join(targetDir, ".opencode"))) return "already-initialized";
|
|
3056
|
-
if (existsSync(targetDir)) {
|
|
3057
|
-
if (readdirSync(targetDir).some((e) => !e.startsWith(".") && !EXCLUDED_DIRS.includes(e) && e !== "node_modules")) return "add-config";
|
|
3058
|
-
}
|
|
3059
|
-
return "scaffold";
|
|
3060
|
-
}
|
|
3061
|
-
function getTemplateRoot$1() {
|
|
3062
|
-
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
3063
|
-
const possiblePaths = [join(__dirname, "template"), join(__dirname, "..", "..", ".opencode")];
|
|
3064
|
-
for (const path of possiblePaths) if (existsSync(join(path, ".opencode"))) return path;
|
|
3065
|
-
return null;
|
|
3066
|
-
}
|
|
3067
|
-
function getPackageVersion$1() {
|
|
3068
|
-
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
3069
|
-
const pkgPaths = [join(__dirname, "..", "..", "package.json"), join(__dirname, "..", "package.json")];
|
|
3070
|
-
for (const pkgPath of pkgPaths) {
|
|
3071
|
-
if (!existsSync(pkgPath)) continue;
|
|
3072
|
-
return JSON.parse(readFileSync(pkgPath, "utf-8")).version;
|
|
3073
|
-
}
|
|
3074
|
-
return "unknown";
|
|
3075
|
-
}
|
|
3076
|
-
async function copyDir(src, dest) {
|
|
3077
|
-
const { mkdir, readdir } = await import("node:fs/promises");
|
|
3078
|
-
await mkdir(dest, { recursive: true });
|
|
3079
|
-
for (const entry of await readdir(src, { withFileTypes: true })) {
|
|
3080
|
-
if (EXCLUDED_DIRS.includes(entry.name)) continue;
|
|
3081
|
-
if (!entry.isDirectory() && EXCLUDED_FILES.includes(entry.name)) continue;
|
|
3082
|
-
const srcPath = join(src, entry.name);
|
|
3083
|
-
const destPath = join(dest, entry.name);
|
|
3084
|
-
if (entry.isSymbolicLink()) {} else if (entry.isDirectory()) await copyDir(srcPath, destPath);
|
|
3085
|
-
else writeFileSync(destPath, readFileSync(srcPath, "utf-8"));
|
|
3086
|
-
}
|
|
3087
|
-
}
|
|
3088
|
-
async function copyOpenCodeOnly(templateRoot, targetDir, skipDirs) {
|
|
3089
|
-
const opencodeSrc = join(templateRoot, ".opencode");
|
|
3090
|
-
const opencodeDest = join(targetDir, ".opencode");
|
|
3091
|
-
if (!existsSync(opencodeSrc)) return false;
|
|
3092
|
-
if (skipDirs && skipDirs.length > 0) {
|
|
3093
|
-
const skipSet = new Set(skipDirs);
|
|
3094
|
-
mkdirSync(opencodeDest, { recursive: true });
|
|
3095
|
-
for (const entry of readdirSync(opencodeSrc, { withFileTypes: true })) {
|
|
3096
|
-
if (EXCLUDED_DIRS.includes(entry.name)) continue;
|
|
3097
|
-
if (!entry.isDirectory() && EXCLUDED_FILES.includes(entry.name)) continue;
|
|
3098
|
-
if (entry.isSymbolicLink()) continue;
|
|
3099
|
-
if (entry.isDirectory() && skipSet.has(entry.name)) continue;
|
|
3100
|
-
const srcPath = join(opencodeSrc, entry.name);
|
|
3101
|
-
const destPath = join(opencodeDest, entry.name);
|
|
3102
|
-
if (entry.isDirectory()) await copyDir(srcPath, destPath);
|
|
3103
|
-
else writeFileSync(destPath, readFileSync(srcPath, "utf-8"));
|
|
3104
|
-
}
|
|
3105
|
-
return true;
|
|
3106
|
-
}
|
|
3107
|
-
await copyDir(opencodeSrc, opencodeDest);
|
|
3108
|
-
return true;
|
|
3109
|
-
}
|
|
3110
|
-
const MODEL_PRESETS = {
|
|
3111
|
-
free: {
|
|
3112
|
-
model: "opencode/glm-5-free",
|
|
3113
|
-
agents: {
|
|
3114
|
-
build: "opencode/minimax-m2.5-free",
|
|
3115
|
-
plan: "opencode/minimax-m2.5-free",
|
|
3116
|
-
review: "opencode/minimax-m2.5-free",
|
|
3117
|
-
explore: "opencode/glm-5-free",
|
|
3118
|
-
general: "opencode/glm-5-free",
|
|
3119
|
-
vision: "opencode/minimax-m2.5-free",
|
|
3120
|
-
scout: "opencode/glm-5-free",
|
|
3121
|
-
painter: "opencode/minimax-m2.5-free"
|
|
3122
|
-
}
|
|
3123
|
-
},
|
|
3124
|
-
recommend: {
|
|
3125
|
-
model: "github-copilot/gpt-5.4",
|
|
3126
|
-
agents: {
|
|
3127
|
-
build: "github-copilot/gpt-5.4",
|
|
3128
|
-
plan: "github-copilot/gpt-5.4",
|
|
3129
|
-
review: "github-copilot/gpt-5.3-codex",
|
|
3130
|
-
explore: "github-copilot/claude-haiku-4.5",
|
|
3131
|
-
general: "github-copilot/gpt-5.3-codex",
|
|
3132
|
-
vision: "github-copilot/gemini-3.1-pro-preview",
|
|
3133
|
-
scout: "github-copilot/claude-sonnet-4.6",
|
|
3134
|
-
painter: "proxypal/gemini-3.1-flash-image"
|
|
3135
|
-
}
|
|
3136
|
-
}
|
|
3137
|
-
};
|
|
3138
|
-
function applyModelPreset(targetDir, preset) {
|
|
3139
|
-
const configPath = join(targetDir, ".opencode", "opencode.json");
|
|
3140
|
-
if (!existsSync(configPath)) return;
|
|
3141
|
-
const config = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
3142
|
-
const presetConfig = MODEL_PRESETS[preset];
|
|
3143
|
-
config.model = presetConfig.model;
|
|
3144
|
-
if (config.agent) {
|
|
3145
|
-
for (const [agentName, model] of Object.entries(presetConfig.agents)) if (config.agent[agentName]) config.agent[agentName].model = model;
|
|
3146
|
-
}
|
|
3147
|
-
writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
3148
|
-
}
|
|
3149
|
-
const AGENT_DESCRIPTIONS = {
|
|
3150
|
-
build: "Main coding agent (complex tasks)",
|
|
3151
|
-
plan: "Planning and design agent",
|
|
3152
|
-
review: "Code review and debugging",
|
|
3153
|
-
explore: "Fast codebase search",
|
|
3154
|
-
general: "Quick, simple tasks",
|
|
3155
|
-
painter: "Image generation and editing",
|
|
3156
|
-
vision: "Visual analysis (quality)",
|
|
3157
|
-
scout: "External research/docs",
|
|
3158
|
-
compaction: "Context summarization"
|
|
3159
|
-
};
|
|
3160
|
-
async function promptCustomModels(targetDir) {
|
|
3161
|
-
const configPath = join(targetDir, ".opencode", "opencode.json");
|
|
3162
|
-
if (!existsSync(configPath)) return;
|
|
3163
|
-
const config = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
3164
|
-
p.log.info(color.dim("Enter model IDs (e.g., github-copilot/gpt-5.4, proxypal/gemini-3.1-flash-image)"));
|
|
3165
|
-
p.log.info(color.dim("Press Enter to keep current value\n"));
|
|
3166
|
-
const mainModel = await p.text({
|
|
3167
|
-
message: "Main session model",
|
|
3168
|
-
placeholder: config.model || "github-copilot/gpt-5.4",
|
|
3169
|
-
defaultValue: config.model
|
|
3170
|
-
});
|
|
3171
|
-
if (p.isCancel(mainModel)) {
|
|
3172
|
-
p.log.warn("Cancelled - keeping defaults");
|
|
3173
|
-
return;
|
|
3174
|
-
}
|
|
3175
|
-
if (mainModel) config.model = mainModel;
|
|
3176
|
-
const agents = Object.keys(AGENT_DESCRIPTIONS);
|
|
3177
|
-
for (const agent of agents) {
|
|
3178
|
-
if (!config.agent?.[agent]) continue;
|
|
3179
|
-
const currentModel = config.agent[agent].model || config.model;
|
|
3180
|
-
const agentModel = await p.text({
|
|
3181
|
-
message: `${agent} - ${AGENT_DESCRIPTIONS[agent]}`,
|
|
3182
|
-
placeholder: currentModel,
|
|
3183
|
-
defaultValue: currentModel
|
|
3184
|
-
});
|
|
3185
|
-
if (p.isCancel(agentModel)) {
|
|
3186
|
-
p.log.warn("Cancelled - saving partial config");
|
|
3187
|
-
break;
|
|
3188
|
-
}
|
|
3189
|
-
if (agentModel) config.agent[agent].model = agentModel;
|
|
3190
|
-
}
|
|
3191
|
-
writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
3192
|
-
}
|
|
3193
|
-
function getAffectedFiles(dir, prefix = "") {
|
|
3194
|
-
if (!existsSync(dir)) return [];
|
|
3195
|
-
const files = [];
|
|
3196
|
-
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
3197
|
-
if (EXCLUDED_DIRS.includes(entry.name)) continue;
|
|
3198
|
-
const path = prefix ? `${prefix}/${entry.name}` : entry.name;
|
|
3199
|
-
if (entry.isDirectory()) files.push(...getAffectedFiles(join(dir, entry.name), path));
|
|
3200
|
-
else files.push(path);
|
|
3201
|
-
}
|
|
3202
|
-
return files;
|
|
3203
|
-
}
|
|
3204
|
-
function backupOpenCode(targetDir) {
|
|
3205
|
-
const opencodeDir = join(targetDir, ".opencode");
|
|
3206
|
-
if (!existsSync(opencodeDir)) return null;
|
|
3207
|
-
const backupDir = join(targetDir, `.opencode.bak-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19)}`);
|
|
3208
|
-
renameSync(opencodeDir, backupDir);
|
|
3209
|
-
return backupDir;
|
|
3210
|
-
}
|
|
3211
|
-
function getTemplateFiles(templateRoot) {
|
|
3212
|
-
const opencodeSrc = join(templateRoot, ".opencode");
|
|
3213
|
-
if (!existsSync(opencodeSrc)) return /* @__PURE__ */ new Set();
|
|
3214
|
-
return new Set(getAffectedFiles(opencodeSrc));
|
|
3215
|
-
}
|
|
3216
|
-
function findOrphans(targetDir, templateFiles) {
|
|
3217
|
-
const opencodeDir = join(targetDir, ".opencode");
|
|
3218
|
-
if (!existsSync(opencodeDir)) return [];
|
|
3219
|
-
return getAffectedFiles(opencodeDir).filter((f) => f !== MANIFEST_FILE && !templateFiles.has(f));
|
|
3220
|
-
}
|
|
3221
|
-
/**
|
|
3222
|
-
* Check if an orphan file is a modified template file (exists in template but content differs).
|
|
3223
|
-
* Returns the template content if it's a modified template file, null otherwise.
|
|
3224
|
-
*/
|
|
3225
|
-
function getModifiedTemplateContent(templateRoot, orphanPath) {
|
|
3226
|
-
const templateFilePath = join(templateRoot, ".opencode", orphanPath);
|
|
3227
|
-
if (!existsSync(templateFilePath)) return null;
|
|
3228
|
-
return readFileSync(templateFilePath, "utf-8");
|
|
3229
|
-
}
|
|
3230
|
-
/**
|
|
3231
|
-
* Auto-detect and save patches for modified template files among orphans.
|
|
3232
|
-
* Returns the list of orphans that were NOT saved as patches (true orphans).
|
|
3233
|
-
*/
|
|
3234
|
-
async function autoSavePatchesForOrphans(targetDir, templateRoot, orphans) {
|
|
3235
|
-
const opencodeDir = join(targetDir, ".opencode");
|
|
3236
|
-
const savedPatches = [];
|
|
3237
|
-
const trueOrphans = [];
|
|
3238
|
-
for (const orphan of orphans) {
|
|
3239
|
-
const templateContent = getModifiedTemplateContent(templateRoot, orphan);
|
|
3240
|
-
if (!templateContent) {
|
|
3241
|
-
trueOrphans.push(orphan);
|
|
3242
|
-
continue;
|
|
3243
|
-
}
|
|
3244
|
-
const userContent = readFileSync(join(opencodeDir, orphan), "utf-8");
|
|
3245
|
-
if (templateContent === userContent) {
|
|
3246
|
-
trueOrphans.push(orphan);
|
|
3247
|
-
continue;
|
|
3248
|
-
}
|
|
3249
|
-
try {
|
|
3250
|
-
savePatch(opencodeDir, orphan, templateContent, userContent);
|
|
3251
|
-
savedPatches.push(orphan);
|
|
3252
|
-
} catch {
|
|
3253
|
-
trueOrphans.push(orphan);
|
|
3254
|
-
}
|
|
3255
|
-
}
|
|
3256
|
-
return {
|
|
3257
|
-
savedPatches,
|
|
3258
|
-
trueOrphans
|
|
3259
|
-
};
|
|
3260
|
-
}
|
|
3261
|
-
/**
|
|
3262
|
-
* Save existing user files from preserve directories before reinit.
|
|
3263
|
-
* Returns a map of relative paths to file contents.
|
|
3264
|
-
*/
|
|
3265
|
-
function preserveUserFiles(targetDir) {
|
|
3266
|
-
const opencodeDir = join(targetDir, ".opencode");
|
|
3267
|
-
const preserved = /* @__PURE__ */ new Map();
|
|
3268
|
-
function collectFiles(currentDir, relativeDir) {
|
|
3269
|
-
for (const entry of readdirSync(currentDir, { withFileTypes: true })) {
|
|
3270
|
-
const filePath = join(currentDir, entry.name);
|
|
3271
|
-
const relativePath = join(relativeDir, entry.name);
|
|
3272
|
-
if (entry.isDirectory()) {
|
|
3273
|
-
collectFiles(filePath, relativePath);
|
|
3274
|
-
continue;
|
|
3275
|
-
}
|
|
3276
|
-
if (!entry.isFile()) continue;
|
|
3277
|
-
preserved.set(relativePath, readFileSync(filePath, "utf-8"));
|
|
3278
|
-
}
|
|
3279
|
-
}
|
|
3280
|
-
for (const relDir of PRESERVE_USER_DIRS) {
|
|
3281
|
-
const dirPath = join(opencodeDir, relDir);
|
|
3282
|
-
if (!existsSync(dirPath)) continue;
|
|
3283
|
-
collectFiles(dirPath, relDir);
|
|
3284
|
-
}
|
|
3285
|
-
return preserved;
|
|
3286
|
-
}
|
|
3287
|
-
/**
|
|
3288
|
-
* Restore preserved user files after fresh template copy.
|
|
3289
|
-
*/
|
|
3290
|
-
function restoreUserFiles(targetDir, preserved) {
|
|
3291
|
-
const opencodeDir = join(targetDir, ".opencode");
|
|
3292
|
-
for (const [relativePath, content] of preserved) {
|
|
3293
|
-
const filePath = join(opencodeDir, relativePath);
|
|
3294
|
-
mkdirSync(dirname(filePath), { recursive: true });
|
|
3295
|
-
writeFileSync(filePath, content);
|
|
3296
|
-
}
|
|
3297
|
-
}
|
|
3298
|
-
function finalizeInstalledFiles(targetDir, version, preservedFiles) {
|
|
3299
|
-
generateManifest(join(targetDir, ".opencode"), version);
|
|
3300
|
-
if (!preservedFiles || preservedFiles.size === 0) return 0;
|
|
3301
|
-
restoreUserFiles(targetDir, preservedFiles);
|
|
3302
|
-
return preservedFiles.size;
|
|
3303
|
-
}
|
|
3304
|
-
async function initCommand(rawOptions = {}) {
|
|
3305
|
-
const options = parseOptions(InitOptionsSchema, rawOptions);
|
|
3306
|
-
if (process.argv.includes("--quiet")) return;
|
|
3307
|
-
p.intro(color.bgCyan(color.black(" OpenCodeKit ")));
|
|
3308
|
-
if (options.global) {
|
|
3309
|
-
const globalDir = getGlobalConfigDir();
|
|
3310
|
-
const os = platform();
|
|
3311
|
-
const osName = os === "win32" ? "Windows" : os === "darwin" ? "macOS" : "Linux";
|
|
3312
|
-
p.log.info(`Installing to global config (${osName})`);
|
|
3313
|
-
p.log.info(`Target: ${color.cyan(globalDir)}`);
|
|
3314
|
-
const templateRoot = getTemplateRoot$1();
|
|
3315
|
-
if (!templateRoot) {
|
|
3316
|
-
p.log.error("Template not found. Please reinstall opencodekit.");
|
|
3317
|
-
p.outro(color.red("Failed"));
|
|
3318
|
-
process.exit(1);
|
|
3319
|
-
}
|
|
3320
|
-
if (existsSync(globalDir) && !options.force) {
|
|
3321
|
-
p.log.warn(`Global config already exists at ${globalDir}`);
|
|
3322
|
-
p.log.info(`Use ${color.cyan("--force")} to overwrite`);
|
|
3323
|
-
p.outro("Nothing to do");
|
|
3324
|
-
return;
|
|
3325
|
-
}
|
|
3326
|
-
const s = p.spinner();
|
|
3327
|
-
s.start("Copying to global config");
|
|
3328
|
-
const opencodeSrc = join(templateRoot, ".opencode");
|
|
3329
|
-
if (!existsSync(opencodeSrc)) {
|
|
3330
|
-
s.stop("Failed");
|
|
3331
|
-
p.log.error("Template .opencode/ not found");
|
|
3332
|
-
p.outro(color.red("Failed"));
|
|
3333
|
-
process.exit(1);
|
|
3334
|
-
}
|
|
3335
|
-
await copyDir(opencodeSrc, globalDir);
|
|
3336
|
-
s.stop("Done");
|
|
3337
|
-
p.note(`Global config installed at:\n${globalDir}\n\nThis provides default agents, skills, and tools\nfor all OpenCode projects on this machine.`, "Global Installation Complete");
|
|
3338
|
-
p.outro(color.green("Ready!"));
|
|
3339
|
-
return;
|
|
3340
|
-
}
|
|
3341
|
-
const targetDir = process.cwd();
|
|
3342
|
-
const mode = detectMode(targetDir);
|
|
3343
|
-
if (mode === "already-initialized" && !options.force) {
|
|
3344
|
-
p.log.warn("Already initialized (.opencode/ exists)");
|
|
3345
|
-
p.log.info(`Use ${color.cyan("--force")} to reinitialize`);
|
|
2839
|
+
const localDir = join(process.cwd(), ".opencode");
|
|
2840
|
+
p.log.info(`Installing to project: ${color.cyan(localDir)}`);
|
|
2841
|
+
if (existsSync(localDir) && !options.force) {
|
|
2842
|
+
p.log.warn("Project already initialized (.opencode/ exists)");
|
|
2843
|
+
p.log.info(`Use ${color.cyan("--force")} to overwrite`);
|
|
3346
2844
|
p.outro("Nothing to do");
|
|
3347
2845
|
return;
|
|
3348
2846
|
}
|
|
3349
|
-
|
|
3350
|
-
if (mode === "already-initialized" && options.force) {
|
|
3351
|
-
const affected = getAffectedFiles(join(targetDir, ".opencode"));
|
|
3352
|
-
if (affected.length > 0 && !options.yes) {
|
|
3353
|
-
p.log.warn(`${affected.length} files will be overwritten:`);
|
|
3354
|
-
const preview = affected.slice(0, 10);
|
|
3355
|
-
for (const file of preview) p.log.info(color.dim(` .opencode/${file}`));
|
|
3356
|
-
if (affected.length > 10) p.log.info(color.dim(` ... and ${affected.length - 10} more`));
|
|
3357
|
-
if (!options.backup) {
|
|
3358
|
-
const shouldBackup = await p.confirm({
|
|
3359
|
-
message: "Backup existing .opencode before overwriting?",
|
|
3360
|
-
initialValue: true
|
|
3361
|
-
});
|
|
3362
|
-
if (p.isCancel(shouldBackup)) {
|
|
3363
|
-
p.cancel("Cancelled");
|
|
3364
|
-
process.exit(0);
|
|
3365
|
-
}
|
|
3366
|
-
if (shouldBackup) options.backup = true;
|
|
3367
|
-
}
|
|
3368
|
-
const proceed = await p.confirm({
|
|
3369
|
-
message: options.backup ? "Proceed? (existing config will be backed up)" : "Proceed without backup?",
|
|
3370
|
-
initialValue: options.backup
|
|
3371
|
-
});
|
|
3372
|
-
if (p.isCancel(proceed) || !proceed) {
|
|
3373
|
-
p.cancel("Cancelled");
|
|
3374
|
-
process.exit(0);
|
|
3375
|
-
}
|
|
3376
|
-
}
|
|
3377
|
-
if (options.backup) {
|
|
3378
|
-
preservedFiles = preserveUserFiles(targetDir);
|
|
3379
|
-
const backupPath = backupOpenCode(targetDir);
|
|
3380
|
-
if (backupPath) p.log.info(`Backed up to ${color.cyan(basename(backupPath))}`);
|
|
3381
|
-
} else preservedFiles = preserveUserFiles(targetDir);
|
|
3382
|
-
}
|
|
3383
|
-
const templateRoot = getTemplateRoot$1();
|
|
2847
|
+
const templateRoot = getTemplateRoot$2();
|
|
3384
2848
|
if (!templateRoot) {
|
|
3385
2849
|
p.log.error("Template not found. Please reinstall opencodekit.");
|
|
3386
2850
|
p.outro(color.red("Failed"));
|
|
3387
2851
|
process.exit(1);
|
|
3388
2852
|
}
|
|
3389
|
-
let projectName = basename(targetDir);
|
|
3390
|
-
if (mode === "scaffold") {
|
|
3391
|
-
const name = await p.text({
|
|
3392
|
-
message: "Project name",
|
|
3393
|
-
placeholder: projectName,
|
|
3394
|
-
defaultValue: projectName
|
|
3395
|
-
});
|
|
3396
|
-
if (p.isCancel(name)) {
|
|
3397
|
-
p.cancel("Cancelled");
|
|
3398
|
-
process.exit(0);
|
|
3399
|
-
}
|
|
3400
|
-
projectName = name || projectName;
|
|
3401
|
-
}
|
|
3402
|
-
let skipDirs = [];
|
|
3403
|
-
if (!options.global) {
|
|
3404
|
-
const globalConfig = detectGlobalConfig();
|
|
3405
|
-
if (globalConfig && options.projectOnly) {
|
|
3406
|
-
skipDirs = globalConfig.coveredDirs;
|
|
3407
|
-
p.log.info(`Using global config from ${color.cyan(globalConfig.dir)}`);
|
|
3408
|
-
p.log.info(`Skipping: ${skipDirs.map((d) => color.dim(d)).join(", ")}`);
|
|
3409
|
-
} else if (globalConfig && !options.yes) {
|
|
3410
|
-
p.log.info(`Global config found at ${color.cyan(globalConfig.dir)}`);
|
|
3411
|
-
p.log.info(`Available globally: ${globalConfig.coveredDirs.map((d) => color.green(d)).join(", ")}`);
|
|
3412
|
-
const useGlobal = await p.confirm({
|
|
3413
|
-
message: "Skip these (use global config)? Only project-scope files will be created locally.",
|
|
3414
|
-
initialValue: true
|
|
3415
|
-
});
|
|
3416
|
-
if (!p.isCancel(useGlobal) && useGlobal) skipDirs = globalConfig.coveredDirs;
|
|
3417
|
-
} else if (globalConfig && options.yes) p.log.info(`Global config found at ${color.cyan(globalConfig.dir)} — use ${color.bold("--project-only")} to skip shared dirs`);
|
|
3418
|
-
}
|
|
3419
2853
|
const s = p.spinner();
|
|
3420
|
-
|
|
3421
|
-
|
|
3422
|
-
|
|
3423
|
-
} else if (mode === "add-config") s.start("Adding OpenCodeKit");
|
|
3424
|
-
else s.start("Reinitializing");
|
|
3425
|
-
if (!await copyOpenCodeOnly(templateRoot, targetDir, skipDirs)) {
|
|
2854
|
+
s.start("Copying to project");
|
|
2855
|
+
const opencodeSrc = join(templateRoot, ".opencode");
|
|
2856
|
+
if (!existsSync(opencodeSrc)) {
|
|
3426
2857
|
s.stop("Failed");
|
|
3427
|
-
p.
|
|
3428
|
-
|
|
3429
|
-
|
|
3430
|
-
s.stop("Done");
|
|
3431
|
-
if (skipDirs.length > 0) p.log.info(`Project-only init: skipped ${skipDirs.map((d) => color.dim(d)).join(", ")} (using global config)`);
|
|
3432
|
-
const restoredFileCount = finalizeInstalledFiles(targetDir, getPackageVersion$1(), preservedFiles);
|
|
3433
|
-
if (restoredFileCount > 0) p.log.info(`Preserved ${restoredFileCount} user memory files (memory/project/)`);
|
|
3434
|
-
if (options.free) {
|
|
3435
|
-
applyModelPreset(targetDir, "free");
|
|
3436
|
-
p.log.info("Applied free model preset");
|
|
3437
|
-
} else if (options.recommend) {
|
|
3438
|
-
applyModelPreset(targetDir, "recommend");
|
|
3439
|
-
p.log.info("Applied recommended model preset");
|
|
3440
|
-
} else if (options.yes) {
|
|
3441
|
-
applyModelPreset(targetDir, "free");
|
|
3442
|
-
p.log.info("Applied free model preset (default)");
|
|
3443
|
-
} else {
|
|
3444
|
-
const preset = await p.select({
|
|
3445
|
-
message: "Choose model preset",
|
|
3446
|
-
options: [
|
|
3447
|
-
{
|
|
3448
|
-
value: "free",
|
|
3449
|
-
label: "Free models",
|
|
3450
|
-
hint: "minimax, glm, grok (no API costs)"
|
|
3451
|
-
},
|
|
3452
|
-
{
|
|
3453
|
-
value: "recommend",
|
|
3454
|
-
label: "Recommended models",
|
|
3455
|
-
hint: "gpt-5.4, gpt-5.3-codex, sonnet-4.6, gemini-3.1"
|
|
3456
|
-
},
|
|
3457
|
-
{
|
|
3458
|
-
value: "custom",
|
|
3459
|
-
label: "Custom",
|
|
3460
|
-
hint: "configure each agent individually"
|
|
3461
|
-
},
|
|
3462
|
-
{
|
|
3463
|
-
value: "skip",
|
|
3464
|
-
label: "Skip",
|
|
3465
|
-
hint: "keep template defaults"
|
|
3466
|
-
}
|
|
3467
|
-
]
|
|
3468
|
-
});
|
|
3469
|
-
if (!p.isCancel(preset)) {
|
|
3470
|
-
if (preset === "custom") {
|
|
3471
|
-
await promptCustomModels(targetDir);
|
|
3472
|
-
p.log.info("Applied custom model configuration");
|
|
3473
|
-
} else if (preset !== "skip") {
|
|
3474
|
-
applyModelPreset(targetDir, preset);
|
|
3475
|
-
p.log.info(`Applied ${preset} model preset`);
|
|
3476
|
-
}
|
|
3477
|
-
}
|
|
3478
|
-
}
|
|
3479
|
-
const opencodeDir = join(targetDir, ".opencode");
|
|
3480
|
-
if (existsSync(join(opencodeDir, "package.json"))) {
|
|
3481
|
-
const installSpinner = p.spinner();
|
|
3482
|
-
installSpinner.start("Installing dependencies");
|
|
3483
|
-
try {
|
|
3484
|
-
execSync("npm install --no-fund --no-audit", {
|
|
3485
|
-
cwd: opencodeDir,
|
|
3486
|
-
stdio: "ignore"
|
|
3487
|
-
});
|
|
3488
|
-
installSpinner.stop("Dependencies installed");
|
|
3489
|
-
} catch {
|
|
3490
|
-
installSpinner.stop("Failed to install (run manually: cd .opencode && npm install)");
|
|
3491
|
-
}
|
|
3492
|
-
}
|
|
3493
|
-
if (mode === "already-initialized" && options.force && !options.backup) {
|
|
3494
|
-
const orphans = findOrphans(targetDir, getTemplateFiles(templateRoot));
|
|
3495
|
-
if (orphans.length > 0) {
|
|
3496
|
-
p.log.warn(`Found ${orphans.length} orphan files not in template`);
|
|
3497
|
-
const { savedPatches, trueOrphans } = await autoSavePatchesForOrphans(targetDir, templateRoot, orphans);
|
|
3498
|
-
if (savedPatches.length > 0) {
|
|
3499
|
-
p.log.success(`Auto-saved ${savedPatches.length} patches for modified template files`);
|
|
3500
|
-
for (const patch of savedPatches) console.log(` ${color.green("✓")} ${patch}`);
|
|
3501
|
-
p.log.info(color.dim("These patches will be reapplied after template files are updated."));
|
|
3502
|
-
}
|
|
3503
|
-
if (trueOrphans.length === 0) {} else if (options.pruneAll) {
|
|
3504
|
-
const pruneSpinner = p.spinner();
|
|
3505
|
-
pruneSpinner.start("Removing orphan files");
|
|
3506
|
-
for (const orphan of trueOrphans) rmSync(join(opencodeDir, orphan));
|
|
3507
|
-
pruneSpinner.stop(`Removed ${trueOrphans.length} orphan files`);
|
|
3508
|
-
} else if (options.prune) {
|
|
3509
|
-
const selected = await p.multiselect({
|
|
3510
|
-
message: "Select orphan files to delete",
|
|
3511
|
-
options: trueOrphans.map((o) => ({
|
|
3512
|
-
value: o,
|
|
3513
|
-
label: o
|
|
3514
|
-
})),
|
|
3515
|
-
required: false
|
|
3516
|
-
});
|
|
3517
|
-
if (!p.isCancel(selected) && selected.length > 0) {
|
|
3518
|
-
const pruneSpinner = p.spinner();
|
|
3519
|
-
pruneSpinner.start("Deleting files");
|
|
3520
|
-
for (const file of selected) rmSync(join(opencodeDir, file));
|
|
3521
|
-
pruneSpinner.stop(`Deleted ${selected.length} files`);
|
|
3522
|
-
}
|
|
3523
|
-
} else if (!options.yes && trueOrphans.length > 0) {
|
|
3524
|
-
const preview = trueOrphans.slice(0, 5);
|
|
3525
|
-
for (const file of preview) p.log.info(color.dim(` .opencode/${file}`));
|
|
3526
|
-
if (trueOrphans.length > 5) p.log.info(color.dim(` ... and ${trueOrphans.length - 5} more`));
|
|
3527
|
-
const orphanAction = await p.select({
|
|
3528
|
-
message: "How to handle orphan files?",
|
|
3529
|
-
options: [
|
|
3530
|
-
{
|
|
3531
|
-
value: "keep",
|
|
3532
|
-
label: "Keep all",
|
|
3533
|
-
hint: "leave orphan files"
|
|
3534
|
-
},
|
|
3535
|
-
{
|
|
3536
|
-
value: "select",
|
|
3537
|
-
label: "Select",
|
|
3538
|
-
hint: "choose which to delete"
|
|
3539
|
-
},
|
|
3540
|
-
{
|
|
3541
|
-
value: "delete",
|
|
3542
|
-
label: "Delete all",
|
|
3543
|
-
hint: "remove all orphans"
|
|
3544
|
-
}
|
|
3545
|
-
]
|
|
3546
|
-
});
|
|
3547
|
-
if (!p.isCancel(orphanAction)) {
|
|
3548
|
-
if (orphanAction === "delete") {
|
|
3549
|
-
const pruneSpinner = p.spinner();
|
|
3550
|
-
pruneSpinner.start("Removing orphan files");
|
|
3551
|
-
for (const orphan of trueOrphans) rmSync(join(opencodeDir, orphan));
|
|
3552
|
-
pruneSpinner.stop(`Removed ${trueOrphans.length} orphan files`);
|
|
3553
|
-
} else if (orphanAction === "select") {
|
|
3554
|
-
const selected = await p.multiselect({
|
|
3555
|
-
message: "Select orphan files to delete",
|
|
3556
|
-
options: trueOrphans.map((o) => ({
|
|
3557
|
-
value: o,
|
|
3558
|
-
label: o
|
|
3559
|
-
})),
|
|
3560
|
-
required: false
|
|
3561
|
-
});
|
|
3562
|
-
if (!p.isCancel(selected) && selected.length > 0) {
|
|
3563
|
-
const pruneSpinner = p.spinner();
|
|
3564
|
-
pruneSpinner.start("Deleting files");
|
|
3565
|
-
for (const file of selected) rmSync(join(opencodeDir, file));
|
|
3566
|
-
pruneSpinner.stop(`Deleted ${selected.length} files`);
|
|
3567
|
-
}
|
|
3568
|
-
}
|
|
3569
|
-
}
|
|
3570
|
-
}
|
|
3571
|
-
}
|
|
2858
|
+
p.log.error("Template .opencode/ not found");
|
|
2859
|
+
p.outro(color.red("Failed"));
|
|
2860
|
+
process.exit(1);
|
|
3572
2861
|
}
|
|
3573
|
-
|
|
2862
|
+
await copyDir(opencodeSrc, localDir);
|
|
2863
|
+
s.stop("Done");
|
|
2864
|
+
writeFileSync(join(localDir, ".version"), getPackageVersion$2());
|
|
2865
|
+
generateManifest(localDir, getPackageVersion$2());
|
|
2866
|
+
p.note(`OpenCodeKit config installed at:\n${localDir}\n\nThis provides default agents, skills, and tools\nfor this project.`, "Project Installation Complete");
|
|
2867
|
+
p.outro(color.green("Ready!"));
|
|
3574
2868
|
}
|
|
3575
2869
|
|
|
3576
2870
|
//#endregion
|
|
@@ -3913,6 +3207,251 @@ async function removeSkill(skillDir, skillNameArg) {
|
|
|
3913
3207
|
p.log.success(`Removed skill "${skill.name}"`);
|
|
3914
3208
|
}
|
|
3915
3209
|
|
|
3210
|
+
//#endregion
|
|
3211
|
+
//#region src/utils/patch.ts
|
|
3212
|
+
/**
|
|
3213
|
+
* Patch utilities for saving/applying user modifications to template files.
|
|
3214
|
+
* Uses unified diff format for git-friendly, human-readable patches.
|
|
3215
|
+
*/
|
|
3216
|
+
const PATCHES_DIR = "patches";
|
|
3217
|
+
const PATCHES_JSON = ".patches.json";
|
|
3218
|
+
const METADATA_VERSION = "1.0.0";
|
|
3219
|
+
/**
|
|
3220
|
+
* Calculate SHA-256 hash of content.
|
|
3221
|
+
*/
|
|
3222
|
+
function calculateHash(content) {
|
|
3223
|
+
return createHash("sha256").update(content).digest("hex").slice(0, 16);
|
|
3224
|
+
}
|
|
3225
|
+
/**
|
|
3226
|
+
* Convert a relative file path to a safe patch filename.
|
|
3227
|
+
* e.g., "agent/build.md" -> "agent-build.md.patch"
|
|
3228
|
+
*/
|
|
3229
|
+
function pathToPatchFilename(relativePath) {
|
|
3230
|
+
return `${relativePath.replace(/\//g, "-")}.patch`;
|
|
3231
|
+
}
|
|
3232
|
+
/**
|
|
3233
|
+
* Get the patches directory path.
|
|
3234
|
+
*/
|
|
3235
|
+
function getPatchesDir(opencodeDir) {
|
|
3236
|
+
return join(opencodeDir, PATCHES_DIR);
|
|
3237
|
+
}
|
|
3238
|
+
/**
|
|
3239
|
+
* Get the template root directory (from dist/template or dev mode).
|
|
3240
|
+
*/
|
|
3241
|
+
function getTemplateRoot$1() {
|
|
3242
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
3243
|
+
const possiblePaths = [
|
|
3244
|
+
join(__dirname, "template"),
|
|
3245
|
+
join(__dirname, "..", "..", ".opencode"),
|
|
3246
|
+
join(__dirname, "..", "template")
|
|
3247
|
+
];
|
|
3248
|
+
for (const path of possiblePaths) {
|
|
3249
|
+
if (existsSync(join(path, ".opencode"))) return path;
|
|
3250
|
+
if (existsSync(join(path, "opencode.json"))) return dirname(path);
|
|
3251
|
+
}
|
|
3252
|
+
return null;
|
|
3253
|
+
}
|
|
3254
|
+
/**
|
|
3255
|
+
* Load patch metadata from .patches.json.
|
|
3256
|
+
*/
|
|
3257
|
+
function loadPatchMetadata(opencodeDir) {
|
|
3258
|
+
const metadataPath = join(getPatchesDir(opencodeDir), PATCHES_JSON);
|
|
3259
|
+
if (!existsSync(metadataPath)) return {
|
|
3260
|
+
version: METADATA_VERSION,
|
|
3261
|
+
patches: {}
|
|
3262
|
+
};
|
|
3263
|
+
try {
|
|
3264
|
+
const content = readFileSync(metadataPath, "utf-8");
|
|
3265
|
+
return JSON.parse(content);
|
|
3266
|
+
} catch {
|
|
3267
|
+
return {
|
|
3268
|
+
version: METADATA_VERSION,
|
|
3269
|
+
patches: {}
|
|
3270
|
+
};
|
|
3271
|
+
}
|
|
3272
|
+
}
|
|
3273
|
+
/**
|
|
3274
|
+
* Save patch metadata to .patches.json.
|
|
3275
|
+
*/
|
|
3276
|
+
function savePatchMetadata(opencodeDir, metadata) {
|
|
3277
|
+
const patchesDir = getPatchesDir(opencodeDir);
|
|
3278
|
+
if (!existsSync(patchesDir)) mkdirSync(patchesDir, { recursive: true });
|
|
3279
|
+
writeFileSync(join(patchesDir, PATCHES_JSON), JSON.stringify(metadata, null, 2));
|
|
3280
|
+
}
|
|
3281
|
+
/**
|
|
3282
|
+
* Get the current OpenCodeKit version from package.json.
|
|
3283
|
+
*/
|
|
3284
|
+
function getPackageVersion$1() {
|
|
3285
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
3286
|
+
const pkgPaths = [join(__dirname, "..", "..", "package.json"), join(__dirname, "..", "package.json")];
|
|
3287
|
+
for (const pkgPath of pkgPaths) if (existsSync(pkgPath)) try {
|
|
3288
|
+
return JSON.parse(readFileSync(pkgPath, "utf-8")).version || "unknown";
|
|
3289
|
+
} catch {}
|
|
3290
|
+
return "unknown";
|
|
3291
|
+
}
|
|
3292
|
+
/**
|
|
3293
|
+
* Generate a unified diff patch between template and user file.
|
|
3294
|
+
*/
|
|
3295
|
+
function generatePatch(templateContent, userContent, relativePath) {
|
|
3296
|
+
return createPatch(relativePath, templateContent, userContent, "template", "modified");
|
|
3297
|
+
}
|
|
3298
|
+
/**
|
|
3299
|
+
* Save a patch for a modified template file.
|
|
3300
|
+
* @returns The patch entry that was saved.
|
|
3301
|
+
*/
|
|
3302
|
+
function savePatch(opencodeDir, relativePath, templateContent, userContent) {
|
|
3303
|
+
const metadata = loadPatchMetadata(opencodeDir);
|
|
3304
|
+
const patchesDir = getPatchesDir(opencodeDir);
|
|
3305
|
+
if (!existsSync(patchesDir)) mkdirSync(patchesDir, { recursive: true });
|
|
3306
|
+
const patchContent = generatePatch(templateContent, userContent, relativePath);
|
|
3307
|
+
const patchFilename = pathToPatchFilename(relativePath);
|
|
3308
|
+
writeFileSync(join(patchesDir, patchFilename), patchContent);
|
|
3309
|
+
const entry = {
|
|
3310
|
+
originalHash: calculateHash(templateContent),
|
|
3311
|
+
currentHash: calculateHash(userContent),
|
|
3312
|
+
patchFile: patchFilename,
|
|
3313
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3314
|
+
templateVersion: getPackageVersion$1()
|
|
3315
|
+
};
|
|
3316
|
+
metadata.patches[relativePath] = entry;
|
|
3317
|
+
savePatchMetadata(opencodeDir, metadata);
|
|
3318
|
+
return entry;
|
|
3319
|
+
}
|
|
3320
|
+
/**
|
|
3321
|
+
* Remove a patch for a file.
|
|
3322
|
+
*/
|
|
3323
|
+
function removePatch(opencodeDir, relativePath) {
|
|
3324
|
+
const metadata = loadPatchMetadata(opencodeDir);
|
|
3325
|
+
const entry = metadata.patches[relativePath];
|
|
3326
|
+
if (!entry) return false;
|
|
3327
|
+
const patchPath = join(getPatchesDir(opencodeDir), entry.patchFile);
|
|
3328
|
+
if (existsSync(patchPath)) {
|
|
3329
|
+
const { rmSync } = __require("node:fs");
|
|
3330
|
+
rmSync(patchPath);
|
|
3331
|
+
}
|
|
3332
|
+
delete metadata.patches[relativePath];
|
|
3333
|
+
savePatchMetadata(opencodeDir, metadata);
|
|
3334
|
+
return true;
|
|
3335
|
+
}
|
|
3336
|
+
/**
|
|
3337
|
+
* Apply a patch to file content.
|
|
3338
|
+
* @returns The patched content, or null if patch failed.
|
|
3339
|
+
*/
|
|
3340
|
+
function applyPatch$1(originalContent, patchContent) {
|
|
3341
|
+
return applyPatch(originalContent, patchContent);
|
|
3342
|
+
}
|
|
3343
|
+
/**
|
|
3344
|
+
* Apply all saved patches after an upgrade.
|
|
3345
|
+
*/
|
|
3346
|
+
function applyAllPatches(opencodeDir) {
|
|
3347
|
+
const metadata = loadPatchMetadata(opencodeDir);
|
|
3348
|
+
const patchesDir = getPatchesDir(opencodeDir);
|
|
3349
|
+
const results = [];
|
|
3350
|
+
for (const [relativePath, entry] of Object.entries(metadata.patches)) {
|
|
3351
|
+
if (entry.disabled) {
|
|
3352
|
+
results.push({
|
|
3353
|
+
success: true,
|
|
3354
|
+
file: relativePath,
|
|
3355
|
+
message: "Skipped (disabled)"
|
|
3356
|
+
});
|
|
3357
|
+
continue;
|
|
3358
|
+
}
|
|
3359
|
+
const filePath = join(opencodeDir, relativePath);
|
|
3360
|
+
const patchPath = join(patchesDir, entry.patchFile);
|
|
3361
|
+
if (!existsSync(filePath)) {
|
|
3362
|
+
results.push({
|
|
3363
|
+
success: false,
|
|
3364
|
+
file: relativePath,
|
|
3365
|
+
message: "File no longer exists"
|
|
3366
|
+
});
|
|
3367
|
+
continue;
|
|
3368
|
+
}
|
|
3369
|
+
if (!existsSync(patchPath)) {
|
|
3370
|
+
results.push({
|
|
3371
|
+
success: false,
|
|
3372
|
+
file: relativePath,
|
|
3373
|
+
message: "Patch file missing"
|
|
3374
|
+
});
|
|
3375
|
+
continue;
|
|
3376
|
+
}
|
|
3377
|
+
const currentContent = readFileSync(filePath, "utf-8");
|
|
3378
|
+
const patchContent = readFileSync(patchPath, "utf-8");
|
|
3379
|
+
const patched = applyPatch$1(currentContent, patchContent);
|
|
3380
|
+
if (patched === false) {
|
|
3381
|
+
writeFileSync(`${patchPath}.rej`, patchContent);
|
|
3382
|
+
results.push({
|
|
3383
|
+
success: false,
|
|
3384
|
+
file: relativePath,
|
|
3385
|
+
message: "Patch conflict - saved to .rej file",
|
|
3386
|
+
conflict: true
|
|
3387
|
+
});
|
|
3388
|
+
} else {
|
|
3389
|
+
writeFileSync(filePath, patched);
|
|
3390
|
+
metadata.patches[relativePath].currentHash = calculateHash(patched);
|
|
3391
|
+
savePatchMetadata(opencodeDir, metadata);
|
|
3392
|
+
results.push({
|
|
3393
|
+
success: true,
|
|
3394
|
+
file: relativePath,
|
|
3395
|
+
message: "Patch applied successfully"
|
|
3396
|
+
});
|
|
3397
|
+
}
|
|
3398
|
+
}
|
|
3399
|
+
return results;
|
|
3400
|
+
}
|
|
3401
|
+
/**
|
|
3402
|
+
* Check the status of all patches.
|
|
3403
|
+
*/
|
|
3404
|
+
function checkPatchStatus(opencodeDir, templateRoot) {
|
|
3405
|
+
const metadata = loadPatchMetadata(opencodeDir);
|
|
3406
|
+
const statuses = [];
|
|
3407
|
+
for (const [relativePath, entry] of Object.entries(metadata.patches)) {
|
|
3408
|
+
if (!existsSync(join(opencodeDir, relativePath))) {
|
|
3409
|
+
statuses.push({
|
|
3410
|
+
relativePath,
|
|
3411
|
+
entry,
|
|
3412
|
+
status: "missing",
|
|
3413
|
+
message: "User file no longer exists"
|
|
3414
|
+
});
|
|
3415
|
+
continue;
|
|
3416
|
+
}
|
|
3417
|
+
if (templateRoot) {
|
|
3418
|
+
const templateFilePath = join(templateRoot, ".opencode", relativePath);
|
|
3419
|
+
if (existsSync(templateFilePath)) {
|
|
3420
|
+
const templateContent = readFileSync(templateFilePath, "utf-8");
|
|
3421
|
+
if (calculateHash(templateContent) !== entry.originalHash) {
|
|
3422
|
+
const patchPath = join(getPatchesDir(opencodeDir), entry.patchFile);
|
|
3423
|
+
if (existsSync(patchPath)) if (applyPatch$1(templateContent, readFileSync(patchPath, "utf-8")) === false) statuses.push({
|
|
3424
|
+
relativePath,
|
|
3425
|
+
entry,
|
|
3426
|
+
status: "conflict",
|
|
3427
|
+
message: "Template changed and patch cannot apply cleanly"
|
|
3428
|
+
});
|
|
3429
|
+
else statuses.push({
|
|
3430
|
+
relativePath,
|
|
3431
|
+
entry,
|
|
3432
|
+
status: "stale",
|
|
3433
|
+
message: "Template changed but patch can still apply"
|
|
3434
|
+
});
|
|
3435
|
+
else statuses.push({
|
|
3436
|
+
relativePath,
|
|
3437
|
+
entry,
|
|
3438
|
+
status: "missing",
|
|
3439
|
+
message: "Patch file missing"
|
|
3440
|
+
});
|
|
3441
|
+
continue;
|
|
3442
|
+
}
|
|
3443
|
+
}
|
|
3444
|
+
}
|
|
3445
|
+
statuses.push({
|
|
3446
|
+
relativePath,
|
|
3447
|
+
entry,
|
|
3448
|
+
status: "clean",
|
|
3449
|
+
message: "Patch is up to date"
|
|
3450
|
+
});
|
|
3451
|
+
}
|
|
3452
|
+
return statuses;
|
|
3453
|
+
}
|
|
3454
|
+
|
|
3916
3455
|
//#endregion
|
|
3917
3456
|
//#region src/commands/upgrade.ts
|
|
3918
3457
|
const PRESERVE_FILES = ["opencode.json", ".env"];
|
|
@@ -4532,7 +4071,7 @@ function listPatches(opencodeDir) {
|
|
|
4532
4071
|
showEmpty("patches", "ock patch create <file>");
|
|
4533
4072
|
return;
|
|
4534
4073
|
}
|
|
4535
|
-
const statuses = checkPatchStatus(opencodeDir, getTemplateRoot$
|
|
4074
|
+
const statuses = checkPatchStatus(opencodeDir, getTemplateRoot$1());
|
|
4536
4075
|
const statusMap = new Map(statuses.map((s) => [s.relativePath, s]));
|
|
4537
4076
|
p.intro(color.bgCyan(color.black(` ${entries.length} patch${entries.length === 1 ? "" : "es"} `)));
|
|
4538
4077
|
for (const [relativePath, entry] of entries) {
|
|
@@ -4566,7 +4105,7 @@ async function createPatch$1(opencodeDir) {
|
|
|
4566
4105
|
notFound("file", relativePath);
|
|
4567
4106
|
return;
|
|
4568
4107
|
}
|
|
4569
|
-
const templateRoot = getTemplateRoot$
|
|
4108
|
+
const templateRoot = getTemplateRoot$1();
|
|
4570
4109
|
if (!templateRoot) {
|
|
4571
4110
|
p.log.error("Cannot find template root — unable to compute diff");
|
|
4572
4111
|
p.log.info(color.dim("Make sure ock is installed correctly"));
|
|
@@ -5922,7 +5461,7 @@ async function ensureLicenseFor(commandName) {
|
|
|
5922
5461
|
cli.option("--verbose", "Enable verbose logging");
|
|
5923
5462
|
cli.option("--quiet", "Suppress all output");
|
|
5924
5463
|
cli.version(`${packageVersion}`);
|
|
5925
|
-
cli.command("init", "Initialize OpenCodeKit in current directory").option("--force", "Reinitialize even if already exists").option("--global", "Install to global OpenCode config (~/.config/opencode/)").option("
|
|
5464
|
+
cli.command("init", "Initialize OpenCodeKit in current directory").option("--force", "Reinitialize even if already exists").option("--global", "Install to global OpenCode config (~/.config/opencode/)").option("-y, --yes", "Skip prompts, use defaults (for CI)").action(async (options) => {
|
|
5926
5465
|
if (!await ensureLicenseFor("init")) return;
|
|
5927
5466
|
await initCommand(options);
|
|
5928
5467
|
});
|
|
@@ -5932,17 +5471,6 @@ cli.command("activate [key]", "Activate paid license key").action(async (key) =>
|
|
|
5932
5471
|
cli.command("license [action]", "Manage license (status, deactivate)").action(async (action) => {
|
|
5933
5472
|
await licenseCommand(action);
|
|
5934
5473
|
});
|
|
5935
|
-
cli.command("agent [action]", "Manage agents (list, add, view)").action(async (action) => {
|
|
5936
|
-
if (!action) {
|
|
5937
|
-
console.log("\nUsage: ock agent <action>\n");
|
|
5938
|
-
console.log("Actions:");
|
|
5939
|
-
console.log(" list List all agents");
|
|
5940
|
-
console.log(" add Create a new agent");
|
|
5941
|
-
console.log(" view View agent details\n");
|
|
5942
|
-
return;
|
|
5943
|
-
}
|
|
5944
|
-
await agentCommand(action);
|
|
5945
|
-
});
|
|
5946
5474
|
cli.command("command [action]", "Manage slash commands (list, create, show, delete)").action(async (action) => {
|
|
5947
5475
|
if (!action) {
|
|
5948
5476
|
console.log("\nUsage: ock command <action>\n");
|
|
@@ -5955,14 +5483,15 @@ cli.command("command [action]", "Manage slash commands (list, create, show, dele
|
|
|
5955
5483
|
}
|
|
5956
5484
|
await commandCommand(action);
|
|
5957
5485
|
});
|
|
5958
|
-
cli.command("agent [action]", "Manage agents (list, create,
|
|
5486
|
+
cli.command("agent [action]", "Manage agents (list, create, show, delete, edit)").action(async (action) => {
|
|
5959
5487
|
if (!action) {
|
|
5960
5488
|
console.log("\nUsage: ock agent <action>\n");
|
|
5961
5489
|
console.log("Actions:");
|
|
5962
5490
|
console.log(" list List all agents");
|
|
5963
5491
|
console.log(" create Create a new agent");
|
|
5964
|
-
console.log("
|
|
5965
|
-
console.log("
|
|
5492
|
+
console.log(" show View agent details");
|
|
5493
|
+
console.log(" delete Remove an agent");
|
|
5494
|
+
console.log(" edit Edit an agent\n");
|
|
5966
5495
|
return;
|
|
5967
5496
|
}
|
|
5968
5497
|
await agentCommand(action);
|