sra-skills 0.20.3 → 0.20.4

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.

Potentially problematic release.


This version of sra-skills might be problematic. Click here for more details.

Files changed (2) hide show
  1. package/index.mjs +60 -17
  2. package/package.json +1 -1
package/index.mjs CHANGED
@@ -67,6 +67,7 @@ function writeIdentityCache(email, team, role, profileKey) {
67
67
  }, null, 2) + '\n');
68
68
  }
69
69
 
70
+ // Returns: { status: 'ok', team, role } | { status: 'not_found' } | { status: 'error' }
70
71
  async function fetchRoleFromApi(email) {
71
72
  try {
72
73
  const url = `${WHOIS_API_URL}?email=${encodeURIComponent(email)}`;
@@ -78,21 +79,27 @@ async function fetchRoleFromApi(email) {
78
79
  });
79
80
  clearTimeout(timeout);
80
81
  const result = await resp.json();
81
- if (!result.success) return null;
82
+ if (!result.success) return { status: 'not_found' };
82
83
  const data = result.data || {};
83
84
  const team = parseTeamFromDept(data.department_path || '');
84
85
  const role = (data.role || '').toLowerCase().trim();
85
- if (team && role) return [team, role];
86
- } catch {}
87
- return null;
86
+ if (team && role) return { status: 'ok', team, role };
87
+ return { status: 'not_found' };
88
+ } catch {
89
+ return { status: 'error' };
90
+ }
88
91
  }
89
92
 
90
- async function promptProfileSelection(profilesData) {
93
+ async function promptProfileSelection(profilesData, { email = null, reason = 'error' } = {}) {
91
94
  if (!process.stdin.isTTY || !profilesData?.profiles) return null;
92
95
  const profiles = profilesData.profiles;
93
96
  const keys = Object.keys(profiles);
94
97
  if (!keys.length) return null;
95
- console.log(`\n${C.y}⚠ Could not auto-detect your role (API unavailable). Please select manually:${C.x}`);
98
+ const identityHint = email ? ` (identity: ${C.d}${email}${C.x})` : '';
99
+ const msg = reason === 'not_found'
100
+ ? `\n${C.y}⚠ No matching role found for this identity${identityHint}. Please select manually:${C.x}`
101
+ : `\n${C.y}⚠ Role detection API unavailable${identityHint}. Please select manually:${C.x}`;
102
+ console.log(msg);
96
103
  const chosen = await select({
97
104
  message: 'Select your team/role profile',
98
105
  choices: keys.map(k => ({ name: `${k} — ${profiles[k].label || k}`, value: k })),
@@ -112,16 +119,15 @@ async function detectRole(email, opts = {}) {
112
119
  } catch {}
113
120
  }
114
121
  const result = await fetchRoleFromApi(email);
115
- if (result) {
116
- const [team, role] = result;
117
- const profileKey = `${team}/${role}`;
118
- writeIdentityCache(email, team, role, profileKey);
122
+ if (result.status === 'ok') {
123
+ const profileKey = `${result.team}/${result.role}`;
124
+ writeIdentityCache(email, result.team, result.role, profileKey);
119
125
  return profileKey;
120
126
  }
121
127
  if (cache && cache.email === email) return cache.profile_key || fallbackKey || 'other';
122
128
  if (fallbackKey) return fallbackKey;
123
129
  if (interactive && profilesData) {
124
- const chosen = await promptProfileSelection(profilesData);
130
+ const chosen = await promptProfileSelection(profilesData, { email, reason: result.status });
125
131
  if (chosen) {
126
132
  const parts = chosen.split('/');
127
133
  const team = parts[0] || '';
@@ -131,7 +137,10 @@ async function detectRole(email, opts = {}) {
131
137
  }
132
138
  }
133
139
  if (!interactive) {
134
- console.log(`${C.y}⚠ Could not auto-detect role (API unavailable, no cache). Using default profile "other".${C.x}`);
140
+ const hint = result.status === 'not_found' && email
141
+ ? `no matching role found for ${email}`
142
+ : result.status === 'not_found' ? 'no matching role found' : 'API unavailable';
143
+ console.log(`${C.y}⚠ Could not auto-detect role (${hint}, no cache). Using default profile "other".${C.x}`);
135
144
  }
136
145
  return 'other';
137
146
  }
@@ -479,6 +488,21 @@ if (cmd === 'add') {
479
488
  let profileIdentity = null; // email used for auto-detection
480
489
  let skills;
481
490
 
491
+ // --- Skill selection (three-state model) ---
492
+ // Skills have three states: enabled | disabled | available (implicit).
493
+ // enabled: installed, stored in manifest.skills
494
+ // disabled: user explicitly rejected, stored in manifest.disabled_skills
495
+ // available: exists in repo but user hasn't decided — not stored (implicit)
496
+ //
497
+ // `disabled` is populated ONLY when user explicitly rejects a recommended skill
498
+ // in an interactive prompt. Specifically:
499
+ // - Fresh install: disabled = profileSkillNames − selectedSet (rejected recommendations)
500
+ // - Update: disabled += rejected newProfile skills from promptSkillChanges
501
+ // - Non-interactive: never populates disabled (system choice, not user intent)
502
+ //
503
+ // In legacy mode (no profile), disabled_skills = discoveredNames − selectedNames
504
+ // (all uninstalled = disabled, since there's no profile-based update logic).
505
+
482
506
  if (skillsFilter) {
483
507
  const unknown = [...skillsFilter].filter(n => !discoveredNames.has(n));
484
508
  if (unknown.length) console.warn(`Warning: requested skill(s) not found in repo: ${unknown.join(', ')}`);
@@ -493,6 +517,7 @@ if (cmd === 'add') {
493
517
  } else {
494
518
  const profilesData = loadProfiles(repoPath);
495
519
  if (!profilesData) {
520
+ // Legacy path (no profiles.json): disabled_skills computed at manifest write time
496
521
  if (isRepeat) {
497
522
  skills = allDiscovered.filter(s => !disabled.has(s.name));
498
523
  } else if (skipPrompt) {
@@ -501,6 +526,7 @@ if (cmd === 'add') {
501
526
  skills = await selectSkills(allDiscovered);
502
527
  }
503
528
  } else {
529
+ // Profile path: resolve profile key
504
530
  if (explicitProfile) {
505
531
  profileKeyUsed = explicitProfile;
506
532
  profileSource = 'manual';
@@ -519,6 +545,7 @@ if (cmd === 'add') {
519
545
  profileSkillNames = validateProfileSkills(profileSkillNames, discoveredNames);
520
546
 
521
547
  if (isRepeat) {
548
+ // Three-way diff: profile vs enabled vs disabled
522
549
  const diff = computeSkillDiff(allDiscovered, profileSkillNames, enabled, disabled);
523
550
  const [toEnable, toDisable, prompted] = skipPrompt
524
551
  ? [diff.newProfile, new Set(), false]
@@ -533,11 +560,19 @@ if (cmd === 'add') {
533
560
  disabled = new Set([...disabled, ...toDisable].filter(n => !toEnable.has(n) && discoveredNames.has(n)));
534
561
  }
535
562
  } else {
563
+ // Fresh install with profile
536
564
  if (skipPrompt) {
565
+ // Non-interactive: install profile defaults, don't mark others as disabled
537
566
  skills = allDiscovered.filter(s => profileSkillNames.has(s.name));
538
567
  } else {
539
568
  [skills, profileKeyUsed] = await selectSkillsWithProfile(
540
569
  allDiscovered, profileSkillNames, profileKeyUsed, profilesData);
570
+ // disabled = only rejected recommendations (profile-recommended but user unchecked)
571
+ // Skills in "Other" left unchecked are "available", not disabled
572
+ if (process.stdin.isTTY) {
573
+ const selectedSet = new Set(skills.map(s => s.name));
574
+ disabled = new Set([...profileSkillNames].filter(n => !selectedSet.has(n)));
575
+ }
541
576
  }
542
577
  }
543
578
  }
@@ -557,6 +592,9 @@ if (cmd === 'add') {
557
592
  console.log(` Linked ${parts.join(', ')} [${tools.join(', ')}]`);
558
593
  runPostInstall(repoPath);
559
594
 
595
+ // Compute disabled_skills for manifest:
596
+ // - Profile mode: only user-rejected skills (from `disabled` set)
597
+ // - Legacy mode: all uninstalled skills (computed from discovered − selected)
560
598
  const selectedNames = new Set(skills.map(s => s.name));
561
599
  const disabledList = profileKeyUsed != null
562
600
  ? [...disabled].sort()
@@ -581,13 +619,17 @@ if (cmd === 'add') {
581
619
  console.log(`\n${C.b}Done.${C.x} ${name} ${C.g}${ver}${C.x}\n`);
582
620
  if (profileKeyUsed) {
583
621
  const srcLabel = profileSource === 'manual' ? 'manual' : 'auto';
584
- console.log(` ${C.b}Profile:${C.x} ${C.g}${profileKeyUsed}${C.x} (${srcLabel})`);
622
+ const identityPart = profileIdentity ? `, ${C.d}${profileIdentity}${C.x}` : '';
623
+ console.log(` ${C.b}Profile:${C.x} ${C.g}${profileKeyUsed}${C.x} (${srcLabel}${identityPart})`);
585
624
  }
586
625
  if (isLocal) {
587
626
  console.log(` ${C.b}Mode:${C.x} ${C.y}local${C.x} → ${repoPath}`);
588
627
  }
589
628
  console.log(` ${C.b}Tools:${C.x} ${tools.join(', ')}`);
590
- console.log(` ${C.b}Skills:${C.x} ${C.g}${skills.length} enabled${C.x}, ${C.d}${disabledList.length} disabled${C.x}`);
629
+ const skillsSummary = disabledList.length
630
+ ? `${C.g}${skills.length} enabled${C.x}, ${C.d}${disabledList.length} disabled${C.x}`
631
+ : `${C.g}${skills.length} enabled${C.x}`;
632
+ console.log(` ${C.b}Skills:${C.x} ${skillsSummary}`);
591
633
  if (isLocal) {
592
634
  let localRemoteUrl = '';
593
635
  try { localRemoteUrl = execSync('git remote get-url origin', { cwd: repoPath, encoding: 'utf8' }).trim(); } catch {}
@@ -844,7 +886,8 @@ if (cmd === 'add') {
844
886
  }
845
887
  if (repo.profile) {
846
888
  const srcLabel = repo.profile_source === 'manual' ? 'manual' : 'auto';
847
- console.log(` ${C.b}Profile:${C.x} ${C.g}${repo.profile}${C.x} (${srcLabel})`);
889
+ const identityPart = repo.profile_identity ? `, ${C.d}${repo.profile_identity}${C.x}` : '';
890
+ console.log(` ${C.b}Profile:${C.x} ${C.g}${repo.profile}${C.x} (${srcLabel}${identityPart})`);
848
891
  }
849
892
 
850
893
  const totalEnabled = (repo.skills || []).length;
@@ -858,7 +901,7 @@ if (cmd === 'add') {
858
901
  if (removeCount) dp.push(`${C.y}-${removeCount}${C.x}`);
859
902
  skillsLine += ` (${dp.join(' ')})`;
860
903
  }
861
- skillsLine += `, ${C.d}${totalDisabled} disabled${C.x}`;
904
+ if (totalDisabled) skillsLine += `, ${C.d}${totalDisabled} disabled${C.x}`;
862
905
  console.log(` ${C.b}Skills:${C.x} ${skillsLine}`);
863
906
 
864
907
  const totalChanges = addCount + changes.removed.length + changes.stale.length;
@@ -870,7 +913,7 @@ if (cmd === 'add') {
870
913
  } else if (totalChanges > 5) {
871
914
  let changeLine = ` Changes: ${C.g}+${addCount}${C.x} ${C.y}-${changes.removed.length}${C.x}`;
872
915
  if (changes.stale.length) changeLine += ` ${C.r}✕${changes.stale.length}${C.x}`;
873
- changeLine += ` (run \`npx sra-skills skill list\` for details)`;
916
+ changeLine += ` (run \`npx sra-skills manage\` for details)`;
874
917
  console.log(changeLine);
875
918
  }
876
919
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sra-skills",
3
- "version": "0.20.3",
3
+ "version": "0.20.4",
4
4
  "description": "SRA agent skills installer — manage AI skill repos",
5
5
  "bin": {
6
6
  "sra": "index.mjs",