@meetless/mla 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/README.md +81 -0
- package/dist/build-info.json +9 -0
- package/dist/bundles/ask-core.js +396 -0
- package/dist/bundles/mcp.js +16592 -0
- package/dist/bundles/trace-core.js +263 -0
- package/dist/cli.js +828 -0
- package/dist/commands/activate.js +781 -0
- package/dist/commands/adoption.js +130 -0
- package/dist/commands/ask.js +290 -0
- package/dist/commands/context.js +114 -0
- package/dist/commands/debug.js +313 -0
- package/dist/commands/doctor.js +1021 -0
- package/dist/commands/enrich.js +427 -0
- package/dist/commands/evidence.js +229 -0
- package/dist/commands/flush.js +184 -0
- package/dist/commands/graph.js +104 -0
- package/dist/commands/init.js +272 -0
- package/dist/commands/internal-active-review.js +322 -0
- package/dist/commands/internal-auto-index.js +188 -0
- package/dist/commands/internal-capture-decisions.js +320 -0
- package/dist/commands/internal-evidence-correlate.js +239 -0
- package/dist/commands/internal-evidence-hooks.js +240 -0
- package/dist/commands/internal-evidence-inject.js +231 -0
- package/dist/commands/internal-finalize.js +221 -0
- package/dist/commands/internal-pretool-observe.js +225 -0
- package/dist/commands/internal-refresh.js +136 -0
- package/dist/commands/internal-session-nudge.js +120 -0
- package/dist/commands/internal-steer-sync.js +117 -0
- package/dist/commands/internal-turn-recap.js +140 -0
- package/dist/commands/kb.js +375 -0
- package/dist/commands/kb_add.js +681 -0
- package/dist/commands/kb_forget.js +283 -0
- package/dist/commands/kb_move.js +45 -0
- package/dist/commands/kb_pending.js +410 -0
- package/dist/commands/kb_personal.js +149 -0
- package/dist/commands/kb_promote.js +188 -0
- package/dist/commands/kb_purge.js +168 -0
- package/dist/commands/kb_reingest.js +335 -0
- package/dist/commands/kb_retime.js +170 -0
- package/dist/commands/kb_review.js +391 -0
- package/dist/commands/kb_revision.js +179 -0
- package/dist/commands/kb_show.js +385 -0
- package/dist/commands/label.js +226 -0
- package/dist/commands/login.js +295 -0
- package/dist/commands/logout.js +108 -0
- package/dist/commands/mcp-supervisor.js +93 -0
- package/dist/commands/mcp.js +227 -0
- package/dist/commands/queue-prune.js +98 -0
- package/dist/commands/review.js +358 -0
- package/dist/commands/rewire.js +124 -0
- package/dist/commands/rules.js +728 -0
- package/dist/commands/scan-context.js +67 -0
- package/dist/commands/session.js +347 -0
- package/dist/commands/stats.js +479 -0
- package/dist/commands/status.js +61 -0
- package/dist/commands/summary.js +250 -0
- package/dist/commands/turn.js +114 -0
- package/dist/commands/uninstall.js +222 -0
- package/dist/commands/whoami.js +102 -0
- package/dist/commands/workspace.js +130 -0
- package/dist/hooks-template/ce0-post-tool-use.sh +34 -0
- package/dist/hooks-template/ce0-session-start.sh +49 -0
- package/dist/hooks-template/ce0-stop.sh +29 -0
- package/dist/hooks-template/ce0-user-prompt-submit.sh +38 -0
- package/dist/hooks-template/common.sh +934 -0
- package/dist/hooks-template/event-batch-filter.jq +67 -0
- package/dist/hooks-template/flush.sh +503 -0
- package/dist/hooks-template/post-tool-use.sh +423 -0
- package/dist/hooks-template/pre-tool-use.sh +69 -0
- package/dist/hooks-template/session-start.sh +140 -0
- package/dist/hooks-template/stop.sh +308 -0
- package/dist/hooks-template/user-prompt-submit.sh +1162 -0
- package/dist/lib/activation.js +79 -0
- package/dist/lib/active-conflict-cache.js +141 -0
- package/dist/lib/active-memory.js +59 -0
- package/dist/lib/active-review-runner.js +26 -0
- package/dist/lib/agent-decision/index.js +25 -0
- package/dist/lib/agent-decision/keys.js +49 -0
- package/dist/lib/agent-decision/normalize-claude.js +183 -0
- package/dist/lib/agent-decision/types.js +21 -0
- package/dist/lib/agent-decision/validate.js +216 -0
- package/dist/lib/analytics/capture.js +96 -0
- package/dist/lib/analytics/command-event.js +267 -0
- package/dist/lib/analytics/consent.js +58 -0
- package/dist/lib/analytics/coverage-gap.js +96 -0
- package/dist/lib/analytics/envelope.js +236 -0
- package/dist/lib/analytics/event-id.js +86 -0
- package/dist/lib/analytics/evidence.js +150 -0
- package/dist/lib/analytics/followthrough.js +194 -0
- package/dist/lib/analytics/forwarder.js +109 -0
- package/dist/lib/analytics/logs.js +78 -0
- package/dist/lib/analytics/metrics.js +78 -0
- package/dist/lib/analytics/recorder.js +92 -0
- package/dist/lib/analytics/review-analytics.js +75 -0
- package/dist/lib/analytics/sequence.js +77 -0
- package/dist/lib/analytics/store.js +131 -0
- package/dist/lib/analytics/turn-recap.js +279 -0
- package/dist/lib/artifact_id.js +108 -0
- package/dist/lib/auth-breaker.js +161 -0
- package/dist/lib/auto-index.js +112 -0
- package/dist/lib/classifier.js +88 -0
- package/dist/lib/config.js +298 -0
- package/dist/lib/conflict-advisory.js +64 -0
- package/dist/lib/debug-bundle.js +520 -0
- package/dist/lib/enrichment/ingest.js +301 -0
- package/dist/lib/enrichment/plan.js +253 -0
- package/dist/lib/enrichment/protocol.js +359 -0
- package/dist/lib/enrichment/scout-brief.js +176 -0
- package/dist/lib/failure-telemetry.js +444 -0
- package/dist/lib/git.js +200 -0
- package/dist/lib/governance-cache.js +77 -0
- package/dist/lib/governed-path-cache.js +76 -0
- package/dist/lib/http.js +677 -0
- package/dist/lib/identity-envelope.js +23 -0
- package/dist/lib/kb-candidate.js +65 -0
- package/dist/lib/kb_acl.js +98 -0
- package/dist/lib/login.js +353 -0
- package/dist/lib/mcp-fetchers.js +130 -0
- package/dist/lib/mcp-restart.js +47 -0
- package/dist/lib/observability.js +805 -0
- package/dist/lib/open-url.js +33 -0
- package/dist/lib/orphan-guard.js +70 -0
- package/dist/lib/packaged.js +21 -0
- package/dist/lib/reconcile-sessions.js +171 -0
- package/dist/lib/redactor.js +89 -0
- package/dist/lib/relationship-candidate-query.js +27 -0
- package/dist/lib/render.js +611 -0
- package/dist/lib/rules/applicability.js +64 -0
- package/dist/lib/rules/attest-code-rule-version.js +47 -0
- package/dist/lib/rules/attest-notes-location.js +217 -0
- package/dist/lib/rules/attest-rule-version.js +69 -0
- package/dist/lib/rules/canonical-json.js +97 -0
- package/dist/lib/rules/ce0-emit.js +64 -0
- package/dist/lib/rules/ce0-evidence.js +281 -0
- package/dist/lib/rules/ce0-recall-sample.js +82 -0
- package/dist/lib/rules/ce0-rule.js +55 -0
- package/dist/lib/rules/ce0-sampling-bucket.js +15 -0
- package/dist/lib/rules/ce0-store.js +683 -0
- package/dist/lib/rules/ce0-telemetry-project.js +93 -0
- package/dist/lib/rules/ce0-telemetry.js +158 -0
- package/dist/lib/rules/code-rule-registry.js +17 -0
- package/dist/lib/rules/command-match.js +185 -0
- package/dist/lib/rules/consult-evidence-binding.js +27 -0
- package/dist/lib/rules/consultation-capture-adapter.js +193 -0
- package/dist/lib/rules/content-match.js +56 -0
- package/dist/lib/rules/deny-admission.js +99 -0
- package/dist/lib/rules/durable-observation.js +190 -0
- package/dist/lib/rules/enforce-notes-version.js +421 -0
- package/dist/lib/rules/evaluation-input-hash.js +126 -0
- package/dist/lib/rules/evaluator.js +108 -0
- package/dist/lib/rules/inert-rule-families.js +51 -0
- package/dist/lib/rules/input-authority-resolver.js +241 -0
- package/dist/lib/rules/interception-schema.js +170 -0
- package/dist/lib/rules/interception-store.js +267 -0
- package/dist/lib/rules/live-input-authority.js +66 -0
- package/dist/lib/rules/local-matcher.js +108 -0
- package/dist/lib/rules/local-observe.js +79 -0
- package/dist/lib/rules/local-rule-version-repo.js +214 -0
- package/dist/lib/rules/memory-requirement.js +109 -0
- package/dist/lib/rules/notes-observe.js +39 -0
- package/dist/lib/rules/notes-path.js +261 -0
- package/dist/lib/rules/notes-rule.js +75 -0
- package/dist/lib/rules/observe-adapter.js +114 -0
- package/dist/lib/rules/observed-rule-hash.js +119 -0
- package/dist/lib/rules/prompt-submit-adapter.js +132 -0
- package/dist/lib/rules/requirement-subject.js +240 -0
- package/dist/lib/rules/rule-activity.js +67 -0
- package/dist/lib/rules/rule-version-hash.js +151 -0
- package/dist/lib/rules/runtime-scope.js +55 -0
- package/dist/lib/rules/stop-adapter.js +116 -0
- package/dist/lib/rules/stop-response-snapshot.js +174 -0
- package/dist/lib/rules/types.js +10 -0
- package/dist/lib/rules/ulid.js +46 -0
- package/dist/lib/rules/version-evaluation.js +156 -0
- package/dist/lib/scanner/agent-memory.js +99 -0
- package/dist/lib/scanner/bootstrap-summary.js +87 -0
- package/dist/lib/scanner/cache.js +59 -0
- package/dist/lib/scanner/frontmatter.js +42 -0
- package/dist/lib/scanner/parse-directives.js +69 -0
- package/dist/lib/scanner/parse-structured.js +72 -0
- package/dist/lib/scanner/render.js +73 -0
- package/dist/lib/scanner/scan.js +132 -0
- package/dist/lib/scanner/score.js +38 -0
- package/dist/lib/scanner/scout-mission.js +126 -0
- package/dist/lib/scanner/types.js +7 -0
- package/dist/lib/session-scope.js +195 -0
- package/dist/lib/spool.js +355 -0
- package/dist/lib/staleness.js +100 -0
- package/dist/lib/steer-cache.js +87 -0
- package/dist/lib/tagged-reference.js +20 -0
- package/dist/lib/temporal.js +109 -0
- package/dist/lib/turn-recap-emit.js +67 -0
- package/dist/lib/unwire.js +253 -0
- package/dist/lib/update-check.js +469 -0
- package/dist/lib/update-notifier.js +217 -0
- package/dist/lib/upgrade-apply.js +643 -0
- package/dist/lib/wire.js +1087 -0
- package/dist/lib/workspace.js +96 -0
- package/dist/lib/zip.js +154 -0
- package/dist/pretool-entry.js +37 -0
- package/package.json +75 -0
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.parseLabelArgs = parseLabelArgs;
|
|
37
|
+
exports.runLabel = runLabel;
|
|
38
|
+
const fs = __importStar(require("fs"));
|
|
39
|
+
const os = __importStar(require("os"));
|
|
40
|
+
const path = __importStar(require("path"));
|
|
41
|
+
// `mla label` -- the A3 operator-label affordance (notes/20260603-mla-kb-agent
|
|
42
|
+
// -proxy-and-evidence-adoption.md §3, §7.2). A lightweight way for the operator
|
|
43
|
+
// to mark a handful of enrichments useful / noisy / harmful / prevented-a
|
|
44
|
+
// -mistake. It writes the reserved `operator_label` block back into a trace line
|
|
45
|
+
// in ~/.meetless/logs/ask-traces.jsonl. Low volume, high signal: this is the
|
|
46
|
+
// ground-truth anchor the composite needs before any weight tuning, and the
|
|
47
|
+
// `harmful` flag is the exact field the A5 carry-forward hook reads to suppress
|
|
48
|
+
// a re-surface, so a `--harmful` label here closes that loop.
|
|
49
|
+
//
|
|
50
|
+
// mla label [<trace_id>] [--useful] [--noisy] [--harmful]
|
|
51
|
+
// [--prevented-mistake] [--note <text>]
|
|
52
|
+
//
|
|
53
|
+
// With no <trace_id> it labels the LATEST trace in the current session, scoping
|
|
54
|
+
// to CLAUDE_CODE_SESSION_ID exactly like `mla summary` / `mla review`. The
|
|
55
|
+
// parent Claude Code shell exports that var, so the operator labels "the
|
|
56
|
+
// enrichment I just saw" without copying its id off the prompt. Pass an explicit
|
|
57
|
+
// trace_id to label any past trace from outside a session.
|
|
58
|
+
//
|
|
59
|
+
// This is the WRITE side of the block that `mla summary` reads and tallies; it
|
|
60
|
+
// is deliberately a standalone command, not a revival of the removed `mla
|
|
61
|
+
// traces` tree (that subtree was dropped 2026-05-31, see summary.ts header).
|
|
62
|
+
// Paths resolve lazily from MEETLESS_HOME (same fallback as lib/config + summary)
|
|
63
|
+
// so the short-lived CLI picks up the operator's env and tests can point at a
|
|
64
|
+
// temp dir.
|
|
65
|
+
function logDir() {
|
|
66
|
+
return path.join(process.env.MEETLESS_HOME || path.join(os.homedir(), ".meetless"), "logs");
|
|
67
|
+
}
|
|
68
|
+
function tracesFile() {
|
|
69
|
+
return path.join(logDir(), "ask-traces.jsonl");
|
|
70
|
+
}
|
|
71
|
+
function readLines() {
|
|
72
|
+
if (!fs.existsSync(tracesFile()))
|
|
73
|
+
return [];
|
|
74
|
+
return fs
|
|
75
|
+
.readFileSync(tracesFile(), "utf8")
|
|
76
|
+
.split("\n")
|
|
77
|
+
.filter((l) => l.trim().length > 0);
|
|
78
|
+
}
|
|
79
|
+
function parse(line) {
|
|
80
|
+
try {
|
|
81
|
+
const o = JSON.parse(line);
|
|
82
|
+
return o && typeof o === "object" ? o : null;
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
function parseLabelArgs(argv) {
|
|
89
|
+
let traceId = null;
|
|
90
|
+
const patch = {};
|
|
91
|
+
let any = false;
|
|
92
|
+
for (let i = 0; i < argv.length; i++) {
|
|
93
|
+
const a = argv[i];
|
|
94
|
+
if (a === "--useful") {
|
|
95
|
+
patch.useful = true;
|
|
96
|
+
any = true;
|
|
97
|
+
}
|
|
98
|
+
else if (a === "--noisy") {
|
|
99
|
+
patch.noisy = true;
|
|
100
|
+
any = true;
|
|
101
|
+
}
|
|
102
|
+
else if (a === "--harmful") {
|
|
103
|
+
patch.harmful = true;
|
|
104
|
+
any = true;
|
|
105
|
+
}
|
|
106
|
+
else if (a === "--prevented-mistake") {
|
|
107
|
+
patch.prevented_mistake = true;
|
|
108
|
+
any = true;
|
|
109
|
+
}
|
|
110
|
+
else if (a === "--note") {
|
|
111
|
+
const v = argv[++i];
|
|
112
|
+
if (v === undefined)
|
|
113
|
+
throw new Error("--note requires a value");
|
|
114
|
+
patch.notes = v;
|
|
115
|
+
any = true;
|
|
116
|
+
}
|
|
117
|
+
else if (a.startsWith("-")) {
|
|
118
|
+
throw new Error(`Unknown flag for \`mla label\`: ${a}`);
|
|
119
|
+
}
|
|
120
|
+
else if (traceId === null) {
|
|
121
|
+
traceId = a;
|
|
122
|
+
}
|
|
123
|
+
else {
|
|
124
|
+
throw new Error(`Unexpected extra argument: ${a}`);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
if (!any) {
|
|
128
|
+
throw new Error("Provide at least one of --useful / --noisy / --harmful / --prevented-mistake / --note.");
|
|
129
|
+
}
|
|
130
|
+
return { traceId, patch };
|
|
131
|
+
}
|
|
132
|
+
// Compact, deterministic render of the merged label state for the confirmation
|
|
133
|
+
// line. Shows the FULL resulting block (not just this patch) so the operator
|
|
134
|
+
// sees the cumulative verdict after a merge.
|
|
135
|
+
function renderLabel(l) {
|
|
136
|
+
const parts = [];
|
|
137
|
+
if (l.useful === true)
|
|
138
|
+
parts.push("useful");
|
|
139
|
+
if (l.noisy === true)
|
|
140
|
+
parts.push("noisy");
|
|
141
|
+
if (l.harmful === true)
|
|
142
|
+
parts.push("harmful");
|
|
143
|
+
if (l.prevented_mistake === true)
|
|
144
|
+
parts.push("prevented-mistake");
|
|
145
|
+
if (typeof l.notes === "string" && l.notes.length > 0)
|
|
146
|
+
parts.push(`note="${l.notes}"`);
|
|
147
|
+
return parts.length ? parts.join(", ") : "(no flags set)";
|
|
148
|
+
}
|
|
149
|
+
function runLabel(argv) {
|
|
150
|
+
let args;
|
|
151
|
+
try {
|
|
152
|
+
args = parseLabelArgs(argv);
|
|
153
|
+
}
|
|
154
|
+
catch (e) {
|
|
155
|
+
console.error(e.message);
|
|
156
|
+
return 2;
|
|
157
|
+
}
|
|
158
|
+
const lines = readLines();
|
|
159
|
+
if (lines.length === 0) {
|
|
160
|
+
console.error(`No traces found at ${tracesFile()}.`);
|
|
161
|
+
return 1;
|
|
162
|
+
}
|
|
163
|
+
// Resolve which line index(es) to label, plus a label for the confirmation.
|
|
164
|
+
let targetIdxs;
|
|
165
|
+
let what;
|
|
166
|
+
if (args.traceId) {
|
|
167
|
+
// Explicit trace_id: rewrite every matching line. trace_id is a unique join
|
|
168
|
+
// key, but if a line were ever duplicated we label them all so the read side
|
|
169
|
+
// can never see a stale copy.
|
|
170
|
+
targetIdxs = lines.flatMap((line, i) => (parse(line)?.trace_id === args.traceId ? [i] : []));
|
|
171
|
+
if (targetIdxs.length === 0) {
|
|
172
|
+
console.error(`Trace not found: ${args.traceId}`);
|
|
173
|
+
return 1;
|
|
174
|
+
}
|
|
175
|
+
what = args.traceId;
|
|
176
|
+
}
|
|
177
|
+
else {
|
|
178
|
+
// Default selector: the latest trace in the current session.
|
|
179
|
+
const session = (process.env.CLAUDE_CODE_SESSION_ID || "").trim();
|
|
180
|
+
if (!session) {
|
|
181
|
+
console.error("No <trace_id> given and no current session (CLAUDE_CODE_SESSION_ID unset). " +
|
|
182
|
+
"Pass an explicit trace_id, or run `mla label` inside a Claude Code session.");
|
|
183
|
+
return 2;
|
|
184
|
+
}
|
|
185
|
+
let lastIdx = -1;
|
|
186
|
+
let lastTid = "";
|
|
187
|
+
lines.forEach((line, i) => {
|
|
188
|
+
const t = parse(line);
|
|
189
|
+
if (t && t.session_id === session) {
|
|
190
|
+
lastIdx = i;
|
|
191
|
+
lastTid = t.trace_id ?? "";
|
|
192
|
+
}
|
|
193
|
+
});
|
|
194
|
+
if (lastIdx < 0) {
|
|
195
|
+
console.error(`No traces for the current session (${session}) at ${tracesFile()}. ` +
|
|
196
|
+
"Pass an explicit trace_id to label a trace from another session.");
|
|
197
|
+
return 1;
|
|
198
|
+
}
|
|
199
|
+
targetIdxs = [lastIdx];
|
|
200
|
+
what = lastTid || `(latest in ${session})`;
|
|
201
|
+
}
|
|
202
|
+
const targets = new Set(targetIdxs);
|
|
203
|
+
let merged = {};
|
|
204
|
+
const rewritten = lines.map((line, i) => {
|
|
205
|
+
if (!targets.has(i))
|
|
206
|
+
return line;
|
|
207
|
+
const t = parse(line);
|
|
208
|
+
if (!t)
|
|
209
|
+
return line; // defensive: selection only picks parseable lines.
|
|
210
|
+
merged = { ...(t.operator_label ?? {}), ...args.patch };
|
|
211
|
+
return JSON.stringify({ ...t, operator_label: merged });
|
|
212
|
+
});
|
|
213
|
+
// Atomic replace: write a sibling temp file, then rename over the target.
|
|
214
|
+
// Node has no native advisory lock and we deliberately do NOT shell out to
|
|
215
|
+
// flock(1) for this; labeling is a single-operator action that happens
|
|
216
|
+
// BETWEEN prompts (never during a hook write), so the lost-append window if
|
|
217
|
+
// the hook appends a brand-new trace between our read and rename is
|
|
218
|
+
// negligible, and the temp+rename guarantees any concurrent reader always
|
|
219
|
+
// sees a complete, consistent file.
|
|
220
|
+
const tmp = path.join(logDir(), `.ask-traces.${process.pid}.tmp`);
|
|
221
|
+
fs.writeFileSync(tmp, rewritten.join("\n") + "\n");
|
|
222
|
+
fs.renameSync(tmp, tracesFile());
|
|
223
|
+
const n = targetIdxs.length;
|
|
224
|
+
console.log(`Labeled ${what}: ${renderLabel(merged)} (${n} line${n === 1 ? "" : "s"}).`);
|
|
225
|
+
return 0;
|
|
226
|
+
}
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.parseLoginArgs = parseLoginArgs;
|
|
4
|
+
exports.runLogin = runLogin;
|
|
5
|
+
const config_1 = require("../lib/config");
|
|
6
|
+
const http_1 = require("../lib/http");
|
|
7
|
+
const auth_breaker_1 = require("../lib/auth-breaker");
|
|
8
|
+
const login_1 = require("../lib/login");
|
|
9
|
+
const wire_1 = require("../lib/wire");
|
|
10
|
+
// Default liveness probe: GET /internal/v1/auth/me through the control `get`
|
|
11
|
+
// helper, which already does the §6.5 refresh-on-401 dance. A genuinely live
|
|
12
|
+
// session (or one the access token silently refreshes for) resolves; a session
|
|
13
|
+
// whose refresh token is dead server-side rejects with HttpError.status === 401.
|
|
14
|
+
async function defaultVerifySession(cfg) {
|
|
15
|
+
await (0, http_1.get)(cfg, "/internal/v1/auth/me");
|
|
16
|
+
}
|
|
17
|
+
// `mla login` (proposal §6.6, T24).
|
|
18
|
+
//
|
|
19
|
+
// Browser-based user login over the loopback OAuth + PKCE flow (the transport
|
|
20
|
+
// lives in lib/login.ts, T21). This command is the thin policy layer on top:
|
|
21
|
+
// - refuses to run before `mla init` (no cli-config.json to write into);
|
|
22
|
+
// - is a no-op when already logged in with a comfortably-fresh refresh token;
|
|
23
|
+
// - resolves the Console URL by an explicit precedence ladder;
|
|
24
|
+
// - validates the --no-browser / --port pairing;
|
|
25
|
+
// - on success REPLACES auth.* in cli-config.json with the user-token shape
|
|
26
|
+
// (the prior shared-key value is NOT preserved, §6.6); `mla logout` is the
|
|
27
|
+
// only path back to shared-key, via a fresh `mla init --control-token`.
|
|
28
|
+
//
|
|
29
|
+
// SECURITY: this command never logs the access token, refresh token, grant code,
|
|
30
|
+
// or PKCE verifier. It prints only identity (display name / email / workspace)
|
|
31
|
+
// and the (non-secret) expiry timestamps.
|
|
32
|
+
// `mla login` is a no-op when the live user-token's refresh window still has
|
|
33
|
+
// more than this much runway. Below it (or any other mode), we re-run the flow.
|
|
34
|
+
// Mirrors §6.6: "more than 24h remaining".
|
|
35
|
+
const REFRESH_FRESH_THRESHOLD_MS = 24 * 60 * 60 * 1000;
|
|
36
|
+
// Strict argv parsing, mirroring `mla init`'s VALUE_FLAGS/BOOLEAN_FLAGS shape
|
|
37
|
+
// (init.ts): a value flag must be followed by a non-flag value; unknown flags
|
|
38
|
+
// and positionals throw. `mla login` takes no positionals.
|
|
39
|
+
const VALUE_FLAGS = new Set(["--console-url", "--port"]);
|
|
40
|
+
// --force skips every no-op/self-heal short-circuit and runs the browser flow
|
|
41
|
+
// unconditionally (escape hatch for "just re-mint my tokens").
|
|
42
|
+
const BOOLEAN_FLAGS = new Set(["--no-browser", "--force"]);
|
|
43
|
+
function parseLoginArgs(argv) {
|
|
44
|
+
const out = {};
|
|
45
|
+
for (let i = 0; i < argv.length; i++) {
|
|
46
|
+
const a = argv[i];
|
|
47
|
+
if (VALUE_FLAGS.has(a)) {
|
|
48
|
+
const v = argv[i + 1];
|
|
49
|
+
if (v === undefined) {
|
|
50
|
+
throw new Error(`Missing value for ${a}`);
|
|
51
|
+
}
|
|
52
|
+
if (v.startsWith("--") || v.startsWith("-")) {
|
|
53
|
+
throw new Error(`Missing value for ${a} (got the next flag ${v} instead)`);
|
|
54
|
+
}
|
|
55
|
+
if (a === "--console-url") {
|
|
56
|
+
out.consoleUrl = v;
|
|
57
|
+
}
|
|
58
|
+
else if (a === "--port") {
|
|
59
|
+
const port = Number(v);
|
|
60
|
+
if (!Number.isInteger(port) || port < 1 || port > 65535) {
|
|
61
|
+
throw new Error(`Invalid --port value "${v}": expected an integer in 1..65535.`);
|
|
62
|
+
}
|
|
63
|
+
out.port = port;
|
|
64
|
+
}
|
|
65
|
+
i += 1;
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
if (BOOLEAN_FLAGS.has(a)) {
|
|
69
|
+
if (a === "--no-browser")
|
|
70
|
+
out.noBrowser = true;
|
|
71
|
+
else if (a === "--force")
|
|
72
|
+
out.force = true;
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
if (a.startsWith("--") || a.startsWith("-")) {
|
|
76
|
+
throw new Error(`Unknown flag: ${a}. Supported flags: ${[...VALUE_FLAGS, ...BOOLEAN_FLAGS].sort().join(", ")}`);
|
|
77
|
+
}
|
|
78
|
+
throw new Error(`Unexpected positional argument: ${a}. \`mla login\` takes no positional arguments.`);
|
|
79
|
+
}
|
|
80
|
+
return out;
|
|
81
|
+
}
|
|
82
|
+
// Resolve the Console origin by the §6.6 precedence ladder, stopping at the first
|
|
83
|
+
// defined value: --console-url > MEETLESS_CONSOLE_URL > raw cfg.consoleUrl. When
|
|
84
|
+
// all three are absent this returns undefined and runBrowserLogin infers the
|
|
85
|
+
// origin from the control URL via its pair table (failing loud if no pair
|
|
86
|
+
// matches). We deliberately read the RAW `cfg.consoleUrl`, NOT getConsoleUrl():
|
|
87
|
+
// the latter defaults to localhost:3000 and would mask the pair-table inference
|
|
88
|
+
// (and silently point login at the wrong origin for a prod control URL).
|
|
89
|
+
function resolveConsoleOverride(flags, cfg) {
|
|
90
|
+
const fromFlag = flags.consoleUrl?.trim();
|
|
91
|
+
if (fromFlag)
|
|
92
|
+
return fromFlag;
|
|
93
|
+
const fromEnv = process.env.MEETLESS_CONSOLE_URL?.trim();
|
|
94
|
+
if (fromEnv)
|
|
95
|
+
return fromEnv;
|
|
96
|
+
const fromCfg = cfg.consoleUrl?.trim();
|
|
97
|
+
if (fromCfg)
|
|
98
|
+
return fromCfg;
|
|
99
|
+
return undefined;
|
|
100
|
+
}
|
|
101
|
+
// Map control's exchange bundle into the on-disk user-token credential. Only the
|
|
102
|
+
// four display fields of `user` survive: role here is display-only (§4.6); every
|
|
103
|
+
// authorization decision re-reads the live WorkspaceUser.role server-side.
|
|
104
|
+
function bundleToUserTokenAuth(bundle) {
|
|
105
|
+
return {
|
|
106
|
+
mode: "user-token",
|
|
107
|
+
accessToken: bundle.accessToken,
|
|
108
|
+
refreshToken: bundle.refreshToken,
|
|
109
|
+
accessExpiresAt: bundle.accessExpiresAt,
|
|
110
|
+
refreshExpiresAt: bundle.refreshExpiresAt,
|
|
111
|
+
sessionId: bundle.sessionId,
|
|
112
|
+
user: {
|
|
113
|
+
id: bundle.user.id,
|
|
114
|
+
displayName: bundle.user.displayName,
|
|
115
|
+
email: bundle.user.email,
|
|
116
|
+
role: bundle.user.role,
|
|
117
|
+
},
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
// Best-effort humanizer for "how much runway is left" on an ISO expiry. Returns
|
|
121
|
+
// null for an unparseable/empty timestamp so callers can degrade gracefully.
|
|
122
|
+
function formatRemaining(iso) {
|
|
123
|
+
const ms = Date.parse(iso) - Date.now();
|
|
124
|
+
if (Number.isNaN(ms))
|
|
125
|
+
return null;
|
|
126
|
+
if (ms <= 0)
|
|
127
|
+
return "expired";
|
|
128
|
+
const hours = Math.floor(ms / (60 * 60 * 1000));
|
|
129
|
+
if (hours < 48)
|
|
130
|
+
return `in ~${hours}h`;
|
|
131
|
+
return `in ~${Math.floor(hours / 24)}d`;
|
|
132
|
+
}
|
|
133
|
+
// The shared "you're still logged in" message. Pulled out so the fast path, the
|
|
134
|
+
// verify-confirmed path, and the offline-fallback path all print identically.
|
|
135
|
+
function printAlreadyLoggedIn(auth) {
|
|
136
|
+
const who = auth.user.displayName || auth.user.id;
|
|
137
|
+
const email = auth.user.email ? ` <${auth.user.email}>` : "";
|
|
138
|
+
const runway = formatRemaining(auth.refreshExpiresAt);
|
|
139
|
+
console.log(`Already logged in as ${who}${email}.`);
|
|
140
|
+
console.log(` Session expires ${runway ?? "soon"} (run \`mla logout && mla login\` to re-login).`);
|
|
141
|
+
}
|
|
142
|
+
async function runLogin(argv, deps = {}) {
|
|
143
|
+
const verifySession = deps.verifySession ?? defaultVerifySession;
|
|
144
|
+
const browserLogin = deps.browserLogin ?? login_1.runBrowserLogin;
|
|
145
|
+
let flags;
|
|
146
|
+
try {
|
|
147
|
+
flags = parseLoginArgs(argv);
|
|
148
|
+
}
|
|
149
|
+
catch (e) {
|
|
150
|
+
console.error(e.message);
|
|
151
|
+
return 2;
|
|
152
|
+
}
|
|
153
|
+
// --no-browser needs a fixed loopback port: the browser runs on a different
|
|
154
|
+
// machine (SSH), so the operator forwards `ssh -L <port>:127.0.0.1:<port>`
|
|
155
|
+
// ahead of time and the redirect_uri must target that known port (§6.6). With
|
|
156
|
+
// a browser on this machine, port 0 (kernel-assigned) is correct.
|
|
157
|
+
if (flags.noBrowser && flags.port === undefined) {
|
|
158
|
+
console.error("--port <n> is required with --no-browser: the loopback redirect must " +
|
|
159
|
+
"target a port you have forwarded (e.g. `ssh -L 8765:127.0.0.1:8765`).");
|
|
160
|
+
return 2;
|
|
161
|
+
}
|
|
162
|
+
// `mla login` writes INTO cli-config.json. When none exists yet (a fresh
|
|
163
|
+
// install that goes straight to `mla login`, which is the documented flow on
|
|
164
|
+
// the install page), bootstrap a minimal MACHINE config pointing at the hosted
|
|
165
|
+
// prod backend, then carry on -- so login works with zero extra steps instead
|
|
166
|
+
// of dead-ending on "run `mla init` first".
|
|
167
|
+
//
|
|
168
|
+
// Multi-repo safety: this is HOME-level (one cli-config.json per MEETLESS_HOME,
|
|
169
|
+
// shared by every repo on the machine, the long-standing model) and writes NO
|
|
170
|
+
// per-folder workspace binding. A user with several repos still binds each one
|
|
171
|
+
// to its own workspace through its `.meetless.json` marker (`mla activate`);
|
|
172
|
+
// login never reads or writes that, so it is correct from any directory. The
|
|
173
|
+
// bootstrap is idempotent: it fires only when the config is absent, so a second
|
|
174
|
+
// `mla login` from another repo just reads the existing config.
|
|
175
|
+
//
|
|
176
|
+
// It deliberately does NOT wire capture hooks or the MCP server (that is
|
|
177
|
+
// `mla init`'s runWire job, §6.6) -- it only creates the config the browser
|
|
178
|
+
// login writes tokens into. A non-default backend (dogfood/staging/self-host)
|
|
179
|
+
// is still pinned with `mla init --control-url ...`; MEETLESS_BACKEND_URL /
|
|
180
|
+
// MEETLESS_INTEL_URL continue to override these defaults at read time
|
|
181
|
+
// (readConfig), so a one-off `MEETLESS_BACKEND_URL=... mla login` still works.
|
|
182
|
+
if (!(0, config_1.configExists)()) {
|
|
183
|
+
(0, config_1.writeConfig)({
|
|
184
|
+
controlUrl: config_1.DEFAULT_CONTROL_URL,
|
|
185
|
+
controlToken: "", // auth.mode 'none': no bearer until the login below
|
|
186
|
+
intelUrl: config_1.DEFAULT_INTEL_URL,
|
|
187
|
+
mlaPath: (0, wire_1.resolveMlaPath)(),
|
|
188
|
+
auth: { mode: "none" },
|
|
189
|
+
});
|
|
190
|
+
console.log(`No cli-config.json found; created ${config_1.CFG_PATH} for the hosted backend ` +
|
|
191
|
+
`(${config_1.DEFAULT_CONTROL_URL}).`);
|
|
192
|
+
console.log("Tip: run `mla init` to wire capture hooks and the Meetless MCP server " +
|
|
193
|
+
"into your coding agent.");
|
|
194
|
+
}
|
|
195
|
+
let cfg;
|
|
196
|
+
try {
|
|
197
|
+
cfg = (0, config_1.readConfig)();
|
|
198
|
+
}
|
|
199
|
+
catch (e) {
|
|
200
|
+
// readConfig throws loudly on a corrupt config or the Gate-4 env conflict
|
|
201
|
+
// (user-token on disk + MEETLESS_CONTROL_TOKEN set). Surface it verbatim.
|
|
202
|
+
console.error(e.message);
|
|
203
|
+
return 1;
|
|
204
|
+
}
|
|
205
|
+
// No-op when already logged in with a comfortably-fresh refresh token. Forcing
|
|
206
|
+
// a re-login is `mla login --force` (or `mla logout && mla login`). A corrupt/
|
|
207
|
+
// empty refreshExpiresAt parses to NaN, so this guard safely falls through to a
|
|
208
|
+
// real login rather than mis-treating a broken session as fresh.
|
|
209
|
+
//
|
|
210
|
+
// T29 self-heal: NO local timestamp is proof the session is alive. The on-disk
|
|
211
|
+
// refresh token can be dead server-side (rotated/revoked) while refreshExpiresAt
|
|
212
|
+
// still reads ~Nd out, AND the access JWT can sit well inside its 24h TTL while
|
|
213
|
+
// the session it belongs to was revoked (e.g. a control-dev reseed). The original
|
|
214
|
+
// T29 trusted a still-live access token and fast-no-op'd without probing, so An
|
|
215
|
+
// hit the exact closed loop it was meant to kill: every hook 401'd telling him to
|
|
216
|
+
// "run `mla login`", and `mla login` answered "already logged in" all day. So we
|
|
217
|
+
// NEVER short-circuit on a local timestamp alone:
|
|
218
|
+
// - refresh window locally-fresh -> ALWAYS PROBE control (GET /auth/me, which
|
|
219
|
+
// refreshes transparently). Live -> no-op. Rejected (401/403) -> the session
|
|
220
|
+
// is dead server-side: fall through to a real browser login (self-heal).
|
|
221
|
+
// Unreachable (network error, no .status) -> keep the cached session rather
|
|
222
|
+
// than force a doomed flow on someone merely offline.
|
|
223
|
+
// - refresh window locally-expired -> no probe; re-auth is required regardless,
|
|
224
|
+
// so drop straight through to a browser login.
|
|
225
|
+
// - --force always re-authenticates and skips the probe entirely.
|
|
226
|
+
if (cfg.auth.mode === "user-token" && !flags.force) {
|
|
227
|
+
const auth = cfg.auth;
|
|
228
|
+
const refreshRemainingMs = Date.parse(auth.refreshExpiresAt) - Date.now();
|
|
229
|
+
const refreshLocallyFresh = !Number.isNaN(refreshRemainingMs) && refreshRemainingMs > REFRESH_FRESH_THRESHOLD_MS;
|
|
230
|
+
if (refreshLocallyFresh) {
|
|
231
|
+
// Verify against control before ever declaring "already logged in". login is a
|
|
232
|
+
// rare, interactive command, so one GET /auth/me on the happy path is a non-issue
|
|
233
|
+
// next to the all-day dead loop a blind no-op can hide.
|
|
234
|
+
try {
|
|
235
|
+
await verifySession(cfg);
|
|
236
|
+
printAlreadyLoggedIn(auth);
|
|
237
|
+
return 0;
|
|
238
|
+
}
|
|
239
|
+
catch (e) {
|
|
240
|
+
const status = e.status;
|
|
241
|
+
if (status === 401 || status === 403) {
|
|
242
|
+
// Session is dead server-side. Self-heal: drop through to a real browser
|
|
243
|
+
// login instead of the old no-op dead end.
|
|
244
|
+
console.log("Cached session is no longer valid server-side. Re-authenticating...");
|
|
245
|
+
}
|
|
246
|
+
else {
|
|
247
|
+
// No HTTP status -> never reached the server (control down / offline).
|
|
248
|
+
// Keep the cached session; do not open a browser flow that cannot reach
|
|
249
|
+
// control anyway.
|
|
250
|
+
printAlreadyLoggedIn(auth);
|
|
251
|
+
console.log(" (could not verify with control; keeping cached session for now.)");
|
|
252
|
+
return 0;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
const consoleUrl = resolveConsoleOverride(flags, cfg);
|
|
258
|
+
let bundle;
|
|
259
|
+
try {
|
|
260
|
+
bundle = await browserLogin({
|
|
261
|
+
controlUrl: cfg.controlUrl,
|
|
262
|
+
consoleUrl,
|
|
263
|
+
noBrowser: flags.noBrowser ?? false,
|
|
264
|
+
port: flags.port,
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
catch (e) {
|
|
268
|
+
// runBrowserLogin already keeps tokens/codes out of its messages. Print the
|
|
269
|
+
// message (timeout, CSRF refusal, exchange failure, missing console URL).
|
|
270
|
+
console.error(e.message);
|
|
271
|
+
return 1;
|
|
272
|
+
}
|
|
273
|
+
const auth = bundleToUserTokenAuth(bundle);
|
|
274
|
+
// REPLACE auth.* outright: no shared-key preservation (§6.6). actorUserId and
|
|
275
|
+
// controlToken are re-derived by writeConfig/readConfig from the new auth.
|
|
276
|
+
(0, config_1.writeConfig)({
|
|
277
|
+
...cfg,
|
|
278
|
+
auth,
|
|
279
|
+
controlToken: auth.accessToken,
|
|
280
|
+
actorUserId: auth.user.id,
|
|
281
|
+
});
|
|
282
|
+
// A fresh login retires any dead-auth circuit breaker proactively. consult also
|
|
283
|
+
// self-clears on the fingerprint mismatch, but clearing here reopens the gate
|
|
284
|
+
// for live `mla mcp` workers the instant the new token lands on disk.
|
|
285
|
+
(0, auth_breaker_1.clearAuthBreaker)();
|
|
286
|
+
const email = bundle.user.email ? ` <${bundle.user.email}>` : "";
|
|
287
|
+
const accessRunway = formatRemaining(bundle.accessExpiresAt);
|
|
288
|
+
const refreshRunway = formatRemaining(bundle.refreshExpiresAt);
|
|
289
|
+
console.log(`Logged in as ${bundle.user.displayName}${email}.`);
|
|
290
|
+
console.log(` Workspace: ${bundle.workspace.name} (${bundle.workspace.slug})`);
|
|
291
|
+
console.log(` Role: ${bundle.user.role}`);
|
|
292
|
+
console.log(` Access token expires ${accessRunway ?? "soon"}; refresh token ${refreshRunway ?? "soon"}.`);
|
|
293
|
+
console.log("Next: mla whoami");
|
|
294
|
+
return 0;
|
|
295
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.revokeCliSession = revokeCliSession;
|
|
4
|
+
exports.runLogout = runLogout;
|
|
5
|
+
const config_1 = require("../lib/config");
|
|
6
|
+
// Raw fetch to the guardless revoke route. Deliberately bypasses http.ts's
|
|
7
|
+
// doFetch (which always stamps an Authorization header): the refresh token in the
|
|
8
|
+
// body is the proof, and we send no bearer because the access token may be dead.
|
|
9
|
+
// Never throws: a network failure becomes a non-cleared result so the caller can
|
|
10
|
+
// still clear local state.
|
|
11
|
+
async function revokeCliSession(controlUrl, sessionId, refreshToken, timeoutMs = 10000) {
|
|
12
|
+
const url = `${controlUrl.replace(/\/+$/, "")}/internal/v1/auth/sessions/revoke`;
|
|
13
|
+
const controller = new AbortController();
|
|
14
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
15
|
+
try {
|
|
16
|
+
const res = await fetch(url, {
|
|
17
|
+
method: "POST",
|
|
18
|
+
// Content-Type only; NO Authorization header (body proof-of-possession).
|
|
19
|
+
headers: { "Content-Type": "application/json" },
|
|
20
|
+
body: JSON.stringify({ sessionId, refreshToken }),
|
|
21
|
+
signal: controller.signal,
|
|
22
|
+
});
|
|
23
|
+
if (res.ok)
|
|
24
|
+
return { serverCleared: true, detail: "session revoked" };
|
|
25
|
+
if (res.status === 401 || res.status === 410) {
|
|
26
|
+
return {
|
|
27
|
+
serverCleared: true,
|
|
28
|
+
detail: "session was already revoked server-side",
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
return { serverCleared: false, detail: `control returned HTTP ${res.status}` };
|
|
32
|
+
}
|
|
33
|
+
catch (e) {
|
|
34
|
+
// Timeout / DNS / connection refused: do not block the local clear.
|
|
35
|
+
return {
|
|
36
|
+
serverCleared: false,
|
|
37
|
+
detail: `control unreachable (${e.name})`,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
finally {
|
|
41
|
+
clearTimeout(timer);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
async function runLogout(argv, deps = {}) {
|
|
45
|
+
if (argv.length > 0) {
|
|
46
|
+
console.error(`\`mla logout\` takes no arguments (got: ${argv.join(" ")}). ` +
|
|
47
|
+
"There is no --all flag; revoke other sessions from the Console.");
|
|
48
|
+
return 2;
|
|
49
|
+
}
|
|
50
|
+
const log = deps.log ?? ((m) => console.log(m));
|
|
51
|
+
const revokeFn = deps.revokeFn ?? revokeCliSession;
|
|
52
|
+
if (!(0, config_1.configExists)()) {
|
|
53
|
+
// Nothing to log out of, and nothing to write. Idempotent success.
|
|
54
|
+
log("Not logged in (no cli-config.json). Nothing to do.");
|
|
55
|
+
return 0;
|
|
56
|
+
}
|
|
57
|
+
let cfg;
|
|
58
|
+
try {
|
|
59
|
+
cfg = (0, config_1.readConfig)();
|
|
60
|
+
}
|
|
61
|
+
catch (e) {
|
|
62
|
+
// A corrupt/conflicting config can't be safely rewritten here; surface it.
|
|
63
|
+
console.error(e.message);
|
|
64
|
+
return 1;
|
|
65
|
+
}
|
|
66
|
+
if (cfg.auth.mode !== "user-token") {
|
|
67
|
+
// shared-key or none: there is no user session to revoke. We deliberately do
|
|
68
|
+
// NOT touch a shared-key config (logout is not "downgrade my shared key");
|
|
69
|
+
// the operator manages that via `mla init`.
|
|
70
|
+
if (cfg.auth.mode === "shared-key") {
|
|
71
|
+
log("Logged in with a shared key, not a user session; nothing to revoke.");
|
|
72
|
+
log("To remove it, edit cli-config.json or re-run `mla init`.");
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
log("Already logged out (auth.mode: none).");
|
|
76
|
+
}
|
|
77
|
+
return 0;
|
|
78
|
+
}
|
|
79
|
+
const { sessionId, refreshToken } = cfg.auth;
|
|
80
|
+
const who = cfg.auth.user.displayName || cfg.auth.user.id;
|
|
81
|
+
// Best-effort server revoke. Missing sessionId/refreshToken (corrupt session)
|
|
82
|
+
// skips the network call and goes straight to local clear.
|
|
83
|
+
if (sessionId && refreshToken) {
|
|
84
|
+
const result = await revokeFn(cfg.controlUrl, sessionId, refreshToken);
|
|
85
|
+
if (result.serverCleared) {
|
|
86
|
+
log(`Revoked: ${result.detail}.`);
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
log(`Local logout complete, but ${result.detail}.`);
|
|
90
|
+
log("The session may still be active server-side until it expires.");
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
log("Local session was incomplete; clearing it without a server revoke.");
|
|
95
|
+
}
|
|
96
|
+
// Clear auth.* to the terminal `none` state. NEVER restore shared-key (§6.6).
|
|
97
|
+
// Top-level controlUrl/intelUrl/mlaPath/etc. survive so the next
|
|
98
|
+
// `mla init --control-token` or `mla login` runs cleanly. writeConfig
|
|
99
|
+
// re-derives controlToken ("") and drops actorUserId under none.
|
|
100
|
+
(0, config_1.writeConfig)({
|
|
101
|
+
...cfg,
|
|
102
|
+
auth: { mode: "none" },
|
|
103
|
+
controlToken: "",
|
|
104
|
+
actorUserId: undefined,
|
|
105
|
+
});
|
|
106
|
+
log(`Logged out ${who}. Run \`mla login\` to log back in.`);
|
|
107
|
+
return 0;
|
|
108
|
+
}
|