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
package/src/agents-client.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
const { postJsonWithTimeout } = require('./backend-request');
|
|
4
4
|
const { reasonForError, WORKS_CODE_TO_REASON } = require('./skills-client');
|
|
5
5
|
|
|
6
|
-
// Talent-agent client for `start`'s "Add agent" route
|
|
6
|
+
// Talent-agent client for `start`'s "Add agent" route.
|
|
7
7
|
|
|
8
8
|
const DEFAULT_TIMEOUT_MS = 20000;
|
|
9
9
|
|
|
@@ -88,14 +88,14 @@ async function requestDeclareAgent(
|
|
|
88
88
|
|
|
89
89
|
const json = parseJson(res.raw);
|
|
90
90
|
if (!json || json.status !== 'OK') return { ok: false, reason: 'bad-response' };
|
|
91
|
-
// Thread the created agent's id back (relayed from Hub's declare response) so the relate step can PATCH `.../agents/:id/portfolios
|
|
91
|
+
// Thread the created agent's id back (relayed from Hub's declare response) so the relate step can PATCH `.../agents/:id/portfolios`.
|
|
92
92
|
const data = json.data && typeof json.data === 'object' ? json.data : null;
|
|
93
93
|
const rawId = data ? data.id : null;
|
|
94
94
|
const agentId = typeof rawId === 'string' || typeof rawId === 'number' ? String(rawId) : null;
|
|
95
95
|
return { ok: true, agentId };
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
-
// DIRECT to Hub
|
|
98
|
+
// DIRECT to Hub, replace-set.
|
|
99
99
|
async function requestRelateAgentPortfolios(
|
|
100
100
|
{ agentId, items, hubAccessToken } = {},
|
|
101
101
|
{ endpoint, timeoutMs = DEFAULT_TIMEOUT_MS } = {},
|
|
@@ -124,7 +124,7 @@ async function requestRelateAgentPortfolios(
|
|
|
124
124
|
}
|
|
125
125
|
|
|
126
126
|
// `POST .../agents/draft-fields` -> { ok, whatItDoes, humanDecides } | { ok:false, reason }.
|
|
127
|
-
// No X-Hub-Token. The CALLER gates consent
|
|
127
|
+
// No X-Hub-Token. The CALLER gates consent, so `consent:true` is always sent.
|
|
128
128
|
async function requestDraftAgentFields(
|
|
129
129
|
{ name, tools, model, parent, definition, accessToken } = {},
|
|
130
130
|
{ endpoint, timeoutMs = DEFAULT_TIMEOUT_MS } = {},
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const { derivePayload } = require('./share-payload');
|
|
5
|
+
const { classify } = require('./maturity');
|
|
6
|
+
const { reviewCopy } = require('./signup-copy');
|
|
7
|
+
const { sanitizeRenderText } = require('./sanitize-network-text');
|
|
8
|
+
|
|
9
|
+
function repoName(repo) {
|
|
10
|
+
return path.basename(String(repo)) || String(repo);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function uploadFacts({ report, maturity, repos = [] }) {
|
|
14
|
+
const payload = derivePayload(report, maturity);
|
|
15
|
+
const sessions = payload.sessions || {};
|
|
16
|
+
const conversationExcerpts =
|
|
17
|
+
(payload.steering ? payload.steering.redactedSteeringExcerpts.length : 0)
|
|
18
|
+
+ (payload.decisions ? payload.decisions.redactedDecisionExcerpts.length : 0);
|
|
19
|
+
const commandExcerpts = Array.isArray(sessions.redactedCommandSample) ? sessions.redactedCommandSample.length : 0;
|
|
20
|
+
return {
|
|
21
|
+
tools: (report.tools || []).filter((t) => t && t.detected).map((t) => t.name || t.id),
|
|
22
|
+
technologies: payload.technologies.filter((t) => typeof t === 'string'),
|
|
23
|
+
sessionCount: sessions.sessionCount || 0,
|
|
24
|
+
activeDaysPerWeek: typeof sessions.activeDaysPerWeek === 'number' ? sessions.activeDaysPerWeek : null,
|
|
25
|
+
commitCount: payload.gitActivity ? payload.gitActivity.commitCount : 0,
|
|
26
|
+
repos: repos.map(repoName),
|
|
27
|
+
agents: payload.agents.map((a) => a.name),
|
|
28
|
+
agentDefinitions: payload.agents.filter((a) => a.definition).length,
|
|
29
|
+
agentSummaries: payload.agentSynthesis.filter((a) => a.whatItDoes || a.symbolicName).length,
|
|
30
|
+
conversationExcerpts,
|
|
31
|
+
commandExcerpts,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function keepNamed(list, excluded) {
|
|
36
|
+
return Array.isArray(list) ? list.filter((x) => !(x && excluded.has(x.name))) : list;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function withoutExcluded(report, { agents = [], excerpts = true } = {}) {
|
|
40
|
+
const excluded = new Set(agents);
|
|
41
|
+
const next = structuredClone(report);
|
|
42
|
+
if (excluded.size > 0) {
|
|
43
|
+
next.agents = keepNamed(next.agents, excluded)
|
|
44
|
+
.map((a) => (a && excluded.has(a.parent) ? { ...a, parent: null } : a));
|
|
45
|
+
next.agentDefinitions = keepNamed(next.agentDefinitions, excluded);
|
|
46
|
+
next.agentDescriptions = keepNamed(next.agentDescriptions, excluded);
|
|
47
|
+
if (next.agentEvaluation) next.agentEvaluation.evaluations = keepNamed(next.agentEvaluation.evaluations, excluded);
|
|
48
|
+
if (next.agentSynthesis) next.agentSynthesis.agents = keepNamed(next.agentSynthesis.agents, excluded);
|
|
49
|
+
if (next.detectionEvidence) next.detectionEvidence.agents = keepNamed(next.detectionEvidence.agents, excluded);
|
|
50
|
+
if (next.agentUsage && next.agentUsage.byAgent) {
|
|
51
|
+
for (const name of excluded) delete next.agentUsage.byAgent[name];
|
|
52
|
+
}
|
|
53
|
+
if (next.agentCounts) next.agentCounts.agents = Array.isArray(next.agents) ? next.agents.length : 0;
|
|
54
|
+
}
|
|
55
|
+
if (!excerpts) {
|
|
56
|
+
if (next.steering) next.steering.redactedSteeringExcerpts = [];
|
|
57
|
+
if (next.decisions) next.decisions.redactedDecisionExcerpts = [];
|
|
58
|
+
if (next.sessions) next.sessions.redactedCommandSample = [];
|
|
59
|
+
}
|
|
60
|
+
return { report: next, maturity: classify(next) };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function joined(list) {
|
|
64
|
+
return list.join(', ');
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function summaryText(lang, facts, { sent = false } = {}) {
|
|
68
|
+
const c = reviewCopy(lang);
|
|
69
|
+
return [
|
|
70
|
+
sent ? c.sentTitle : c.title,
|
|
71
|
+
facts.tools.length ? c.tools(joined(facts.tools)) : null,
|
|
72
|
+
facts.technologies.length ? c.technologies(joined(facts.technologies)) : null,
|
|
73
|
+
c.activity(facts.sessionCount, facts.activeDaysPerWeek),
|
|
74
|
+
facts.repos.length ? c.repos(joined(facts.repos), facts.commitCount) : null,
|
|
75
|
+
facts.agents.length ? c.agents(joined(facts.agents), facts.agentDefinitions, facts.agentSummaries) : null,
|
|
76
|
+
facts.conversationExcerpts + facts.commandExcerpts > 0 ? c.excerpts(facts.conversationExcerpts, facts.commandExcerpts) : c.noExcerpts,
|
|
77
|
+
c.notSent,
|
|
78
|
+
].filter(Boolean).map((line, i) => (i === 0 ? line : `- ${line}`)).join('\n');
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function explanationText(lang) {
|
|
82
|
+
const c = reviewCopy(lang);
|
|
83
|
+
return [c.explanationTitle, ...c.explanation, c.afterwards].join('\n\n');
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function resultText(lang, preview) {
|
|
87
|
+
const c = reviewCopy(lang);
|
|
88
|
+
if (!preview) return [c.resultPending, c.afterwards].join('\n\n');
|
|
89
|
+
const m = preview.matrix || {};
|
|
90
|
+
const lines = [
|
|
91
|
+
c.resultTitle,
|
|
92
|
+
m.setupLevel ? c.setupLevel(c.levels[m.setupLevel] || m.setupLevel) : null,
|
|
93
|
+
preview.setup && preview.setup.explanation,
|
|
94
|
+
m.usageLevel ? c.usageLevel(c.levels[m.usageLevel] || m.usageLevel) : null,
|
|
95
|
+
preview.usage && preview.usage.explanation,
|
|
96
|
+
m.cell ? c.cell(m.cell) : null,
|
|
97
|
+
].filter(Boolean).map(sanitizeRenderText);
|
|
98
|
+
return [lines.join('\n'), c.explanation[0], c.afterwards].join('\n\n');
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
module.exports = { uploadFacts, withoutExcluded, repoName, summaryText, explanationText, resultText };
|
package/src/ai.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
// The single named surface for every model-backed (LLM) call this CLI makes
|
|
3
|
+
// The single named surface for every model-backed (LLM) call this CLI makes.
|
|
4
4
|
|
|
5
5
|
const agentSynthesis = require('./agent-synthesis');
|
|
6
6
|
const agentEvaluation = require('./agent-evaluation');
|
package/src/ansi.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
const { isInteractive } = require('./terminal-progress');
|
|
4
4
|
const { BRAND_ANSI } = require('./brand-ansi');
|
|
5
5
|
|
|
6
|
-
// THE ONE GATE for terminal colour
|
|
6
|
+
// THE ONE GATE for terminal colour.
|
|
7
7
|
|
|
8
8
|
// `NO_COLOR` per the de-facto standard: present and non-empty disables colour, whatever the value.
|
|
9
9
|
function noColorRequested(env = process.env) {
|
package/src/auth-client.js
CHANGED
|
@@ -2,9 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
const { postJsonWithTimeout, extractSetCookie } = require('./backend-request');
|
|
4
4
|
|
|
5
|
-
// Login / token acquisition against the Hub's better-auth email engine
|
|
5
|
+
// Login / token acquisition against the Hub's better-auth email engine.
|
|
6
6
|
|
|
7
7
|
const DEFAULT_TIMEOUT_MS = 20000;
|
|
8
|
+
const SIGN_OUT_TIMEOUT_MS = 3000;
|
|
8
9
|
const SESSION_COOKIE_NAME = 'better-auth.session_token';
|
|
9
10
|
// Refresh the cookie session's own validity from the Set-Cookie Max-Age; fall
|
|
10
11
|
// back to 7 days (better-auth's default) when the attribute is absent.
|
|
@@ -88,12 +89,13 @@ function normalizeSignIn(status, raw, headers) {
|
|
|
88
89
|
}
|
|
89
90
|
|
|
90
91
|
// Mints a hub JWT from a better-auth session cookie: `GET {tokenEndpoint}` with the `Cookie` header.
|
|
91
|
-
async function requestAuthToken({ cookie } = {}, { endpoint, timeoutMs = DEFAULT_TIMEOUT_MS } = {}) {
|
|
92
|
+
async function requestAuthToken({ cookie, sessionToken } = {}, { endpoint, timeoutMs = DEFAULT_TIMEOUT_MS } = {}) {
|
|
92
93
|
if (!endpoint) return { ok: false, reason: 'no-endpoint' };
|
|
93
|
-
if (!cookie) return { ok: false, reason: 'no-cookie' };
|
|
94
|
+
if (!cookie && !sessionToken) return { ok: false, reason: 'no-cookie' };
|
|
95
|
+
const credential = cookie ? { Cookie: cookie } : { Authorization: `Bearer ${sessionToken}` };
|
|
94
96
|
let res;
|
|
95
97
|
try {
|
|
96
|
-
res = await postJsonWithTimeout(endpoint, null, timeoutMs, 'GET', null,
|
|
98
|
+
res = await postJsonWithTimeout(endpoint, null, timeoutMs, 'GET', null, credential);
|
|
97
99
|
} catch (e) {
|
|
98
100
|
return { ok: false, reason: (e && e.kind) || 'network-error' };
|
|
99
101
|
}
|
|
@@ -133,7 +135,19 @@ async function requestLogin(
|
|
|
133
135
|
};
|
|
134
136
|
}
|
|
135
137
|
|
|
138
|
+
async function requestSignOut({ cookie, sessionToken } = {}, { endpoint, origin = null, timeoutMs = SIGN_OUT_TIMEOUT_MS } = {}) {
|
|
139
|
+
if (!endpoint || (!cookie && !sessionToken)) return { ok: false, reason: 'no-credential' };
|
|
140
|
+
const credential = cookie ? { Cookie: cookie, ...(origin ? { Origin: origin } : {}) } : { Authorization: `Bearer ${sessionToken}` };
|
|
141
|
+
try {
|
|
142
|
+
const res = await postJsonWithTimeout(endpoint, {}, timeoutMs, 'POST', null, credential);
|
|
143
|
+
return { ok: res.status >= 200 && res.status < 300, status: res.status };
|
|
144
|
+
} catch (e) {
|
|
145
|
+
return { ok: false, reason: (e && e.kind) || 'network-error' };
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
136
149
|
module.exports = {
|
|
150
|
+
requestSignOut,
|
|
137
151
|
requestLogin,
|
|
138
152
|
requestAuthToken,
|
|
139
153
|
normalizeSignIn,
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
const { runInteractiveMultiSelect } = require('./interactive-select');
|
|
4
4
|
const { methodsForFlow, methodLabel } = require('./auth-methods');
|
|
5
5
|
|
|
6
|
-
// The shared interactive method picker for the CLI
|
|
6
|
+
// The shared interactive method picker for the CLI.
|
|
7
7
|
async function askNumberedChoice(ask, promptText, count) {
|
|
8
8
|
const raw = (await ask(promptText)).trim();
|
|
9
9
|
if (!raw) return null;
|
package/src/auth-methods.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
// The SINGLE source of truth for which auth methods exist and where each one is offered
|
|
3
|
+
// The SINGLE source of truth for which auth methods exist and where each one is offered.
|
|
4
4
|
const AUTH_METHODS = [
|
|
5
5
|
{ id: 'email', labelKey: 'methodEmail', availableFor: { login: true, register: true } },
|
|
6
6
|
{ id: 'google', labelKey: 'methodGoogle', availableFor: { login: true, register: true } },
|
|
@@ -4,8 +4,9 @@ const crypto = require('crypto');
|
|
|
4
4
|
const fs = require('fs');
|
|
5
5
|
const path = require('path');
|
|
6
6
|
const { getConfigDir } = require('./config-dir');
|
|
7
|
+
const { recordAccountMarker } = require('./account-marker');
|
|
7
8
|
|
|
8
|
-
// `~/.config/shakers/auth-session.json` — the logged-in Talent's session
|
|
9
|
+
// `~/.config/shakers/auth-session.json` — the logged-in Talent's session.
|
|
9
10
|
|
|
10
11
|
// Hub's `UserSubtypeEnum` (shared/domain/enum/user-subtype.enum.ts), copied VERBATIM — this repo never invents a value the wire does not send.
|
|
11
12
|
const PLATFORM_TALENT_USER_TYPES = new Set(['WORKS_TALENT', 'WORKS_PARTNER']);
|
|
@@ -89,12 +90,12 @@ function saveAuthSession(session, env = process.env, { fs: fsImpl = fs } = {}) {
|
|
|
89
90
|
const hubAccessToken = explicitHubToken || (typeof session.accessToken === 'string' && session.accessToken ? session.accessToken : null);
|
|
90
91
|
|
|
91
92
|
const cookie = typeof session.cookie === 'string' && session.cookie ? session.cookie : null;
|
|
93
|
+
const sessionToken = typeof session.sessionToken === 'string' && session.sessionToken ? session.sessionToken : null;
|
|
92
94
|
let expiresAt;
|
|
93
95
|
let accessTokenExpiresAt;
|
|
94
|
-
if (cookie) {
|
|
95
|
-
expiresAt =
|
|
96
|
-
|
|
97
|
-
: (typeof session.expiresAt === 'string' && session.expiresAt ? session.expiresAt : null);
|
|
96
|
+
if (cookie || sessionToken) {
|
|
97
|
+
expiresAt = [session.cookieExpiresAt, session.sessionExpiresAt, session.expiresAt]
|
|
98
|
+
.find((v) => typeof v === 'string' && v) || expiresAtFromToken(session.accessToken);
|
|
98
99
|
accessTokenExpiresAt = (typeof session.accessTokenExpiresAt === 'string' && session.accessTokenExpiresAt)
|
|
99
100
|
? session.accessTokenExpiresAt
|
|
100
101
|
: expiresAtFromToken(session.accessToken);
|
|
@@ -109,6 +110,7 @@ function saveAuthSession(session, env = process.env, { fs: fsImpl = fs } = {}) {
|
|
|
109
110
|
expiresAt: expiresAt ?? null,
|
|
110
111
|
accessTokenExpiresAt: accessTokenExpiresAt ?? null,
|
|
111
112
|
cookie,
|
|
113
|
+
sessionToken,
|
|
112
114
|
email: (typeof session.email === 'string' && session.email ? session.email : null)
|
|
113
115
|
|| emailFromHubToken(hubAccessToken),
|
|
114
116
|
hubAccessToken,
|
|
@@ -140,6 +142,7 @@ function saveAuthSession(session, env = process.env, { fs: fsImpl = fs } = {}) {
|
|
|
140
142
|
try { fsImpl.unlinkSync(tmp); } catch { /* never existed */ }
|
|
141
143
|
throw e;
|
|
142
144
|
}
|
|
145
|
+
recordAccountMarker(cookie ? 'email' : sessionToken ? 'device' : 'token', env, { fs: fsImpl });
|
|
143
146
|
return target;
|
|
144
147
|
}
|
|
145
148
|
|
|
@@ -157,7 +160,8 @@ function loadAuthSession(env = process.env, { fs: fsImpl = fs } = {}) {
|
|
|
157
160
|
// better-auth) or a JWT (Google/legacy, or an email session whose JWT is cached).
|
|
158
161
|
const hasCookie = typeof parsed.cookie === 'string' && parsed.cookie;
|
|
159
162
|
const hasToken = typeof parsed.accessToken === 'string' && parsed.accessToken;
|
|
160
|
-
|
|
163
|
+
const sessionToken = typeof parsed.sessionToken === 'string' && parsed.sessionToken ? parsed.sessionToken : null;
|
|
164
|
+
if (!hasCookie && !hasToken && !sessionToken) return null;
|
|
161
165
|
const hubAccessToken = typeof parsed.hubAccessToken === 'string' && parsed.hubAccessToken ? parsed.hubAccessToken : null;
|
|
162
166
|
const email = (typeof parsed.email === 'string' && parsed.email ? parsed.email : null) || emailFromHubToken(hubAccessToken);
|
|
163
167
|
return {
|
|
@@ -165,12 +169,17 @@ function loadAuthSession(env = process.env, { fs: fsImpl = fs } = {}) {
|
|
|
165
169
|
expiresAt: typeof parsed.expiresAt === 'string' ? parsed.expiresAt : null,
|
|
166
170
|
accessTokenExpiresAt: typeof parsed.accessTokenExpiresAt === 'string' ? parsed.accessTokenExpiresAt : null,
|
|
167
171
|
cookie: hasCookie ? parsed.cookie : null,
|
|
172
|
+
sessionToken,
|
|
168
173
|
email,
|
|
169
174
|
hubAccessToken,
|
|
170
175
|
userType: typeof parsed.userType === 'string' && KNOWN_USER_TYPES.has(parsed.userType) ? parsed.userType : null,
|
|
171
176
|
};
|
|
172
177
|
}
|
|
173
178
|
|
|
179
|
+
function hasAuthSessionFile(env = process.env, { fs: fsImpl = fs } = {}) {
|
|
180
|
+
return fsImpl.existsSync(authSessionPath(env));
|
|
181
|
+
}
|
|
182
|
+
|
|
174
183
|
// Removes the file. Idempotent and never throws — `logout` on no session is a
|
|
175
184
|
// no-op, not a failure.
|
|
176
185
|
function clearAuthSession(env = process.env, { fs: fsImpl = fs } = {}) {
|
|
@@ -184,8 +193,8 @@ function clearAuthSession(env = process.env, { fs: fsImpl = fs } = {}) {
|
|
|
184
193
|
|
|
185
194
|
// `none | active | expired`, the three states the caller needs to give the Talent a true message.
|
|
186
195
|
function sessionStatus(session, now = Date.now()) {
|
|
187
|
-
// A credential is
|
|
188
|
-
const hasCred = !!(session &&
|
|
196
|
+
// A credential is a durable cookie or session token, or a JWT (Google/legacy).
|
|
197
|
+
const hasCred = !!(session && [session.accessToken, session.cookie, session.sessionToken].some((v) => typeof v === 'string' && v));
|
|
189
198
|
if (!hasCred) return 'none';
|
|
190
199
|
const expiry = Date.parse(String(session.expiresAt || ''));
|
|
191
200
|
if (!Number.isFinite(expiry)) return 'expired';
|
|
@@ -204,6 +213,7 @@ module.exports = {
|
|
|
204
213
|
authSessionPath,
|
|
205
214
|
saveAuthSession,
|
|
206
215
|
loadAuthSession,
|
|
216
|
+
hasAuthSessionFile,
|
|
207
217
|
clearAuthSession,
|
|
208
218
|
sessionStatus,
|
|
209
219
|
isLoggedIn,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
// Display-only authorship-transparency check
|
|
3
|
+
// Display-only authorship-transparency check.
|
|
4
4
|
//
|
|
5
5
|
// Warns the talent when their commits in the selected repos are authored under
|
|
6
6
|
// an email that is not their Shakers account email, so their own work would be
|
package/src/authorship.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
const { execFileSync } = require('child_process');
|
|
4
4
|
const path = require('path');
|
|
5
5
|
|
|
6
|
-
// Git AUTHORSHIP + provenance for the certify phase
|
|
6
|
+
// Git AUTHORSHIP + provenance for the certify phase.
|
|
7
7
|
|
|
8
8
|
const { takePrefetchedGitLog } = require('./git-log-prefetch');
|
|
9
9
|
|
|
@@ -164,7 +164,7 @@ function buildAttributionPredicate(verifiedEmail, authorizedSet) {
|
|
|
164
164
|
};
|
|
165
165
|
}
|
|
166
166
|
|
|
167
|
-
// Applies the
|
|
167
|
+
// Applies the authorship gate (widened for test identities) to ONE sampled Skill.
|
|
168
168
|
function attributeSample(sample, verifiedEmail, authorship, authorizedSet = null) {
|
|
169
169
|
const isAttributable = buildAttributionPredicate(verifiedEmail, authorizedSet);
|
|
170
170
|
const files = Array.isArray(sample.files) ? sample.files : [];
|
|
@@ -5,7 +5,7 @@ const path = require('path');
|
|
|
5
5
|
const { execFileSync } = require('child_process');
|
|
6
6
|
const { getHomeDir } = require('./env-paths');
|
|
7
7
|
|
|
8
|
-
// Deterministic (no-LLM) automations detector
|
|
8
|
+
// Deterministic (no-LLM) automations detector.
|
|
9
9
|
|
|
10
10
|
const AI_CLI_NAMES = ['claude', 'aider', 'gemini', 'codex'];
|
|
11
11
|
const AI_CLI_RE = new RegExp(`\\b(${AI_CLI_NAMES.join('|')})\\b`, 'i');
|
package/src/backend-request.js
CHANGED
|
@@ -5,7 +5,7 @@ const https = require('https');
|
|
|
5
5
|
const crypto = require('crypto');
|
|
6
6
|
const { getHubBase } = require('./config');
|
|
7
7
|
|
|
8
|
-
// ONE BACKEND: the certifications service
|
|
8
|
+
// ONE BACKEND: the certifications service.
|
|
9
9
|
|
|
10
10
|
const MAX_RESPONSE_BYTES = 8 * 1024 * 1024;
|
|
11
11
|
|
|
@@ -31,7 +31,7 @@ function profileUpdateSourceHeader(url, method) {
|
|
|
31
31
|
return { 'X-Profile-Update-Source': clientIdentity };
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
// Extra request headers, sanitized (
|
|
34
|
+
// Extra request headers, sanitized (the agent certification interview needs `Authorization: Bearer <sessionToken>`).
|
|
35
35
|
const UNSAFE_HEADER_VALUE_RE = /[\r\n\0]/;
|
|
36
36
|
|
|
37
37
|
function sanitizeExtraHeaders(extraHeaders) {
|
|
@@ -71,6 +71,7 @@ function postJsonWithTimeout(url, body, timeoutMs, method = 'POST', idempotencyK
|
|
|
71
71
|
...sanitizeExtraHeaders(extraHeaders),
|
|
72
72
|
},
|
|
73
73
|
timeout: timeoutMs,
|
|
74
|
+
agent: false,
|
|
74
75
|
},
|
|
75
76
|
(res) => {
|
|
76
77
|
let raw = '';
|
|
@@ -151,6 +152,7 @@ function postMultipartWithTimeout(url, fields, timeoutMs, method = 'POST', extra
|
|
|
151
152
|
...sanitizeExtraHeaders(extraHeaders),
|
|
152
153
|
},
|
|
153
154
|
timeout: timeoutMs,
|
|
155
|
+
agent: false,
|
|
154
156
|
},
|
|
155
157
|
(res) => {
|
|
156
158
|
let raw = '';
|
|
@@ -183,7 +185,7 @@ async function requestBackend({
|
|
|
183
185
|
throw Object.assign(new Error('no endpoint configured'), { kind: 'no-endpoint' });
|
|
184
186
|
}
|
|
185
187
|
|
|
186
|
-
// Optional trace
|
|
188
|
+
// Optional trace.
|
|
187
189
|
const note = (entry) => {
|
|
188
190
|
if (Array.isArray(trace)) trace.push(entry);
|
|
189
191
|
};
|
package/src/brand-ansi.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
// BRAND_ANSI — the Shakers brand palette, reincarnated as ANSI 256-colour SGR codes
|
|
3
|
+
// BRAND_ANSI — the Shakers brand palette, reincarnated as ANSI 256-colour SGR codes.
|
|
4
4
|
|
|
5
5
|
const BRAND_ANSI = {
|
|
6
6
|
primary: '\x1b[38;5;73m',
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
// Deterministic (no-LLM) browser tools detector
|
|
3
|
+
// Deterministic (no-LLM) browser tools detector.
|
|
4
4
|
|
|
5
5
|
const BROWSER_DEPENDENCY_NAMES = new Set(['playwright', '@playwright/test', 'puppeteer', 'puppeteer-core']);
|
|
6
6
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
// Legal disclaimer + EXPLICIT acceptance gate
|
|
3
|
+
// Legal disclaimer + EXPLICIT acceptance gate, shown before egress. Skill-code certify was retired (certify is now a dimension LiveKit interview); consumers today are the add-skill/add-agent/add-project consent gates.
|
|
4
4
|
|
|
5
5
|
const { renderLegalNotice } = require('./legal-notice');
|
|
6
6
|
const { styleQuestion } = require('./ansi');
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const { getCatalog } = require('./i18n');
|
|
4
4
|
|
|
5
|
-
// Client-side "remediation prompt" for the certify report
|
|
5
|
+
// Client-side "remediation prompt" for the certify report.
|
|
6
6
|
function buildRemediationPrompt(item, lang) {
|
|
7
7
|
const r = getCatalog(lang).certify.report;
|
|
8
8
|
const improvements =
|
package/src/cli-args.js
CHANGED
|
@@ -39,7 +39,7 @@ function parseArgs(argv) {
|
|
|
39
39
|
repo: false,
|
|
40
40
|
scope: null,
|
|
41
41
|
noAi: false,
|
|
42
|
-
//
|
|
42
|
+
// Turns the section reveal off. Same name in both parsers, and the
|
|
43
43
|
// env var `NO_ANIMATION` does the same thing (the `NO_COLOR` pattern of 079).
|
|
44
44
|
noAnimation: false,
|
|
45
45
|
};
|
|
@@ -59,7 +59,7 @@ function parseArgs(argv) {
|
|
|
59
59
|
else if (a === '--show-endpoint') opts.showEndpoint = true;
|
|
60
60
|
else if (a === '--build-next-level') opts.buildNextLevel = true;
|
|
61
61
|
else if (a === '--force') opts.force = true;
|
|
62
|
-
//
|
|
62
|
+
// The tier roadmap / "next steps" block is removed from the
|
|
63
63
|
// default footprint output; `--roadmap` (alias `--next`) shows it again.
|
|
64
64
|
else if (a === '--roadmap' || a === '--next') opts.roadmap = true;
|
|
65
65
|
else if (a === '--lang') opts.lang = VALID_LANGS.has(argv[++i]) ? argv[i] : null;
|
package/src/command-audit.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
const os = require('os');
|
|
4
4
|
const path = require('path');
|
|
5
5
|
const { scrubSecrets } = require('./agent-synthesis');
|
|
6
|
-
const { loadAuthSession } = require('./auth-session-store');
|
|
6
|
+
const { loadAuthSession, decodeJwtPayload } = require('./auth-session-store');
|
|
7
7
|
const { getCertsBase } = require('./config');
|
|
8
8
|
const { postJsonWithTimeout } = require('./backend-request');
|
|
9
9
|
|
|
@@ -46,6 +46,11 @@ function machineHint() {
|
|
|
46
46
|
return { osUser, hostname };
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
+
function jwtExpired(token, now) {
|
|
50
|
+
const payload = decodeJwtPayload(token);
|
|
51
|
+
return !!payload && typeof payload.exp === 'number' && payload.exp * 1000 <= now;
|
|
52
|
+
}
|
|
53
|
+
|
|
49
54
|
// Builds the record + headers; returns null when there is no endpoint to post to.
|
|
50
55
|
function buildAudit(command, argv, env) {
|
|
51
56
|
let base;
|
|
@@ -57,10 +62,8 @@ function buildAudit(command, argv, env) {
|
|
|
57
62
|
if (!base) return null;
|
|
58
63
|
|
|
59
64
|
const session = loadAuthSession(env);
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
? session.accessToken || session.hubAccessToken
|
|
63
|
-
: null;
|
|
65
|
+
const stored = (session && (session.accessToken || session.hubAccessToken)) || null;
|
|
66
|
+
const token = stored && !jwtExpired(stored, Date.now()) ? stored : null;
|
|
64
67
|
|
|
65
68
|
const body = {
|
|
66
69
|
command: String(command || 'unknown').slice(0, 100),
|
package/src/config-dir.js
CHANGED
|
@@ -4,7 +4,7 @@ const path = require('path');
|
|
|
4
4
|
const fs = require('fs');
|
|
5
5
|
const { getHomeDir, readEnv } = require('./env-paths');
|
|
6
6
|
|
|
7
|
-
// Single source of truth for the CLI's config directory
|
|
7
|
+
// Single source of truth for the CLI's config directory.
|
|
8
8
|
|
|
9
9
|
const DIR_NAME = 'shakers';
|
|
10
10
|
const LEGACY_DIR_NAME = 'ai-footprint';
|