sra-skills 0.14.0 → 0.14.2

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 CHANGED
@@ -7,6 +7,7 @@ import {
7
7
  getVersion, parseSkillFrontmatter, DEFAULT_CRED_PATH,
8
8
  loadCredentials, getCredValue, repoNameFromUrl, mergeCredentials,
9
9
  runToolSetup, runPostInstall, promptSkillDeps, findSkillsWithDeps,
10
+ ensureIdentity,
10
11
  } from './lib.mjs';
11
12
  import { existsSync, mkdirSync, lstatSync, unlinkSync } from 'fs';
12
13
  import { join, basename, resolve } from 'path';
@@ -80,6 +81,8 @@ const cmdArgs = rawArgs.slice(1);
80
81
  if (cmd === 'add') {
81
82
  const isLocal = cmdArgs.includes('--local');
82
83
  const skipPrompt = cmdArgs.includes('-y') || cmdArgs.includes('--yes');
84
+ const identityIdx = cmdArgs.indexOf('--default-identity');
85
+ const defaultIdentity = identityIdx >= 0 ? cmdArgs[identityIdx + 1] : null;
83
86
  const tools = parseToolFlag(cmdArgs) || detectTools();
84
87
  const nameIdx = cmdArgs.indexOf('--name');
85
88
  let url, name, repoPath;
@@ -140,6 +143,7 @@ if (cmd === 'add') {
140
143
  console.log(`Installing ${skills.length} skills, tools: [${tools.join(', ')}]`);
141
144
  linkSkills(skills, tools);
142
145
  const credPath = mergeCredentials(repoPath);
146
+ await ensureIdentity(skipPrompt, defaultIdentity);
143
147
  runToolSetup(repoPath, tools);
144
148
  runPostInstall(repoPath);
145
149
 
@@ -189,6 +193,8 @@ if (cmd === 'add') {
189
193
  const m = readManifest();
190
194
  const tools = parseToolFlag(cmdArgs) || m.tools || detectTools();
191
195
  const skipPrompt = cmdArgs.includes('-y') || cmdArgs.includes('--yes');
196
+ const identityIdx = cmdArgs.indexOf('--default-identity');
197
+ const defaultIdentity = identityIdx >= 0 ? cmdArgs[identityIdx + 1] : null;
192
198
  const target = cmdArgs.find(a => !a.startsWith('-'));
193
199
  const targets = target ? [target] : Object.keys(m.repos);
194
200
  const depsEntries = [];
@@ -264,6 +270,7 @@ if (cmd === 'add') {
264
270
  const skills = allSkills.filter(s => !disabled.has(s.name));
265
271
  linkSkills(skills, tools);
266
272
  mergeCredentials(repo.path);
273
+ await ensureIdentity(skipPrompt, defaultIdentity);
267
274
  runToolSetup(repo.path, tools);
268
275
  runPostInstall(repo.path);
269
276
  depsEntries.push({ repoPath: repo.path, skills });
package/lib.mjs CHANGED
@@ -317,6 +317,94 @@ export function parseSkillFrontmatter(skillPath) {
317
317
 
318
318
  export const DEFAULT_CRED_PATH = join(HOME, '.config', 'sra', 'credentials.json');
319
319
 
320
+ export const COMPANY_EMAIL_DOMAINS = new Set(['shopee.com', 'garena.com', 'seagroup.com', 'sea.com']);
321
+ const PLACEHOLDER_LOCAL_PARTS = new Set([
322
+ 'your.name', 'yourname', 'your_name', 'your-name',
323
+ 'your.email', 'youremail', 'your_email',
324
+ 'test', 'example', 'placeholder', 'changeme',
325
+ ]);
326
+
327
+ export function isCompanyEmail(email) {
328
+ if (!email || !email.includes('@')) return false;
329
+ const [local, domain] = [email.substring(0, email.lastIndexOf('@')), email.substring(email.lastIndexOf('@') + 1)];
330
+ if (PLACEHOLDER_LOCAL_PARTS.has(local.toLowerCase())) return false;
331
+ return COMPANY_EMAIL_DOMAINS.has(domain.toLowerCase());
332
+ }
333
+
334
+ export async function ensureIdentity(skipPrompt, defaultIdentityArg) {
335
+ const credPath = DEFAULT_CRED_PATH;
336
+ let existing = {};
337
+ if (existsSync(credPath)) {
338
+ try {
339
+ existing = JSON.parse(readFileSync(credPath, 'utf8'));
340
+ } catch {
341
+ console.error(`Warning: ${credPath} contains invalid JSON, credentials will be reset`);
342
+ }
343
+ }
344
+
345
+ const current = existing?.default?.identity || '';
346
+ if (isCompanyEmail(current)) return current;
347
+
348
+ // --default-identity provided
349
+ if (defaultIdentityArg) {
350
+ if (!isCompanyEmail(defaultIdentityArg)) {
351
+ const domains = [...COMPANY_EMAIL_DOMAINS].sort().join(', ');
352
+ console.error(`Error: --default-identity "${defaultIdentityArg}" is not a company email (allowed domains: ${domains})`);
353
+ process.exit(1);
354
+ }
355
+ existing = existing || {};
356
+ (existing.default = existing.default || {}).identity = defaultIdentityArg;
357
+ mkdirSync(join(HOME, '.config', 'sra'), { recursive: true });
358
+ writeFileSync(credPath, JSON.stringify(existing, null, 2) + '\n');
359
+ console.log(` Company email set: ${defaultIdentityArg}`);
360
+ return defaultIdentityArg;
361
+ }
362
+
363
+ // --yes mode without --default-identity
364
+ if (skipPrompt) {
365
+ const domains = [...COMPANY_EMAIL_DOMAINS].sort().join(', ');
366
+ console.error(`Error: --yes requires --default-identity <email@company.com> when no valid identity is set.`);
367
+ process.exit(1);
368
+ }
369
+
370
+ // Non-TTY
371
+ if (!process.stdin.isTTY) {
372
+ const domains = [...COMPANY_EMAIL_DOMAINS].sort().join(', ');
373
+ console.error(`Error: default.identity not configured and stdin is not a terminal.`);
374
+ console.error(`Use --default-identity=you@company.com (${domains})`);
375
+ process.exit(1);
376
+ }
377
+
378
+ // Interactive prompt
379
+ let gitEmail = '';
380
+ try {
381
+ gitEmail = execSync('git config --global user.email', { encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] }).trim();
382
+ } catch {}
383
+ const prefill = isCompanyEmail(gitEmail) ? gitEmail : '';
384
+ const domains = [...COMPANY_EMAIL_DOMAINS].sort().join(', ');
385
+
386
+ // Need to import input from @inquirer/prompts
387
+ const { input } = await import('@inquirer/prompts');
388
+ console.log(`Company email required for telemetry (accepted domains: ${domains})`);
389
+
390
+ while (true) {
391
+ const raw = await input({
392
+ message: 'Company email',
393
+ default: prefill || undefined,
394
+ });
395
+ const email = raw.trim();
396
+ if (isCompanyEmail(email)) {
397
+ existing = existing || {};
398
+ (existing.default = existing.default || {}).identity = email;
399
+ mkdirSync(join(HOME, '.config', 'sra'), { recursive: true });
400
+ writeFileSync(credPath, JSON.stringify(existing, null, 2) + '\n');
401
+ console.log(` Company email set: ${email}`);
402
+ return email;
403
+ }
404
+ console.log(` Invalid: "${email}" — must end with one of: ${domains}`);
405
+ }
406
+ }
407
+
320
408
  export function loadCredentials(credPath) {
321
409
  const p = credPath || DEFAULT_CRED_PATH;
322
410
  if (!existsSync(p)) return {};
package/manage.mjs CHANGED
@@ -152,7 +152,11 @@ export async function main() {
152
152
  const enabled = (repo.skills || []).length;
153
153
  return `${name} (${enabled}/${all.length})`;
154
154
  });
155
+ // Preserve current repo selection — setItems resets to 0 and fires
156
+ // 'select item', which would switch currentRepoName to the first repo.
157
+ const prevIdx = currentRepoName ? names.indexOf(currentRepoName) : 0;
155
158
  reposPanel.setItems(items);
159
+ if (prevIdx >= 0) reposPanel.select(prevIdx);
156
160
  }
157
161
 
158
162
  function renderSkills() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sra-skills",
3
- "version": "0.14.0",
3
+ "version": "0.14.2",
4
4
  "description": "SRA agent skills installer — manage AI skill repos",
5
5
  "bin": {
6
6
  "sra": "index.mjs",