agentsys 5.2.0 → 5.3.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/bin/cli.js CHANGED
@@ -22,7 +22,7 @@ const discovery = require('../lib/discovery');
22
22
  const transforms = require('../lib/adapter-transforms');
23
23
 
24
24
  // Valid tool names
25
- const VALID_TOOLS = ['claude', 'opencode', 'codex', 'cursor'];
25
+ const VALID_TOOLS = ['claude', 'opencode', 'codex', 'cursor', 'kiro'];
26
26
 
27
27
  function getInstallDir() {
28
28
  const home = process.env.HOME || process.env.USERPROFILE;
@@ -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
@@ -874,6 +949,9 @@ function detectInstalledPlatforms() {
874
949
  // Cursor rules are project-scoped; detect only if Cursor rules/commands/skills exist in CWD
875
950
  const cursorDir = path.join(process.cwd(), '.cursor');
876
951
  if (fs.existsSync(path.join(cursorDir, 'rules')) || fs.existsSync(path.join(cursorDir, 'commands')) || fs.existsSync(path.join(cursorDir, 'skills'))) platforms.push('cursor');
952
+ // Kiro is project-scoped; detect if .kiro/ directory exists in CWD
953
+ const kiroDir = path.join(process.cwd(), '.kiro');
954
+ if (fs.existsSync(path.join(kiroDir, 'steering')) || fs.existsSync(path.join(kiroDir, 'skills')) || fs.existsSync(path.join(kiroDir, 'agents'))) platforms.push('kiro');
877
955
  return platforms;
878
956
  }
879
957
 
@@ -914,12 +992,15 @@ async function installPlugin(nameWithVersion, args) {
914
992
  // Fetch all
915
993
  for (const depName of toFetch) {
916
994
  const dep = pluginMap[depName];
917
- const depSourceUrl = resolveSourceUrl(dep && dep.source);
918
- if (!dep || !depSourceUrl || depSourceUrl.startsWith('./')) continue;
995
+ if (!dep) continue;
996
+
997
+ const source = resolvePluginSource(dep.source);
998
+ if (!source || source.type === 'local') continue;
999
+
919
1000
  checkCoreCompat(dep);
920
1001
  const ver = depName === name && requestedVersion ? requestedVersion : dep.version;
921
1002
  try {
922
- await fetchPlugin(depName, depSourceUrl, ver);
1003
+ await fetchPlugin(depName, source.value, ver);
923
1004
  } catch (err) {
924
1005
  console.error(` [ERROR] Failed to fetch ${depName}: ${err.message}`);
925
1006
  }
@@ -967,7 +1048,7 @@ async function installPlugin(nameWithVersion, args) {
967
1048
 
968
1049
  // Use cache as install source
969
1050
  const installDir = getInstallDir();
970
- const needsLocal = platforms.includes('opencode') || platforms.includes('codex') || platforms.includes('cursor');
1051
+ const needsLocal = platforms.includes('opencode') || platforms.includes('codex') || platforms.includes('cursor') || platforms.includes('kiro');
971
1052
  if (needsLocal && !fs.existsSync(path.join(installDir, 'lib'))) {
972
1053
  // Need local install for transforms
973
1054
  cleanOldInstallation(installDir);
@@ -1004,6 +1085,9 @@ async function installPlugin(nameWithVersion, args) {
1004
1085
  if (platforms.includes('cursor') && installDir) {
1005
1086
  installForCursor(installDir, { filter });
1006
1087
  }
1088
+ if (platforms.includes('kiro') && installDir) {
1089
+ installForKiro(installDir, { filter });
1090
+ }
1007
1091
 
1008
1092
  // Record in installed.json
1009
1093
  for (const depName of toFetch) {
@@ -1615,6 +1699,158 @@ function installForCursor(installDir, options = {}) {
1615
1699
  return true;
1616
1700
  }
1617
1701
 
1702
+ function installForKiro(installDir, options = {}) {
1703
+ console.log('\n[INSTALL] Installing for Kiro...\n');
1704
+ const { filter = null } = options;
1705
+ const cwd = process.cwd();
1706
+
1707
+ // Create target directories (all project-scoped)
1708
+ const skillsDir = path.join(cwd, '.kiro', 'skills');
1709
+ const steeringDir = path.join(cwd, '.kiro', 'steering');
1710
+ const agentsDir = path.join(cwd, '.kiro', 'agents');
1711
+ fs.mkdirSync(skillsDir, { recursive: true });
1712
+ fs.mkdirSync(steeringDir, { recursive: true });
1713
+ fs.mkdirSync(agentsDir, { recursive: true });
1714
+
1715
+ // Cleanup old agentsys steering files (only those matching known commands)
1716
+ const steeringMappingsForCleanup = discovery.getKiroSteeringMappings(installDir);
1717
+ const knownSteeringFiles = new Set(steeringMappingsForCleanup.map(([name]) => `${name}.md`));
1718
+ for (const f of fs.readdirSync(steeringDir).filter(f => f.endsWith('.md'))) {
1719
+ if (knownSteeringFiles.has(f)) {
1720
+ fs.unlinkSync(path.join(steeringDir, f));
1721
+ }
1722
+ }
1723
+
1724
+ // Collect known skill names from discovery before cleanup
1725
+ const pluginDirs = discovery.discoverPlugins(installDir);
1726
+ const knownSkillNames = new Set();
1727
+ for (const pluginName of pluginDirs) {
1728
+ const srcSkillsDir = path.join(installDir, 'plugins', pluginName, 'skills');
1729
+ if (!fs.existsSync(srcSkillsDir)) continue;
1730
+ for (const d of fs.readdirSync(srcSkillsDir, { withFileTypes: true })) {
1731
+ if (d.isDirectory() && /^[a-zA-Z0-9_-]+$/.test(d.name)) knownSkillNames.add(d.name);
1732
+ }
1733
+ }
1734
+
1735
+ // Cleanup old agentsys skill dirs (only known names, preserve user-created skills)
1736
+ for (const entry of fs.readdirSync(skillsDir, { withFileTypes: true })) {
1737
+ if (entry.isDirectory() && knownSkillNames.has(entry.name)) {
1738
+ fs.rmSync(path.join(skillsDir, entry.name), { recursive: true, force: true });
1739
+ }
1740
+ }
1741
+
1742
+ // Cleanup old agentsys agent JSON files (only those matching known agents)
1743
+ const knownAgentFiles = new Set();
1744
+ for (const pluginName of pluginDirs) {
1745
+ const srcAgentsDir = path.join(installDir, 'plugins', pluginName, 'agents');
1746
+ if (!fs.existsSync(srcAgentsDir)) continue;
1747
+ for (const f of fs.readdirSync(srcAgentsDir).filter(f => f.endsWith('.md'))) {
1748
+ knownAgentFiles.add(f.replace(/\.md$/, '.json'));
1749
+ }
1750
+ }
1751
+ for (const f of fs.readdirSync(agentsDir).filter(f => f.endsWith('.json'))) {
1752
+ if (knownAgentFiles.has(f)) {
1753
+ fs.unlinkSync(path.join(agentsDir, f));
1754
+ }
1755
+ }
1756
+
1757
+ // Install skills
1758
+ let skillCount = 0;
1759
+ for (const pluginName of pluginDirs) {
1760
+ const srcSkillsDir = path.join(installDir, 'plugins', pluginName, 'skills');
1761
+ if (!fs.existsSync(srcSkillsDir)) continue;
1762
+ const entries = fs.readdirSync(srcSkillsDir, { withFileTypes: true }).filter(d => d.isDirectory());
1763
+ for (const entry of entries) {
1764
+ if (!/^[a-zA-Z0-9_-]+$/.test(entry.name)) continue;
1765
+ if (filter && filter.skills && filter.skills.length > 0 && !filter.skills.includes(entry.name)) continue;
1766
+ const srcPath = path.join(srcSkillsDir, entry.name, 'SKILL.md');
1767
+ if (!fs.existsSync(srcPath)) continue;
1768
+ const destDir = path.join(skillsDir, entry.name);
1769
+ fs.mkdirSync(destDir, { recursive: true });
1770
+ let content = fs.readFileSync(srcPath, 'utf8');
1771
+ content = transforms.transformSkillForKiro(content, {
1772
+ pluginInstallPath: path.join(installDir, 'plugins', pluginName)
1773
+ });
1774
+ fs.writeFileSync(path.join(destDir, 'SKILL.md'), content);
1775
+ skillCount++;
1776
+ }
1777
+ }
1778
+
1779
+ // Install commands as steering files
1780
+ const steeringMappings = discovery.getKiroSteeringMappings(installDir);
1781
+ let steeringCount = 0;
1782
+ for (const [steeringName, plugin, sourceFile, description] of steeringMappings) {
1783
+ if (filter && filter.commands && filter.commands.length > 0) {
1784
+ if (!filter.commands.includes(steeringName)) continue;
1785
+ }
1786
+ const srcPath = path.join(installDir, 'plugins', plugin, 'commands', sourceFile);
1787
+ if (!fs.existsSync(srcPath)) {
1788
+ console.log(` [WARN] Source file not found: ${srcPath}`);
1789
+ continue;
1790
+ }
1791
+ let content = fs.readFileSync(srcPath, 'utf8');
1792
+ content = transforms.transformCommandForKiro(content, {
1793
+ pluginInstallPath: path.join(installDir, 'plugins', plugin),
1794
+ name: steeringName,
1795
+ description
1796
+ });
1797
+ fs.writeFileSync(path.join(steeringDir, `${steeringName}.md`), content);
1798
+ steeringCount++;
1799
+ }
1800
+
1801
+ // Install agents as JSON files
1802
+ let agentCount = 0;
1803
+ for (const pluginName of pluginDirs) {
1804
+ const srcAgentsDir = path.join(installDir, 'plugins', pluginName, 'agents');
1805
+ if (!fs.existsSync(srcAgentsDir)) continue;
1806
+ const agentFiles = fs.readdirSync(srcAgentsDir).filter(f => f.endsWith('.md'));
1807
+ for (const agentFile of agentFiles) {
1808
+ const agentName = agentFile.replace(/\.md$/, '');
1809
+ if (filter && filter.agents && filter.agents.length > 0 && !filter.agents.includes(agentName)) continue;
1810
+ const srcPath = path.join(srcAgentsDir, agentFile);
1811
+ let content = fs.readFileSync(srcPath, 'utf8');
1812
+ const jsonContent = transforms.transformAgentForKiro(content, {
1813
+ pluginInstallPath: path.join(installDir, 'plugins', pluginName)
1814
+ });
1815
+ fs.writeFileSync(path.join(agentsDir, `${agentName}.json`), jsonContent);
1816
+ agentCount++;
1817
+ }
1818
+ }
1819
+
1820
+ // Generate combined reviewer agents for Kiro's 4-agent experimental limit.
1821
+ // These are fallback agents that merge 2 review passes into 1 agent session.
1822
+ const combinedReviewers = [
1823
+ {
1824
+ name: 'reviewer-quality-security',
1825
+ description: 'Combined code quality and security reviewer for Kiro',
1826
+ roles: [
1827
+ { name: 'Code Quality', focus: 'Error handling, maintainability, naming, duplication, dead code, logging quality' },
1828
+ { name: 'Security', focus: 'Auth vulnerabilities, input validation, injection risks, secrets exposure, OWASP top 10' },
1829
+ ]
1830
+ },
1831
+ {
1832
+ name: 'reviewer-perf-test',
1833
+ description: 'Combined performance and test coverage reviewer for Kiro',
1834
+ roles: [
1835
+ { name: 'Performance', focus: 'Hot paths, algorithmic complexity, unnecessary allocations, N+1 queries, caching opportunities' },
1836
+ { name: 'Test Coverage', focus: 'Missing tests, edge cases, assertion quality, test isolation, mock correctness' },
1837
+ ]
1838
+ },
1839
+ ];
1840
+ for (const cr of combinedReviewers) {
1841
+ const json = transforms.generateCombinedReviewerAgent(cr.roles, cr.name, cr.description);
1842
+ fs.writeFileSync(path.join(agentsDir, `${cr.name}.json`), json);
1843
+ agentCount++;
1844
+ }
1845
+
1846
+ console.log(`\n[OK] Kiro installation complete!`);
1847
+ console.log(` Skills: ${skillCount} installed to ${skillsDir}`);
1848
+ console.log(` Steering: ${steeringCount} installed to ${steeringDir}`);
1849
+ console.log(` Agents: ${agentCount} installed to ${agentsDir} (includes 2 combined reviewers)`);
1850
+ console.log(' All content is project-scoped under .kiro/.\n');
1851
+ return true;
1852
+ }
1853
+
1618
1854
  function removeInstallation() {
1619
1855
  const installDir = getInstallDir();
1620
1856
 
@@ -1632,6 +1868,7 @@ function removeInstallation() {
1632
1868
  console.log(' - OpenCode: Remove files under ~/.config/opencode/ (commands/*.md, agents/*.md, skills/*/SKILL.md) and ~/.config/opencode/plugins/agentsys.ts');
1633
1869
  console.log(' - Codex: Remove ~/.codex/skills/*/');
1634
1870
  console.log(' - Cursor: Remove .cursor/skills/, .cursor/commands/, and .cursor/rules/agentsys-*.mdc from your project');
1871
+ console.log(' - Kiro: Remove .kiro/skills/, .kiro/steering/, and .kiro/agents/ from your project');
1635
1872
  }
1636
1873
 
1637
1874
  function printSubcommandHelp(subcommand) {
@@ -1652,7 +1889,7 @@ Examples:
1652
1889
  agentsys install perf@1.2.0 Install perf at version 1.2.0
1653
1890
 
1654
1891
  Options:
1655
- --tool <name> Install for a specific platform (claude, opencode, codex, cursor)
1892
+ --tool <name> Install for a specific platform (claude, opencode, codex, cursor, kiro)
1656
1893
  --tools <list> Install for multiple platforms (comma-separated)
1657
1894
 
1658
1895
  Notes:
@@ -1746,7 +1983,7 @@ agentsys v${VERSION} - Workflow automation for AI coding assistants
1746
1983
 
1747
1984
  Usage:
1748
1985
  agentsys Interactive installer (select platforms)
1749
- agentsys --tool <name> Install for single tool (claude, opencode, codex, cursor)
1986
+ agentsys --tool <name> Install for single tool (claude, opencode, codex, cursor, kiro)
1750
1987
  agentsys --tools <list> Install for multiple tools (comma-separated)
1751
1988
  agentsys --only <plugins> Install only specified plugins (comma-separated, resolves deps)
1752
1989
  agentsys --development Development mode: install to ~/.claude/plugins
@@ -1773,7 +2010,7 @@ Non-Interactive Examples:
1773
2010
  agentsys --tool claude # Install for Claude Code only
1774
2011
  agentsys --tool opencode # Install for OpenCode only
1775
2012
  agentsys --tools "claude,opencode" # Install for both
1776
- agentsys --tools claude,opencode,codex,cursor # Install for all
2013
+ agentsys --tools claude,opencode,codex,cursor,kiro # Install for all
1777
2014
  agentsys --only next-task # Install next-task + its dependencies
1778
2015
  agentsys --only "next-task,perf" # Install specific plugins + deps
1779
2016
 
@@ -1793,7 +2030,8 @@ Supported Platforms:
1793
2030
  claude - Claude Code (marketplace install or development mode)
1794
2031
  opencode - OpenCode (local commands + native plugin)
1795
2032
  codex - Codex CLI (local skills)
1796
- cursor - Cursor (project-scoped .mdc rules)
2033
+ cursor - Cursor (project-scoped skills + commands)
2034
+ kiro - Kiro (project-scoped steering + skills + agents)
1797
2035
 
1798
2036
  Install: npm install -g agentsys && agentsys
1799
2037
  Update: npm update -g agentsys && agentsys
@@ -1899,7 +2137,8 @@ async function main() {
1899
2137
  { value: 'claude', label: 'Claude Code' },
1900
2138
  { value: 'opencode', label: 'OpenCode' },
1901
2139
  { value: 'codex', label: 'Codex CLI' },
1902
- { value: 'cursor', label: 'Cursor' }
2140
+ { value: 'cursor', label: 'Cursor' },
2141
+ { value: 'kiro', label: 'Kiro' }
1903
2142
  ];
1904
2143
 
1905
2144
  selected = await multiSelect(
@@ -1928,10 +2167,8 @@ async function main() {
1928
2167
  if (entry) checkCoreCompat(entry);
1929
2168
  }
1930
2169
 
1931
- await fetchExternalPlugins(pluginNames, marketplace);
1932
-
1933
2170
  // Only copy to ~/.agentsys if OpenCode, Codex, or Cursor selected (they need local files)
1934
- const needsLocalInstall = selected.includes('opencode') || selected.includes('codex') || selected.includes('cursor');
2171
+ const needsLocalInstall = selected.includes('opencode') || selected.includes('codex') || selected.includes('cursor') || selected.includes('kiro');
1935
2172
  let installDir = null;
1936
2173
 
1937
2174
  if (needsLocalInstall) {
@@ -1941,6 +2178,8 @@ async function main() {
1941
2178
  installDependencies(installDir);
1942
2179
  }
1943
2180
 
2181
+ await fetchExternalPlugins(pluginNames, marketplace);
2182
+
1944
2183
  // Install for each platform
1945
2184
  const failedPlatforms = [];
1946
2185
  for (const platform of selected) {
@@ -1969,6 +2208,11 @@ async function main() {
1969
2208
  failedPlatforms.push('cursor');
1970
2209
  }
1971
2210
  break;
2211
+ case 'kiro':
2212
+ if (!installForKiro(installDir)) {
2213
+ failedPlatforms.push('kiro');
2214
+ }
2215
+ break;
1972
2216
  }
1973
2217
  }
1974
2218
 
@@ -2020,5 +2264,8 @@ module.exports = {
2020
2264
  loadComponents,
2021
2265
  resolveComponent,
2022
2266
  buildFilterFromComponent,
2023
- installForCursor
2267
+ resolvePluginSource,
2268
+ parseGitHubSource,
2269
+ installForCursor,
2270
+ installForKiro
2024
2271
  };