@wpro-eng/opencode-config 1.0.0 → 1.1.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/README.md +9 -49
- package/agent/aragorn.md +304 -0
- package/agent/celebrimbor.md +52 -0
- package/agent/elrond.md +88 -0
- package/agent/galadriel.md +28 -0
- package/agent/gandalf.md +51 -0
- package/agent/legolas.md +64 -0
- package/agent/radagast.md +51 -0
- package/agent/samwise.md +42 -0
- package/agent/treebeard.md +39 -0
- package/command/continue.md +9 -0
- package/command/diagnostics.md +38 -0
- package/command/doctor.md +9 -0
- package/command/example.md +9 -0
- package/command/look-at.md +11 -0
- package/command/stop.md +9 -0
- package/command/task.md +11 -0
- package/command/tasks.md +9 -0
- package/command/test-orchestration.md +42 -0
- package/command/wpromote-list.md +45 -0
- package/command/wpromote-status.md +23 -0
- package/dist/index.js +60 -391
- package/instruction/getting-started.md +24 -0
- package/instruction/orchestration-runtime.md +79 -0
- package/instruction/team-conventions.md +17 -0
- package/manifest.json +8 -0
- package/mcp/chrome-devtools/mcp.json +4 -0
- package/mcp/context7/mcp.json +4 -0
- package/mcp/exa/mcp.json +4 -0
- package/package.json +10 -5
- package/plugin/wpromote-look-at.ts +33 -0
- package/plugin/wpromote-orchestration.ts +1385 -0
- package/skill/example/SKILL.md +18 -0
- package/skill/orchestration-core/SKILL.md +29 -0
- package/skill/readme-editor/SKILL.md +529 -0
package/dist/index.js
CHANGED
|
@@ -4,25 +4,43 @@ var __getProtoOf = Object.getPrototypeOf;
|
|
|
4
4
|
var __defProp = Object.defineProperty;
|
|
5
5
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
6
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
function __accessProp(key) {
|
|
8
|
+
return this[key];
|
|
9
|
+
}
|
|
10
|
+
var __toESMCache_node;
|
|
11
|
+
var __toESMCache_esm;
|
|
7
12
|
var __toESM = (mod, isNodeMode, target) => {
|
|
13
|
+
var canCache = mod != null && typeof mod === "object";
|
|
14
|
+
if (canCache) {
|
|
15
|
+
var cache = isNodeMode ? __toESMCache_node ??= new WeakMap : __toESMCache_esm ??= new WeakMap;
|
|
16
|
+
var cached = cache.get(mod);
|
|
17
|
+
if (cached)
|
|
18
|
+
return cached;
|
|
19
|
+
}
|
|
8
20
|
target = mod != null ? __create(__getProtoOf(mod)) : {};
|
|
9
21
|
const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
|
|
10
22
|
for (let key of __getOwnPropNames(mod))
|
|
11
23
|
if (!__hasOwnProp.call(to, key))
|
|
12
24
|
__defProp(to, key, {
|
|
13
|
-
get: (
|
|
25
|
+
get: __accessProp.bind(mod, key),
|
|
14
26
|
enumerable: true
|
|
15
27
|
});
|
|
28
|
+
if (canCache)
|
|
29
|
+
cache.set(mod, to);
|
|
16
30
|
return to;
|
|
17
31
|
};
|
|
18
32
|
var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
|
|
33
|
+
var __returnValue = (v) => v;
|
|
34
|
+
function __exportSetter(name, newValue) {
|
|
35
|
+
this[name] = __returnValue.bind(null, newValue);
|
|
36
|
+
}
|
|
19
37
|
var __export = (target, all) => {
|
|
20
38
|
for (var name in all)
|
|
21
39
|
__defProp(target, name, {
|
|
22
40
|
get: all[name],
|
|
23
41
|
enumerable: true,
|
|
24
42
|
configurable: true,
|
|
25
|
-
set: (
|
|
43
|
+
set: __exportSetter.bind(all, name)
|
|
26
44
|
});
|
|
27
45
|
};
|
|
28
46
|
var __require = import.meta.require;
|
|
@@ -7476,7 +7494,6 @@ var NEVER = INVALID;
|
|
|
7476
7494
|
// src/config.ts
|
|
7477
7495
|
import { existsSync, readFileSync } from "fs";
|
|
7478
7496
|
import { dirname, join as join2 } from "path";
|
|
7479
|
-
import { homedir as homedir2 } from "os";
|
|
7480
7497
|
|
|
7481
7498
|
// node_modules/jsonc-parser/lib/esm/impl/scanner.js
|
|
7482
7499
|
function createScanner(text, ignoreTrivia = false) {
|
|
@@ -8283,7 +8300,6 @@ var ParseErrorCode;
|
|
|
8283
8300
|
})(ParseErrorCode || (ParseErrorCode = {}));
|
|
8284
8301
|
|
|
8285
8302
|
// src/constants.ts
|
|
8286
|
-
var TEAM_REPO_URL = "git@github.com:wpromote/opencode-config.git";
|
|
8287
8303
|
var REPO_SHORT_NAME = "opencode-config";
|
|
8288
8304
|
var CONFIG_FILENAMES = ["wpromote.json", "wpromote.jsonc"];
|
|
8289
8305
|
|
|
@@ -8329,7 +8345,6 @@ function logWarn(message) {
|
|
|
8329
8345
|
// src/config.ts
|
|
8330
8346
|
var UserConfigSchema = exports_external.object({
|
|
8331
8347
|
disable: exports_external.array(exports_external.string()).default([]),
|
|
8332
|
-
ref: exports_external.string().optional(),
|
|
8333
8348
|
installMethod: exports_external.enum(["link", "copy"]).default("link"),
|
|
8334
8349
|
dryRun: exports_external.boolean().default(false),
|
|
8335
8350
|
orchestration: exports_external.object({
|
|
@@ -8390,7 +8405,6 @@ var UserConfigSchema = exports_external.object({
|
|
|
8390
8405
|
}).strict();
|
|
8391
8406
|
var DEFAULT_CONFIG = {
|
|
8392
8407
|
disable: [],
|
|
8393
|
-
ref: undefined,
|
|
8394
8408
|
installMethod: "link",
|
|
8395
8409
|
dryRun: false,
|
|
8396
8410
|
orchestration: {
|
|
@@ -8445,7 +8459,8 @@ var DEFAULT_CONFIG = {
|
|
|
8445
8459
|
};
|
|
8446
8460
|
function getConfigPaths() {
|
|
8447
8461
|
const projectDir = join2(process.cwd(), ".opencode");
|
|
8448
|
-
const
|
|
8462
|
+
const home = process.env.HOME || process.env.USERPROFILE || "~";
|
|
8463
|
+
const userDir = join2(home, ".config", "opencode");
|
|
8449
8464
|
return [
|
|
8450
8465
|
...CONFIG_FILENAMES.map((filename) => join2(projectDir, filename)),
|
|
8451
8466
|
...CONFIG_FILENAMES.map((filename) => join2(userDir, filename))
|
|
@@ -8485,9 +8500,8 @@ function loadConfigWithLocation() {
|
|
|
8485
8500
|
return { config: DEFAULT_CONFIG, configDir: null };
|
|
8486
8501
|
}
|
|
8487
8502
|
|
|
8488
|
-
// src/
|
|
8503
|
+
// src/assets.ts
|
|
8489
8504
|
var import_gray_matter = __toESM(require_gray_matter(), 1);
|
|
8490
|
-
var {$ } = globalThis.Bun;
|
|
8491
8505
|
import * as path from "path";
|
|
8492
8506
|
import * as fs from "fs";
|
|
8493
8507
|
|
|
@@ -8610,201 +8624,27 @@ function discoverInstructions(repoPath) {
|
|
|
8610
8624
|
return instructions;
|
|
8611
8625
|
}
|
|
8612
8626
|
|
|
8613
|
-
// src/
|
|
8614
|
-
function getRepoId(url) {
|
|
8615
|
-
const sshMatch = url.match(/^git@([^:]+):(.+?)(\.git)?$/);
|
|
8616
|
-
if (sshMatch) {
|
|
8617
|
-
const host = sshMatch[1];
|
|
8618
|
-
const p = sshMatch[2].replace(/\//g, "-");
|
|
8619
|
-
return `${host}-${p}`;
|
|
8620
|
-
}
|
|
8621
|
-
const httpsMatch = url.match(/^https?:\/\/([^/]+)\/(.+?)(\.git)?$/);
|
|
8622
|
-
if (httpsMatch) {
|
|
8623
|
-
const host = httpsMatch[1];
|
|
8624
|
-
const p = httpsMatch[2].replace(/\//g, "-");
|
|
8625
|
-
return `${host}-${p}`;
|
|
8626
|
-
}
|
|
8627
|
-
return url.replace(/[^a-zA-Z0-9-]/g, "-").replace(/-+/g, "-").slice(0, 100);
|
|
8628
|
-
}
|
|
8629
|
-
|
|
8630
|
-
// src/git.ts
|
|
8631
|
-
var DEFAULT_RETRY_CONFIG = {
|
|
8632
|
-
maxRetries: 3,
|
|
8633
|
-
initialDelayMs: 1000,
|
|
8634
|
-
maxDelayMs: 30000,
|
|
8635
|
-
backoffMultiplier: 2
|
|
8636
|
-
};
|
|
8637
|
-
function sleep(ms) {
|
|
8638
|
-
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
8639
|
-
}
|
|
8640
|
-
async function withRetry(operation, operationName, config = DEFAULT_RETRY_CONFIG) {
|
|
8641
|
-
let lastError;
|
|
8642
|
-
let delay = config.initialDelayMs;
|
|
8643
|
-
for (let attempt = 0;attempt <= config.maxRetries; attempt++) {
|
|
8644
|
-
try {
|
|
8645
|
-
return await operation();
|
|
8646
|
-
} catch (error) {
|
|
8647
|
-
lastError = error;
|
|
8648
|
-
const isNetworkError = isNetworkRelatedError(lastError);
|
|
8649
|
-
if (!isNetworkError) {
|
|
8650
|
-
throw lastError;
|
|
8651
|
-
}
|
|
8652
|
-
if (attempt >= config.maxRetries) {
|
|
8653
|
-
logError(`${operationName} failed after ${config.maxRetries + 1} attempts: ${lastError.message}`);
|
|
8654
|
-
throw lastError;
|
|
8655
|
-
}
|
|
8656
|
-
logWarn(`${operationName} failed (attempt ${attempt + 1}/${config.maxRetries + 1}), retrying in ${delay}ms: ${lastError.message}`);
|
|
8657
|
-
await sleep(delay);
|
|
8658
|
-
delay = Math.min(delay * config.backoffMultiplier, config.maxDelayMs);
|
|
8659
|
-
}
|
|
8660
|
-
}
|
|
8661
|
-
throw lastError || new Error(`${operationName} failed`);
|
|
8662
|
-
}
|
|
8663
|
-
function isNetworkRelatedError(error) {
|
|
8664
|
-
const message = error.message.toLowerCase();
|
|
8665
|
-
const networkPatterns = [
|
|
8666
|
-
"could not resolve host",
|
|
8667
|
-
"connection refused",
|
|
8668
|
-
"connection timed out",
|
|
8669
|
-
"connection reset",
|
|
8670
|
-
"network is unreachable",
|
|
8671
|
-
"no route to host",
|
|
8672
|
-
"temporary failure in name resolution",
|
|
8673
|
-
"ssl",
|
|
8674
|
-
"tls",
|
|
8675
|
-
"certificate",
|
|
8676
|
-
"handshake",
|
|
8677
|
-
"unable to access",
|
|
8678
|
-
"couldn't connect",
|
|
8679
|
-
"failed to connect",
|
|
8680
|
-
"ssh_exchange_identification",
|
|
8681
|
-
"kex_exchange_identification",
|
|
8682
|
-
"connection closed by remote host",
|
|
8683
|
-
"early eof",
|
|
8684
|
-
"unexpected disconnect",
|
|
8685
|
-
"remote end hung up",
|
|
8686
|
-
"the remote end hung up unexpectedly"
|
|
8687
|
-
];
|
|
8688
|
-
return networkPatterns.some((pattern) => message.includes(pattern));
|
|
8689
|
-
}
|
|
8690
|
-
var CACHE_BASE = path.join(process.env.HOME || "~", ".cache", "opencode", "wpromote-config", "repos");
|
|
8627
|
+
// src/assets.ts
|
|
8691
8628
|
var DISCOVERY_LIMITS = {
|
|
8692
8629
|
maxFiles: 100,
|
|
8693
8630
|
maxFileSize: 256 * 1024,
|
|
8694
8631
|
maxDepth: 10
|
|
8695
8632
|
};
|
|
8696
|
-
function
|
|
8697
|
-
|
|
8698
|
-
|
|
8699
|
-
|
|
8700
|
-
|
|
8701
|
-
|
|
8702
|
-
|
|
8703
|
-
fs.mkdirSync(path.dirname(repoPath), { recursive: true });
|
|
8704
|
-
await withRetry(async () => {
|
|
8705
|
-
const result = await $`git clone ${url} ${repoPath}`.quiet().nothrow();
|
|
8706
|
-
if (result.exitCode !== 0) {
|
|
8707
|
-
const stderr = result.stderr.toString().trim();
|
|
8708
|
-
const stdout = result.stdout.toString().trim();
|
|
8709
|
-
const output = [stderr, stdout].filter(Boolean).join(`
|
|
8710
|
-
`) || `exit code ${result.exitCode}`;
|
|
8711
|
-
throw new Error(`git clone failed: ${output}`);
|
|
8712
|
-
}
|
|
8713
|
-
}, `git clone ${url}`, retryConfig);
|
|
8714
|
-
}
|
|
8715
|
-
async function fetchAndCheckout(repoPath, ref, retryConfig = DEFAULT_RETRY_CONFIG) {
|
|
8716
|
-
const beforeCommit = await $`git -C ${repoPath} rev-parse HEAD`.quiet();
|
|
8717
|
-
const beforeHash = beforeCommit.stdout.toString().trim();
|
|
8718
|
-
await withRetry(async () => {
|
|
8719
|
-
const fetchResult = await $`git -C ${repoPath} fetch --all --prune`.quiet();
|
|
8720
|
-
if (fetchResult.exitCode !== 0) {
|
|
8721
|
-
const stderr = fetchResult.stderr.toString().trim();
|
|
8722
|
-
const stdout = fetchResult.stdout.toString().trim();
|
|
8723
|
-
const output = [stderr, stdout].filter(Boolean).join(`
|
|
8724
|
-
`) || `exit code ${fetchResult.exitCode}`;
|
|
8725
|
-
throw new Error(`git fetch failed: ${output}`);
|
|
8726
|
-
}
|
|
8727
|
-
}, `git fetch (${repoPath})`, retryConfig);
|
|
8728
|
-
const checkoutResult = await $`git -C ${repoPath} checkout ${ref}`.quiet().nothrow();
|
|
8729
|
-
if (checkoutResult.exitCode !== 0) {
|
|
8730
|
-
const stderr = checkoutResult.stderr.toString().trim();
|
|
8731
|
-
const stdout = checkoutResult.stdout.toString().trim();
|
|
8732
|
-
const output = [stderr, stdout].filter(Boolean).join(`
|
|
8733
|
-
`) || `exit code ${checkoutResult.exitCode}`;
|
|
8734
|
-
throw new Error(`git checkout ${ref} failed: ${output}`);
|
|
8735
|
-
}
|
|
8736
|
-
const isBranch = await $`git -C ${repoPath} symbolic-ref -q HEAD`.quiet().nothrow();
|
|
8737
|
-
if (isBranch.exitCode === 0) {
|
|
8738
|
-
await withRetry(async () => {
|
|
8739
|
-
const pullResult = await $`git -C ${repoPath} pull --ff-only`.quiet();
|
|
8740
|
-
if (pullResult.exitCode !== 0) {
|
|
8741
|
-
const stderr = pullResult.stderr.toString().trim();
|
|
8742
|
-
const stdout = pullResult.stdout.toString().trim();
|
|
8743
|
-
const output = [stderr, stdout].filter(Boolean).join(`
|
|
8744
|
-
`) || `exit code ${pullResult.exitCode}`;
|
|
8745
|
-
throw new Error(`git pull failed: ${output}`);
|
|
8746
|
-
}
|
|
8747
|
-
}, `git pull (${repoPath})`, retryConfig);
|
|
8748
|
-
}
|
|
8749
|
-
const afterCommit = await $`git -C ${repoPath} rev-parse HEAD`.quiet();
|
|
8750
|
-
const afterHash = afterCommit.stdout.toString().trim();
|
|
8751
|
-
return beforeHash !== afterHash;
|
|
8752
|
-
}
|
|
8753
|
-
async function getCurrentRef(repoPath) {
|
|
8754
|
-
const branchResult = await $`git -C ${repoPath} symbolic-ref --short HEAD`.quiet().nothrow();
|
|
8755
|
-
if (branchResult.exitCode === 0)
|
|
8756
|
-
return branchResult.stdout.toString().trim();
|
|
8757
|
-
const commitResult = await $`git -C ${repoPath} rev-parse --short HEAD`.quiet();
|
|
8758
|
-
return commitResult.stdout.toString().trim();
|
|
8759
|
-
}
|
|
8760
|
-
async function syncTeamRepository(ref) {
|
|
8761
|
-
const effectiveRef = ref ?? "main";
|
|
8762
|
-
const repoId = getRepoId(TEAM_REPO_URL);
|
|
8763
|
-
const repoPath = getRepoPath(TEAM_REPO_URL);
|
|
8764
|
-
const shortName = REPO_SHORT_NAME;
|
|
8765
|
-
let updated = false;
|
|
8766
|
-
let error;
|
|
8767
|
-
try {
|
|
8768
|
-
if (!isCloned(repoPath)) {
|
|
8769
|
-
if (fs.existsSync(repoPath)) {
|
|
8770
|
-
fs.rmSync(repoPath, { recursive: true, force: true });
|
|
8771
|
-
}
|
|
8772
|
-
await cloneRepo(TEAM_REPO_URL, repoPath);
|
|
8773
|
-
updated = true;
|
|
8774
|
-
}
|
|
8775
|
-
updated = await fetchAndCheckout(repoPath, effectiveRef) || updated;
|
|
8776
|
-
} catch (err) {
|
|
8777
|
-
error = err instanceof Error ? err.message : String(err);
|
|
8778
|
-
}
|
|
8779
|
-
let skills = [];
|
|
8780
|
-
let agents = [];
|
|
8781
|
-
let commands = [];
|
|
8782
|
-
let plugins = [];
|
|
8783
|
-
let instructions = [];
|
|
8784
|
-
let mcps = [];
|
|
8785
|
-
let currentRef = effectiveRef;
|
|
8786
|
-
if (isCloned(repoPath)) {
|
|
8787
|
-
currentRef = await getCurrentRef(repoPath);
|
|
8788
|
-
skills = await discoverSkills(repoPath);
|
|
8789
|
-
agents = await discoverAgents(repoPath);
|
|
8790
|
-
commands = await discoverCommands(repoPath);
|
|
8791
|
-
plugins = await discoverPlugins(repoPath, shortName);
|
|
8792
|
-
instructions = discoverInstructions(repoPath);
|
|
8793
|
-
mcps = await discoverMcps(repoPath);
|
|
8794
|
-
}
|
|
8633
|
+
async function discoverAssets(packageRoot) {
|
|
8634
|
+
const skills = await discoverSkills(packageRoot);
|
|
8635
|
+
const agents = await discoverAgents(packageRoot);
|
|
8636
|
+
const commands = await discoverCommands(packageRoot);
|
|
8637
|
+
const plugins = await discoverPlugins(packageRoot, REPO_SHORT_NAME);
|
|
8638
|
+
const instructions = discoverInstructions(packageRoot);
|
|
8639
|
+
const mcps = await discoverMcps(packageRoot);
|
|
8795
8640
|
return {
|
|
8796
|
-
|
|
8797
|
-
repoPath,
|
|
8798
|
-
shortName,
|
|
8799
|
-
ref: currentRef,
|
|
8641
|
+
shortName: REPO_SHORT_NAME,
|
|
8800
8642
|
skills,
|
|
8801
8643
|
agents,
|
|
8802
8644
|
commands,
|
|
8803
8645
|
plugins,
|
|
8804
8646
|
instructions,
|
|
8805
|
-
mcps
|
|
8806
|
-
updated,
|
|
8807
|
-
error
|
|
8647
|
+
mcps
|
|
8808
8648
|
};
|
|
8809
8649
|
}
|
|
8810
8650
|
async function discoverSkills(repoPath) {
|
|
@@ -9130,10 +8970,10 @@ async function discoverMcps(repoPath) {
|
|
|
9130
8970
|
// src/install.ts
|
|
9131
8971
|
import * as path3 from "path";
|
|
9132
8972
|
import * as fs3 from "fs";
|
|
9133
|
-
var {$: $
|
|
8973
|
+
var {$: $2 } = globalThis.Bun;
|
|
9134
8974
|
|
|
9135
8975
|
// src/copy.ts
|
|
9136
|
-
var {
|
|
8976
|
+
var {$ } = globalThis.Bun;
|
|
9137
8977
|
import * as fs2 from "fs";
|
|
9138
8978
|
import * as path2 from "path";
|
|
9139
8979
|
var rsyncAvailable = null;
|
|
@@ -9142,7 +8982,7 @@ async function detectRsync() {
|
|
|
9142
8982
|
return rsyncAvailable;
|
|
9143
8983
|
}
|
|
9144
8984
|
try {
|
|
9145
|
-
const result = await
|
|
8985
|
+
const result = await $`which rsync`.quiet();
|
|
9146
8986
|
rsyncAvailable = result.exitCode === 0;
|
|
9147
8987
|
} catch {
|
|
9148
8988
|
rsyncAvailable = false;
|
|
@@ -9157,7 +8997,7 @@ async function copyWithRsync(source, target) {
|
|
|
9157
8997
|
} else if (stat.isDirectory()) {}
|
|
9158
8998
|
}
|
|
9159
8999
|
fs2.mkdirSync(path2.dirname(target), { recursive: true });
|
|
9160
|
-
const result = await
|
|
9000
|
+
const result = await $`rsync -a --delete ${source}/ ${target}/`.quiet();
|
|
9161
9001
|
if (result.exitCode !== 0) {
|
|
9162
9002
|
throw new Error(`rsync failed: ${result.stderr.toString()}`);
|
|
9163
9003
|
}
|
|
@@ -9384,7 +9224,7 @@ function hasLocalConflict(skillName) {
|
|
|
9384
9224
|
}
|
|
9385
9225
|
async function findGitRoot(startPath) {
|
|
9386
9226
|
try {
|
|
9387
|
-
const result = await $
|
|
9227
|
+
const result = await $2`git -C ${startPath} rev-parse --show-toplevel`.quiet();
|
|
9388
9228
|
if (result.exitCode === 0) {
|
|
9389
9229
|
return result.stdout.toString().trim();
|
|
9390
9230
|
}
|
|
@@ -9432,9 +9272,9 @@ ${gitignoreEntry}
|
|
|
9432
9272
|
// src/plugin-install.ts
|
|
9433
9273
|
import * as fs4 from "fs";
|
|
9434
9274
|
import * as path4 from "path";
|
|
9435
|
-
import { homedir as
|
|
9436
|
-
var PLUGIN_DIR_PLURAL = path4.join(
|
|
9437
|
-
var PLUGIN_DIR_SINGULAR = path4.join(
|
|
9275
|
+
import { homedir as homedir2 } from "os";
|
|
9276
|
+
var PLUGIN_DIR_PLURAL = path4.join(homedir2(), ".config", "opencode", "plugins");
|
|
9277
|
+
var PLUGIN_DIR_SINGULAR = path4.join(homedir2(), ".config", "opencode", "plugin");
|
|
9438
9278
|
var REMOTE_PREFIX = "_remote_";
|
|
9439
9279
|
function removePathIfExists(targetPath) {
|
|
9440
9280
|
try {
|
|
@@ -9607,8 +9447,8 @@ function findBrokenUnmanagedPluginLinks(pluginDir) {
|
|
|
9607
9447
|
// src/mcp-install.ts
|
|
9608
9448
|
import * as fs5 from "fs";
|
|
9609
9449
|
import * as path5 from "path";
|
|
9610
|
-
import { homedir as
|
|
9611
|
-
var DEFAULT_MCP_DIR = path5.join(
|
|
9450
|
+
import { homedir as homedir3 } from "os";
|
|
9451
|
+
var DEFAULT_MCP_DIR = path5.join(homedir3(), ".config", "opencode", "mcp");
|
|
9612
9452
|
function getMcpPluginsSubdirForRepo(repoShortName) {
|
|
9613
9453
|
return path5.join("_plugins", repoShortName);
|
|
9614
9454
|
}
|
|
@@ -9808,131 +9648,12 @@ function createDisableMatcher(disable) {
|
|
|
9808
9648
|
};
|
|
9809
9649
|
}
|
|
9810
9650
|
|
|
9811
|
-
// src/
|
|
9812
|
-
import * as
|
|
9813
|
-
import * as path6 from "path";
|
|
9814
|
-
import * as os from "os";
|
|
9815
|
-
var DEFAULT_TIMEOUT_MS = 30000;
|
|
9816
|
-
var POLL_INTERVAL_MS = 500;
|
|
9817
|
-
var STALE_THRESHOLD_MS = 5 * 60 * 1000;
|
|
9818
|
-
function getLockPath() {
|
|
9819
|
-
return path6.join(getRepoPath(TEAM_REPO_URL), ".sync.lock");
|
|
9820
|
-
}
|
|
9821
|
-
function isProcessRunning(pid) {
|
|
9822
|
-
try {
|
|
9823
|
-
process.kill(pid, 0);
|
|
9824
|
-
return true;
|
|
9825
|
-
} catch (err) {
|
|
9826
|
-
if (err.code === "EPERM") {
|
|
9827
|
-
return true;
|
|
9828
|
-
}
|
|
9829
|
-
return false;
|
|
9830
|
-
}
|
|
9831
|
-
}
|
|
9832
|
-
function isLockStale(lockData) {
|
|
9833
|
-
if (lockData.hostname === os.hostname()) {
|
|
9834
|
-
if (!isProcessRunning(lockData.pid)) {
|
|
9835
|
-
logDebug(`Lock held by dead process ${lockData.pid}, considering stale`);
|
|
9836
|
-
return true;
|
|
9837
|
-
}
|
|
9838
|
-
}
|
|
9839
|
-
const age = Date.now() - lockData.timestamp;
|
|
9840
|
-
if (age > STALE_THRESHOLD_MS) {
|
|
9841
|
-
if (lockData.hostname !== os.hostname()) {
|
|
9842
|
-
logDebug(`Lock from remote host ${lockData.hostname} is ${Math.round(age / 1000)}s old, considering stale`);
|
|
9843
|
-
return true;
|
|
9844
|
-
}
|
|
9845
|
-
}
|
|
9846
|
-
return false;
|
|
9847
|
-
}
|
|
9848
|
-
function readLockData() {
|
|
9849
|
-
const lockPath = getLockPath();
|
|
9850
|
-
try {
|
|
9851
|
-
const content = fs6.readFileSync(lockPath, "utf8");
|
|
9852
|
-
const data = JSON.parse(content);
|
|
9853
|
-
if (typeof data.pid !== "number" || typeof data.hostname !== "string" || typeof data.timestamp !== "number") {
|
|
9854
|
-
logDebug("Invalid lockfile structure, ignoring");
|
|
9855
|
-
return;
|
|
9856
|
-
}
|
|
9857
|
-
return data;
|
|
9858
|
-
} catch {
|
|
9859
|
-
return;
|
|
9860
|
-
}
|
|
9861
|
-
}
|
|
9862
|
-
function removeLockFile() {
|
|
9863
|
-
const lockPath = getLockPath();
|
|
9864
|
-
try {
|
|
9865
|
-
fs6.unlinkSync(lockPath);
|
|
9866
|
-
} catch {}
|
|
9867
|
-
}
|
|
9868
|
-
function tryAcquireLock() {
|
|
9869
|
-
const lockPath = getLockPath();
|
|
9870
|
-
fs6.mkdirSync(path6.dirname(lockPath), { recursive: true });
|
|
9871
|
-
const existingLock = readLockData();
|
|
9872
|
-
if (existingLock) {
|
|
9873
|
-
if (isLockStale(existingLock)) {
|
|
9874
|
-
logDebug("Removing stale lock");
|
|
9875
|
-
removeLockFile();
|
|
9876
|
-
} else {
|
|
9877
|
-
return false;
|
|
9878
|
-
}
|
|
9879
|
-
}
|
|
9880
|
-
try {
|
|
9881
|
-
const data = {
|
|
9882
|
-
pid: process.pid,
|
|
9883
|
-
hostname: os.hostname(),
|
|
9884
|
-
timestamp: Date.now()
|
|
9885
|
-
};
|
|
9886
|
-
fs6.writeFileSync(lockPath, JSON.stringify(data, null, 2), { flag: "wx" });
|
|
9887
|
-
return true;
|
|
9888
|
-
} catch (err) {
|
|
9889
|
-
if (err.code === "EEXIST") {
|
|
9890
|
-
return false;
|
|
9891
|
-
}
|
|
9892
|
-
throw err;
|
|
9893
|
-
}
|
|
9894
|
-
}
|
|
9895
|
-
async function acquireLock(timeoutMs = DEFAULT_TIMEOUT_MS) {
|
|
9896
|
-
const startTime = Date.now();
|
|
9897
|
-
while (true) {
|
|
9898
|
-
if (tryAcquireLock()) {
|
|
9899
|
-
logDebug("Acquired sync lock");
|
|
9900
|
-
return { acquired: true };
|
|
9901
|
-
}
|
|
9902
|
-
const elapsed = Date.now() - startTime;
|
|
9903
|
-
if (elapsed >= timeoutMs) {
|
|
9904
|
-
const lockData = readLockData();
|
|
9905
|
-
const holder = lockData ? `pid ${lockData.pid} on ${lockData.hostname}` : "unknown process";
|
|
9906
|
-
return {
|
|
9907
|
-
acquired: false,
|
|
9908
|
-
error: `Failed to acquire sync lock after ${Math.round(elapsed / 1000)}s (held by ${holder})`
|
|
9909
|
-
};
|
|
9910
|
-
}
|
|
9911
|
-
await new Promise((resolve4) => setTimeout(resolve4, POLL_INTERVAL_MS));
|
|
9912
|
-
}
|
|
9913
|
-
}
|
|
9914
|
-
function releaseLock() {
|
|
9915
|
-
const lockData = readLockData();
|
|
9916
|
-
if (lockData && lockData.pid === process.pid && lockData.hostname === os.hostname()) {
|
|
9917
|
-
removeLockFile();
|
|
9918
|
-
logDebug("Released sync lock");
|
|
9919
|
-
}
|
|
9920
|
-
}
|
|
9921
|
-
async function withLock(fn, timeoutMs = DEFAULT_TIMEOUT_MS) {
|
|
9922
|
-
const lockResult = await acquireLock(timeoutMs);
|
|
9923
|
-
if (!lockResult.acquired) {
|
|
9924
|
-
throw new Error(lockResult.error);
|
|
9925
|
-
}
|
|
9926
|
-
try {
|
|
9927
|
-
return await fn();
|
|
9928
|
-
} finally {
|
|
9929
|
-
releaseLock();
|
|
9930
|
-
}
|
|
9931
|
-
}
|
|
9651
|
+
// src/index.ts
|
|
9652
|
+
import * as path8 from "path";
|
|
9932
9653
|
|
|
9933
9654
|
// src/mcp-schema.ts
|
|
9934
|
-
import * as
|
|
9935
|
-
import * as
|
|
9655
|
+
import * as fs6 from "fs";
|
|
9656
|
+
import * as path6 from "path";
|
|
9936
9657
|
function isNonEmptyString(value) {
|
|
9937
9658
|
return typeof value === "string" && value.trim().length > 0;
|
|
9938
9659
|
}
|
|
@@ -10080,7 +9801,7 @@ function validateMcpConfig(obj) {
|
|
|
10080
9801
|
}
|
|
10081
9802
|
function validateMcpFile(mcpJsonPath) {
|
|
10082
9803
|
try {
|
|
10083
|
-
const content =
|
|
9804
|
+
const content = fs6.readFileSync(mcpJsonPath, "utf-8");
|
|
10084
9805
|
const parsed = JSON.parse(content);
|
|
10085
9806
|
return validateMcpConfig(parsed);
|
|
10086
9807
|
} catch (err) {
|
|
@@ -10099,7 +9820,7 @@ function validateMcpFile(mcpJsonPath) {
|
|
|
10099
9820
|
}
|
|
10100
9821
|
}
|
|
10101
9822
|
function validateMcpInfo(mcp) {
|
|
10102
|
-
const mcpJsonPath = mcp.path.endsWith(".json") ? mcp.path :
|
|
9823
|
+
const mcpJsonPath = mcp.path.endsWith(".json") ? mcp.path : path6.join(mcp.path, "mcp.json");
|
|
10103
9824
|
const result = validateMcpFile(mcpJsonPath);
|
|
10104
9825
|
for (const warning of result.warnings) {
|
|
10105
9826
|
logWarn(`MCP '${mcp.name}': ${warning}`);
|
|
@@ -10117,32 +9838,22 @@ function filterValidMcps(mcps) {
|
|
|
10117
9838
|
}
|
|
10118
9839
|
|
|
10119
9840
|
// src/status.ts
|
|
10120
|
-
import * as
|
|
10121
|
-
import * as
|
|
10122
|
-
var STATUS_BASE =
|
|
9841
|
+
import * as fs7 from "fs";
|
|
9842
|
+
import * as path7 from "path";
|
|
9843
|
+
var STATUS_BASE = path7.join(process.env.HOME || "~", ".cache", "opencode", "wpromote-config");
|
|
10123
9844
|
function getStatusFilePath() {
|
|
10124
|
-
return
|
|
10125
|
-
}
|
|
10126
|
-
function readSyncStatus() {
|
|
10127
|
-
const statusPath = getStatusFilePath();
|
|
10128
|
-
try {
|
|
10129
|
-
const content = fs8.readFileSync(statusPath, "utf-8");
|
|
10130
|
-
return JSON.parse(content);
|
|
10131
|
-
} catch {
|
|
10132
|
-
return null;
|
|
10133
|
-
}
|
|
9845
|
+
return path7.join(STATUS_BASE, `${REPO_SHORT_NAME}.sync-status.json`);
|
|
10134
9846
|
}
|
|
10135
9847
|
function writeSyncStatus(status) {
|
|
10136
9848
|
const statusPath = getStatusFilePath();
|
|
10137
|
-
const dir =
|
|
10138
|
-
|
|
10139
|
-
|
|
9849
|
+
const dir = path7.dirname(statusPath);
|
|
9850
|
+
fs7.mkdirSync(dir, { recursive: true });
|
|
9851
|
+
fs7.writeFileSync(statusPath, JSON.stringify(status, null, 2));
|
|
10140
9852
|
logDebug(`Wrote sync status to ${statusPath}`);
|
|
10141
9853
|
}
|
|
10142
9854
|
function updateSyncStatus(data) {
|
|
10143
9855
|
const status = {
|
|
10144
9856
|
lastSyncTime: new Date().toISOString(),
|
|
10145
|
-
currentRef: data.currentRef,
|
|
10146
9857
|
installedSkills: data.installedSkills,
|
|
10147
9858
|
installedPlugins: data.installedPlugins,
|
|
10148
9859
|
installedMcps: data.installedMcps,
|
|
@@ -10156,43 +9867,16 @@ function updateSyncStatus(data) {
|
|
|
10156
9867
|
};
|
|
10157
9868
|
writeSyncStatus(status);
|
|
10158
9869
|
}
|
|
10159
|
-
function recordSyncError(error) {
|
|
10160
|
-
const existing = readSyncStatus();
|
|
10161
|
-
const status = existing ?? {
|
|
10162
|
-
lastSyncTime: null,
|
|
10163
|
-
currentRef: null,
|
|
10164
|
-
installedSkills: 0,
|
|
10165
|
-
installedPlugins: 0,
|
|
10166
|
-
installedMcps: 0,
|
|
10167
|
-
remoteAgents: 0,
|
|
10168
|
-
remoteCommands: 0,
|
|
10169
|
-
remoteInstructions: 0,
|
|
10170
|
-
skippedConflicts: 0,
|
|
10171
|
-
disabledAssets: 0,
|
|
10172
|
-
lastError: null,
|
|
10173
|
-
pluginsChanged: false
|
|
10174
|
-
};
|
|
10175
|
-
status.lastError = error;
|
|
10176
|
-
writeSyncStatus(status);
|
|
10177
|
-
}
|
|
10178
9870
|
|
|
10179
9871
|
// src/index.ts
|
|
9872
|
+
var PACKAGE_ROOT = path8.resolve(import.meta.dir, "..");
|
|
10180
9873
|
var initialized = false;
|
|
10181
9874
|
var REQUIRED_RUNTIME_PLUGINS = ["@tarquinen/opencode-dcp@latest"];
|
|
10182
9875
|
var DEFAULT_OPENCODE_AGENTS_TO_DISABLE = ["build", "plan"];
|
|
10183
9876
|
async function performSync(config) {
|
|
10184
9877
|
const disableMatcher = createDisableMatcher(config.disable);
|
|
10185
9878
|
log(config.dryRun ? "[DRY-RUN] Analyzing team configuration..." : "Syncing team configuration...");
|
|
10186
|
-
const result = await
|
|
10187
|
-
const degradedSyncMode = Boolean(result.error);
|
|
10188
|
-
if (degradedSyncMode) {
|
|
10189
|
-
const syncError = result.error ?? "Unknown sync error";
|
|
10190
|
-
logError(`\u2717 Failed to sync team repository: ${syncError}`);
|
|
10191
|
-
if (!config.dryRun) {
|
|
10192
|
-
recordSyncError(syncError);
|
|
10193
|
-
}
|
|
10194
|
-
log("Continuing with cached assets in degraded mode");
|
|
10195
|
-
}
|
|
9879
|
+
const result = await discoverAssets(PACKAGE_ROOT);
|
|
10196
9880
|
if (!config.dryRun) {
|
|
10197
9881
|
const brokenUnmanagedLinks = findBrokenUnmanagedPluginLinks();
|
|
10198
9882
|
if (brokenUnmanagedLinks.length > 0) {
|
|
@@ -10366,20 +10050,6 @@ async function performSync(config) {
|
|
|
10366
10050
|
dryRun: dryRunReport
|
|
10367
10051
|
};
|
|
10368
10052
|
}
|
|
10369
|
-
if (degradedSyncMode) {
|
|
10370
|
-
return {
|
|
10371
|
-
result,
|
|
10372
|
-
skippedConflicts,
|
|
10373
|
-
totalSkills: 0,
|
|
10374
|
-
remoteAgents,
|
|
10375
|
-
remoteCommands,
|
|
10376
|
-
remoteMcps,
|
|
10377
|
-
remoteInstructions,
|
|
10378
|
-
pluginsChanged: false,
|
|
10379
|
-
totalPlugins: 0,
|
|
10380
|
-
totalMcps: 0
|
|
10381
|
-
};
|
|
10382
|
-
}
|
|
10383
10053
|
const filteredResult = { ...result, skills: skillsToInstall };
|
|
10384
10054
|
const installResults = await createInstallsForRepo(filteredResult, config.installMethod);
|
|
10385
10055
|
for (const sr of installResults) {
|
|
@@ -10390,7 +10060,7 @@ async function performSync(config) {
|
|
|
10390
10060
|
logError(`\u2717 Failed to install skill ${sr.skillName}: ${sr.error}`);
|
|
10391
10061
|
}
|
|
10392
10062
|
}
|
|
10393
|
-
log(`\u2713 ${result.shortName}
|
|
10063
|
+
log(`\u2713 ${result.shortName} - ${totalSkills} skills`);
|
|
10394
10064
|
const cleanup = cleanupStaleInstalls(currentSkills);
|
|
10395
10065
|
if (cleanup.removed.length > 0) {
|
|
10396
10066
|
log(`Cleaned up ${cleanup.removed.length} stale skill installs`);
|
|
@@ -10450,7 +10120,6 @@ async function performSync(config) {
|
|
|
10450
10120
|
}
|
|
10451
10121
|
const totalMcps = newMcpInstalls.size;
|
|
10452
10122
|
updateSyncStatus({
|
|
10453
|
-
currentRef: result.ref,
|
|
10454
10123
|
installedSkills: totalSkills,
|
|
10455
10124
|
installedPlugins: totalPlugins,
|
|
10456
10125
|
installedMcps: totalMcps,
|
|
@@ -10536,7 +10205,7 @@ var WpromoteConfigPlugin = async (ctx) => {
|
|
|
10536
10205
|
pluginsChanged,
|
|
10537
10206
|
totalPlugins,
|
|
10538
10207
|
totalMcps
|
|
10539
|
-
} = await
|
|
10208
|
+
} = await performSync(userConfig);
|
|
10540
10209
|
if (pluginsChanged) {
|
|
10541
10210
|
log("\u26A0 Plugin changes detected. Restart OpenCode to apply.");
|
|
10542
10211
|
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Getting Started with Wpromote OpenCode
|
|
2
|
+
|
|
3
|
+
Welcome to the Wpromote OpenCode configuration.
|
|
4
|
+
|
|
5
|
+
## Quick Start
|
|
6
|
+
|
|
7
|
+
1. Install the plugin
|
|
8
|
+
2. Restart OpenCode
|
|
9
|
+
3. All team configs will be synced automatically
|
|
10
|
+
|
|
11
|
+
## Configuration
|
|
12
|
+
|
|
13
|
+
Create `~/.config/opencode/wpromote.json` (or `~/.config/opencode/wpromote.jsonc`) to customize:
|
|
14
|
+
|
|
15
|
+
```json
|
|
16
|
+
{
|
|
17
|
+
"disable": ["skill:example"],
|
|
18
|
+
"installMethod": "link"
|
|
19
|
+
}
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Overriding Team Configs
|
|
23
|
+
|
|
24
|
+
To override a team skill/agent/command, create a local file with the same name in your personal config directory.
|