agentsys 5.2.0 → 5.2.1

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.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "agentsys",
3
3
  "description": "14 specialized plugins for AI workflow automation - task orchestration, PR workflow, slop detection, code review, drift detection, enhancement analysis, documentation sync, repo mapping, perf investigations, topic research, agent config linting, cross-tool AI consultation, and structured AI debate",
4
- "version": "5.2.0",
4
+ "version": "5.2.1",
5
5
  "owner": {
6
6
  "name": "Avi Fenesh",
7
7
  "url": "https://github.com/avifenesh"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentsys",
3
- "version": "5.2.0",
3
+ "version": "5.2.1",
4
4
  "description": "Professional-grade slash commands for Claude Code with cross-platform support",
5
5
  "keywords": [
6
6
  "workflow",
package/CHANGELOG.md CHANGED
@@ -9,6 +9,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
9
9
 
10
10
  ## [Unreleased]
11
11
 
12
+ ## [5.2.1] - 2026-03-01
13
+
14
+ ### Fixed
15
+
16
+ - **Installer marketplace source parsing** — Added compatibility for both legacy string `source` values and structured source objects (`{ source: "url", url: "..." }`) so installs no longer crash with `plugin.source.startsWith is not a function`.
17
+ - **Plugin fetch resilience and failure behavior** — Normalized `.git` repository URLs, added GitHub ref fallback order (`vX.Y.Z`, `X.Y.Z`, `main`, `master`), and fail-fast behavior when any plugin fetch fails.
18
+ - **Cross-platform install ordering** — Fixed install sequence so local install directory reset no longer wipes the fetched plugin cache before OpenCode/Codex installation.
19
+
12
20
  ## [5.2.0] - 2026-02-27
13
21
 
14
22
  ### Added
package/bin/cli.js CHANGED
@@ -231,18 +231,60 @@ function loadMarketplace() {
231
231
  }
232
232
 
233
233
  /**
234
- * Resolve the source URL string from a plugin's source field.
235
- * Handles both legacy string format ("https://...") and the new object
236
- * format ({ source: "url", url: "https://..." }) from Claude Code plugin schema.
237
- * Returns null for local/bundled sources or missing values.
234
+ * Normalize marketplace plugin source entries.
235
+ *
236
+ * Supported formats:
237
+ * - string URL/path (legacy)
238
+ * - object: { source: "url", url: "..." } (current)
239
+ * - object: { source: "path", path: "..." } (local/bundled)
240
+ *
241
+ * @param {string|Object} source
242
+ * @returns {{type: 'remote'|'local', value: string}|null}
238
243
  */
239
- function resolveSourceUrl(source) {
240
- if (!source) return null;
241
- if (typeof source === 'string') return source;
242
- if (typeof source === 'object' && source.url) return source.url;
244
+ function resolvePluginSource(source) {
245
+ if (typeof source === 'string') {
246
+ const value = source.trim();
247
+ if (!value) return null;
248
+ if (value.startsWith('./') || value.startsWith('../')) {
249
+ return { type: 'local', value };
250
+ }
251
+ return { type: 'remote', value };
252
+ }
253
+
254
+ if (!source || typeof source !== 'object') return null;
255
+
256
+ const sourceType = typeof source.source === 'string' ? source.source.toLowerCase() : null;
257
+
258
+ if ((sourceType === 'path' || sourceType === 'local') && typeof source.path === 'string') {
259
+ return { type: 'local', value: source.path };
260
+ }
261
+
262
+ if (sourceType === 'url' && typeof source.url === 'string') {
263
+ return { type: 'remote', value: source.url };
264
+ }
265
+
266
+ // Backward/forward-compatible fallbacks
267
+ if (typeof source.path === 'string') {
268
+ return { type: 'local', value: source.path };
269
+ }
270
+ if (typeof source.url === 'string') {
271
+ return { type: 'remote', value: source.url };
272
+ }
273
+
243
274
  return null;
244
275
  }
245
276
 
277
+ /**
278
+ * Backward-compatible helper returning only the source URL/path value.
279
+ *
280
+ * @param {string|Object} source
281
+ * @returns {string|null}
282
+ */
283
+ function resolveSourceUrl(source) {
284
+ const normalized = resolvePluginSource(source);
285
+ return normalized ? normalized.value : null;
286
+ }
287
+
246
288
  /**
247
289
  * Resolve plugin dependencies transitively.
248
290
  *
@@ -313,45 +355,72 @@ async function fetchPlugin(name, source, version) {
313
355
  }
314
356
  }
315
357
 
358
+ const parsedSource = parseGitHubSource(source, version, name);
359
+ const owner = parsedSource.owner;
360
+ const repo = parsedSource.repo;
361
+
362
+ const refCandidates = parsedSource.explicitRef
363
+ ? [parsedSource.ref]
364
+ : [parsedSource.ref, version, 'main', 'master'];
365
+
366
+ let lastError = null;
367
+ for (const ref of [...new Set(refCandidates.filter(Boolean))]) {
368
+ const tarballUrl = `https://api.github.com/repos/${owner}/${repo}/tarball/${ref}`;
369
+
370
+ try {
371
+ console.log(` Fetching ${name}@${version} from ${owner}/${repo} (${ref})...`);
372
+
373
+ // Clean and recreate
374
+ if (fs.existsSync(pluginDir)) {
375
+ fs.rmSync(pluginDir, { recursive: true, force: true });
376
+ }
377
+ fs.mkdirSync(pluginDir, { recursive: true });
378
+
379
+ // Download and extract tarball
380
+ await downloadAndExtractTarball(tarballUrl, pluginDir);
381
+
382
+ // Write version marker
383
+ fs.writeFileSync(versionFile, version);
384
+ return pluginDir;
385
+ } catch (err) {
386
+ lastError = err;
387
+ const isNotFound = /HTTP 404/.test(err.message);
388
+ if (isNotFound && !parsedSource.explicitRef) {
389
+ continue;
390
+ }
391
+ throw err;
392
+ }
393
+ }
394
+
395
+ throw new Error(
396
+ `Unable to fetch ${name} from ${owner}/${repo}. Tried refs: ${[...new Set(refCandidates.filter(Boolean))].join(', ')}. Last error: ${lastError ? lastError.message : 'unknown error'}`
397
+ );
398
+ }
399
+
400
+ /**
401
+ * Parse GitHub source URL formats and normalize repo name.
402
+ *
403
+ * @param {string} source
404
+ * @param {string} version
405
+ * @param {string} [name]
406
+ * @returns {{owner: string, repo: string, ref: string, explicitRef: boolean}}
407
+ */
408
+ function parseGitHubSource(source, version, name = 'plugin') {
316
409
  // Parse source formats:
317
410
  // "https://github.com/owner/repo" or "https://github.com/owner/repo#ref"
318
411
  // "github:owner/repo" or "github:owner/repo#ref"
319
- let owner, repo, ref;
320
412
  const urlMatch = source.match(/github\.com\/([^/]+)\/([^/#]+)(?:#(.+))?/);
321
413
  const shortMatch = !urlMatch && source.match(/^github:([^/]+)\/([^#]+)(?:#(.+))?$/);
322
414
  const match = urlMatch || shortMatch;
323
415
  if (!match) {
324
416
  throw new Error(`Unsupported source format for ${name}: ${source}`);
325
417
  }
326
- owner = match[1];
327
- repo = match[2].replace(/\.git$/, '');
328
- ref = match[3] || `v${version}`;
329
-
330
- console.log(` Fetching ${name}@${version} from ${owner}/${repo}...`);
331
-
332
- // Clean and recreate
333
- if (fs.existsSync(pluginDir)) {
334
- fs.rmSync(pluginDir, { recursive: true, force: true });
335
- }
336
- fs.mkdirSync(pluginDir, { recursive: true });
337
-
338
- // Download and extract tarball, falling back to main branch if version tag 404s
339
- const tarballUrl = `https://api.github.com/repos/${owner}/${repo}/tarball/${ref}`;
340
- try {
341
- await downloadAndExtractTarball(tarballUrl, pluginDir);
342
- } catch (err) {
343
- if (ref !== 'main' && err.message && err.message.includes('404')) {
344
- const mainUrl = `https://api.github.com/repos/${owner}/${repo}/tarball/main`;
345
- await downloadAndExtractTarball(mainUrl, pluginDir);
346
- } else {
347
- throw err;
348
- }
349
- }
350
418
 
351
- // Write version marker
352
- fs.writeFileSync(versionFile, version);
353
-
354
- return pluginDir;
419
+ const owner = match[1];
420
+ const repo = match[2].replace(/\.git$/, '');
421
+ const explicitRef = Boolean(match[3]);
422
+ const ref = match[3] || `v${version}`;
423
+ return { owner, repo, ref, explicitRef };
355
424
  }
356
425
 
357
426
  /**
@@ -470,16 +539,17 @@ async function fetchExternalPlugins(pluginNames, marketplace) {
470
539
  const plugin = pluginMap[name];
471
540
  if (!plugin) continue;
472
541
 
473
- // If source is local (starts with ./), plugin is bundled - just use PACKAGE_DIR
474
- const sourceUrl = resolveSourceUrl(plugin.source);
475
- if (!sourceUrl || sourceUrl.startsWith('./') || sourceUrl.startsWith('../')) {
542
+ const source = resolvePluginSource(plugin.source);
543
+
544
+ // Local/bundled plugin, no external fetch needed
545
+ if (!source || source.type === 'local') {
476
546
  // Bundled plugin, no fetch needed
477
547
  fetched.push(name);
478
548
  continue;
479
549
  }
480
550
 
481
551
  try {
482
- await fetchPlugin(name, sourceUrl, plugin.version);
552
+ await fetchPlugin(name, source.value, plugin.version);
483
553
  fetched.push(name);
484
554
  } catch (err) {
485
555
  failed.push(name);
@@ -493,6 +563,8 @@ async function fetchExternalPlugins(pluginNames, marketplace) {
493
563
  console.error(`\n [WARN] Missing dependencies: ${missingDeps.join(', ')}`);
494
564
  console.error(` Some plugins may not work correctly without their dependencies.`);
495
565
  }
566
+
567
+ throw new Error(`Failed to fetch ${failed.length} plugin(s): ${failed.join(', ')}`);
496
568
  }
497
569
 
498
570
  return fetched;
@@ -786,10 +858,13 @@ function loadComponents(pluginDir) {
786
858
  if (fs.existsSync(componentsPath)) {
787
859
  try {
788
860
  const data = JSON.parse(fs.readFileSync(componentsPath, 'utf8'));
861
+ const normalize = (arr) => (arr || []).map(item =>
862
+ typeof item === 'string' ? { name: item } : item
863
+ );
789
864
  return {
790
- agents: data.agents || [],
791
- skills: data.skills || [],
792
- commands: data.commands || []
865
+ agents: normalize(data.agents),
866
+ skills: normalize(data.skills),
867
+ commands: normalize(data.commands)
793
868
  };
794
869
  } catch {
795
870
  // Fall through to filesystem scan
@@ -914,12 +989,15 @@ async function installPlugin(nameWithVersion, args) {
914
989
  // Fetch all
915
990
  for (const depName of toFetch) {
916
991
  const dep = pluginMap[depName];
917
- const depSourceUrl = resolveSourceUrl(dep && dep.source);
918
- if (!dep || !depSourceUrl || depSourceUrl.startsWith('./')) continue;
992
+ if (!dep) continue;
993
+
994
+ const source = resolvePluginSource(dep.source);
995
+ if (!source || source.type === 'local') continue;
996
+
919
997
  checkCoreCompat(dep);
920
998
  const ver = depName === name && requestedVersion ? requestedVersion : dep.version;
921
999
  try {
922
- await fetchPlugin(depName, depSourceUrl, ver);
1000
+ await fetchPlugin(depName, source.value, ver);
923
1001
  } catch (err) {
924
1002
  console.error(` [ERROR] Failed to fetch ${depName}: ${err.message}`);
925
1003
  }
@@ -1928,8 +2006,6 @@ async function main() {
1928
2006
  if (entry) checkCoreCompat(entry);
1929
2007
  }
1930
2008
 
1931
- await fetchExternalPlugins(pluginNames, marketplace);
1932
-
1933
2009
  // Only copy to ~/.agentsys if OpenCode, Codex, or Cursor selected (they need local files)
1934
2010
  const needsLocalInstall = selected.includes('opencode') || selected.includes('codex') || selected.includes('cursor');
1935
2011
  let installDir = null;
@@ -1941,6 +2017,8 @@ async function main() {
1941
2017
  installDependencies(installDir);
1942
2018
  }
1943
2019
 
2020
+ await fetchExternalPlugins(pluginNames, marketplace);
2021
+
1944
2022
  // Install for each platform
1945
2023
  const failedPlatforms = [];
1946
2024
  for (const platform of selected) {
@@ -2020,5 +2098,7 @@ module.exports = {
2020
2098
  loadComponents,
2021
2099
  resolveComponent,
2022
2100
  buildFilterFromComponent,
2101
+ resolvePluginSource,
2102
+ parseGitHubSource,
2023
2103
  installForCursor
2024
2104
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentsys",
3
- "version": "5.2.0",
3
+ "version": "5.2.1",
4
4
  "description": "A modular runtime and orchestration system for AI agents - works with Claude Code, OpenCode, and Codex CLI",
5
5
  "main": "lib/platform/detect-platform.js",
6
6
  "type": "commonjs",
package/site/content.json CHANGED
@@ -5,7 +5,7 @@
5
5
  "url": "https://agent-sh.github.io/agentsys",
6
6
  "repo": "https://github.com/agent-sh/agentsys",
7
7
  "npm": "https://www.npmjs.com/package/agentsys",
8
- "version": "5.2.0",
8
+ "version": "5.2.1",
9
9
  "author": "Avi Fenesh",
10
10
  "author_url": "https://github.com/avifenesh"
11
11
  },