@wpro-eng/opencode-config 1.0.2 → 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 +40 -389
- 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
|
@@ -7494,7 +7494,6 @@ var NEVER = INVALID;
|
|
|
7494
7494
|
// src/config.ts
|
|
7495
7495
|
import { existsSync, readFileSync } from "fs";
|
|
7496
7496
|
import { dirname, join as join2 } from "path";
|
|
7497
|
-
import { homedir as homedir2 } from "os";
|
|
7498
7497
|
|
|
7499
7498
|
// node_modules/jsonc-parser/lib/esm/impl/scanner.js
|
|
7500
7499
|
function createScanner(text, ignoreTrivia = false) {
|
|
@@ -8301,7 +8300,6 @@ var ParseErrorCode;
|
|
|
8301
8300
|
})(ParseErrorCode || (ParseErrorCode = {}));
|
|
8302
8301
|
|
|
8303
8302
|
// src/constants.ts
|
|
8304
|
-
var TEAM_REPO_URL = "git@github.com:wpromote/opencode-config.git";
|
|
8305
8303
|
var REPO_SHORT_NAME = "opencode-config";
|
|
8306
8304
|
var CONFIG_FILENAMES = ["wpromote.json", "wpromote.jsonc"];
|
|
8307
8305
|
|
|
@@ -8347,7 +8345,6 @@ function logWarn(message) {
|
|
|
8347
8345
|
// src/config.ts
|
|
8348
8346
|
var UserConfigSchema = exports_external.object({
|
|
8349
8347
|
disable: exports_external.array(exports_external.string()).default([]),
|
|
8350
|
-
ref: exports_external.string().optional(),
|
|
8351
8348
|
installMethod: exports_external.enum(["link", "copy"]).default("link"),
|
|
8352
8349
|
dryRun: exports_external.boolean().default(false),
|
|
8353
8350
|
orchestration: exports_external.object({
|
|
@@ -8408,7 +8405,6 @@ var UserConfigSchema = exports_external.object({
|
|
|
8408
8405
|
}).strict();
|
|
8409
8406
|
var DEFAULT_CONFIG = {
|
|
8410
8407
|
disable: [],
|
|
8411
|
-
ref: undefined,
|
|
8412
8408
|
installMethod: "link",
|
|
8413
8409
|
dryRun: false,
|
|
8414
8410
|
orchestration: {
|
|
@@ -8463,7 +8459,8 @@ var DEFAULT_CONFIG = {
|
|
|
8463
8459
|
};
|
|
8464
8460
|
function getConfigPaths() {
|
|
8465
8461
|
const projectDir = join2(process.cwd(), ".opencode");
|
|
8466
|
-
const
|
|
8462
|
+
const home = process.env.HOME || process.env.USERPROFILE || "~";
|
|
8463
|
+
const userDir = join2(home, ".config", "opencode");
|
|
8467
8464
|
return [
|
|
8468
8465
|
...CONFIG_FILENAMES.map((filename) => join2(projectDir, filename)),
|
|
8469
8466
|
...CONFIG_FILENAMES.map((filename) => join2(userDir, filename))
|
|
@@ -8503,9 +8500,8 @@ function loadConfigWithLocation() {
|
|
|
8503
8500
|
return { config: DEFAULT_CONFIG, configDir: null };
|
|
8504
8501
|
}
|
|
8505
8502
|
|
|
8506
|
-
// src/
|
|
8503
|
+
// src/assets.ts
|
|
8507
8504
|
var import_gray_matter = __toESM(require_gray_matter(), 1);
|
|
8508
|
-
var {$ } = globalThis.Bun;
|
|
8509
8505
|
import * as path from "path";
|
|
8510
8506
|
import * as fs from "fs";
|
|
8511
8507
|
|
|
@@ -8628,201 +8624,27 @@ function discoverInstructions(repoPath) {
|
|
|
8628
8624
|
return instructions;
|
|
8629
8625
|
}
|
|
8630
8626
|
|
|
8631
|
-
// src/
|
|
8632
|
-
function getRepoId(url) {
|
|
8633
|
-
const sshMatch = url.match(/^git@([^:]+):(.+?)(\.git)?$/);
|
|
8634
|
-
if (sshMatch) {
|
|
8635
|
-
const host = sshMatch[1];
|
|
8636
|
-
const p = sshMatch[2].replace(/\//g, "-");
|
|
8637
|
-
return `${host}-${p}`;
|
|
8638
|
-
}
|
|
8639
|
-
const httpsMatch = url.match(/^https?:\/\/([^/]+)\/(.+?)(\.git)?$/);
|
|
8640
|
-
if (httpsMatch) {
|
|
8641
|
-
const host = httpsMatch[1];
|
|
8642
|
-
const p = httpsMatch[2].replace(/\//g, "-");
|
|
8643
|
-
return `${host}-${p}`;
|
|
8644
|
-
}
|
|
8645
|
-
return url.replace(/[^a-zA-Z0-9-]/g, "-").replace(/-+/g, "-").slice(0, 100);
|
|
8646
|
-
}
|
|
8647
|
-
|
|
8648
|
-
// src/git.ts
|
|
8649
|
-
var DEFAULT_RETRY_CONFIG = {
|
|
8650
|
-
maxRetries: 3,
|
|
8651
|
-
initialDelayMs: 1000,
|
|
8652
|
-
maxDelayMs: 30000,
|
|
8653
|
-
backoffMultiplier: 2
|
|
8654
|
-
};
|
|
8655
|
-
function sleep(ms) {
|
|
8656
|
-
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
8657
|
-
}
|
|
8658
|
-
async function withRetry(operation, operationName, config = DEFAULT_RETRY_CONFIG) {
|
|
8659
|
-
let lastError;
|
|
8660
|
-
let delay = config.initialDelayMs;
|
|
8661
|
-
for (let attempt = 0;attempt <= config.maxRetries; attempt++) {
|
|
8662
|
-
try {
|
|
8663
|
-
return await operation();
|
|
8664
|
-
} catch (error) {
|
|
8665
|
-
lastError = error;
|
|
8666
|
-
const isNetworkError = isNetworkRelatedError(lastError);
|
|
8667
|
-
if (!isNetworkError) {
|
|
8668
|
-
throw lastError;
|
|
8669
|
-
}
|
|
8670
|
-
if (attempt >= config.maxRetries) {
|
|
8671
|
-
logError(`${operationName} failed after ${config.maxRetries + 1} attempts: ${lastError.message}`);
|
|
8672
|
-
throw lastError;
|
|
8673
|
-
}
|
|
8674
|
-
logWarn(`${operationName} failed (attempt ${attempt + 1}/${config.maxRetries + 1}), retrying in ${delay}ms: ${lastError.message}`);
|
|
8675
|
-
await sleep(delay);
|
|
8676
|
-
delay = Math.min(delay * config.backoffMultiplier, config.maxDelayMs);
|
|
8677
|
-
}
|
|
8678
|
-
}
|
|
8679
|
-
throw lastError || new Error(`${operationName} failed`);
|
|
8680
|
-
}
|
|
8681
|
-
function isNetworkRelatedError(error) {
|
|
8682
|
-
const message = error.message.toLowerCase();
|
|
8683
|
-
const networkPatterns = [
|
|
8684
|
-
"could not resolve host",
|
|
8685
|
-
"connection refused",
|
|
8686
|
-
"connection timed out",
|
|
8687
|
-
"connection reset",
|
|
8688
|
-
"network is unreachable",
|
|
8689
|
-
"no route to host",
|
|
8690
|
-
"temporary failure in name resolution",
|
|
8691
|
-
"ssl",
|
|
8692
|
-
"tls",
|
|
8693
|
-
"certificate",
|
|
8694
|
-
"handshake",
|
|
8695
|
-
"unable to access",
|
|
8696
|
-
"couldn't connect",
|
|
8697
|
-
"failed to connect",
|
|
8698
|
-
"ssh_exchange_identification",
|
|
8699
|
-
"kex_exchange_identification",
|
|
8700
|
-
"connection closed by remote host",
|
|
8701
|
-
"early eof",
|
|
8702
|
-
"unexpected disconnect",
|
|
8703
|
-
"remote end hung up",
|
|
8704
|
-
"the remote end hung up unexpectedly"
|
|
8705
|
-
];
|
|
8706
|
-
return networkPatterns.some((pattern) => message.includes(pattern));
|
|
8707
|
-
}
|
|
8708
|
-
var CACHE_BASE = path.join(process.env.HOME || "~", ".cache", "opencode", "wpromote-config", "repos");
|
|
8627
|
+
// src/assets.ts
|
|
8709
8628
|
var DISCOVERY_LIMITS = {
|
|
8710
8629
|
maxFiles: 100,
|
|
8711
8630
|
maxFileSize: 256 * 1024,
|
|
8712
8631
|
maxDepth: 10
|
|
8713
8632
|
};
|
|
8714
|
-
function
|
|
8715
|
-
|
|
8716
|
-
|
|
8717
|
-
|
|
8718
|
-
|
|
8719
|
-
|
|
8720
|
-
|
|
8721
|
-
fs.mkdirSync(path.dirname(repoPath), { recursive: true });
|
|
8722
|
-
await withRetry(async () => {
|
|
8723
|
-
const result = await $`git clone ${url} ${repoPath}`.quiet().nothrow();
|
|
8724
|
-
if (result.exitCode !== 0) {
|
|
8725
|
-
const stderr = result.stderr.toString().trim();
|
|
8726
|
-
const stdout = result.stdout.toString().trim();
|
|
8727
|
-
const output = [stderr, stdout].filter(Boolean).join(`
|
|
8728
|
-
`) || `exit code ${result.exitCode}`;
|
|
8729
|
-
throw new Error(`git clone failed: ${output}`);
|
|
8730
|
-
}
|
|
8731
|
-
}, `git clone ${url}`, retryConfig);
|
|
8732
|
-
}
|
|
8733
|
-
async function fetchAndCheckout(repoPath, ref, retryConfig = DEFAULT_RETRY_CONFIG) {
|
|
8734
|
-
const beforeCommit = await $`git -C ${repoPath} rev-parse HEAD`.quiet();
|
|
8735
|
-
const beforeHash = beforeCommit.stdout.toString().trim();
|
|
8736
|
-
await withRetry(async () => {
|
|
8737
|
-
const fetchResult = await $`git -C ${repoPath} fetch --all --prune`.quiet();
|
|
8738
|
-
if (fetchResult.exitCode !== 0) {
|
|
8739
|
-
const stderr = fetchResult.stderr.toString().trim();
|
|
8740
|
-
const stdout = fetchResult.stdout.toString().trim();
|
|
8741
|
-
const output = [stderr, stdout].filter(Boolean).join(`
|
|
8742
|
-
`) || `exit code ${fetchResult.exitCode}`;
|
|
8743
|
-
throw new Error(`git fetch failed: ${output}`);
|
|
8744
|
-
}
|
|
8745
|
-
}, `git fetch (${repoPath})`, retryConfig);
|
|
8746
|
-
const checkoutResult = await $`git -C ${repoPath} checkout ${ref}`.quiet().nothrow();
|
|
8747
|
-
if (checkoutResult.exitCode !== 0) {
|
|
8748
|
-
const stderr = checkoutResult.stderr.toString().trim();
|
|
8749
|
-
const stdout = checkoutResult.stdout.toString().trim();
|
|
8750
|
-
const output = [stderr, stdout].filter(Boolean).join(`
|
|
8751
|
-
`) || `exit code ${checkoutResult.exitCode}`;
|
|
8752
|
-
throw new Error(`git checkout ${ref} failed: ${output}`);
|
|
8753
|
-
}
|
|
8754
|
-
const isBranch = await $`git -C ${repoPath} symbolic-ref -q HEAD`.quiet().nothrow();
|
|
8755
|
-
if (isBranch.exitCode === 0) {
|
|
8756
|
-
await withRetry(async () => {
|
|
8757
|
-
const pullResult = await $`git -C ${repoPath} pull --ff-only`.quiet();
|
|
8758
|
-
if (pullResult.exitCode !== 0) {
|
|
8759
|
-
const stderr = pullResult.stderr.toString().trim();
|
|
8760
|
-
const stdout = pullResult.stdout.toString().trim();
|
|
8761
|
-
const output = [stderr, stdout].filter(Boolean).join(`
|
|
8762
|
-
`) || `exit code ${pullResult.exitCode}`;
|
|
8763
|
-
throw new Error(`git pull failed: ${output}`);
|
|
8764
|
-
}
|
|
8765
|
-
}, `git pull (${repoPath})`, retryConfig);
|
|
8766
|
-
}
|
|
8767
|
-
const afterCommit = await $`git -C ${repoPath} rev-parse HEAD`.quiet();
|
|
8768
|
-
const afterHash = afterCommit.stdout.toString().trim();
|
|
8769
|
-
return beforeHash !== afterHash;
|
|
8770
|
-
}
|
|
8771
|
-
async function getCurrentRef(repoPath) {
|
|
8772
|
-
const branchResult = await $`git -C ${repoPath} symbolic-ref --short HEAD`.quiet().nothrow();
|
|
8773
|
-
if (branchResult.exitCode === 0)
|
|
8774
|
-
return branchResult.stdout.toString().trim();
|
|
8775
|
-
const commitResult = await $`git -C ${repoPath} rev-parse --short HEAD`.quiet();
|
|
8776
|
-
return commitResult.stdout.toString().trim();
|
|
8777
|
-
}
|
|
8778
|
-
async function syncTeamRepository(ref) {
|
|
8779
|
-
const effectiveRef = ref ?? "main";
|
|
8780
|
-
const repoId = getRepoId(TEAM_REPO_URL);
|
|
8781
|
-
const repoPath = getRepoPath(TEAM_REPO_URL);
|
|
8782
|
-
const shortName = REPO_SHORT_NAME;
|
|
8783
|
-
let updated = false;
|
|
8784
|
-
let error;
|
|
8785
|
-
try {
|
|
8786
|
-
if (!isCloned(repoPath)) {
|
|
8787
|
-
if (fs.existsSync(repoPath)) {
|
|
8788
|
-
fs.rmSync(repoPath, { recursive: true, force: true });
|
|
8789
|
-
}
|
|
8790
|
-
await cloneRepo(TEAM_REPO_URL, repoPath);
|
|
8791
|
-
updated = true;
|
|
8792
|
-
}
|
|
8793
|
-
updated = await fetchAndCheckout(repoPath, effectiveRef) || updated;
|
|
8794
|
-
} catch (err) {
|
|
8795
|
-
error = err instanceof Error ? err.message : String(err);
|
|
8796
|
-
}
|
|
8797
|
-
let skills = [];
|
|
8798
|
-
let agents = [];
|
|
8799
|
-
let commands = [];
|
|
8800
|
-
let plugins = [];
|
|
8801
|
-
let instructions = [];
|
|
8802
|
-
let mcps = [];
|
|
8803
|
-
let currentRef = effectiveRef;
|
|
8804
|
-
if (isCloned(repoPath)) {
|
|
8805
|
-
currentRef = await getCurrentRef(repoPath);
|
|
8806
|
-
skills = await discoverSkills(repoPath);
|
|
8807
|
-
agents = await discoverAgents(repoPath);
|
|
8808
|
-
commands = await discoverCommands(repoPath);
|
|
8809
|
-
plugins = await discoverPlugins(repoPath, shortName);
|
|
8810
|
-
instructions = discoverInstructions(repoPath);
|
|
8811
|
-
mcps = await discoverMcps(repoPath);
|
|
8812
|
-
}
|
|
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);
|
|
8813
8640
|
return {
|
|
8814
|
-
|
|
8815
|
-
repoPath,
|
|
8816
|
-
shortName,
|
|
8817
|
-
ref: currentRef,
|
|
8641
|
+
shortName: REPO_SHORT_NAME,
|
|
8818
8642
|
skills,
|
|
8819
8643
|
agents,
|
|
8820
8644
|
commands,
|
|
8821
8645
|
plugins,
|
|
8822
8646
|
instructions,
|
|
8823
|
-
mcps
|
|
8824
|
-
updated,
|
|
8825
|
-
error
|
|
8647
|
+
mcps
|
|
8826
8648
|
};
|
|
8827
8649
|
}
|
|
8828
8650
|
async function discoverSkills(repoPath) {
|
|
@@ -9148,10 +8970,10 @@ async function discoverMcps(repoPath) {
|
|
|
9148
8970
|
// src/install.ts
|
|
9149
8971
|
import * as path3 from "path";
|
|
9150
8972
|
import * as fs3 from "fs";
|
|
9151
|
-
var {$: $
|
|
8973
|
+
var {$: $2 } = globalThis.Bun;
|
|
9152
8974
|
|
|
9153
8975
|
// src/copy.ts
|
|
9154
|
-
var {
|
|
8976
|
+
var {$ } = globalThis.Bun;
|
|
9155
8977
|
import * as fs2 from "fs";
|
|
9156
8978
|
import * as path2 from "path";
|
|
9157
8979
|
var rsyncAvailable = null;
|
|
@@ -9160,7 +8982,7 @@ async function detectRsync() {
|
|
|
9160
8982
|
return rsyncAvailable;
|
|
9161
8983
|
}
|
|
9162
8984
|
try {
|
|
9163
|
-
const result = await
|
|
8985
|
+
const result = await $`which rsync`.quiet();
|
|
9164
8986
|
rsyncAvailable = result.exitCode === 0;
|
|
9165
8987
|
} catch {
|
|
9166
8988
|
rsyncAvailable = false;
|
|
@@ -9175,7 +8997,7 @@ async function copyWithRsync(source, target) {
|
|
|
9175
8997
|
} else if (stat.isDirectory()) {}
|
|
9176
8998
|
}
|
|
9177
8999
|
fs2.mkdirSync(path2.dirname(target), { recursive: true });
|
|
9178
|
-
const result = await
|
|
9000
|
+
const result = await $`rsync -a --delete ${source}/ ${target}/`.quiet();
|
|
9179
9001
|
if (result.exitCode !== 0) {
|
|
9180
9002
|
throw new Error(`rsync failed: ${result.stderr.toString()}`);
|
|
9181
9003
|
}
|
|
@@ -9402,7 +9224,7 @@ function hasLocalConflict(skillName) {
|
|
|
9402
9224
|
}
|
|
9403
9225
|
async function findGitRoot(startPath) {
|
|
9404
9226
|
try {
|
|
9405
|
-
const result = await $
|
|
9227
|
+
const result = await $2`git -C ${startPath} rev-parse --show-toplevel`.quiet();
|
|
9406
9228
|
if (result.exitCode === 0) {
|
|
9407
9229
|
return result.stdout.toString().trim();
|
|
9408
9230
|
}
|
|
@@ -9450,9 +9272,9 @@ ${gitignoreEntry}
|
|
|
9450
9272
|
// src/plugin-install.ts
|
|
9451
9273
|
import * as fs4 from "fs";
|
|
9452
9274
|
import * as path4 from "path";
|
|
9453
|
-
import { homedir as
|
|
9454
|
-
var PLUGIN_DIR_PLURAL = path4.join(
|
|
9455
|
-
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");
|
|
9456
9278
|
var REMOTE_PREFIX = "_remote_";
|
|
9457
9279
|
function removePathIfExists(targetPath) {
|
|
9458
9280
|
try {
|
|
@@ -9625,8 +9447,8 @@ function findBrokenUnmanagedPluginLinks(pluginDir) {
|
|
|
9625
9447
|
// src/mcp-install.ts
|
|
9626
9448
|
import * as fs5 from "fs";
|
|
9627
9449
|
import * as path5 from "path";
|
|
9628
|
-
import { homedir as
|
|
9629
|
-
var DEFAULT_MCP_DIR = path5.join(
|
|
9450
|
+
import { homedir as homedir3 } from "os";
|
|
9451
|
+
var DEFAULT_MCP_DIR = path5.join(homedir3(), ".config", "opencode", "mcp");
|
|
9630
9452
|
function getMcpPluginsSubdirForRepo(repoShortName) {
|
|
9631
9453
|
return path5.join("_plugins", repoShortName);
|
|
9632
9454
|
}
|
|
@@ -9826,131 +9648,12 @@ function createDisableMatcher(disable) {
|
|
|
9826
9648
|
};
|
|
9827
9649
|
}
|
|
9828
9650
|
|
|
9829
|
-
// src/
|
|
9830
|
-
import * as
|
|
9831
|
-
import * as path6 from "path";
|
|
9832
|
-
import * as os from "os";
|
|
9833
|
-
var DEFAULT_TIMEOUT_MS = 30000;
|
|
9834
|
-
var POLL_INTERVAL_MS = 500;
|
|
9835
|
-
var STALE_THRESHOLD_MS = 5 * 60 * 1000;
|
|
9836
|
-
function getLockPath() {
|
|
9837
|
-
return path6.join(getRepoPath(TEAM_REPO_URL), ".sync.lock");
|
|
9838
|
-
}
|
|
9839
|
-
function isProcessRunning(pid) {
|
|
9840
|
-
try {
|
|
9841
|
-
process.kill(pid, 0);
|
|
9842
|
-
return true;
|
|
9843
|
-
} catch (err) {
|
|
9844
|
-
if (err.code === "EPERM") {
|
|
9845
|
-
return true;
|
|
9846
|
-
}
|
|
9847
|
-
return false;
|
|
9848
|
-
}
|
|
9849
|
-
}
|
|
9850
|
-
function isLockStale(lockData) {
|
|
9851
|
-
if (lockData.hostname === os.hostname()) {
|
|
9852
|
-
if (!isProcessRunning(lockData.pid)) {
|
|
9853
|
-
logDebug(`Lock held by dead process ${lockData.pid}, considering stale`);
|
|
9854
|
-
return true;
|
|
9855
|
-
}
|
|
9856
|
-
}
|
|
9857
|
-
const age = Date.now() - lockData.timestamp;
|
|
9858
|
-
if (age > STALE_THRESHOLD_MS) {
|
|
9859
|
-
if (lockData.hostname !== os.hostname()) {
|
|
9860
|
-
logDebug(`Lock from remote host ${lockData.hostname} is ${Math.round(age / 1000)}s old, considering stale`);
|
|
9861
|
-
return true;
|
|
9862
|
-
}
|
|
9863
|
-
}
|
|
9864
|
-
return false;
|
|
9865
|
-
}
|
|
9866
|
-
function readLockData() {
|
|
9867
|
-
const lockPath = getLockPath();
|
|
9868
|
-
try {
|
|
9869
|
-
const content = fs6.readFileSync(lockPath, "utf8");
|
|
9870
|
-
const data = JSON.parse(content);
|
|
9871
|
-
if (typeof data.pid !== "number" || typeof data.hostname !== "string" || typeof data.timestamp !== "number") {
|
|
9872
|
-
logDebug("Invalid lockfile structure, ignoring");
|
|
9873
|
-
return;
|
|
9874
|
-
}
|
|
9875
|
-
return data;
|
|
9876
|
-
} catch {
|
|
9877
|
-
return;
|
|
9878
|
-
}
|
|
9879
|
-
}
|
|
9880
|
-
function removeLockFile() {
|
|
9881
|
-
const lockPath = getLockPath();
|
|
9882
|
-
try {
|
|
9883
|
-
fs6.unlinkSync(lockPath);
|
|
9884
|
-
} catch {}
|
|
9885
|
-
}
|
|
9886
|
-
function tryAcquireLock() {
|
|
9887
|
-
const lockPath = getLockPath();
|
|
9888
|
-
fs6.mkdirSync(path6.dirname(lockPath), { recursive: true });
|
|
9889
|
-
const existingLock = readLockData();
|
|
9890
|
-
if (existingLock) {
|
|
9891
|
-
if (isLockStale(existingLock)) {
|
|
9892
|
-
logDebug("Removing stale lock");
|
|
9893
|
-
removeLockFile();
|
|
9894
|
-
} else {
|
|
9895
|
-
return false;
|
|
9896
|
-
}
|
|
9897
|
-
}
|
|
9898
|
-
try {
|
|
9899
|
-
const data = {
|
|
9900
|
-
pid: process.pid,
|
|
9901
|
-
hostname: os.hostname(),
|
|
9902
|
-
timestamp: Date.now()
|
|
9903
|
-
};
|
|
9904
|
-
fs6.writeFileSync(lockPath, JSON.stringify(data, null, 2), { flag: "wx" });
|
|
9905
|
-
return true;
|
|
9906
|
-
} catch (err) {
|
|
9907
|
-
if (err.code === "EEXIST") {
|
|
9908
|
-
return false;
|
|
9909
|
-
}
|
|
9910
|
-
throw err;
|
|
9911
|
-
}
|
|
9912
|
-
}
|
|
9913
|
-
async function acquireLock(timeoutMs = DEFAULT_TIMEOUT_MS) {
|
|
9914
|
-
const startTime = Date.now();
|
|
9915
|
-
while (true) {
|
|
9916
|
-
if (tryAcquireLock()) {
|
|
9917
|
-
logDebug("Acquired sync lock");
|
|
9918
|
-
return { acquired: true };
|
|
9919
|
-
}
|
|
9920
|
-
const elapsed = Date.now() - startTime;
|
|
9921
|
-
if (elapsed >= timeoutMs) {
|
|
9922
|
-
const lockData = readLockData();
|
|
9923
|
-
const holder = lockData ? `pid ${lockData.pid} on ${lockData.hostname}` : "unknown process";
|
|
9924
|
-
return {
|
|
9925
|
-
acquired: false,
|
|
9926
|
-
error: `Failed to acquire sync lock after ${Math.round(elapsed / 1000)}s (held by ${holder})`
|
|
9927
|
-
};
|
|
9928
|
-
}
|
|
9929
|
-
await new Promise((resolve4) => setTimeout(resolve4, POLL_INTERVAL_MS));
|
|
9930
|
-
}
|
|
9931
|
-
}
|
|
9932
|
-
function releaseLock() {
|
|
9933
|
-
const lockData = readLockData();
|
|
9934
|
-
if (lockData && lockData.pid === process.pid && lockData.hostname === os.hostname()) {
|
|
9935
|
-
removeLockFile();
|
|
9936
|
-
logDebug("Released sync lock");
|
|
9937
|
-
}
|
|
9938
|
-
}
|
|
9939
|
-
async function withLock(fn, timeoutMs = DEFAULT_TIMEOUT_MS) {
|
|
9940
|
-
const lockResult = await acquireLock(timeoutMs);
|
|
9941
|
-
if (!lockResult.acquired) {
|
|
9942
|
-
throw new Error(lockResult.error);
|
|
9943
|
-
}
|
|
9944
|
-
try {
|
|
9945
|
-
return await fn();
|
|
9946
|
-
} finally {
|
|
9947
|
-
releaseLock();
|
|
9948
|
-
}
|
|
9949
|
-
}
|
|
9651
|
+
// src/index.ts
|
|
9652
|
+
import * as path8 from "path";
|
|
9950
9653
|
|
|
9951
9654
|
// src/mcp-schema.ts
|
|
9952
|
-
import * as
|
|
9953
|
-
import * as
|
|
9655
|
+
import * as fs6 from "fs";
|
|
9656
|
+
import * as path6 from "path";
|
|
9954
9657
|
function isNonEmptyString(value) {
|
|
9955
9658
|
return typeof value === "string" && value.trim().length > 0;
|
|
9956
9659
|
}
|
|
@@ -10098,7 +9801,7 @@ function validateMcpConfig(obj) {
|
|
|
10098
9801
|
}
|
|
10099
9802
|
function validateMcpFile(mcpJsonPath) {
|
|
10100
9803
|
try {
|
|
10101
|
-
const content =
|
|
9804
|
+
const content = fs6.readFileSync(mcpJsonPath, "utf-8");
|
|
10102
9805
|
const parsed = JSON.parse(content);
|
|
10103
9806
|
return validateMcpConfig(parsed);
|
|
10104
9807
|
} catch (err) {
|
|
@@ -10117,7 +9820,7 @@ function validateMcpFile(mcpJsonPath) {
|
|
|
10117
9820
|
}
|
|
10118
9821
|
}
|
|
10119
9822
|
function validateMcpInfo(mcp) {
|
|
10120
|
-
const mcpJsonPath = mcp.path.endsWith(".json") ? mcp.path :
|
|
9823
|
+
const mcpJsonPath = mcp.path.endsWith(".json") ? mcp.path : path6.join(mcp.path, "mcp.json");
|
|
10121
9824
|
const result = validateMcpFile(mcpJsonPath);
|
|
10122
9825
|
for (const warning of result.warnings) {
|
|
10123
9826
|
logWarn(`MCP '${mcp.name}': ${warning}`);
|
|
@@ -10135,32 +9838,22 @@ function filterValidMcps(mcps) {
|
|
|
10135
9838
|
}
|
|
10136
9839
|
|
|
10137
9840
|
// src/status.ts
|
|
10138
|
-
import * as
|
|
10139
|
-
import * as
|
|
10140
|
-
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");
|
|
10141
9844
|
function getStatusFilePath() {
|
|
10142
|
-
return
|
|
10143
|
-
}
|
|
10144
|
-
function readSyncStatus() {
|
|
10145
|
-
const statusPath = getStatusFilePath();
|
|
10146
|
-
try {
|
|
10147
|
-
const content = fs8.readFileSync(statusPath, "utf-8");
|
|
10148
|
-
return JSON.parse(content);
|
|
10149
|
-
} catch {
|
|
10150
|
-
return null;
|
|
10151
|
-
}
|
|
9845
|
+
return path7.join(STATUS_BASE, `${REPO_SHORT_NAME}.sync-status.json`);
|
|
10152
9846
|
}
|
|
10153
9847
|
function writeSyncStatus(status) {
|
|
10154
9848
|
const statusPath = getStatusFilePath();
|
|
10155
|
-
const dir =
|
|
10156
|
-
|
|
10157
|
-
|
|
9849
|
+
const dir = path7.dirname(statusPath);
|
|
9850
|
+
fs7.mkdirSync(dir, { recursive: true });
|
|
9851
|
+
fs7.writeFileSync(statusPath, JSON.stringify(status, null, 2));
|
|
10158
9852
|
logDebug(`Wrote sync status to ${statusPath}`);
|
|
10159
9853
|
}
|
|
10160
9854
|
function updateSyncStatus(data) {
|
|
10161
9855
|
const status = {
|
|
10162
9856
|
lastSyncTime: new Date().toISOString(),
|
|
10163
|
-
currentRef: data.currentRef,
|
|
10164
9857
|
installedSkills: data.installedSkills,
|
|
10165
9858
|
installedPlugins: data.installedPlugins,
|
|
10166
9859
|
installedMcps: data.installedMcps,
|
|
@@ -10174,43 +9867,16 @@ function updateSyncStatus(data) {
|
|
|
10174
9867
|
};
|
|
10175
9868
|
writeSyncStatus(status);
|
|
10176
9869
|
}
|
|
10177
|
-
function recordSyncError(error) {
|
|
10178
|
-
const existing = readSyncStatus();
|
|
10179
|
-
const status = existing ?? {
|
|
10180
|
-
lastSyncTime: null,
|
|
10181
|
-
currentRef: null,
|
|
10182
|
-
installedSkills: 0,
|
|
10183
|
-
installedPlugins: 0,
|
|
10184
|
-
installedMcps: 0,
|
|
10185
|
-
remoteAgents: 0,
|
|
10186
|
-
remoteCommands: 0,
|
|
10187
|
-
remoteInstructions: 0,
|
|
10188
|
-
skippedConflicts: 0,
|
|
10189
|
-
disabledAssets: 0,
|
|
10190
|
-
lastError: null,
|
|
10191
|
-
pluginsChanged: false
|
|
10192
|
-
};
|
|
10193
|
-
status.lastError = error;
|
|
10194
|
-
writeSyncStatus(status);
|
|
10195
|
-
}
|
|
10196
9870
|
|
|
10197
9871
|
// src/index.ts
|
|
9872
|
+
var PACKAGE_ROOT = path8.resolve(import.meta.dir, "..");
|
|
10198
9873
|
var initialized = false;
|
|
10199
9874
|
var REQUIRED_RUNTIME_PLUGINS = ["@tarquinen/opencode-dcp@latest"];
|
|
10200
9875
|
var DEFAULT_OPENCODE_AGENTS_TO_DISABLE = ["build", "plan"];
|
|
10201
9876
|
async function performSync(config) {
|
|
10202
9877
|
const disableMatcher = createDisableMatcher(config.disable);
|
|
10203
9878
|
log(config.dryRun ? "[DRY-RUN] Analyzing team configuration..." : "Syncing team configuration...");
|
|
10204
|
-
const result = await
|
|
10205
|
-
const degradedSyncMode = Boolean(result.error);
|
|
10206
|
-
if (degradedSyncMode) {
|
|
10207
|
-
const syncError = result.error ?? "Unknown sync error";
|
|
10208
|
-
logError(`\u2717 Failed to sync team repository: ${syncError}`);
|
|
10209
|
-
if (!config.dryRun) {
|
|
10210
|
-
recordSyncError(syncError);
|
|
10211
|
-
}
|
|
10212
|
-
log("Continuing with cached assets in degraded mode");
|
|
10213
|
-
}
|
|
9879
|
+
const result = await discoverAssets(PACKAGE_ROOT);
|
|
10214
9880
|
if (!config.dryRun) {
|
|
10215
9881
|
const brokenUnmanagedLinks = findBrokenUnmanagedPluginLinks();
|
|
10216
9882
|
if (brokenUnmanagedLinks.length > 0) {
|
|
@@ -10384,20 +10050,6 @@ async function performSync(config) {
|
|
|
10384
10050
|
dryRun: dryRunReport
|
|
10385
10051
|
};
|
|
10386
10052
|
}
|
|
10387
|
-
if (degradedSyncMode) {
|
|
10388
|
-
return {
|
|
10389
|
-
result,
|
|
10390
|
-
skippedConflicts,
|
|
10391
|
-
totalSkills: 0,
|
|
10392
|
-
remoteAgents,
|
|
10393
|
-
remoteCommands,
|
|
10394
|
-
remoteMcps,
|
|
10395
|
-
remoteInstructions,
|
|
10396
|
-
pluginsChanged: false,
|
|
10397
|
-
totalPlugins: 0,
|
|
10398
|
-
totalMcps: 0
|
|
10399
|
-
};
|
|
10400
|
-
}
|
|
10401
10053
|
const filteredResult = { ...result, skills: skillsToInstall };
|
|
10402
10054
|
const installResults = await createInstallsForRepo(filteredResult, config.installMethod);
|
|
10403
10055
|
for (const sr of installResults) {
|
|
@@ -10408,7 +10060,7 @@ async function performSync(config) {
|
|
|
10408
10060
|
logError(`\u2717 Failed to install skill ${sr.skillName}: ${sr.error}`);
|
|
10409
10061
|
}
|
|
10410
10062
|
}
|
|
10411
|
-
log(`\u2713 ${result.shortName}
|
|
10063
|
+
log(`\u2713 ${result.shortName} - ${totalSkills} skills`);
|
|
10412
10064
|
const cleanup = cleanupStaleInstalls(currentSkills);
|
|
10413
10065
|
if (cleanup.removed.length > 0) {
|
|
10414
10066
|
log(`Cleaned up ${cleanup.removed.length} stale skill installs`);
|
|
@@ -10468,7 +10120,6 @@ async function performSync(config) {
|
|
|
10468
10120
|
}
|
|
10469
10121
|
const totalMcps = newMcpInstalls.size;
|
|
10470
10122
|
updateSyncStatus({
|
|
10471
|
-
currentRef: result.ref,
|
|
10472
10123
|
installedSkills: totalSkills,
|
|
10473
10124
|
installedPlugins: totalPlugins,
|
|
10474
10125
|
installedMcps: totalMcps,
|
|
@@ -10554,7 +10205,7 @@ var WpromoteConfigPlugin = async (ctx) => {
|
|
|
10554
10205
|
pluginsChanged,
|
|
10555
10206
|
totalPlugins,
|
|
10556
10207
|
totalMcps
|
|
10557
|
-
} = await
|
|
10208
|
+
} = await performSync(userConfig);
|
|
10558
10209
|
if (pluginsChanged) {
|
|
10559
10210
|
log("\u26A0 Plugin changes detected. Restart OpenCode to apply.");
|
|
10560
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.
|