@wpro-eng/opencode-config 1.0.2 → 1.2.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/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 userDir = join2(homedir2(), ".config", "opencode");
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/git.ts
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/repo.ts
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 getRepoPath(url) {
8715
- return path.join(CACHE_BASE, getRepoId(url));
8716
- }
8717
- function isCloned(repoPath) {
8718
- return fs.existsSync(path.join(repoPath, ".git"));
8719
- }
8720
- async function cloneRepo(url, repoPath, retryConfig = DEFAULT_RETRY_CONFIG) {
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
- repoId,
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 {$: $3 } = globalThis.Bun;
8973
+ var {$: $2 } = globalThis.Bun;
9152
8974
 
9153
8975
  // src/copy.ts
9154
- var {$: $2 } = globalThis.Bun;
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 $2`which rsync`.quiet();
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 $2`rsync -a --delete ${source}/ ${target}/`.quiet();
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 $3`git -C ${startPath} rev-parse --show-toplevel`.quiet();
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 homedir3 } from "os";
9454
- var PLUGIN_DIR_PLURAL = path4.join(homedir3(), ".config", "opencode", "plugins");
9455
- var PLUGIN_DIR_SINGULAR = path4.join(homedir3(), ".config", "opencode", "plugin");
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 homedir4 } from "os";
9629
- var DEFAULT_MCP_DIR = path5.join(homedir4(), ".config", "opencode", "mcp");
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/lock.ts
9830
- import * as fs6 from "fs";
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 fs7 from "fs";
9953
- import * as path7 from "path";
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 = fs7.readFileSync(mcpJsonPath, "utf-8");
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 : path7.join(mcp.path, "mcp.json");
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,82 +9838,57 @@ function filterValidMcps(mcps) {
10135
9838
  }
10136
9839
 
10137
9840
  // src/status.ts
10138
- import * as fs8 from "fs";
10139
- import * as path8 from "path";
10140
- var STATUS_BASE = path8.join(process.env.HOME || "~", ".cache", "opencode", "wpromote-config");
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 path8.join(STATUS_BASE, `${REPO_SHORT_NAME}.sync-status.json`);
9845
+ return path7.join(STATUS_BASE, `${REPO_SHORT_NAME}.plugin-status.json`);
10143
9846
  }
10144
- function readSyncStatus() {
9847
+ function writePluginStatus(status) {
10145
9848
  const statusPath = getStatusFilePath();
9849
+ const dir = path7.dirname(statusPath);
9850
+ fs7.mkdirSync(dir, { recursive: true });
9851
+ fs7.writeFileSync(statusPath, JSON.stringify(status, null, 2));
9852
+ logDebug(`Wrote plugin status to ${statusPath}`);
9853
+ }
9854
+ function getPluginVersion() {
10146
9855
  try {
10147
- const content = fs8.readFileSync(statusPath, "utf-8");
10148
- return JSON.parse(content);
9856
+ const pkgPath = path7.resolve(import.meta.dir, "..", "package.json");
9857
+ const content = fs7.readFileSync(pkgPath, "utf-8");
9858
+ const pkg = JSON.parse(content);
9859
+ return pkg.version ?? null;
10149
9860
  } catch {
10150
9861
  return null;
10151
9862
  }
10152
9863
  }
10153
- function writeSyncStatus(status) {
10154
- const statusPath = getStatusFilePath();
10155
- const dir = path8.dirname(statusPath);
10156
- fs8.mkdirSync(dir, { recursive: true });
10157
- fs8.writeFileSync(statusPath, JSON.stringify(status, null, 2));
10158
- logDebug(`Wrote sync status to ${statusPath}`);
10159
- }
10160
- function updateSyncStatus(data) {
9864
+ function updatePluginStatus(data) {
10161
9865
  const status = {
10162
- lastSyncTime: new Date().toISOString(),
10163
- currentRef: data.currentRef,
10164
- installedSkills: data.installedSkills,
10165
- installedPlugins: data.installedPlugins,
10166
- installedMcps: data.installedMcps,
10167
- remoteAgents: data.remoteAgents,
10168
- remoteCommands: data.remoteCommands,
10169
- remoteInstructions: data.remoteInstructions,
9866
+ lastLoadTime: new Date().toISOString(),
9867
+ pluginVersion: getPluginVersion(),
9868
+ skillNames: data.skillNames,
9869
+ pluginNames: data.pluginNames,
9870
+ runtimePluginNames: data.runtimePluginNames,
9871
+ mcpNames: data.mcpNames,
9872
+ agentNames: data.agentNames,
9873
+ commandNames: data.commandNames,
9874
+ instructionNames: data.instructionNames,
10170
9875
  skippedConflicts: data.skippedConflicts,
10171
9876
  disabledAssets: data.disabledAssets,
10172
9877
  lastError: null,
10173
9878
  pluginsChanged: data.pluginsChanged
10174
9879
  };
10175
- writeSyncStatus(status);
10176
- }
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);
9880
+ writePluginStatus(status);
10195
9881
  }
10196
9882
 
10197
9883
  // src/index.ts
9884
+ var PACKAGE_ROOT = path8.resolve(import.meta.dir, "..");
10198
9885
  var initialized = false;
10199
9886
  var REQUIRED_RUNTIME_PLUGINS = ["@tarquinen/opencode-dcp@latest"];
10200
9887
  var DEFAULT_OPENCODE_AGENTS_TO_DISABLE = ["build", "plan"];
10201
- async function performSync(config) {
9888
+ async function performLoad(config) {
10202
9889
  const disableMatcher = createDisableMatcher(config.disable);
10203
- log(config.dryRun ? "[DRY-RUN] Analyzing team configuration..." : "Syncing team configuration...");
10204
- const result = await syncTeamRepository(config.ref);
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
- }
9890
+ log(config.dryRun ? "[DRY-RUN] Analyzing team configuration..." : "Loading team configuration...");
9891
+ const result = await discoverAssets(PACKAGE_ROOT);
10214
9892
  if (!config.dryRun) {
10215
9893
  const brokenUnmanagedLinks = findBrokenUnmanagedPluginLinks();
10216
9894
  if (brokenUnmanagedLinks.length > 0) {
@@ -10384,20 +10062,6 @@ async function performSync(config) {
10384
10062
  dryRun: dryRunReport
10385
10063
  };
10386
10064
  }
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
10065
  const filteredResult = { ...result, skills: skillsToInstall };
10402
10066
  const installResults = await createInstallsForRepo(filteredResult, config.installMethod);
10403
10067
  for (const sr of installResults) {
@@ -10408,7 +10072,7 @@ async function performSync(config) {
10408
10072
  logError(`\u2717 Failed to install skill ${sr.skillName}: ${sr.error}`);
10409
10073
  }
10410
10074
  }
10411
- log(`\u2713 ${result.shortName} (${result.ref}) - ${totalSkills} skills`);
10075
+ log(`\u2713 ${result.shortName} - ${totalSkills} skills`);
10412
10076
  const cleanup = cleanupStaleInstalls(currentSkills);
10413
10077
  if (cleanup.removed.length > 0) {
10414
10078
  log(`Cleaned up ${cleanup.removed.length} stale skill installs`);
@@ -10467,16 +10131,16 @@ async function performSync(config) {
10467
10131
  log(`Cleaned up ${mcpCleanup.removed.length} stale MCP installs`);
10468
10132
  }
10469
10133
  const totalMcps = newMcpInstalls.size;
10470
- updateSyncStatus({
10471
- currentRef: result.ref,
10472
- installedSkills: totalSkills,
10473
- installedPlugins: totalPlugins,
10474
- installedMcps: totalMcps,
10475
- remoteAgents: remoteAgents.size,
10476
- remoteCommands: remoteCommands.size,
10477
- remoteInstructions: remoteInstructions.length,
10478
- skippedConflicts: skippedConflicts.length,
10479
- disabledAssets: disabledAssets.length,
10134
+ updatePluginStatus({
10135
+ skillNames: skillsToInstall.map((s) => s.name),
10136
+ pluginNames: pluginsToInstall.map((p) => p.name),
10137
+ runtimePluginNames: [...REQUIRED_RUNTIME_PLUGINS],
10138
+ mcpNames: [...remoteMcps.keys()],
10139
+ agentNames: [...remoteAgents.keys()],
10140
+ commandNames: [...remoteCommands.keys()],
10141
+ instructionNames: remoteInstructions.map((p) => p.split("/").pop() ?? p),
10142
+ skippedConflicts,
10143
+ disabledAssets,
10480
10144
  pluginsChanged
10481
10145
  });
10482
10146
  return {
@@ -10554,7 +10218,7 @@ var WpromoteConfigPlugin = async (ctx) => {
10554
10218
  pluginsChanged,
10555
10219
  totalPlugins,
10556
10220
  totalMcps
10557
- } = await withLock(async () => performSync(userConfig));
10221
+ } = await performLoad(userConfig);
10558
10222
  if (pluginsChanged) {
10559
10223
  log("\u26A0 Plugin changes detected. Restart OpenCode to apply.");
10560
10224
  }
@@ -10700,7 +10364,7 @@ var WpromoteConfigPlugin = async (ctx) => {
10700
10364
  var RemoteSkillsPlugin = WpromoteConfigPlugin;
10701
10365
  var src_default = WpromoteConfigPlugin;
10702
10366
  export {
10703
- performSync,
10367
+ performLoad,
10704
10368
  src_default as default,
10705
10369
  WpromoteConfigPlugin,
10706
10370
  RemoteSkillsPlugin
@@ -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.