sra-skills 0.20.2 → 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.
- package/index.mjs +76 -22
- 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,10 +619,24 @@ 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})`);
|
|
624
|
+
}
|
|
625
|
+
if (isLocal) {
|
|
626
|
+
console.log(` ${C.b}Mode:${C.x} ${C.y}local${C.x} → ${repoPath}`);
|
|
585
627
|
}
|
|
586
628
|
console.log(` ${C.b}Tools:${C.x} ${tools.join(', ')}`);
|
|
587
|
-
|
|
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}`);
|
|
633
|
+
if (isLocal) {
|
|
634
|
+
let localRemoteUrl = '';
|
|
635
|
+
try { localRemoteUrl = execSync('git remote get-url origin', { cwd: repoPath, encoding: 'utf8' }).trim(); } catch {}
|
|
636
|
+
console.log(`\n ${C.r}⚠ Local mode will NOT auto-update with new releases.${C.x}`);
|
|
637
|
+
console.log(` ${C.d}To switch to clone mode (recommended):${C.x}`);
|
|
638
|
+
console.log(` ${C.d} npx sra-skills add ${localRemoteUrl || '<git-url>'} --name ${name}${C.x}`);
|
|
639
|
+
}
|
|
588
640
|
await promptSkillDeps([{ repoPath, skills }]);
|
|
589
641
|
warnMissingCredentials(skills, credPath || DEFAULT_CRED_PATH, repoPath);
|
|
590
642
|
console.log('');
|
|
@@ -692,15 +744,16 @@ if (cmd === 'add') {
|
|
|
692
744
|
}
|
|
693
745
|
console.log(` Updated ${prevRev.slice(0, 7)} → ${newRev.slice(0, 7)}`);
|
|
694
746
|
} else {
|
|
695
|
-
|
|
696
|
-
|
|
747
|
+
let localBranch = '';
|
|
748
|
+
try { localBranch = execSync('git rev-parse --abbrev-ref HEAD', { cwd: repo.path, encoding: 'utf8' }).trim(); } catch {}
|
|
749
|
+
console.log(` ${C.y}⚠ Local mode → ${repo.path}${localBranch ? ` (branch: ${localBranch})` : ''}${C.x}`);
|
|
750
|
+
console.log(` ${C.r}Will NOT auto-update — skills stay at the version on disk.${C.x}`);
|
|
697
751
|
let remoteUrl = '';
|
|
698
752
|
try {
|
|
699
753
|
remoteUrl = execSync('git remote get-url origin', { cwd: repo.path, encoding: 'utf8' }).trim();
|
|
700
754
|
} catch {}
|
|
701
|
-
console.log(` To
|
|
702
|
-
console.log(`
|
|
703
|
-
console.log(` Or: git -C ${repo.path} pull, then re-run ./scripts/install.sh`);
|
|
755
|
+
console.log(` ${C.d}To switch to clone mode (recommended):${C.x}`);
|
|
756
|
+
console.log(` ${C.d}npx sra-skills add ${remoteUrl || '<git-url>'} --name ${name}${C.x}`);
|
|
704
757
|
repo.updated_at = new Date().toISOString();
|
|
705
758
|
}
|
|
706
759
|
const allSkills = discoverSkills(repo.path);
|
|
@@ -833,7 +886,8 @@ if (cmd === 'add') {
|
|
|
833
886
|
}
|
|
834
887
|
if (repo.profile) {
|
|
835
888
|
const srcLabel = repo.profile_source === 'manual' ? 'manual' : 'auto';
|
|
836
|
-
|
|
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})`);
|
|
837
891
|
}
|
|
838
892
|
|
|
839
893
|
const totalEnabled = (repo.skills || []).length;
|
|
@@ -847,7 +901,7 @@ if (cmd === 'add') {
|
|
|
847
901
|
if (removeCount) dp.push(`${C.y}-${removeCount}${C.x}`);
|
|
848
902
|
skillsLine += ` (${dp.join(' ')})`;
|
|
849
903
|
}
|
|
850
|
-
skillsLine += `, ${C.d}${totalDisabled} disabled${C.x}`;
|
|
904
|
+
if (totalDisabled) skillsLine += `, ${C.d}${totalDisabled} disabled${C.x}`;
|
|
851
905
|
console.log(` ${C.b}Skills:${C.x} ${skillsLine}`);
|
|
852
906
|
|
|
853
907
|
const totalChanges = addCount + changes.removed.length + changes.stale.length;
|
|
@@ -859,7 +913,7 @@ if (cmd === 'add') {
|
|
|
859
913
|
} else if (totalChanges > 5) {
|
|
860
914
|
let changeLine = ` Changes: ${C.g}+${addCount}${C.x} ${C.y}-${changes.removed.length}${C.x}`;
|
|
861
915
|
if (changes.stale.length) changeLine += ` ${C.r}✕${changes.stale.length}${C.x}`;
|
|
862
|
-
changeLine += ` (run \`npx sra-skills
|
|
916
|
+
changeLine += ` (run \`npx sra-skills manage\` for details)`;
|
|
863
917
|
console.log(changeLine);
|
|
864
918
|
}
|
|
865
919
|
|