shakers-cli-beta 0.8.0-beta.0 → 0.8.0-beta.1
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/config.js +7 -3
- package/package.json +1 -1
- package/src/config.js +15 -0
- package/src/i18n.js +11 -5
- package/src/locale.js +23 -2
package/bin/config.js
CHANGED
|
@@ -15,24 +15,26 @@ const {
|
|
|
15
15
|
const VALID_LANGS = new Set(['es', 'en']);
|
|
16
16
|
const ENDPOINT_KEYS = new Set(['certsBase', 'hubBase', 'ingestEndpoint', 'profileUrl']);
|
|
17
17
|
const PROFILE_VALUES = new Set(['external', 'talent']);
|
|
18
|
-
const SETTABLE = new Set([...ENDPOINT_KEYS, 'profile']);
|
|
18
|
+
const SETTABLE = new Set([...ENDPOINT_KEYS, 'profile', 'lang']);
|
|
19
19
|
|
|
20
20
|
const COPY = {
|
|
21
21
|
es: {
|
|
22
|
-
help: 'config — lee/escribe la configuracion NO sensible del CLI (bases de hub/certs, perfil). Uso: shakers config <list|get [clave]|set clave valor>\n Claves: certsBase, hubBase, ingestEndpoint, profileUrl, profile.\n Los secretos (tokens/credenciales) NUNCA se guardan aqui: se obtienen con `shakers login` o por entorno.',
|
|
22
|
+
help: 'config — lee/escribe la configuracion NO sensible del CLI (bases de hub/certs, perfil, idioma). Uso: shakers config <list|get [clave]|set clave valor>\n Claves: certsBase, hubBase, ingestEndpoint, profileUrl, profile, lang.\n Los secretos (tokens/credenciales) NUNCA se guardan aqui: se obtienen con `shakers login` o por entorno.',
|
|
23
23
|
unknownKey: (k) => `clave desconocida: ${k}. Claves validas: ${[...SETTABLE].join(', ')}.`,
|
|
24
24
|
needValue: 'set necesita <clave> <valor>.',
|
|
25
25
|
badEndpoint: (k, r) => `valor invalido para ${k} (${r}). Debe ser una URL http de loopback o https.`,
|
|
26
26
|
badProfile: 'profile debe ser "external" o "talent".',
|
|
27
|
+
badLang: 'lang debe ser "es" o "en".',
|
|
27
28
|
saved: (k, v) => `guardado ${k} = ${v}`,
|
|
28
29
|
resolvedHeading: 'Resueltos (env > config.json > default local):',
|
|
29
30
|
},
|
|
30
31
|
en: {
|
|
31
|
-
help: 'config — read/write the CLI NON-sensitive configuration (hub/certs bases, profile). Usage: shakers config <list|get [key]|set key value>\n Keys: certsBase, hubBase, ingestEndpoint, profileUrl, profile.\n Secrets (tokens/credentials) are NEVER stored here: get them via `shakers login` or the environment.',
|
|
32
|
+
help: 'config — read/write the CLI NON-sensitive configuration (hub/certs bases, profile, language). Usage: shakers config <list|get [key]|set key value>\n Keys: certsBase, hubBase, ingestEndpoint, profileUrl, profile, lang.\n Secrets (tokens/credentials) are NEVER stored here: get them via `shakers login` or the environment.',
|
|
32
33
|
unknownKey: (k) => `unknown key: ${k}. Valid keys: ${[...SETTABLE].join(', ')}.`,
|
|
33
34
|
needValue: 'set needs <key> <value>.',
|
|
34
35
|
badEndpoint: (k, r) => `invalid value for ${k} (${r}). Must be a loopback http URL or https.`,
|
|
35
36
|
badProfile: 'profile must be "external" or "talent".',
|
|
37
|
+
badLang: 'lang must be "es" or "en".',
|
|
36
38
|
saved: (k, v) => `saved ${k} = ${v}`,
|
|
37
39
|
resolvedHeading: 'Resolved (env > config.json > local default):',
|
|
38
40
|
},
|
|
@@ -98,6 +100,8 @@ async function run(argv = process.argv.slice(2), { env = process.env, out = null
|
|
|
98
100
|
if (!SETTABLE.has(key)) { line(c.unknownKey(key)); process.exitCode = 1; return; }
|
|
99
101
|
if (key === 'profile') {
|
|
100
102
|
if (!PROFILE_VALUES.has(value)) { line(c.badProfile); process.exitCode = 1; return; }
|
|
103
|
+
} else if (key === 'lang') {
|
|
104
|
+
if (!VALID_LANGS.has(value)) { line(c.badLang); process.exitCode = 1; return; }
|
|
101
105
|
} else if (key !== 'profileUrl') {
|
|
102
106
|
const v = validateEndpoint(value);
|
|
103
107
|
if (!v.ok) { line(c.badEndpoint(key, v.reason)); process.exitCode = 1; return; }
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "shakers-cli-beta",
|
|
3
|
-
"version": "0.8.0-beta.
|
|
3
|
+
"version": "0.8.0-beta.1",
|
|
4
4
|
"description": "Shakers AI usage & skills evaluator: a local-first CLI (`shakers` binary) plus a local MCP server. Commands: ai-usage (scan + classify your AI-tool setup, T0-T7), certify (certify Skills from your code), share (branded LinkedIn card).",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"bin": {
|
package/src/config.js
CHANGED
|
@@ -36,6 +36,18 @@ function isTalentProfile(env = process.env) {
|
|
|
36
36
|
return getProfile(env) === 'talent';
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
+
// Explicit UI-language preference: SHAKERS_CLI_LANG env > config.json `lang`.
|
|
40
|
+
// Returns 'es' | 'en', or null when neither is set (OS detection then fills it, see i18n.js).
|
|
41
|
+
const VALID_UI_LANGS = new Set(['es', 'en']);
|
|
42
|
+
|
|
43
|
+
function getUiLang(env = process.env) {
|
|
44
|
+
const fromEnv = String(readEnv(env, 'LANG') || '').trim().toLowerCase();
|
|
45
|
+
if (VALID_UI_LANGS.has(fromEnv)) return fromEnv;
|
|
46
|
+
const fromFile = String(loadConfigFile(env).lang || '').trim().toLowerCase();
|
|
47
|
+
if (VALID_UI_LANGS.has(fromFile)) return fromFile;
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
|
|
39
51
|
// config.json holds the superadmin session token (ADR-027) and consent/ endpoint data, so it must never be briefly world/group-readable.
|
|
40
52
|
function saveConfigFile(config, env = process.env) {
|
|
41
53
|
const dir = configDir(env);
|
|
@@ -771,6 +783,9 @@ module.exports = {
|
|
|
771
783
|
VALID_PROFILES,
|
|
772
784
|
getProfile,
|
|
773
785
|
isTalentProfile,
|
|
786
|
+
// UI language preference (env SHAKERS_CLI_LANG > config.json `lang`).
|
|
787
|
+
VALID_UI_LANGS,
|
|
788
|
+
getUiLang,
|
|
774
789
|
// Baked backend target flavor (staging | dev | local).
|
|
775
790
|
ENV_PROFILES,
|
|
776
791
|
getEnvProfileName,
|
package/src/i18n.js
CHANGED
|
@@ -3238,13 +3238,19 @@ function resolveLang(langCode) {
|
|
|
3238
3238
|
return langCode && /^es/i.test(langCode) ? 'es' : 'en';
|
|
3239
3239
|
}
|
|
3240
3240
|
|
|
3241
|
-
//
|
|
3242
|
-
function
|
|
3243
|
-
return
|
|
3241
|
+
// Explicit user preference (SHAKERS_CLI_LANG env > config.json `lang`) wins over OS detection; null when unset.
|
|
3242
|
+
function preferredUiLang(env = process.env) {
|
|
3243
|
+
return require('./config').getUiLang(env);
|
|
3244
3244
|
}
|
|
3245
3245
|
|
|
3246
|
-
|
|
3247
|
-
|
|
3246
|
+
// Single entry point for report callers (bin/report.js): explicit preference > OS language (see locale.js), resolved to the catalog code ('es' or 'en').
|
|
3247
|
+
// `sys` overrides the macOS/Intl readers (tests inject nulls to exercise the env path deterministically).
|
|
3248
|
+
function detectReportLang(env = process.env, sys) {
|
|
3249
|
+
return resolveLang(preferredUiLang(env) || detectLangCode(env, sys));
|
|
3250
|
+
}
|
|
3251
|
+
|
|
3252
|
+
function detectFlowLang(env = process.env, sys) {
|
|
3253
|
+
return toLowerLang(preferredUiLang(env) || detectLangCode(env, sys));
|
|
3248
3254
|
}
|
|
3249
3255
|
|
|
3250
3256
|
function getCatalog(lang) {
|
package/src/locale.js
CHANGED
|
@@ -22,6 +22,22 @@ function langFromEnv(env) {
|
|
|
22
22
|
return null;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
+
// macOS DISPLAY language (top of "Preferred languages"), e.g. ("en-US", "es-ES").
|
|
26
|
+
// This is the UI-language intent, unlike AppleLocale/LANG which carry the region.
|
|
27
|
+
function langFromAppleLanguages() {
|
|
28
|
+
if (process.platform !== 'darwin') return null;
|
|
29
|
+
try {
|
|
30
|
+
const out = execFileSync('defaults', ['read', '-g', 'AppleLanguages'], {
|
|
31
|
+
timeout: 2000,
|
|
32
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
33
|
+
}).toString('utf8');
|
|
34
|
+
const first = out.replace(/[()]/g, '').split(',')[0].replace(/["\s]/g, '');
|
|
35
|
+
return langFromLocaleString(first);
|
|
36
|
+
} catch {
|
|
37
|
+
return null; // key not set, command missing, or any other failure: ignored
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
25
41
|
function langFromAppleLocale() {
|
|
26
42
|
if (process.platform !== 'darwin') return null;
|
|
27
43
|
try {
|
|
@@ -44,8 +60,13 @@ function langFromIntl() {
|
|
|
44
60
|
}
|
|
45
61
|
|
|
46
62
|
// Returns the detected language code ('es', 'en', 'fr', ...) or null if no signal could be resolved.
|
|
47
|
-
|
|
48
|
-
|
|
63
|
+
// macOS display language (AppleLanguages) wins over env LANG/LC_ALL (region/format), then AppleLocale, then Intl.
|
|
64
|
+
// `sys` overrides the macOS/Intl readers for deterministic tests (they ignore `env` and read the real host).
|
|
65
|
+
function detectLangCode(env = process.env, sys = {}) {
|
|
66
|
+
const appleLanguages = sys.appleLanguages || langFromAppleLanguages;
|
|
67
|
+
const appleLocale = sys.appleLocale || langFromAppleLocale;
|
|
68
|
+
const intl = sys.intl || langFromIntl;
|
|
69
|
+
return appleLanguages() || langFromEnv(env) || appleLocale() || intl() || null;
|
|
49
70
|
}
|
|
50
71
|
|
|
51
72
|
module.exports = { detectLangCode, langFromLocaleString };
|