opencode-architect 0.3.0 → 0.4.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/README.md +78 -17
- package/agent-loader.ts +1 -1
- package/assets/agents/opencode-agent-designer.md +13 -53
- package/assets/agents/opencode-architect.md +49 -236
- package/assets/agents/opencode-command-crafter.md +9 -32
- package/assets/agents/opencode-extension-auditor.md +25 -36
- package/assets/agents/opencode-mcp-integrator.md +13 -23
- package/assets/agents/opencode-packager.md +31 -203
- package/assets/agents/opencode-plugin-engineer.md +16 -34
- package/assets/agents/opencode-publisher.md +27 -211
- package/assets/agents/opencode-skill-creator.md +15 -38
- package/assets/agents/opencode-tool-builder.md +13 -22
- package/assets/references/agents.md +1 -1
- package/assets/references/opencode-architect-oneshots.md +19 -58
- package/assets/references/skills.md +2 -4
- package/assets/templates/skill-structure.template.md +4 -4
- package/cli.ts +120 -0
- package/installer.ts +319 -0
- package/package.json +20 -1
- package/assets/templates/package-analysis.template.md +0 -60
package/cli.ts
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
import { parseArgs } from "node:util";
|
|
3
|
+
import { Installer, type Scope } from "./installer";
|
|
4
|
+
|
|
5
|
+
const VERSION = (JSON.parse(await Bun.file(`${import.meta.dirname}/package.json`).text()) as { version: string }).version;
|
|
6
|
+
|
|
7
|
+
async function main(): Promise<void> {
|
|
8
|
+
const { positionals, values } = parseArgs({
|
|
9
|
+
options: {
|
|
10
|
+
scope: { type: "string", short: "s" },
|
|
11
|
+
force: { type: "boolean", short: "f", default: false },
|
|
12
|
+
help: { type: "boolean", short: "h", default: false },
|
|
13
|
+
version: { type: "boolean", short: "v", default: false },
|
|
14
|
+
},
|
|
15
|
+
allowPositionals: true,
|
|
16
|
+
strict: true,
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
if (values.version) {
|
|
20
|
+
console.log(`opencode-architect v${VERSION}`);
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
if (values.help || positionals.length === 0) {
|
|
24
|
+
printHelp();
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const command = positionals[0];
|
|
29
|
+
const scopeInput = values.scope;
|
|
30
|
+
if (scopeInput !== undefined && scopeInput !== "local" && scopeInput !== "global") {
|
|
31
|
+
console.error(`Invalid scope: ${scopeInput}. Must be "local" or "global".`);
|
|
32
|
+
process.exit(1);
|
|
33
|
+
}
|
|
34
|
+
const scope: Scope = scopeInput === "global" ? "global" : "local";
|
|
35
|
+
const installer = new Installer();
|
|
36
|
+
|
|
37
|
+
try {
|
|
38
|
+
switch (command) {
|
|
39
|
+
case "install": {
|
|
40
|
+
const outcome = await installer.install(scope, { force: values.force, projectDir: process.cwd() });
|
|
41
|
+
if (outcome.action === "noop") {
|
|
42
|
+
console.log(`Already installed at version ${VERSION}; nothing to do.`);
|
|
43
|
+
} else {
|
|
44
|
+
console.log(`${outcome.action === "installed" ? "Installed" : "Upgraded"} (${scope} scope):`);
|
|
45
|
+
console.log(` Agents: ${outcome.agentsDir}`);
|
|
46
|
+
console.log(` References: ${outcome.referencesDir}`);
|
|
47
|
+
console.log(` Templates: ${outcome.templatesDir}`);
|
|
48
|
+
}
|
|
49
|
+
if (outcome.pluginRemoved) {
|
|
50
|
+
console.log(" Removed the plugin entry from opencode.json (switched to copy install).");
|
|
51
|
+
}
|
|
52
|
+
if (outcome.skipped.length > 0) {
|
|
53
|
+
console.log(" Skipped locally modified files (re-run with --force to overwrite):");
|
|
54
|
+
for (const file of outcome.skipped) console.log(` ${file}`);
|
|
55
|
+
}
|
|
56
|
+
break;
|
|
57
|
+
}
|
|
58
|
+
case "uninstall": {
|
|
59
|
+
const outcome = await installer.uninstall(scope, process.cwd());
|
|
60
|
+
if (outcome.mode === "none") {
|
|
61
|
+
console.log(`Nothing to uninstall for the ${scope} scope.`);
|
|
62
|
+
break;
|
|
63
|
+
}
|
|
64
|
+
if (outcome.mode === "copy") {
|
|
65
|
+
console.log(`Uninstalled (${scope} scope):`);
|
|
66
|
+
for (const file of outcome.removed) console.log(` Removed: ${file}`);
|
|
67
|
+
}
|
|
68
|
+
if (outcome.pluginRemoved) {
|
|
69
|
+
console.log(" Removed the plugin entry from opencode.json.");
|
|
70
|
+
}
|
|
71
|
+
break;
|
|
72
|
+
}
|
|
73
|
+
case "status": {
|
|
74
|
+
const outcome = await installer.status(scope, process.cwd());
|
|
75
|
+
const version = outcome.version ?? "-";
|
|
76
|
+
console.log(`${scope} scope: mode=${outcome.mode} version=${version}`);
|
|
77
|
+
break;
|
|
78
|
+
}
|
|
79
|
+
default:
|
|
80
|
+
console.error(`Unknown command: ${command}`);
|
|
81
|
+
printHelp();
|
|
82
|
+
process.exit(1);
|
|
83
|
+
}
|
|
84
|
+
} catch (error) {
|
|
85
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
86
|
+
console.error(`Error: ${message}`);
|
|
87
|
+
process.exit(1);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function printHelp(): void {
|
|
92
|
+
console.log(`
|
|
93
|
+
opencode-architect v${VERSION}
|
|
94
|
+
|
|
95
|
+
Installs the opencode-architect agent suite by copying agents into the scope
|
|
96
|
+
base's agents/, references into opencode-architect/references/, and templates
|
|
97
|
+
into opencode-architect/templates/, rewriting relative reference paths to
|
|
98
|
+
absolute paths at install time. Copy install and plugin install are mutually
|
|
99
|
+
exclusive per scope.
|
|
100
|
+
|
|
101
|
+
Commands:
|
|
102
|
+
install Copy agents, references, and templates into the scope base
|
|
103
|
+
uninstall Remove exactly the files a copy install wrote (or the plugin entry)
|
|
104
|
+
status Show install mode and version for a scope
|
|
105
|
+
|
|
106
|
+
Options:
|
|
107
|
+
-s, --scope <scope> "local" (project .opencode/) or "global" (~/.config/opencode/); default local
|
|
108
|
+
-f, --force install: remove an existing plugin entry and overwrite locally modified files
|
|
109
|
+
-h, --help Show this help message
|
|
110
|
+
-v, --version Show version
|
|
111
|
+
|
|
112
|
+
Examples:
|
|
113
|
+
opencode-architect install
|
|
114
|
+
opencode-architect install --scope global
|
|
115
|
+
opencode-architect uninstall
|
|
116
|
+
opencode-architect status
|
|
117
|
+
`);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
main();
|
package/installer.ts
ADDED
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { copyFile, exists, mkdir, readdir, readFile, rm, rmdir, writeFile } from "node:fs/promises";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { AGENT_FILENAMES, RELATIVE_REFERENCE_REGEX } from "./agent-loader";
|
|
6
|
+
|
|
7
|
+
export type Scope = "local" | "global";
|
|
8
|
+
export type InstallMode = "none" | "copy" | "plugin";
|
|
9
|
+
export type InstallAction = "installed" | "upgraded" | "noop";
|
|
10
|
+
|
|
11
|
+
export interface ManifestHashEntry {
|
|
12
|
+
path: string;
|
|
13
|
+
hash: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface Manifest {
|
|
17
|
+
version: string;
|
|
18
|
+
agentFiles: string[];
|
|
19
|
+
referencesDir: string;
|
|
20
|
+
templatesDir: string;
|
|
21
|
+
hashes: ManifestHashEntry[];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface InstallOptions {
|
|
25
|
+
force: boolean;
|
|
26
|
+
projectDir: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface InstallOutcome {
|
|
30
|
+
action: InstallAction;
|
|
31
|
+
scope: Scope;
|
|
32
|
+
agentsDir: string;
|
|
33
|
+
referencesDir: string;
|
|
34
|
+
templatesDir: string;
|
|
35
|
+
manifestPath: string;
|
|
36
|
+
copied: string[];
|
|
37
|
+
skipped: string[];
|
|
38
|
+
overwritten: string[];
|
|
39
|
+
pluginRemoved: boolean;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface UninstallOutcome {
|
|
43
|
+
scope: Scope;
|
|
44
|
+
mode: InstallMode;
|
|
45
|
+
removed: string[];
|
|
46
|
+
pluginRemoved: boolean;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface StatusOutcome {
|
|
50
|
+
scope: Scope;
|
|
51
|
+
mode: InstallMode;
|
|
52
|
+
version: string | null;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const PACKAGE_NAME = "opencode-architect";
|
|
56
|
+
const MANIFEST_NAME = "opencode-architect.json";
|
|
57
|
+
const ASSETS_AGENTS_DIR = path.join(import.meta.dirname, "assets", "agents");
|
|
58
|
+
const ASSETS_REFERENCES_DIR = path.join(import.meta.dirname, "assets", "references");
|
|
59
|
+
const ASSETS_TEMPLATES_DIR = path.join(import.meta.dirname, "assets", "templates");
|
|
60
|
+
|
|
61
|
+
export class Installer {
|
|
62
|
+
public async install(scope: Scope, options: InstallOptions): Promise<InstallOutcome> {
|
|
63
|
+
const base = this.scopeBase(scope, options.projectDir);
|
|
64
|
+
const configPath = path.join(base, "opencode.json");
|
|
65
|
+
const manifestPath = path.join(base, MANIFEST_NAME);
|
|
66
|
+
const agentsDir = path.join(base, "agents");
|
|
67
|
+
const referencesDir = path.join(base, "opencode-architect", "references");
|
|
68
|
+
const templatesDir = path.join(base, "opencode-architect", "templates");
|
|
69
|
+
const version = await this.getPackageVersion();
|
|
70
|
+
|
|
71
|
+
let pluginRemoved = false;
|
|
72
|
+
if (await this.hasPluginEntry(configPath)) {
|
|
73
|
+
if (!options.force) {
|
|
74
|
+
throw new Error(
|
|
75
|
+
`The "${PACKAGE_NAME}" plugin entry in ${configPath} must be removed before a copy install ` +
|
|
76
|
+
`(copied agents would silently shadow the plugin's agents). Re-run with --force to remove ` +
|
|
77
|
+
`the entry and switch this scope to copy install.`,
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
await this.removePluginEntry(configPath);
|
|
81
|
+
pluginRemoved = true;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const existingManifest = await this.readManifest(manifestPath);
|
|
85
|
+
if (existingManifest !== null && existingManifest.version === version) {
|
|
86
|
+
return {
|
|
87
|
+
action: "noop",
|
|
88
|
+
scope,
|
|
89
|
+
agentsDir,
|
|
90
|
+
referencesDir,
|
|
91
|
+
templatesDir,
|
|
92
|
+
manifestPath,
|
|
93
|
+
copied: [],
|
|
94
|
+
skipped: [],
|
|
95
|
+
overwritten: [],
|
|
96
|
+
pluginRemoved,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const action: InstallAction = existingManifest === null ? "installed" : "upgraded";
|
|
101
|
+
const copied: string[] = [];
|
|
102
|
+
const skipped: string[] = [];
|
|
103
|
+
const overwritten: string[] = [];
|
|
104
|
+
const hashes: ManifestHashEntry[] = [];
|
|
105
|
+
|
|
106
|
+
await mkdir(agentsDir, { recursive: true });
|
|
107
|
+
await mkdir(referencesDir, { recursive: true });
|
|
108
|
+
await mkdir(templatesDir, { recursive: true });
|
|
109
|
+
|
|
110
|
+
for (const filename of AGENT_FILENAMES) {
|
|
111
|
+
const relativePath = path.join("agents", filename);
|
|
112
|
+
const { action: fileAction, priorHash } = await this.disposition(
|
|
113
|
+
existingManifest,
|
|
114
|
+
base,
|
|
115
|
+
relativePath,
|
|
116
|
+
options.force,
|
|
117
|
+
);
|
|
118
|
+
if (fileAction === "skip") {
|
|
119
|
+
skipped.push(relativePath);
|
|
120
|
+
hashes.push({ path: relativePath, hash: priorHash as string });
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
const source = await readFile(path.join(ASSETS_AGENTS_DIR, filename), "utf-8");
|
|
124
|
+
await writeFile(path.join(base, relativePath), rewriteReferencePaths(source, referencesDir, templatesDir));
|
|
125
|
+
hashes.push({ path: relativePath, hash: await this.sha256File(path.join(base, relativePath)) });
|
|
126
|
+
if (fileAction === "overwrite") overwritten.push(relativePath);
|
|
127
|
+
else copied.push(relativePath);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
for (const entry of await readdir(ASSETS_REFERENCES_DIR)) {
|
|
131
|
+
const relativePath = path.join("opencode-architect", "references", entry);
|
|
132
|
+
const { action: fileAction, priorHash } = await this.disposition(
|
|
133
|
+
existingManifest,
|
|
134
|
+
base,
|
|
135
|
+
relativePath,
|
|
136
|
+
options.force,
|
|
137
|
+
);
|
|
138
|
+
if (fileAction === "skip") {
|
|
139
|
+
skipped.push(relativePath);
|
|
140
|
+
hashes.push({ path: relativePath, hash: priorHash as string });
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
await copyFile(path.join(ASSETS_REFERENCES_DIR, entry), path.join(base, relativePath));
|
|
144
|
+
hashes.push({ path: relativePath, hash: await this.sha256File(path.join(base, relativePath)) });
|
|
145
|
+
if (fileAction === "overwrite") overwritten.push(relativePath);
|
|
146
|
+
else copied.push(relativePath);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
for (const entry of await readdir(ASSETS_TEMPLATES_DIR)) {
|
|
150
|
+
const relativePath = path.join("opencode-architect", "templates", entry);
|
|
151
|
+
const { action: fileAction, priorHash } = await this.disposition(
|
|
152
|
+
existingManifest,
|
|
153
|
+
base,
|
|
154
|
+
relativePath,
|
|
155
|
+
options.force,
|
|
156
|
+
);
|
|
157
|
+
if (fileAction === "skip") {
|
|
158
|
+
skipped.push(relativePath);
|
|
159
|
+
hashes.push({ path: relativePath, hash: priorHash as string });
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
await copyFile(path.join(ASSETS_TEMPLATES_DIR, entry), path.join(base, relativePath));
|
|
163
|
+
hashes.push({ path: relativePath, hash: await this.sha256File(path.join(base, relativePath)) });
|
|
164
|
+
if (fileAction === "overwrite") overwritten.push(relativePath);
|
|
165
|
+
else copied.push(relativePath);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const manifest: Manifest = { version, agentFiles: [...AGENT_FILENAMES], referencesDir, templatesDir, hashes };
|
|
169
|
+
await writeFile(manifestPath, JSON.stringify(manifest, null, 2) + "\n");
|
|
170
|
+
|
|
171
|
+
return { action, scope, agentsDir, referencesDir, templatesDir, manifestPath, copied, skipped, overwritten, pluginRemoved };
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
public async uninstall(scope: Scope, projectDir: string): Promise<UninstallOutcome> {
|
|
175
|
+
const base = this.scopeBase(scope, projectDir);
|
|
176
|
+
const configPath = path.join(base, "opencode.json");
|
|
177
|
+
const manifestPath = path.join(base, MANIFEST_NAME);
|
|
178
|
+
const manifest = await this.readManifest(manifestPath);
|
|
179
|
+
const hadPluginEntry = await this.hasPluginEntry(configPath);
|
|
180
|
+
const removed: string[] = [];
|
|
181
|
+
|
|
182
|
+
if (manifest !== null) {
|
|
183
|
+
for (const entry of manifest.hashes) {
|
|
184
|
+
const target = path.join(base, entry.path);
|
|
185
|
+
if (await exists(target)) {
|
|
186
|
+
await rm(target);
|
|
187
|
+
removed.push(target);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
await rm(manifestPath);
|
|
191
|
+
removed.push(manifestPath);
|
|
192
|
+
await this.removeIfEmpty(path.join(base, "opencode-architect", "templates"));
|
|
193
|
+
await this.removeIfEmpty(path.join(base, "opencode-architect", "references"));
|
|
194
|
+
await this.removeIfEmpty(path.join(base, "opencode-architect"));
|
|
195
|
+
await this.removeIfEmpty(path.join(base, "agents"));
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
let pluginRemoved = false;
|
|
199
|
+
if (hadPluginEntry) {
|
|
200
|
+
await this.removePluginEntry(configPath);
|
|
201
|
+
pluginRemoved = true;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const mode: InstallMode = manifest !== null ? "copy" : hadPluginEntry ? "plugin" : "none";
|
|
205
|
+
return { scope, mode, removed, pluginRemoved };
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
public async status(scope: Scope, projectDir: string): Promise<StatusOutcome> {
|
|
209
|
+
const base = this.scopeBase(scope, projectDir);
|
|
210
|
+
const manifest = await this.readManifest(path.join(base, MANIFEST_NAME));
|
|
211
|
+
|
|
212
|
+
if (manifest !== null) {
|
|
213
|
+
return { scope, mode: "copy", version: manifest.version };
|
|
214
|
+
}
|
|
215
|
+
if (await this.hasPluginEntry(path.join(base, "opencode.json"))) {
|
|
216
|
+
return { scope, mode: "plugin", version: null };
|
|
217
|
+
}
|
|
218
|
+
return { scope, mode: "none", version: null };
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
private async disposition(
|
|
222
|
+
manifest: Manifest | null,
|
|
223
|
+
base: string,
|
|
224
|
+
relativePath: string,
|
|
225
|
+
force: boolean,
|
|
226
|
+
): Promise<{ action: "copy" | "overwrite" | "skip"; priorHash: string | null }> {
|
|
227
|
+
if (manifest === null) return { action: "copy", priorHash: null };
|
|
228
|
+
const priorHash = manifest.hashes.find((entry) => entry.path === relativePath)?.hash ?? null;
|
|
229
|
+
if (priorHash === null) return { action: "copy", priorHash };
|
|
230
|
+
const installedPath = path.join(base, relativePath);
|
|
231
|
+
if (!(await exists(installedPath))) return { action: "copy", priorHash };
|
|
232
|
+
if ((await this.sha256File(installedPath)) === priorHash) return { action: "copy", priorHash };
|
|
233
|
+
return { action: force ? "overwrite" : "skip", priorHash };
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
private async sha256File(filePath: string): Promise<string> {
|
|
237
|
+
return createHash("sha256").update(await readFile(filePath)).digest("hex");
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
private scopeBase(scope: Scope, projectDir: string): string {
|
|
241
|
+
if (scope === "local") return path.join(projectDir, ".opencode");
|
|
242
|
+
const xdgConfigHome = process.env.XDG_CONFIG_HOME;
|
|
243
|
+
if (xdgConfigHome) return path.join(xdgConfigHome, "opencode");
|
|
244
|
+
return path.join(homedir(), ".config", "opencode");
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
private async readManifest(manifestPath: string): Promise<Manifest | null> {
|
|
248
|
+
try {
|
|
249
|
+
return JSON.parse(await readFile(manifestPath, "utf-8")) as Manifest;
|
|
250
|
+
} catch {
|
|
251
|
+
return null;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
private async hasPluginEntry(configPath: string): Promise<boolean> {
|
|
256
|
+
const plugins = await this.readPluginArray(configPath);
|
|
257
|
+
return plugins.some((name) => this.isPluginEntry(name));
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
private async removePluginEntry(configPath: string): Promise<void> {
|
|
261
|
+
const plugins = await this.readPluginArray(configPath);
|
|
262
|
+
const remaining = plugins.filter((name) => !this.isPluginEntry(name));
|
|
263
|
+
const config = await this.readConfig(configPath);
|
|
264
|
+
if (remaining.length === 0) delete config.plugin;
|
|
265
|
+
else config.plugin = remaining;
|
|
266
|
+
await mkdir(path.dirname(configPath), { recursive: true });
|
|
267
|
+
await writeFile(configPath, JSON.stringify(config, null, 2) + "\n");
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
private isPluginEntry(name: string): boolean {
|
|
271
|
+
return name === PACKAGE_NAME || name.startsWith(`${PACKAGE_NAME}@`);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
private async readPluginArray(configPath: string): Promise<string[]> {
|
|
275
|
+
const config = await this.readConfig(configPath);
|
|
276
|
+
const plugins = config.plugin;
|
|
277
|
+
return Array.isArray(plugins) ? plugins.filter((name): name is string => typeof name === "string") : [];
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
private async readConfig(configPath: string): Promise<Record<string, unknown>> {
|
|
281
|
+
try {
|
|
282
|
+
return JSON.parse(await readFile(configPath, "utf-8")) as Record<string, unknown>;
|
|
283
|
+
} catch {
|
|
284
|
+
return {};
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
private async getPackageVersion(): Promise<string> {
|
|
289
|
+
const content = await readFile(path.join(import.meta.dirname, "package.json"), "utf-8");
|
|
290
|
+
return (JSON.parse(content) as { version: string }).version;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
private async removeIfEmpty(directory: string): Promise<void> {
|
|
294
|
+
if (!(await exists(directory))) return;
|
|
295
|
+
const contents = await readdir(directory);
|
|
296
|
+
if (contents.length === 0) await rmdir(directory);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
export function rewriteReferencePaths(content: string, referencesDir: string, templatesDir: string): string {
|
|
301
|
+
return content.replace(
|
|
302
|
+
RELATIVE_REFERENCE_REGEX,
|
|
303
|
+
(token: string, relativePath: string): string => {
|
|
304
|
+
const normalized = relativePath.replaceAll("\\", "/");
|
|
305
|
+
const packagedPath = path.resolve(ASSETS_AGENTS_DIR, normalized);
|
|
306
|
+
const withinReferences = path.relative(ASSETS_REFERENCES_DIR, packagedPath);
|
|
307
|
+
if (!withinReferences.startsWith("..")) {
|
|
308
|
+
const installedPath = path.resolve(referencesDir, withinReferences).replaceAll("\\", "/");
|
|
309
|
+
return `\`${installedPath}\``;
|
|
310
|
+
}
|
|
311
|
+
const withinTemplates = path.relative(ASSETS_TEMPLATES_DIR, packagedPath);
|
|
312
|
+
if (!withinTemplates.startsWith("..")) {
|
|
313
|
+
const installedPath = path.resolve(templatesDir, withinTemplates).replaceAll("\\", "/");
|
|
314
|
+
return `\`${installedPath}\``;
|
|
315
|
+
}
|
|
316
|
+
return token;
|
|
317
|
+
},
|
|
318
|
+
);
|
|
319
|
+
}
|
package/package.json
CHANGED
|
@@ -1,9 +1,26 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-architect",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
|
+
"description": "OpenCode plugin and CLI with ten specialist agents for agent skills, slash commands, custom tools, plugins, and MCP server integration",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"opencode",
|
|
7
|
+
"opencode-plugin",
|
|
8
|
+
"opencode-agents",
|
|
9
|
+
"agent-skills",
|
|
10
|
+
"ai-agents",
|
|
11
|
+
"slash-commands",
|
|
12
|
+
"mcp",
|
|
13
|
+
"mcp-server",
|
|
14
|
+
"coding-agent",
|
|
15
|
+
"bun"
|
|
16
|
+
],
|
|
17
|
+
"license": "MIT",
|
|
4
18
|
"private": false,
|
|
5
19
|
"type": "module",
|
|
6
20
|
"module": "index.ts",
|
|
21
|
+
"bin": {
|
|
22
|
+
"opencode-architect": "cli.ts"
|
|
23
|
+
},
|
|
7
24
|
"repository": {
|
|
8
25
|
"type": "git",
|
|
9
26
|
"url": "git+https://github.com/Expert-Vision-Software/opencode-architect.git"
|
|
@@ -26,6 +43,8 @@
|
|
|
26
43
|
"files": [
|
|
27
44
|
"index.ts",
|
|
28
45
|
"agent-loader.ts",
|
|
46
|
+
"cli.ts",
|
|
47
|
+
"installer.ts",
|
|
29
48
|
"assets"
|
|
30
49
|
],
|
|
31
50
|
"dependencies": {
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
# Package Analysis Template
|
|
2
|
-
|
|
3
|
-
Use this template to document extension analysis before packaging.
|
|
4
|
-
|
|
5
|
-
## Source Information
|
|
6
|
-
|
|
7
|
-
- **Source Path**: [path to .opencode/ or existing package]
|
|
8
|
-
- **Source Type**: [project-local | existing-package]
|
|
9
|
-
- **Target Package Name**: opencode-[name]
|
|
10
|
-
|
|
11
|
-
## Extension Inventory
|
|
12
|
-
|
|
13
|
-
### Skills
|
|
14
|
-
| Name | Description | Dependencies | Issues |
|
|
15
|
-
| ---- | ----------- | ------------ | ------ |
|
|
16
|
-
| | | | |
|
|
17
|
-
|
|
18
|
-
### Commands
|
|
19
|
-
| Name | Description | Template Vars | Issues |
|
|
20
|
-
| ---- | ----------- | ------------- | ------ |
|
|
21
|
-
| | | | |
|
|
22
|
-
|
|
23
|
-
### Agents
|
|
24
|
-
| Name | Description | Mode | Issues |
|
|
25
|
-
| ---- | ----------- | ---- | ------ |
|
|
26
|
-
| | | | |
|
|
27
|
-
|
|
28
|
-
### Plugins
|
|
29
|
-
| File | Hooks Used | Merge Strategy | Issues |
|
|
30
|
-
| ---- | ---------- | -------------- | ------ |
|
|
31
|
-
| | | | |
|
|
32
|
-
|
|
33
|
-
### Tools
|
|
34
|
-
| File | Tool Name | Merge Strategy | Issues |
|
|
35
|
-
| ---- | --------- | -------------- | ------ |
|
|
36
|
-
| | | | |
|
|
37
|
-
|
|
38
|
-
## Dependencies
|
|
39
|
-
|
|
40
|
-
From `.opencode/package.json`:
|
|
41
|
-
- [list dependencies]
|
|
42
|
-
|
|
43
|
-
## Packaging Assessment
|
|
44
|
-
|
|
45
|
-
- **Complexity**: [simple | medium | complex]
|
|
46
|
-
- **Requires Guidance**: [yes | no]
|
|
47
|
-
- **Guidance Needed For**: [plugins | tools | both | none]
|
|
48
|
-
|
|
49
|
-
## Recommendations
|
|
50
|
-
|
|
51
|
-
1. [recommendation 1]
|
|
52
|
-
2. [recommendation 2]
|
|
53
|
-
|
|
54
|
-
## Merge Decisions (if applicable)
|
|
55
|
-
|
|
56
|
-
### Plugin Merge
|
|
57
|
-
- [decision and rationale]
|
|
58
|
-
|
|
59
|
-
### Tool Merge
|
|
60
|
-
- [decision and rationale]
|