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.
- package/index.mjs +60 -17
- 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
|
|
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
|
|
86
|
-
|
|
87
|
-
|
|
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
|
-
|
|
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
|
|
117
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
916
|
+
changeLine += ` (run \`npx sra-skills manage\` for details)`;
|
|
874
917
|
console.log(changeLine);
|
|
875
918
|
}
|
|
876
919
|
|