shakers-cli 0.15.0 → 0.17.0
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/README.md +56 -1203
- package/bin/ai-usage.js +32 -58
- package/bin/login.js +10 -10
- package/bin/mcp.js +17 -12
- package/bin/mcpb.js +1 -1
- package/bin/onboarding.js +3 -2
- package/bin/register.js +19 -5
- package/bin/report-html.js +2 -2
- package/bin/shakers-cli.js +352 -0
- package/bin/shakers.js +20 -344
- package/bin/share.js +1 -1
- package/bin/superadmin.js +5 -5
- package/bin/version.js +3 -0
- package/package.json +12 -3
- package/src/account-marker.js +40 -0
- package/src/account-trace.js +23 -0
- package/src/agent-category.js +1 -1
- package/src/agent-evaluation.js +3 -3
- package/src/agent-org-chart.js +5 -5
- package/src/agent-synthesis.js +3 -3
- package/src/agent-usage.js +1 -1
- package/src/agents-client.js +4 -4
- package/src/ai-usage-review.js +101 -0
- package/src/ai.js +1 -1
- package/src/ansi.js +1 -1
- package/src/auth-client.js +18 -4
- package/src/auth-method-picker.js +1 -1
- package/src/auth-methods.js +1 -1
- package/src/auth-session-store.js +18 -8
- package/src/authorship-mismatch.js +1 -1
- package/src/authorship.js +2 -2
- package/src/automations-detector.js +1 -1
- package/src/backend-request.js +5 -3
- package/src/brand-ansi.js +1 -1
- package/src/browser-tools-detector.js +1 -1
- package/src/certify-disclaimer.js +1 -1
- package/src/certify-remediation-prompt.js +1 -1
- package/src/cli-args.js +2 -2
- package/src/command-audit.js +8 -5
- package/src/config-dir.js +1 -1
- package/src/config.js +114 -48
- package/src/consent-flow.js +7 -6
- package/src/consent-skip.js +1 -1
- package/src/detection-evidence.js +2 -2
- package/src/detection-noise.js +1 -1
- package/src/detectors.js +1 -1
- package/src/device-login.js +12 -1
- package/src/email-verification.js +3 -3
- package/src/env-paths.js +1 -1
- package/src/find-projects-client.js +12 -0
- package/src/find-projects-flow.js +1 -13
- package/src/git-activity.js +1 -1
- package/src/graph-certs.js +3 -3
- package/src/graph-scan.js +9 -9
- package/src/i18n.js +148 -138
- package/src/interactive-select.js +1 -1
- package/src/interview-client.js +6 -1
- package/src/interview-transcript.js +1 -1
- package/src/legal-notice.js +1 -1
- package/src/maturity.js +6 -6
- package/src/mcp-ai-usage-result-tool.js +34 -12
- package/src/mcp-ai-usage-tool.js +282 -163
- package/src/mcp-auth-form.js +7 -3
- package/src/mcp-detector.js +3 -3
- package/src/mcp-device-login-tools.js +2 -1
- package/src/mcp-find-projects-tools.js +1 -2
- package/src/mcp-install.js +279 -44
- package/src/mcp-instructions.js +1 -1
- package/src/mcp-onboarding-tools.js +4 -4
- package/src/mcp-prompts.js +4 -4
- package/src/mcp-register-tools.js +1 -0
- package/src/mcp-role-tools.js +24 -1
- package/src/mcp-server.js +5 -1
- package/src/mcp-signup-client.js +2 -2
- package/src/mcp-signup-tools.js +39 -9
- package/src/memory-structure-detector.js +1 -1
- package/src/model-call-record.js +2 -2
- package/src/onboarding-flow.js +63 -42
- package/src/onboarding-funnel.js +153 -20
- package/src/open-file.js +1 -1
- package/src/osc-link.js +1 -1
- package/src/portfolio-client.js +11 -1
- package/src/portfolio-git-info.js +1 -1
- package/src/profile-flow.js +1 -1
- package/src/profile-switch.js +5 -12
- package/src/render-certification.js +10 -10
- package/src/render-html.js +6 -6
- package/src/render-report-text.js +1 -1
- package/src/render-sheet.js +10 -10
- package/src/render-terminal.js +6 -6
- package/src/repo-scope.js +132 -0
- package/src/report-gating.js +5 -3
- package/src/report-store.js +13 -8
- package/src/roadmap-content.js +5 -5
- package/src/roadmap-personalization.js +3 -3
- package/src/roadmap-prompt.js +2 -2
- package/src/roles-flow.js +8 -5
- package/src/scan-exclusions.js +1 -1
- package/src/scanner.js +14 -14
- package/src/secret-prompt.js +1 -1
- package/src/session-gate.js +1 -1
- package/src/session-refresh.js +33 -3
- package/src/session-scan.js +1 -1
- package/src/shakers-wordmark.js +1 -1
- package/src/share-card.js +2 -2
- package/src/share-certification.js +4 -4
- package/src/share-consent-state.js +9 -9
- package/src/share-payload.js +7 -7
- package/src/share.js +7 -7
- package/src/signup-client.js +1 -1
- package/src/signup-copy.js +177 -2
- package/src/skills-client.js +1 -1
- package/src/start-add-agents.js +2 -2
- package/src/start-add-portfolio.js +3 -13
- package/src/start-add-skills.js +2 -2
- package/src/stdin-ask.js +16 -4
- package/src/strip-internal-comments.js +1 -1
- package/src/tech-detector.js +2 -2
- package/src/templates/report-sheet.html +11 -13
- package/src/terminal-agents.js +8 -8
- package/src/terminal-format.js +3 -3
- package/src/terminal-sections.js +11 -11
- package/src/tier-engine.js +3 -3
- package/src/tier-projection.js +2 -2
- package/src/transcript-turns.js +1 -1
- package/src/typewriter.js +1 -1
- package/src/usage-model-calls.js +6 -6
- package/src/usage-oneshot-commands.js +5 -5
- package/src/usage-repo-scope.js +11 -129
- package/src/usage-run-steps.js +82 -181
- package/src/usage-scan.js +235 -0
- package/src/work-streams.js +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
// Consent state + email identity + client-side throttle, extracted from src/share.js
|
|
3
|
+
// Consent state + email identity + client-side throttle, extracted from src/share.js.
|
|
4
4
|
|
|
5
5
|
const fs = require('fs');
|
|
6
6
|
const path = require('path');
|
|
@@ -10,8 +10,8 @@ function configDir() {
|
|
|
10
10
|
return getConfigDir(process.env);
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
-
function consentPath() {
|
|
14
|
-
return path.join(
|
|
13
|
+
function consentPath(env = process.env) {
|
|
14
|
+
return path.join(getConfigDir(env), 'consent.json');
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
// Client-side throttle: don't retry a submission if the last one was less than 1h ago.
|
|
@@ -19,9 +19,9 @@ const SEND_THROTTLE_MS = 60 * 60 * 1000;
|
|
|
19
19
|
|
|
20
20
|
// consent state (~/.config/ai-footprint/consent.json) Replaces the old `credentials.json` (token model).
|
|
21
21
|
|
|
22
|
-
function loadConsentState() {
|
|
22
|
+
function loadConsentState(env = process.env) {
|
|
23
23
|
try {
|
|
24
|
-
return JSON.parse(fs.readFileSync(consentPath(), 'utf8'));
|
|
24
|
+
return JSON.parse(fs.readFileSync(consentPath(env), 'utf8'));
|
|
25
25
|
} catch {
|
|
26
26
|
return null;
|
|
27
27
|
}
|
|
@@ -70,7 +70,7 @@ function recordConsent(decision, email = null, { verified } = {}) {
|
|
|
70
70
|
state.consent = decision;
|
|
71
71
|
if (email !== null) state.email = normalizeEmail(email);
|
|
72
72
|
if (state.lastSentAt === undefined) state.lastSentAt = null;
|
|
73
|
-
// Email-ownership verification flag
|
|
73
|
+
// Email-ownership verification flag.
|
|
74
74
|
if (decision === 'granted' && verified !== undefined) {
|
|
75
75
|
state.emailVerified = verified === true;
|
|
76
76
|
}
|
|
@@ -78,7 +78,7 @@ function recordConsent(decision, email = null, { verified } = {}) {
|
|
|
78
78
|
return state;
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
-
// consent management (
|
|
81
|
+
// consent management (status / revoke / change email). One-shot actions (do not scan), same pattern the retired `--enroll` used.
|
|
82
82
|
|
|
83
83
|
// Read-only snapshot for `--consent-status`. Never throws.
|
|
84
84
|
function getConsentStatus() {
|
|
@@ -100,7 +100,7 @@ function revokeConsent() {
|
|
|
100
100
|
function resetConsent() {
|
|
101
101
|
const state = loadConsentState() || {};
|
|
102
102
|
delete state.consent;
|
|
103
|
-
// A re-grant is "concede de cero"
|
|
103
|
+
// A re-grant is "concede de cero": drop the verification flag so the OTP runs again from scratch and nothing is sent under the old email until it's re-verified.
|
|
104
104
|
delete state.emailVerified;
|
|
105
105
|
if (state.lastSentAt === undefined) state.lastSentAt = null;
|
|
106
106
|
saveConsentState(state);
|
|
@@ -121,7 +121,7 @@ function setEmail(newEmail) {
|
|
|
121
121
|
}
|
|
122
122
|
const state = loadConsentState() || {};
|
|
123
123
|
state.email = normalizeEmail(newEmail);
|
|
124
|
-
// A new email is unverified until proven
|
|
124
|
+
// A new email is unverified until proven: reset the flag so
|
|
125
125
|
// autoShare won't send under an address whose ownership wasn't checked.
|
|
126
126
|
state.emailVerified = false;
|
|
127
127
|
saveConsentState(state);
|
package/src/share-payload.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
// The derived-payload whitelist for the `usage/reports` ingest, extracted from src/share.js
|
|
3
|
+
// The derived-payload whitelist for the `usage/reports` ingest, extracted from src/share.js.
|
|
4
4
|
|
|
5
5
|
const { scrubSecrets } = require('./agent-synthesis');
|
|
6
6
|
const { MAX_DEFINITION_CHARS } = require('./agent-evaluation');
|
|
@@ -13,7 +13,7 @@ function whitelistSchedulerProbe(probe) {
|
|
|
13
13
|
};
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
// WHY ONE MORE FIELD IS ADMISSIBLE ON THE AGENT WHITELIST
|
|
16
|
+
// WHY ONE MORE FIELD IS ADMISSIBLE ON THE AGENT WHITELIST.
|
|
17
17
|
function agentCatalogIdsByName(report) {
|
|
18
18
|
const evaluations =
|
|
19
19
|
report.agentEvaluation && Array.isArray(report.agentEvaluation.evaluations)
|
|
@@ -86,7 +86,7 @@ function derivePayload(report, maturity) {
|
|
|
86
86
|
tools: Array.isArray(a.tools) ? a.tools : [],
|
|
87
87
|
model: a.model || null,
|
|
88
88
|
parent: a.parent || null,
|
|
89
|
-
//
|
|
89
|
+
// The ONLY classification field that travels.
|
|
90
90
|
catalogId: catalogIdsByName.get(a.name) || null,
|
|
91
91
|
// agent-classifier fix — the agent's own definition (frontmatter description + body), scrubbed + capped.
|
|
92
92
|
...(definitionsByName.get(a.name)
|
|
@@ -132,14 +132,14 @@ function derivePayload(report, maturity) {
|
|
|
132
132
|
mcp: Array.isArray(browserToolsVia.mcp) && browserToolsVia.mcp.length > 0,
|
|
133
133
|
},
|
|
134
134
|
},
|
|
135
|
-
//
|
|
135
|
+
// Two DETERMINISTIC behavioural blocks (no LLM).
|
|
136
136
|
gitActivity: whitelistGitActivity(report.gitActivity),
|
|
137
137
|
sessions: whitelistSessions(report.sessions),
|
|
138
|
-
//
|
|
138
|
+
// One DETERMINISTIC block (workStreams) and two REDACTED-excerpt blocks (steering, decisions).
|
|
139
139
|
workStreams: whitelistWorkStreams(report.workStreams),
|
|
140
140
|
steering: whitelistSteering(report.steering),
|
|
141
141
|
decisions: whitelistDecisions(report.decisions),
|
|
142
|
-
//
|
|
142
|
+
// PER-CANDIDATE authorship + depth (per detected skill, per detected agent).
|
|
143
143
|
detectionEvidence: whitelistDetectionEvidence(report.detectionEvidence),
|
|
144
144
|
};
|
|
145
145
|
}
|
|
@@ -176,7 +176,7 @@ function whitelistDetectionEvidence(evidence) {
|
|
|
176
176
|
return { skills, agents, provenance: whitelistProvenance(evidence.provenance) };
|
|
177
177
|
}
|
|
178
178
|
|
|
179
|
-
// Re-derives the repo-level anti-noise `provenance
|
|
179
|
+
// Re-derives the repo-level anti-noise `provenance`: the (weak) fork signal and the (conservative) boilerplate/tutorial signal.
|
|
180
180
|
function whitelistProvenance(provenance) {
|
|
181
181
|
if (!provenance || typeof provenance !== 'object') return null;
|
|
182
182
|
const marker =
|
package/src/share.js
CHANGED
|
@@ -33,7 +33,7 @@ const {
|
|
|
33
33
|
|
|
34
34
|
/* ---------- minimal HTTP utility (no dependencies) ---------- */
|
|
35
35
|
|
|
36
|
-
// POSTs `body` to the ONE backend (
|
|
36
|
+
// POSTs `body` to the ONE backend (the PRIMARY -> FALLBACK chain is retired), via src/backend-request.js.
|
|
37
37
|
async function requestJsonBackend(endpoint, body, { timeoutMs = 15000, idempotencyKey = null } = {}) {
|
|
38
38
|
const { status, raw, backend } = await requestBackend({ endpoint, body, timeoutMs, idempotencyKey });
|
|
39
39
|
let json = null;
|
|
@@ -53,7 +53,7 @@ async function autoShare(report, maturity, { root, bypassThrottle = false } = {}
|
|
|
53
53
|
if (!state.email) {
|
|
54
54
|
return { ok: false, skipped: true, reason: 'no-email' };
|
|
55
55
|
}
|
|
56
|
-
//
|
|
56
|
+
// Never SEND under an email whose ownership wasn't proven — every
|
|
57
57
|
// profile, `external` included (legal requirement), must have verified.
|
|
58
58
|
if (state.emailVerified === false) {
|
|
59
59
|
return { ok: false, skipped: true, reason: 'email-unverified' };
|
|
@@ -92,14 +92,14 @@ async function autoShare(report, maturity, { root, bypassThrottle = false } = {}
|
|
|
92
92
|
return { ok: false, skipped: false, reason: 'rate-limited' };
|
|
93
93
|
}
|
|
94
94
|
if (res.status === 503) {
|
|
95
|
-
// Generic "service unavailable" from the server (
|
|
96
|
-
// server-side kill switch
|
|
95
|
+
// Generic "service unavailable" from the server (the retired
|
|
96
|
+
// server-side kill switch used to be the main 503 cause).
|
|
97
97
|
return { ok: false, skipped: false, reason: 'service-unavailable' };
|
|
98
98
|
}
|
|
99
99
|
return { ok: false, skipped: false, reason: `http-${res.status}` };
|
|
100
100
|
}
|
|
101
101
|
|
|
102
|
-
/* ---------- certification persistence
|
|
102
|
+
/* ---------- certification persistence ---------- */
|
|
103
103
|
|
|
104
104
|
// Persists the analyzed certification result if (and only if) consent is granted.
|
|
105
105
|
async function shareCertification(items, { repository = null, commitRange = null, toolVersion = null, superadminToken = null, root = null } = {}) {
|
|
@@ -112,7 +112,7 @@ async function shareCertification(items, { repository = null, commitRange = null
|
|
|
112
112
|
if (!state.email) {
|
|
113
113
|
return { ok: false, skipped: true, reason: 'no-email' };
|
|
114
114
|
}
|
|
115
|
-
//
|
|
115
|
+
// Don't persist under an unverified email.
|
|
116
116
|
if (state.emailVerified === false) {
|
|
117
117
|
return { ok: false, skipped: true, reason: 'email-unverified' };
|
|
118
118
|
}
|
|
@@ -132,7 +132,7 @@ async function shareCertification(items, { repository = null, commitRange = null
|
|
|
132
132
|
|
|
133
133
|
let res;
|
|
134
134
|
try {
|
|
135
|
-
//
|
|
135
|
+
// Forward the superadmin session token so the backend stamps test_origin=true (server-authoritative, non-prod only).
|
|
136
136
|
const body = { email: state.email, payload };
|
|
137
137
|
if (superadminToken) body.superadminToken = superadminToken;
|
|
138
138
|
// Idempotency-Key (three-way security review, cross-repo): ONE key per
|
package/src/signup-client.js
CHANGED
|
@@ -4,7 +4,7 @@ const { postJsonWithTimeout, extractSetCookie } = require('./backend-request');
|
|
|
4
4
|
const { SESSION_COOKIE_NAME } = require('./auth-client');
|
|
5
5
|
const { toUpperLang } = require('./lang-codes');
|
|
6
6
|
|
|
7
|
-
// Register a talent via the Hub's better-auth email engine, in two steps (
|
|
7
|
+
// Register a talent via the Hub's better-auth email engine, in two steps (WEB-login parity): 1.
|
|
8
8
|
|
|
9
9
|
const DEFAULT_TIMEOUT_MS = 30000;
|
|
10
10
|
|
package/src/signup-copy.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
// Talent-facing copy of the MCP sign-up
|
|
3
|
+
// Talent-facing copy of the MCP sign-up: fixed text the AI shows word for word, in the talent's language.
|
|
4
4
|
|
|
5
5
|
const SIGNUP_LANGUAGES = ['es', 'en', 'it', 'pt'];
|
|
6
6
|
|
|
@@ -405,6 +405,177 @@ const COPY = {
|
|
|
405
405
|
},
|
|
406
406
|
};
|
|
407
407
|
|
|
408
|
+
const REVIEW = {
|
|
409
|
+
es: {
|
|
410
|
+
cliQuestion: '¿Lo subo a tu perfil? [s/N]:',
|
|
411
|
+
cliIncludeExcerpts: '¿Incluir los extractos de texto? [S/n]:',
|
|
412
|
+
cliExcludeAgents: 'Si quieres dejar fuera algún agente, escribe sus nombres separados por comas (Enter para ninguno):',
|
|
413
|
+
cliUnknownAgents: (unknown, known) => `No reconozco: ${unknown}. Los agentes son: ${known}.`,
|
|
414
|
+
cliRepoHint: 'Para dejar fuera un repositorio, vuelve a ejecutar el comando eligiendo solo los que quieras con --repos.',
|
|
415
|
+
title: 'Todavía no se ha subido nada a tu perfil de Shakers. Esto es lo que se subiría:',
|
|
416
|
+
sentTitle: 'Esto es lo que se ha subido:',
|
|
417
|
+
tools: (list) => `Herramientas de IA detectadas: ${list}.`,
|
|
418
|
+
technologies: (list) => `Tecnologías de tus proyectos: ${list}.`,
|
|
419
|
+
activity: (sessions, days) => `Tu actividad con IA: ${sessions === 1 ? '1 sesión' : `${sessions} sesiones`}${days === null ? '' : `, unos ${days} días activos por semana`}.`,
|
|
420
|
+
repos: (list, commits) => `Repositorios analizados: ${list}. De ellos solo se envían cifras (${commits === 1 ? '1 commit' : `${commits} commits`}, líneas cambiadas, tipos de fichero), nunca su nombre ni su código.`,
|
|
421
|
+
agents: (list, definitions, summaries) => `Agentes de IA detectados: ${list}. De cada uno se envía ${listOf('es', ['su nombre', 'sus herramientas', 'su modelo', 'de qué agente depende', ...(definitions > 0 ? ['el texto de su definición (sin secretos)'] : []), ...(summaries > 0 ? ['el resumen de lo que hace'] : [])])}.`,
|
|
422
|
+
excerpts: (conversation, commands) => `Extractos de texto: ${conversation} frases cortas de lo que le pides a la IA y ${commands} comandos, de 200 caracteres como máximo, sin secretos, rutas ni emails.`,
|
|
423
|
+
noExcerpts: 'Extractos de texto: ninguno.',
|
|
424
|
+
notSent: 'No se envía: tu código, tus conversaciones completas ni los nombres de tus repositorios.',
|
|
425
|
+
explanationTitle: 'Qué hace Shakers con esto:',
|
|
426
|
+
explanation: [
|
|
427
|
+
'Te sitúa en dos ejes. Configuración: qué tienes montado (herramientas, servidores MCP, agentes), de menos a más: Asistido, Extendido u Orquestado. Uso: cómo diriges a la IA (si le das instrucciones concretas, si revisas lo que propone), de menos a más: Reactivo, Deliberado o Riguroso.',
|
|
428
|
+
'La configuración se calcula con reglas fijas a partir de lo detectado. El nivel de uso y los textos sobre cómo trabajas los escribe un modelo de IA en los servidores de Shakers.',
|
|
429
|
+
'En tu perfil aparece la sección «Mi trabajo con IA»: tus dos niveles, tu casilla en la matriz (por ejemplo Practitioner o Architect), tus herramientas y agentes, y dos textos que puedes editar en la web.',
|
|
430
|
+
],
|
|
431
|
+
afterwards: 'Si no estás de acuerdo con el resultado, pulsa «Solicitar revisión humana» en esa sección de la web o escribe a talent@shakersworks.com. Hoy no puedes ocultar la sección tú mismo; para pedir que se borre, escribe a ese mismo correo.',
|
|
432
|
+
excludeHint: 'Si quieres dejar fuera algún repositorio, algún agente o los extractos de texto, dímelo al responder.',
|
|
433
|
+
question: '¿Lo subo a tu perfil?',
|
|
434
|
+
yes: 'Subir a mi perfil',
|
|
435
|
+
no: 'No subir',
|
|
436
|
+
formExcerpts: 'Incluir los extractos de texto',
|
|
437
|
+
formRepo: (name) => `Incluir el repositorio ${name}`,
|
|
438
|
+
formAgent: (name) => `Incluir el agente ${name}`,
|
|
439
|
+
declined: 'No se ha subido nada. El análisis se queda solo en tu equipo.',
|
|
440
|
+
nothingLeft: 'Has dejado fuera todos los repositorios, así que no se ha subido nada.',
|
|
441
|
+
throttled: 'Ya subiste un análisis hace menos de una hora, así que no se ha subido nada. Puedes volver a intentarlo más tarde.',
|
|
442
|
+
uploaded: 'Subido a tu perfil. Shakers lo está evaluando y el resultado tarda unos minutos.',
|
|
443
|
+
uploadFailed: 'No se ha podido subir. Tu análisis se queda solo en tu equipo; puedes volver a intentarlo más tarde.',
|
|
444
|
+
resultTitle: 'Tu resultado en «Mi trabajo con IA»:',
|
|
445
|
+
setupLevel: (level) => `Configuración: ${level}.`,
|
|
446
|
+
usageLevel: (level) => `Uso: ${level}.`,
|
|
447
|
+
cell: (name) => `Tu casilla en la matriz: ${name}.`,
|
|
448
|
+
levels: { ASSISTED: 'Asistido', EXTENDED: 'Extendido', ORCHESTRATED: 'Orquestado', REACTIVE: 'Reactivo', DELIBERATE: 'Deliberado', RIGOROUS: 'Riguroso' },
|
|
449
|
+
resultPending: 'Tu resultado todavía no está en «Mi trabajo con IA». Si acabas de subir tu análisis, aparecerá en unos minutos.',
|
|
450
|
+
},
|
|
451
|
+
en: {
|
|
452
|
+
cliQuestion: 'Shall I upload it to your profile? [y/N]:',
|
|
453
|
+
cliIncludeExcerpts: 'Include the text excerpts? [Y/n]:',
|
|
454
|
+
cliExcludeAgents: 'If you want to leave out an agent, type their names separated by commas (Enter for none):',
|
|
455
|
+
cliUnknownAgents: (unknown, known) => `I do not recognise: ${unknown}. The agents are: ${known}.`,
|
|
456
|
+
cliRepoHint: 'To leave out a repository, run the command again choosing only the ones you want with --repos.',
|
|
457
|
+
title: 'Nothing has been uploaded to your Shakers profile yet. This is what would be uploaded:',
|
|
458
|
+
sentTitle: 'This is what was uploaded:',
|
|
459
|
+
tools: (list) => `AI tools detected: ${list}.`,
|
|
460
|
+
technologies: (list) => `Technologies in your projects: ${list}.`,
|
|
461
|
+
activity: (sessions, days) => `Your AI activity: ${sessions === 1 ? '1 session' : `${sessions} sessions`}${days === null ? '' : `, about ${days} active days a week`}.`,
|
|
462
|
+
repos: (list, commits) => `Repositories analysed: ${list}. Only figures about them are sent (${commits === 1 ? '1 commit' : `${commits} commits`}, lines changed, file types), never their name or their code.`,
|
|
463
|
+
agents: (list, definitions, summaries) => `AI agents detected: ${list}. For each one we send ${listOf('en', ['its name', 'its tools', 'its model', 'which agent it reports to', ...(definitions > 0 ? ['the text of its definition (with secrets removed)'] : []), ...(summaries > 0 ? ['the summary of what it does'] : [])])}.`,
|
|
464
|
+
excerpts: (conversation, commands) => `Text excerpts: ${conversation} short sentences of what you ask the AI and ${commands} commands, 200 characters at most, with secrets, paths and emails removed.`,
|
|
465
|
+
noExcerpts: 'Text excerpts: none.',
|
|
466
|
+
notSent: 'Not sent: your code, your full conversations or the names of your repositories.',
|
|
467
|
+
explanationTitle: 'What Shakers does with it:',
|
|
468
|
+
explanation: [
|
|
469
|
+
'It places you on two axes. Setup: what you have in place (tools, MCP servers, agents), from less to more: Assisted, Extended or Orchestrated. Usage: how you steer the AI (whether you give it concrete instructions, whether you review what it proposes), from less to more: Reactive, Deliberate or Rigorous.',
|
|
470
|
+
'Setup is worked out with fixed rules from what was detected. The usage level and the texts about how you work are written by an AI model on Shakers servers.',
|
|
471
|
+
'Your profile shows the "My work with AI" section: your two levels, your square in the matrix (for example Practitioner or Architect), your tools and agents, and two texts you can edit on the web.',
|
|
472
|
+
],
|
|
473
|
+
afterwards: 'If you disagree with the result, press "Request human review" in that section on the web or write to talent@shakersworks.com. Today you cannot hide the section yourself; to ask for it to be deleted, write to that same address.',
|
|
474
|
+
excludeHint: 'If you want to leave out a repository, an agent or the text excerpts, tell me when you answer.',
|
|
475
|
+
question: 'Shall I upload it to your profile?',
|
|
476
|
+
yes: 'Upload to my profile',
|
|
477
|
+
no: 'Do not upload',
|
|
478
|
+
formExcerpts: 'Include the text excerpts',
|
|
479
|
+
formRepo: (name) => `Include the repository ${name}`,
|
|
480
|
+
formAgent: (name) => `Include the agent ${name}`,
|
|
481
|
+
declined: 'Nothing was uploaded. The analysis stays on your computer only.',
|
|
482
|
+
nothingLeft: 'You left out every repository, so nothing was uploaded.',
|
|
483
|
+
throttled: 'You uploaded an analysis less than an hour ago, so nothing was uploaded. You can try again later.',
|
|
484
|
+
uploaded: 'Uploaded to your profile. Shakers is evaluating it and the result takes a few minutes.',
|
|
485
|
+
uploadFailed: 'It could not be uploaded. Your analysis stays on your computer only; you can try again later.',
|
|
486
|
+
resultTitle: 'Your result in "My work with AI":',
|
|
487
|
+
setupLevel: (level) => `Setup: ${level}.`,
|
|
488
|
+
usageLevel: (level) => `Usage: ${level}.`,
|
|
489
|
+
cell: (name) => `Your square in the matrix: ${name}.`,
|
|
490
|
+
levels: { ASSISTED: 'Assisted', EXTENDED: 'Extended', ORCHESTRATED: 'Orchestrated', REACTIVE: 'Reactive', DELIBERATE: 'Deliberate', RIGOROUS: 'Rigorous' },
|
|
491
|
+
resultPending: 'Your result is not in "My work with AI" yet. If you have just uploaded your analysis, it will show in a few minutes.',
|
|
492
|
+
},
|
|
493
|
+
it: {
|
|
494
|
+
cliQuestion: 'Lo carico sul tuo profilo? [s/N]:',
|
|
495
|
+
cliIncludeExcerpts: 'Includere gli estratti di testo? [S/n]:',
|
|
496
|
+
cliExcludeAgents: 'Se vuoi escludere un agente, scrivi i nomi separati da virgole (Invio per nessuno):',
|
|
497
|
+
cliUnknownAgents: (unknown, known) => `Non riconosco: ${unknown}. Gli agenti sono: ${known}.`,
|
|
498
|
+
cliRepoHint: 'Per escludere un repository, esegui di nuovo il comando scegliendo solo quelli che vuoi con --repos.',
|
|
499
|
+
title: "Non è stato ancora caricato nulla sul tuo profilo Shakers. Ecco cosa verrebbe caricato:",
|
|
500
|
+
sentTitle: "Ecco cosa è stato caricato:",
|
|
501
|
+
tools: (list) => `Strumenti di IA rilevati: ${list}.`,
|
|
502
|
+
technologies: (list) => `Tecnologie dei tuoi progetti: ${list}.`,
|
|
503
|
+
activity: (sessions, days) => `La tua attività con l'IA: ${sessions === 1 ? '1 sessione' : `${sessions} sessioni`}${days === null ? '' : `, circa ${days} giorni attivi a settimana`}.`,
|
|
504
|
+
repos: (list, commits) => `Repository analizzati: ${list}. Di questi si inviano solo cifre (${commits} commit, righe modificate, tipi di file), mai il nome né il codice.`,
|
|
505
|
+
agents: (list, definitions, summaries) => `Agenti di IA rilevati: ${list}. Di ognuno si inviano ${listOf('it', ['il nome', 'gli strumenti', 'il modello', 'da quale agente dipende', ...(definitions > 0 ? ['il testo della sua definizione (senza segreti)'] : []), ...(summaries > 0 ? ['il riassunto di ciò che fa'] : [])])}.`,
|
|
506
|
+
excerpts: (conversation, commands) => `Estratti di testo: ${conversation} frasi brevi di ciò che chiedi all'IA e ${commands} comandi, al massimo 200 caratteri, senza segreti, percorsi né email.`,
|
|
507
|
+
noExcerpts: 'Estratti di testo: nessuno.',
|
|
508
|
+
notSent: 'Non si inviano: il tuo codice, le tue conversazioni complete né i nomi dei tuoi repository.',
|
|
509
|
+
explanationTitle: 'Cosa fa Shakers con questi dati:',
|
|
510
|
+
explanation: [
|
|
511
|
+
"Ti colloca su due assi. Configurazione: cosa hai impostato (strumenti, server MCP, agenti), dal meno al più: Assistito, Esteso o Orchestrato. Uso: come guidi l'IA (se le dai istruzioni concrete, se controlli ciò che propone), dal meno al più: Reattivo, Deliberato o Rigoroso.",
|
|
512
|
+
"La configurazione si calcola con regole fisse da ciò che è stato rilevato. Il livello di uso e i testi su come lavori li scrive un modello di IA sui server di Shakers.",
|
|
513
|
+
"Sul tuo profilo compare la sezione «Il mio lavoro con l'IA»: i tuoi due livelli, la tua casella nella matrice (per esempio Practitioner o Architect), i tuoi strumenti e agenti, e due testi che puoi modificare sul sito.",
|
|
514
|
+
],
|
|
515
|
+
afterwards: 'Se non sei d\'accordo con il risultato, premi «Richiedi una revisione umana» in quella sezione del sito o scrivi a talent@shakersworks.com. Oggi non puoi nascondere la sezione da solo; per chiedere che venga cancellata, scrivi allo stesso indirizzo.',
|
|
516
|
+
excludeHint: 'Se vuoi escludere un repository, un agente o gli estratti di testo, dimmelo quando rispondi.',
|
|
517
|
+
question: 'Lo carico sul tuo profilo?',
|
|
518
|
+
yes: 'Carica sul mio profilo',
|
|
519
|
+
no: 'Non caricare',
|
|
520
|
+
formExcerpts: 'Includi gli estratti di testo',
|
|
521
|
+
formRepo: (name) => `Includi il repository ${name}`,
|
|
522
|
+
formAgent: (name) => `Includi l'agente ${name}`,
|
|
523
|
+
declined: "Non è stato caricato nulla. L'analisi resta solo sul tuo computer.",
|
|
524
|
+
nothingLeft: 'Hai escluso tutti i repository, quindi non è stato caricato nulla.',
|
|
525
|
+
throttled: "Hai caricato un'analisi meno di un'ora fa, quindi non è stato caricato nulla. Puoi riprovare più tardi.",
|
|
526
|
+
uploaded: 'Caricato sul tuo profilo. Shakers lo sta valutando e il risultato richiede qualche minuto.',
|
|
527
|
+
uploadFailed: 'Non è stato possibile caricarlo. La tua analisi resta solo sul tuo computer; puoi riprovare più tardi.',
|
|
528
|
+
resultTitle: "Il tuo risultato in «Il mio lavoro con l'IA»:",
|
|
529
|
+
setupLevel: (level) => `Configurazione: ${level}.`,
|
|
530
|
+
usageLevel: (level) => `Uso: ${level}.`,
|
|
531
|
+
cell: (name) => `La tua casella nella matrice: ${name}.`,
|
|
532
|
+
levels: { ASSISTED: 'Assistito', EXTENDED: 'Esteso', ORCHESTRATED: 'Orchestrato', REACTIVE: 'Reattivo', DELIBERATE: 'Deliberato', RIGOROUS: 'Rigoroso' },
|
|
533
|
+
resultPending: "Il tuo risultato non è ancora in «Il mio lavoro con l'IA». Se hai appena caricato la tua analisi, comparirà tra qualche minuto.",
|
|
534
|
+
},
|
|
535
|
+
pt: {
|
|
536
|
+
cliQuestion: 'Carrego-o no seu perfil? [s/N]:',
|
|
537
|
+
cliIncludeExcerpts: 'Incluir os excertos de texto? [S/n]:',
|
|
538
|
+
cliExcludeAgents: 'Se quiser deixar de fora algum agente, escreva os nomes separados por vírgulas (Enter para nenhum):',
|
|
539
|
+
cliUnknownAgents: (unknown, known) => `Não reconheço: ${unknown}. Os agentes são: ${known}.`,
|
|
540
|
+
cliRepoHint: 'Para deixar de fora um repositório, volte a executar o comando escolhendo só os que quiser com --repos.',
|
|
541
|
+
title: 'Ainda não foi carregado nada no seu perfil da Shakers. Isto é o que seria carregado:',
|
|
542
|
+
sentTitle: 'Isto é o que foi carregado:',
|
|
543
|
+
tools: (list) => `Ferramentas de IA detetadas: ${list}.`,
|
|
544
|
+
technologies: (list) => `Tecnologias dos seus projetos: ${list}.`,
|
|
545
|
+
activity: (sessions, days) => `A sua atividade com IA: ${sessions === 1 ? '1 sessão' : `${sessions} sessões`}${days === null ? '' : `, cerca de ${days} dias ativos por semana`}.`,
|
|
546
|
+
repos: (list, commits) => `Repositórios analisados: ${list}. Deles só se enviam números (${commits === 1 ? '1 commit' : `${commits} commits`}, linhas alteradas, tipos de ficheiro), nunca o nome nem o código.`,
|
|
547
|
+
agents: (list, definitions, summaries) => `Agentes de IA detetados: ${list}. De cada um envia-se ${listOf('pt', ['o nome', 'as ferramentas', 'o modelo', 'de que agente depende', ...(definitions > 0 ? ['o texto da sua definição (sem segredos)'] : []), ...(summaries > 0 ? ['o resumo do que faz'] : [])])}.`,
|
|
548
|
+
excerpts: (conversation, commands) => `Excertos de texto: ${conversation} frases curtas do que pede à IA e ${commands} comandos, com 200 caracteres no máximo, sem segredos, caminhos nem emails.`,
|
|
549
|
+
noExcerpts: 'Excertos de texto: nenhum.',
|
|
550
|
+
notSent: 'Não se envia: o seu código, as suas conversas completas nem os nomes dos seus repositórios.',
|
|
551
|
+
explanationTitle: 'O que a Shakers faz com isto:',
|
|
552
|
+
explanation: [
|
|
553
|
+
'Situa-o em dois eixos. Configuração: o que tem montado (ferramentas, servidores MCP, agentes), de menos para mais: Assistido, Estendido ou Orquestrado. Uso: como orienta a IA (se lhe dá instruções concretas, se revê o que propõe), de menos para mais: Reativo, Deliberado ou Rigoroso.',
|
|
554
|
+
'A configuração calcula-se com regras fixas a partir do que foi detetado. O nível de uso e os textos sobre como trabalha são escritos por um modelo de IA nos servidores da Shakers.',
|
|
555
|
+
'No seu perfil aparece a secção «Meu trabalho com IA»: os seus dois níveis, a sua casa na matriz (por exemplo Practitioner ou Architect), as suas ferramentas e agentes, e dois textos que pode editar na web.',
|
|
556
|
+
],
|
|
557
|
+
afterwards: 'Se não concordar com o resultado, carregue em «Solicitar revisão humana» nessa secção da web ou escreva para talent@shakersworks.com. Hoje não pode ocultar a secção sozinho; para pedir que seja apagada, escreva para esse mesmo endereço.',
|
|
558
|
+
excludeHint: 'Se quiser deixar de fora algum repositório, algum agente ou os excertos de texto, diga-me ao responder.',
|
|
559
|
+
question: 'Carrego-o no seu perfil?',
|
|
560
|
+
yes: 'Carregar no meu perfil',
|
|
561
|
+
no: 'Não carregar',
|
|
562
|
+
formExcerpts: 'Incluir os excertos de texto',
|
|
563
|
+
formRepo: (name) => `Incluir o repositório ${name}`,
|
|
564
|
+
formAgent: (name) => `Incluir o agente ${name}`,
|
|
565
|
+
declined: 'Não foi carregado nada. A análise fica só no seu computador.',
|
|
566
|
+
nothingLeft: 'Deixou de fora todos os repositórios, por isso não foi carregado nada.',
|
|
567
|
+
throttled: 'Carregou uma análise há menos de uma hora, por isso não foi carregado nada. Pode tentar mais tarde.',
|
|
568
|
+
uploaded: 'Carregado no seu perfil. A Shakers está a avaliá-lo e o resultado demora alguns minutos.',
|
|
569
|
+
uploadFailed: 'Não foi possível carregá-lo. A sua análise fica só no seu computador; pode tentar mais tarde.',
|
|
570
|
+
resultTitle: 'O seu resultado em «Meu trabalho com IA»:',
|
|
571
|
+
setupLevel: (level) => `Configuração: ${level}.`,
|
|
572
|
+
usageLevel: (level) => `Uso: ${level}.`,
|
|
573
|
+
cell: (name) => `A sua casa na matriz: ${name}.`,
|
|
574
|
+
levels: { ASSISTED: 'Assistido', EXTENDED: 'Estendido', ORCHESTRATED: 'Orquestrado', REACTIVE: 'Reativo', DELIBERATE: 'Deliberado', RIGOROUS: 'Rigoroso' },
|
|
575
|
+
resultPending: 'O seu resultado ainda não está em «Meu trabalho com IA». Se acabou de carregar a sua análise, vai aparecer dentro de alguns minutos.',
|
|
576
|
+
},
|
|
577
|
+
};
|
|
578
|
+
|
|
408
579
|
function toSignupLanguage(code) {
|
|
409
580
|
const base = String(code || '').toLowerCase().split(/[-_]/)[0];
|
|
410
581
|
return SIGNUP_LANGUAGES.includes(base) ? base : null;
|
|
@@ -418,6 +589,10 @@ function legalCopy(lang) {
|
|
|
418
589
|
return LEGAL[toSignupLanguage(lang) || 'en'];
|
|
419
590
|
}
|
|
420
591
|
|
|
592
|
+
function reviewCopy(lang) {
|
|
593
|
+
return REVIEW[toSignupLanguage(lang) || 'en'];
|
|
594
|
+
}
|
|
595
|
+
|
|
421
596
|
// The profile draft the talent confirms before the account exists: one line per group, empty parts left out.
|
|
422
597
|
function renderDraft(lang, d = {}) {
|
|
423
598
|
const c = signupCopy(lang);
|
|
@@ -447,4 +622,4 @@ function renderRole(lang, { name, source, evidence, openProjects, recommended })
|
|
|
447
622
|
].filter(Boolean).join('\n');
|
|
448
623
|
}
|
|
449
624
|
|
|
450
|
-
module.exports = { SIGNUP_LANGUAGES, LEGAL, signupCopy, legalCopy, toSignupLanguage, renderDraft, renderRole, amount };
|
|
625
|
+
module.exports = { SIGNUP_LANGUAGES, LEGAL, REVIEW, signupCopy, legalCopy, reviewCopy, toSignupLanguage, renderDraft, renderRole, amount };
|
package/src/skills-client.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const { postJsonWithTimeout } = require('./backend-request');
|
|
4
4
|
|
|
5
|
-
// Talent-skills client (
|
|
5
|
+
// Talent-skills client (`start`'s "Añadir skills" route, src/start-add-skills.js).
|
|
6
6
|
|
|
7
7
|
const DEFAULT_TIMEOUT_MS = 20000;
|
|
8
8
|
|
package/src/start-add-agents.js
CHANGED
|
@@ -24,7 +24,7 @@ const { reasonCopy, pickPortfolios } = require('./start-add-skills');
|
|
|
24
24
|
const { askWithDefault } = require('./start-add-portfolio');
|
|
25
25
|
const { styleQuestion, styleSuccess } = require('./ansi');
|
|
26
26
|
|
|
27
|
-
// `start`'s "Add agent to profile" route
|
|
27
|
+
// `start`'s "Add agent to profile" route, mirror of src/start-add-portfolio.js: the IA pre-drafts, the Talent edits.
|
|
28
28
|
|
|
29
29
|
const MAX_DEFINITION_CHARS = 4000;
|
|
30
30
|
|
|
@@ -68,7 +68,7 @@ async function chooseAgent({ ask, stdinIsTTY, agents, c, input, output }) {
|
|
|
68
68
|
return idx === null ? null : agents[idx];
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
-
// Step 4 — the two AI-drafted fields, consent-gated
|
|
71
|
+
// Step 4 — the two AI-drafted fields, consent-gated before any egress.
|
|
72
72
|
async function resolveAgentFields({ ask, catalog, c, stdinIsTTY, agent, accessToken, preAccepted = false }) {
|
|
73
73
|
const freeText = async () => ({
|
|
74
74
|
whatItDoes: await askFreeText(ask, c.agentWhatItDoesFreeTextPrompt),
|
|
@@ -8,7 +8,7 @@ const {
|
|
|
8
8
|
getPortfoliosListEndpoint, getPortfoliosDeclareEndpoint, getPortfolioDraftDescriptionEndpoint,
|
|
9
9
|
getSkillsResolveMatchedEndpoint, getSkillsResolveAddableEndpoint, getSkillsDeclareEndpoint,
|
|
10
10
|
} = require('./config');
|
|
11
|
-
const { requestListPortfolios, requestDeclarePortfolio, requestDraftDescription } = require('./portfolio-client');
|
|
11
|
+
const { requestListPortfolios, requestDeclarePortfolio, requestDraftDescription, normalizeDomain } = require('./portfolio-client');
|
|
12
12
|
const { requestResolveAddableSkills: requestResolveMatchedSkills, requestResolveAddableSkills } = require('./skills-client');
|
|
13
13
|
const { getRemoteUrl, getCommitDateRange } = require('./portfolio-git-info');
|
|
14
14
|
const { confirmDisclaimerAcceptance } = require('./certify-disclaimer');
|
|
@@ -21,7 +21,7 @@ const { palette, styleQuestion, styleSuccess } = require('./ansi');
|
|
|
21
21
|
// Only `dim`/`reset` for the numbered fallback's descriptions — the exact treatment bin/start.js's own menu and bin/certify.js's picker give theirs.
|
|
22
22
|
const ANSI = palette({ reset: '\x1b[0m', dim: '\x1b[2m' });
|
|
23
23
|
|
|
24
|
-
// `start`'s "Add project to portfolio" route
|
|
24
|
+
// `start`'s "Add project to portfolio" route.
|
|
25
25
|
|
|
26
26
|
const MAX_README_CHARS = 4000;
|
|
27
27
|
const MAX_PACKAGE_DESCRIPTION_CHARS = 500;
|
|
@@ -56,15 +56,6 @@ async function askFreeText(ask, promptText) {
|
|
|
56
56
|
return String(await ask(styleQuestion(promptText))).trim();
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
-
function normalizeDomain(raw) {
|
|
60
|
-
let s = String(raw || '').trim().toLowerCase();
|
|
61
|
-
if (!s) return '';
|
|
62
|
-
s = s.replace(/^https?:\/\//, '');
|
|
63
|
-
s = s.split('/')[0].split('?')[0].split('#')[0];
|
|
64
|
-
s = s.replace(/^www\./, '');
|
|
65
|
-
return s;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
59
|
// Step 2 — Type.
|
|
69
60
|
function typeOptions(c) {
|
|
70
61
|
return [
|
|
@@ -345,7 +336,7 @@ async function runAddPortfolio({
|
|
|
345
336
|
|
|
346
337
|
if (declared.ok) {
|
|
347
338
|
process.stdout.write(`\n ${styleSuccess(c.portfolioDeclaredSuccess(title))}\n\n`);
|
|
348
|
-
// OPT-IN
|
|
339
|
+
// OPT-IN: offer the REST of the detected skills for the Talent's PROFILE, now that the portfolio itself (and whatever skillIds it carried) is safely declared.
|
|
349
340
|
await offerAddableSkillsForProfile({ ask, catalog, c, technologies, session, stdinIsTTY, input, output, runLogin });
|
|
350
341
|
return;
|
|
351
342
|
}
|
|
@@ -383,6 +374,5 @@ module.exports = {
|
|
|
383
374
|
askWithDefault,
|
|
384
375
|
chooseType,
|
|
385
376
|
typeOptions,
|
|
386
|
-
normalizeDomain,
|
|
387
377
|
offerAddableSkillsForProfile,
|
|
388
378
|
};
|
package/src/start-add-skills.js
CHANGED
|
@@ -9,7 +9,7 @@ const { runInteractiveMultiSelect } = require('./interactive-select');
|
|
|
9
9
|
const { loadAuthSession, sessionStatus } = require('./auth-session-store');
|
|
10
10
|
const { styleSuccess } = require('./ansi');
|
|
11
11
|
|
|
12
|
-
// `start`'s "Añadir skills" route
|
|
12
|
+
// `start`'s "Añadir skills" route.
|
|
13
13
|
|
|
14
14
|
// Maps a technical failure reason to the localized `start.errorReason` copy.
|
|
15
15
|
function reasonCopy(reason, c) {
|
|
@@ -255,6 +255,6 @@ module.exports = {
|
|
|
255
255
|
declareAll,
|
|
256
256
|
printProfileBoost,
|
|
257
257
|
offerRelateToPortfolios,
|
|
258
|
-
// Exported for src/start-add-agents.js's own relate step
|
|
258
|
+
// Exported for src/start-add-agents.js's own relate step: the multi-select over the Talent's portfolios, reused verbatim.
|
|
259
259
|
pickPortfolios,
|
|
260
260
|
};
|
package/src/stdin-ask.js
CHANGED
|
@@ -49,14 +49,14 @@ function createLineQueueAsk(writeQuestion) {
|
|
|
49
49
|
|
|
50
50
|
// `onInterrupt` (optional): a Ctrl-C (SIGINT) handler for a TTY.
|
|
51
51
|
function createStdinAsk({ onInterrupt } = {}) {
|
|
52
|
-
// `rl` is re-createable
|
|
52
|
+
// `rl` is re-createable, not a `const`.
|
|
53
53
|
let rl = null;
|
|
54
54
|
let ended = false;
|
|
55
55
|
let suspending = false;
|
|
56
56
|
let currentPrompt = '';
|
|
57
57
|
|
|
58
58
|
const { ask, pushLine, markEnded, drain } = createLineQueueAsk((question) => {
|
|
59
|
-
// Long-answer wrap bug (
|
|
59
|
+
// Long-answer wrap bug (certify agents): in a TTY, `readline` runs in terminal mode and redraws the current line on every keystroke (wrap, cursor).
|
|
60
60
|
const text = ` ${question} `;
|
|
61
61
|
const lastNewline = text.lastIndexOf('\n');
|
|
62
62
|
if (lastNewline >= 0) {
|
|
@@ -98,7 +98,7 @@ function createStdinAsk({ onInterrupt } = {}) {
|
|
|
98
98
|
r.close();
|
|
99
99
|
suspending = false;
|
|
100
100
|
}
|
|
101
|
-
// SECRETS DO NOT ECHO
|
|
101
|
+
// SECRETS DO NOT ECHO.
|
|
102
102
|
ask.secret = async (question) => {
|
|
103
103
|
if (!canPromptSecretly(process.stdin)) return ask(question);
|
|
104
104
|
process.stdout.write(` ${question} `);
|
|
@@ -126,4 +126,16 @@ function createStdinAsk({ onInterrupt } = {}) {
|
|
|
126
126
|
return ask;
|
|
127
127
|
}
|
|
128
128
|
|
|
129
|
-
|
|
129
|
+
function createLazyStdinAsk() {
|
|
130
|
+
let real = null;
|
|
131
|
+
const open = () => { real = real || createStdinAsk(); return real; };
|
|
132
|
+
const ask = (question) => open()(question);
|
|
133
|
+
ask.secret = (question) => open().secret(question);
|
|
134
|
+
ask.drain = () => { if (real) real.drain(); };
|
|
135
|
+
ask.close = () => { if (real) real.close(); };
|
|
136
|
+
ask.suspend = () => { if (real) real.suspend(); };
|
|
137
|
+
ask.resume = () => { if (real) real.resume(); };
|
|
138
|
+
return ask;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
module.exports = { createStdinAsk, createLazyStdinAsk, createLineQueueAsk };
|
package/src/tech-detector.js
CHANGED
|
@@ -10,7 +10,7 @@ const {
|
|
|
10
10
|
isExcludedWalkPath,
|
|
11
11
|
} = require('./scan-exclusions');
|
|
12
12
|
|
|
13
|
-
// Monorepo support
|
|
13
|
+
// Monorepo support: manifests are discovered RECURSIVELY under `root`, not just at the top level.
|
|
14
14
|
|
|
15
15
|
// Exact dependency-name -> canonical framework/library name (npm + pip).
|
|
16
16
|
const EXACT_FRAMEWORK_MAP = {
|
|
@@ -202,7 +202,7 @@ function fromPyprojectTomlFile(filePath) {
|
|
|
202
202
|
return names;
|
|
203
203
|
}
|
|
204
204
|
|
|
205
|
-
// Manifest filename -> reader
|
|
205
|
+
// Manifest filename -> reader.
|
|
206
206
|
const MANIFEST_READERS = {
|
|
207
207
|
'package.json': fromPackageJsonFile,
|
|
208
208
|
'requirements.txt': fromRequirementsTxtFile,
|
|
@@ -146,7 +146,7 @@ header.top{
|
|
|
146
146
|
.col-head{display:flex; align-items:baseline; gap:10px; margin:2px 0 14px}
|
|
147
147
|
.col-head h2{margin:0; font-size:13px; letter-spacing:.12em; text-transform:uppercase; color:var(--fg-muted); font-weight:650}
|
|
148
148
|
.col-head .dot{width:6px;height:6px;border-radius:50%; background:var(--accent)}
|
|
149
|
-
/* Column note
|
|
149
|
+
/* Column note: one line under the column head, for copy that has to
|
|
150
150
|
be read before the numbers below it. `.sub` only exists inside `.card`. */
|
|
151
151
|
.col-note{margin:-8px 0 14px; font-size:12.5px; line-height:1.5; color:var(--fg-muted)}
|
|
152
152
|
.col-note b{color:var(--fg); font-weight:650}
|
|
@@ -211,16 +211,15 @@ header.top{
|
|
|
211
211
|
.chip .d{width:7px;height:7px;border-radius:50%; background:var(--accent)}
|
|
212
212
|
|
|
213
213
|
/* --- Agent tree --------------------------------------------- *
|
|
214
|
-
* ORPHANED BUT OWNED
|
|
214
|
+
* ORPHANED BUT OWNED.
|
|
215
215
|
*
|
|
216
216
|
* Nothing emits `class="agent"` today: `render-sheet.js` builds no agent tree, so
|
|
217
217
|
* this block styles markup that does not exist. It is NOT dead by accident — it
|
|
218
|
-
* came with the approved mockup and it is exactly the material
|
|
219
|
-
* shareable report has no agents at all) needs, so that design does not
|
|
220
|
-
* re-invent the nesting rail.
|
|
221
|
-
* `git show 7fae2c8^:src/render-html.js` (see `agentNodeHtml`/`agentCardsSection`).
|
|
218
|
+
* came with the approved mockup and it is exactly the material an agent section in
|
|
219
|
+
* the shareable report (which has no agents at all) needs, so that design does not
|
|
220
|
+
* have to re-invent the nesting rail.
|
|
222
221
|
*
|
|
223
|
-
* If
|
|
222
|
+
* If that section is dropped or solved another way, this goes with it. What it must not
|
|
224
223
|
* do is sit here with no owner, which is how it got orphaned in the first place.
|
|
225
224
|
* ------------------------------------------------------------ */
|
|
226
225
|
.tree{margin:0; padding:0; list-style:none}
|
|
@@ -312,7 +311,7 @@ header.top{
|
|
|
312
311
|
.tag-na{background:var(--chip-bg); color:var(--fg-muted)}
|
|
313
312
|
.area .icn{width:16px; text-align:center}
|
|
314
313
|
|
|
315
|
-
/* anchored quotes
|
|
314
|
+
/* anchored quotes: one block per quote, under its own area row, with
|
|
316
315
|
a left rail whose colour differs per SOURCE. The .qt-k label is the real
|
|
317
316
|
distinction between the two — the rail is lost in greyscale, the label is not. */
|
|
318
317
|
.area-block{display:flex; flex-direction:column; gap:3px}
|
|
@@ -401,7 +400,7 @@ __BODY__
|
|
|
401
400
|
"use strict";
|
|
402
401
|
|
|
403
402
|
/* Runs ONE independent init/update step in isolation from its siblings
|
|
404
|
-
|
|
403
|
+
`load`, `resize` and the theme toggle each chain several
|
|
405
404
|
UNRELATED effects (paint the ring, count up the score, slide the tab
|
|
406
405
|
indicator, sync a label…) as a plain sequence of statements. Before this,
|
|
407
406
|
one throwing — as `paintRing()` did pre-066, on ITS OWN unguarded lookup —
|
|
@@ -448,9 +447,8 @@ __BODY__
|
|
|
448
447
|
reached countUp / moveInd / syncExpandLabel — leaving the tab indicator
|
|
449
448
|
unpositioned and the expand-all label unsynced — and the theme toggle, whose
|
|
450
449
|
handler ends in paintRing(), threw on every single click. A broken script
|
|
451
|
-
fails no test; it silently degrades a report someone is reading. Found
|
|
452
|
-
|
|
453
|
-
for the first time. */
|
|
450
|
+
fails no test; it silently degrades a report someone is reading. Found when
|
|
451
|
+
a report with only an agent certification first became reachable. */
|
|
454
452
|
if(!fill) return;
|
|
455
453
|
var circumference = 2 * Math.PI * 54; // r=54
|
|
456
454
|
fill.style.strokeDasharray = circumference;
|
|
@@ -556,7 +554,7 @@ __BODY__
|
|
|
556
554
|
});
|
|
557
555
|
|
|
558
556
|
/* ---- init ---- */
|
|
559
|
-
// Four UNRELATED effects
|
|
557
|
+
// Four UNRELATED effects: each isolated so one failing never
|
|
560
558
|
// takes the other three down with it — see safeStep() above for why this,
|
|
561
559
|
// and not a guard on #ringFill/#scoreNum alone, is the fix at this level.
|
|
562
560
|
window.addEventListener('load', function(){
|