@mnemom/mnemom 0.16.2 → 0.16.3
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 +1 -0
- package/dist/commands/agents.d.ts +14 -0
- package/dist/commands/agents.js +100 -2
- package/dist/commands/card.d.ts +43 -0
- package/dist/commands/card.js +153 -102
- package/dist/commands/logs.js +11 -1
- package/dist/commands/onboard.d.ts +59 -0
- package/dist/commands/onboard.js +395 -0
- package/dist/commands/org.d.ts +13 -0
- package/dist/commands/org.js +63 -2
- package/dist/commands/protection.d.ts +10 -0
- package/dist/commands/protection.js +109 -0
- package/dist/commands/status.js +5 -0
- package/dist/commands/try-me.js +9 -0
- package/dist/commands/usage.d.ts +35 -0
- package/dist/commands/usage.js +265 -0
- package/dist/commands/wrap.d.ts +25 -0
- package/dist/commands/wrap.js +331 -0
- package/dist/index.js +192 -6
- package/dist/lib/agent-config.d.ts +27 -0
- package/dist/lib/agent-config.js +86 -0
- package/dist/lib/api.d.ts +122 -1
- package/dist/lib/api.js +128 -183
- package/dist/lib/auth.js +21 -1
- package/dist/lib/cli-config.d.ts +33 -0
- package/dist/lib/cli-config.js +70 -0
- package/dist/lib/config.d.ts +10 -0
- package/dist/lib/config.js +39 -3
- package/dist/lib/keyed-identity.d.ts +35 -0
- package/dist/lib/keyed-identity.js +363 -0
- package/dist/lib/oauth.d.ts +26 -4
- package/dist/lib/oauth.js +98 -29
- package/dist/lib/protection-drift.d.ts +117 -0
- package/dist/lib/protection-drift.js +180 -0
- package/dist/lib/skills.js +25 -12
- package/dist/lib/version-gate.d.ts +37 -0
- package/dist/lib/version-gate.js +84 -0
- package/package.json +7 -7
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `mnemom onboard` — self-onboard the CALLING coding agent end-to-end (MNE-933,
|
|
3
|
+
* A2, epic MNE-931). Job #1 of the skill lane.
|
|
4
|
+
*
|
|
5
|
+
* Walks the canonical `become_sovereign` sequence for the agent that is calling
|
|
6
|
+
* right now — scan its trust posture, claim its identity, declare an alignment
|
|
7
|
+
* card, surface its Trust Rating, and hand back a public badge URL — in one
|
|
8
|
+
* command, with no manifest and no hand-assembled sub-command chain.
|
|
9
|
+
*
|
|
10
|
+
* scan_trust → claim → alignment → rating → badge
|
|
11
|
+
*
|
|
12
|
+
* AUTH MODEL (the confirmed MNE-944 contract):
|
|
13
|
+
* - The calling agent authenticates as ITS OWN principal via the device-grant
|
|
14
|
+
* OAuth flow (`loginWithDeviceFlow`) — deliberately NOT the human's full
|
|
15
|
+
* org-membership session (the MNE-870 lesson).
|
|
16
|
+
* - The per-agent `agent:<id>:cards:write` scope is standing, minted server-
|
|
17
|
+
* side at claim; that scope alone authorizes the alignment PUT. No separate
|
|
18
|
+
* protection-capability mint, no protection card, no grant handoff, no
|
|
19
|
+
* dojo bind — those belong to the try-me / dojo flow, not this skill.
|
|
20
|
+
* - Claiming needs a proof of key possession. The claim endpoint's client
|
|
21
|
+
* contract (`claimAgent`) requires a `hash_proof` on every call regardless
|
|
22
|
+
* of the OAuth principal, so `--key` (or `--hash-proof`) is REQUIRED to
|
|
23
|
+
* claim a not-yet-claimed agent; an already-claimed agent skips the claim
|
|
24
|
+
* entirely (idempotency). Claiming lands in the agent's PERSONAL org by
|
|
25
|
+
* default (`getMyPersonalOrg`), never silently into a shared org.
|
|
26
|
+
*
|
|
27
|
+
* HUMAN-IN-THE-LOOP: the only mutation is the agent's own alignment-card
|
|
28
|
+
* declaration, authorized by its own standing scoped token. Every privileged
|
|
29
|
+
* act (protection card, grant, dojo bind, org move) stays server/human-gated
|
|
30
|
+
* and is intentionally out of scope here.
|
|
31
|
+
*
|
|
32
|
+
* The Trust Rating surfaced here is the CLI-reachable `integrity_score`. On a
|
|
33
|
+
* clean-machine first run the agent has 0 traces, so the rating is reported as
|
|
34
|
+
* `provisional` — never a bare perfect score. The SIGNED Trust Rating and the
|
|
35
|
+
* rendered public badge are computed server-side (observer / mnemom-api, post-
|
|
36
|
+
* hoc) and become authoritative once the observer pipeline generates traces.
|
|
37
|
+
*
|
|
38
|
+
* See SKILL-RUNNER-CONTRACT.md §2–§4 for the lifecycle + --json output contract.
|
|
39
|
+
*/
|
|
40
|
+
export interface OnboardOptions {
|
|
41
|
+
/** Machine-readable output (implies non-interactive). */
|
|
42
|
+
json?: boolean;
|
|
43
|
+
/** Non-interactive: accept defaults, skip all prompts. */
|
|
44
|
+
yes?: boolean;
|
|
45
|
+
/** The calling agent's id (else agent-config → MNEMOM_AGENT → prompt). */
|
|
46
|
+
agent?: string;
|
|
47
|
+
/** Commander `--no-open` sets this false → never auto-open the badge URL. */
|
|
48
|
+
open?: boolean;
|
|
49
|
+
/** The agent's provider API key — the CLI derives the claim hash proof from it. */
|
|
50
|
+
key?: string;
|
|
51
|
+
/** A pre-computed 64-hex hash proof (advanced/CI; alternative to --key). */
|
|
52
|
+
hashProof?: string;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Entry point for `mnemom onboard`. Throws on a fatal error (caught by the
|
|
56
|
+
* index.ts wrapper, which prints + exits non-zero); non-fatal steps degrade to
|
|
57
|
+
* `skipped`/`error` and the run still reports a `partial` verdict.
|
|
58
|
+
*/
|
|
59
|
+
export declare function onboardCommand(options?: OnboardOptions): Promise<void>;
|
|
@@ -0,0 +1,395 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `mnemom onboard` — self-onboard the CALLING coding agent end-to-end (MNE-933,
|
|
3
|
+
* A2, epic MNE-931). Job #1 of the skill lane.
|
|
4
|
+
*
|
|
5
|
+
* Walks the canonical `become_sovereign` sequence for the agent that is calling
|
|
6
|
+
* right now — scan its trust posture, claim its identity, declare an alignment
|
|
7
|
+
* card, surface its Trust Rating, and hand back a public badge URL — in one
|
|
8
|
+
* command, with no manifest and no hand-assembled sub-command chain.
|
|
9
|
+
*
|
|
10
|
+
* scan_trust → claim → alignment → rating → badge
|
|
11
|
+
*
|
|
12
|
+
* AUTH MODEL (the confirmed MNE-944 contract):
|
|
13
|
+
* - The calling agent authenticates as ITS OWN principal via the device-grant
|
|
14
|
+
* OAuth flow (`loginWithDeviceFlow`) — deliberately NOT the human's full
|
|
15
|
+
* org-membership session (the MNE-870 lesson).
|
|
16
|
+
* - The per-agent `agent:<id>:cards:write` scope is standing, minted server-
|
|
17
|
+
* side at claim; that scope alone authorizes the alignment PUT. No separate
|
|
18
|
+
* protection-capability mint, no protection card, no grant handoff, no
|
|
19
|
+
* dojo bind — those belong to the try-me / dojo flow, not this skill.
|
|
20
|
+
* - Claiming needs a proof of key possession. The claim endpoint's client
|
|
21
|
+
* contract (`claimAgent`) requires a `hash_proof` on every call regardless
|
|
22
|
+
* of the OAuth principal, so `--key` (or `--hash-proof`) is REQUIRED to
|
|
23
|
+
* claim a not-yet-claimed agent; an already-claimed agent skips the claim
|
|
24
|
+
* entirely (idempotency). Claiming lands in the agent's PERSONAL org by
|
|
25
|
+
* default (`getMyPersonalOrg`), never silently into a shared org.
|
|
26
|
+
*
|
|
27
|
+
* HUMAN-IN-THE-LOOP: the only mutation is the agent's own alignment-card
|
|
28
|
+
* declaration, authorized by its own standing scoped token. Every privileged
|
|
29
|
+
* act (protection card, grant, dojo bind, org move) stays server/human-gated
|
|
30
|
+
* and is intentionally out of scope here.
|
|
31
|
+
*
|
|
32
|
+
* The Trust Rating surfaced here is the CLI-reachable `integrity_score`. On a
|
|
33
|
+
* clean-machine first run the agent has 0 traces, so the rating is reported as
|
|
34
|
+
* `provisional` — never a bare perfect score. The SIGNED Trust Rating and the
|
|
35
|
+
* rendered public badge are computed server-side (observer / mnemom-api, post-
|
|
36
|
+
* hoc) and become authoritative once the observer pipeline generates traces.
|
|
37
|
+
*
|
|
38
|
+
* See SKILL-RUNNER-CONTRACT.md §2–§4 for the lifecycle + --json output contract.
|
|
39
|
+
*/
|
|
40
|
+
import { getAgent, getIntegrity, claimAgent, putAlignmentCard, getMyPersonalOrg, MnemomApiError, } from "../lib/api.js";
|
|
41
|
+
import { resolveAuth, loginWithDeviceFlow } from "../lib/auth.js";
|
|
42
|
+
import { getAgentId, getAgentName, mergeAgentConfig } from "../lib/agent-config.js";
|
|
43
|
+
import { deriveHashProof } from "./agents.js";
|
|
44
|
+
import { isInteractive, askInput, askYesNo } from "../lib/prompt.js";
|
|
45
|
+
import { getWebsiteUrl } from "../lib/config.js";
|
|
46
|
+
import { openBrowser } from "../lib/oauth.js";
|
|
47
|
+
import { fmt } from "../lib/format.js";
|
|
48
|
+
const FULL_PROOF_RE = /^[0-9a-f]{64}$/;
|
|
49
|
+
/**
|
|
50
|
+
* Entry point for `mnemom onboard`. Throws on a fatal error (caught by the
|
|
51
|
+
* index.ts wrapper, which prints + exits non-zero); non-fatal steps degrade to
|
|
52
|
+
* `skipped`/`error` and the run still reports a `partial` verdict.
|
|
53
|
+
*/
|
|
54
|
+
export async function onboardCommand(options = {}) {
|
|
55
|
+
const json = !!options.json;
|
|
56
|
+
const nonInteractive = !!options.yes || json || !isInteractive();
|
|
57
|
+
const autoOpen = options.open !== false; // --no-open → false
|
|
58
|
+
const steps = [];
|
|
59
|
+
// A human-facing log that is silenced in --json mode (the JSON is the output).
|
|
60
|
+
const say = (line = "") => {
|
|
61
|
+
if (!json)
|
|
62
|
+
console.log(line);
|
|
63
|
+
};
|
|
64
|
+
const result = {
|
|
65
|
+
skill: "onboard",
|
|
66
|
+
verdict: "error",
|
|
67
|
+
agent_id: null,
|
|
68
|
+
claimed: false,
|
|
69
|
+
card_published: false,
|
|
70
|
+
rating: null,
|
|
71
|
+
badge_url: null,
|
|
72
|
+
next_step: null,
|
|
73
|
+
steps,
|
|
74
|
+
};
|
|
75
|
+
say(fmt.header("Mnemom — onboard"));
|
|
76
|
+
say();
|
|
77
|
+
say(fmt.dim("Self-onboard this agent: scan → claim → declare → rating → badge."));
|
|
78
|
+
// Resolve the calling agent's id (no network).
|
|
79
|
+
const agentId = await resolveCallingAgentId(options, nonInteractive, say);
|
|
80
|
+
result.agent_id = agentId;
|
|
81
|
+
// ── State 1: scan_trust ─────────────────────────────────────────────────────
|
|
82
|
+
const posture = await scanTrust(agentId, steps, say);
|
|
83
|
+
// ── State 2: claim identity ─────────────────────────────────────────────────
|
|
84
|
+
await ensureAgentSession(nonInteractive, result, json, say);
|
|
85
|
+
if (posture.alreadyClaimed) {
|
|
86
|
+
result.claimed = true;
|
|
87
|
+
steps.push({ step: "claim", status: "skipped", detail: "already claimed" });
|
|
88
|
+
say(fmt.success("Already claimed — nothing to do."));
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
await claimIdentity(agentId, posture.name, options, result, json, steps, say);
|
|
92
|
+
}
|
|
93
|
+
// ── State 3: declare alignment card ─────────────────────────────────────────
|
|
94
|
+
say();
|
|
95
|
+
say(`${fmt.badge("alignment", "cyan")} Declaring your alignment card…`);
|
|
96
|
+
try {
|
|
97
|
+
await putAlignmentCard(agentId, JSON.stringify(buildStarterAlignmentCard(agentId)), "application/json",
|
|
98
|
+
// A STABLE key so a re-run replays the same server-side reservation
|
|
99
|
+
// (idempotent no-op) rather than writing a divergent duplicate card.
|
|
100
|
+
{ idempotencyKey: `onboard-alignment-${agentId}` });
|
|
101
|
+
result.card_published = true;
|
|
102
|
+
steps.push({ step: "alignment", status: "ok" });
|
|
103
|
+
say(fmt.success("Alignment card declared."));
|
|
104
|
+
}
|
|
105
|
+
catch (err) {
|
|
106
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
107
|
+
steps.push({ step: "alignment", status: "error", detail: msg });
|
|
108
|
+
say(fmt.warn(`Alignment card skipped — declare it later with \`mnemom card publish\`. (${msg})`));
|
|
109
|
+
}
|
|
110
|
+
// ── State 4: earn Trust Rating ──────────────────────────────────────────────
|
|
111
|
+
result.rating = await earnRating(agentId, steps, say);
|
|
112
|
+
// ── State 5: badge ──────────────────────────────────────────────────────────
|
|
113
|
+
const badgeUrl = `${getWebsiteUrl().replace(/\/$/, "")}/agents/${encodeURIComponent(agentId)}`;
|
|
114
|
+
result.badge_url = badgeUrl;
|
|
115
|
+
steps.push({ step: "badge", status: "ok", detail: badgeUrl });
|
|
116
|
+
say();
|
|
117
|
+
say(`${fmt.badge("badge", "cyan")} Your public agent page:`);
|
|
118
|
+
say(fmt.label(" Badge:", badgeUrl));
|
|
119
|
+
if (!nonInteractive && autoOpen) {
|
|
120
|
+
const open = await askYesNo("Open your badge page in a browser?", true);
|
|
121
|
+
if (open)
|
|
122
|
+
openBrowser(badgeUrl);
|
|
123
|
+
}
|
|
124
|
+
// ── Report ──────────────────────────────────────────────────────────────────
|
|
125
|
+
result.verdict = result.claimed && result.card_published ? "ok" : "partial";
|
|
126
|
+
result.next_step = result.card_published ? null : "declare";
|
|
127
|
+
say();
|
|
128
|
+
say(renderRecount(result));
|
|
129
|
+
if (json)
|
|
130
|
+
console.log(JSON.stringify(result, null, 2));
|
|
131
|
+
}
|
|
132
|
+
// ── identity resolution ────────────────────────────────────────────────────────
|
|
133
|
+
/**
|
|
134
|
+
* Resolve the calling agent's id: --agent → agent-config (`getAgentId`) →
|
|
135
|
+
* MNEMOM_AGENT → an interactive prompt. Fails CLOSED with a clear, actionable
|
|
136
|
+
* error when nothing is available and we can't prompt — never proceeds silently.
|
|
137
|
+
*/
|
|
138
|
+
async function resolveCallingAgentId(options, nonInteractive, say) {
|
|
139
|
+
const fromFlag = options.agent?.trim();
|
|
140
|
+
if (fromFlag)
|
|
141
|
+
return fromFlag;
|
|
142
|
+
const fromConfig = getAgentId();
|
|
143
|
+
if (fromConfig) {
|
|
144
|
+
say(fmt.dim(`Using saved agent id ${fromConfig} (override with --agent).`));
|
|
145
|
+
return fromConfig;
|
|
146
|
+
}
|
|
147
|
+
const fromEnv = process.env.MNEMOM_AGENT?.trim();
|
|
148
|
+
if (fromEnv)
|
|
149
|
+
return fromEnv;
|
|
150
|
+
if (nonInteractive) {
|
|
151
|
+
throw new Error("No agent identity to onboard. Pass --agent <id>, set MNEMOM_AGENT, or run " +
|
|
152
|
+
"`mnemom wrap` first to provision one.");
|
|
153
|
+
}
|
|
154
|
+
const typed = (await askInput("Which agent id should I onboard?")).trim();
|
|
155
|
+
if (!typed) {
|
|
156
|
+
throw new Error("An agent id is required. Pass --agent <id>, set MNEMOM_AGENT, or run `mnemom wrap` first.");
|
|
157
|
+
}
|
|
158
|
+
return typed;
|
|
159
|
+
}
|
|
160
|
+
// ── State 1: scan_trust ────────────────────────────────────────────────────────
|
|
161
|
+
/**
|
|
162
|
+
* Read the calling agent's CURRENT posture before we claim/declare, so the run
|
|
163
|
+
* can report "here is what you look like now" first. A not-yet-visible or
|
|
164
|
+
* unclaimed agent is a cold read that must degrade gracefully (report
|
|
165
|
+
* `unclaimed`/`unknown`, status `ok` with a detail — never crash the run).
|
|
166
|
+
*/
|
|
167
|
+
async function scanTrust(agentId, steps, say) {
|
|
168
|
+
say();
|
|
169
|
+
say(`${fmt.badge("scan_trust", "cyan")} Reading your current trust posture…`);
|
|
170
|
+
let alreadyClaimed = false;
|
|
171
|
+
let name;
|
|
172
|
+
const parts = [];
|
|
173
|
+
try {
|
|
174
|
+
const agent = await getAgent(agentId);
|
|
175
|
+
alreadyClaimed = agent.claimed === true || !!agent.claimed_by;
|
|
176
|
+
name = agent.name ?? undefined;
|
|
177
|
+
parts.push(alreadyClaimed ? "claimed" : "unclaimed");
|
|
178
|
+
}
|
|
179
|
+
catch {
|
|
180
|
+
// Cold read: the agent isn't visible to us yet (unclaimed/unknown/offline).
|
|
181
|
+
parts.push("unknown (not yet visible)");
|
|
182
|
+
}
|
|
183
|
+
try {
|
|
184
|
+
const integ = await getIntegrity(agentId);
|
|
185
|
+
parts.push(`integrity_score ${integ.integrity_score.toFixed(2)} (${integ.total_traces} traces)`);
|
|
186
|
+
}
|
|
187
|
+
catch {
|
|
188
|
+
parts.push("integrity unavailable");
|
|
189
|
+
}
|
|
190
|
+
const detail = parts.join(", ");
|
|
191
|
+
steps.push({ step: "scan_trust", status: "ok", detail });
|
|
192
|
+
say(fmt.dim(` ${detail}`));
|
|
193
|
+
return { alreadyClaimed, name: name ?? getAgentName() };
|
|
194
|
+
}
|
|
195
|
+
// ── State 2: claim identity ────────────────────────────────────────────────────
|
|
196
|
+
/**
|
|
197
|
+
* Ensure a device-grant session as the AGENT principal before the claim/declare
|
|
198
|
+
* mutations. If no credential is present, run the device-grant OAuth flow (the
|
|
199
|
+
* agent's own principal — never the human's full org-membership creds, MNE-944 /
|
|
200
|
+
* MNE-870). In non-interactive mode with no session we fail CLOSED with a clear
|
|
201
|
+
* error rather than hanging or silently degrading.
|
|
202
|
+
*/
|
|
203
|
+
async function ensureAgentSession(nonInteractive, result, json, say) {
|
|
204
|
+
const cred = await resolveAuth();
|
|
205
|
+
if (cred.type !== "none")
|
|
206
|
+
return;
|
|
207
|
+
if (nonInteractive) {
|
|
208
|
+
result.steps.push({ step: "claim", status: "error", detail: "no session" });
|
|
209
|
+
if (json)
|
|
210
|
+
console.log(JSON.stringify(result, null, 2));
|
|
211
|
+
throw new Error("Authentication required. Run `mnemom login`, or set MNEMOM_TOKEN / MNEMOM_API_KEY, " +
|
|
212
|
+
"then re-run `mnemom onboard`.");
|
|
213
|
+
}
|
|
214
|
+
say();
|
|
215
|
+
say(fmt.dim("Signing this agent in via the device-grant flow (its own principal)…"));
|
|
216
|
+
await loginWithDeviceFlow();
|
|
217
|
+
say(fmt.success("Signed in."));
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Claim the agent into the caller's PERSONAL org, deriving the hash proof from
|
|
221
|
+
* `--key` (or accepting `--hash-proof` directly). The claim endpoint requires a
|
|
222
|
+
* proof on every call, so a fresh claim needs one; an already-claimed agent
|
|
223
|
+
* never reaches here (the caller skips claim on `posture.alreadyClaimed`). The
|
|
224
|
+
* 403/409/503 taxonomy is mapped to the same teaching errors `agents.ts` uses.
|
|
225
|
+
*/
|
|
226
|
+
async function claimIdentity(agentId, scannedName, options, result, json, steps, say) {
|
|
227
|
+
say();
|
|
228
|
+
say(`${fmt.badge("claim", "cyan")} Claiming your identity…`);
|
|
229
|
+
const hashProof = resolveHashProof(options, scannedName);
|
|
230
|
+
if (!hashProof) {
|
|
231
|
+
steps.push({ step: "claim", status: "error", detail: "no key proof" });
|
|
232
|
+
if (json)
|
|
233
|
+
console.log(JSON.stringify(result, null, 2));
|
|
234
|
+
throw new Error("Claiming needs proof you hold the agent's key. Pass --key <agent-api-key> (the CLI " +
|
|
235
|
+
"derives the proof), or --hash-proof <64-hex> directly.");
|
|
236
|
+
}
|
|
237
|
+
// Land in the agent's personal org by default — never a shared org silently.
|
|
238
|
+
let orgId;
|
|
239
|
+
try {
|
|
240
|
+
orgId = (await getMyPersonalOrg()).org_id;
|
|
241
|
+
}
|
|
242
|
+
catch {
|
|
243
|
+
// Fall back to the server default (personal org) rather than aborting; the
|
|
244
|
+
// claim below still lands personal when orgId is omitted.
|
|
245
|
+
orgId = undefined;
|
|
246
|
+
}
|
|
247
|
+
try {
|
|
248
|
+
const claim = await claimAgent(agentId, { hashProof, orgId });
|
|
249
|
+
result.claimed = claim.claimed;
|
|
250
|
+
steps.push({ step: "claim", status: "ok", detail: agentId });
|
|
251
|
+
mergeAgentConfig({ agent_id: agentId, org_id: claim.org_id ?? orgId });
|
|
252
|
+
say(fmt.success(`Claimed ${claim.agent_id}.`));
|
|
253
|
+
}
|
|
254
|
+
catch (err) {
|
|
255
|
+
if (err instanceof MnemomApiError) {
|
|
256
|
+
const status = err.effectiveStatus;
|
|
257
|
+
// Specific 403 taxonomy + 503 are classified FIRST, before the idempotency
|
|
258
|
+
// branch. A cross-tenant/org-not-member 403 carries an "already claimed"
|
|
259
|
+
// message, so matching that message loosely here would misclassify a
|
|
260
|
+
// genuine failure as a harmless skip (fail-open) — so idempotency is gated
|
|
261
|
+
// on status/code only, and comes last.
|
|
262
|
+
if (status === 403 &&
|
|
263
|
+
(err.code === "invalid_hash_proof" || /hash[ _]?proof/i.test(err.message))) {
|
|
264
|
+
throw teachingClaimError(agentId, "the --key (and agent name) didn't match the agent's stored proof (invalid hash proof).", "Confirm --key is the exact provider key the agent runs with. If the name couldn't be " +
|
|
265
|
+
`auto-resolved, pass it via \`mnemom agents claim ${agentId} --key <key> --name <name>\`.`, json, result, steps);
|
|
266
|
+
}
|
|
267
|
+
if (status === 403 && err.code === "agent_cross_tenant") {
|
|
268
|
+
throw teachingClaimError(agentId, "this agent is already claimed by a different owner.", "Onboard an agent you own, or provision a fresh one with `mnemom wrap`.", json, result, steps);
|
|
269
|
+
}
|
|
270
|
+
if (status === 403 &&
|
|
271
|
+
(err.code === "agent_org_not_member" || /not[ _]?member/i.test(err.code ?? ""))) {
|
|
272
|
+
throw teachingClaimError(agentId, "you are not a member of the target org.", "Run `mnemom org list` to see the orgs you belong to; onboard lands in your personal org by default.", json, result, steps);
|
|
273
|
+
}
|
|
274
|
+
if (status === 503) {
|
|
275
|
+
throw teachingClaimError(agentId, "your personal org is still being set up.", "Give it a moment, then re-run `mnemom onboard`.", json, result, steps);
|
|
276
|
+
}
|
|
277
|
+
// Idempotency (LAST): a same-owner re-claim that raced the scan. Gated on
|
|
278
|
+
// status/code — NOT a loose message match — so the specific 403s above are
|
|
279
|
+
// never swallowed here.
|
|
280
|
+
if (status === 409 || err.code === "agent_already_claimed") {
|
|
281
|
+
result.claimed = true;
|
|
282
|
+
steps.push({ step: "claim", status: "skipped", detail: "already claimed" });
|
|
283
|
+
mergeAgentConfig({ agent_id: agentId, org_id: orgId });
|
|
284
|
+
say(fmt.success("Already claimed — nothing to do."));
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
289
|
+
throw teachingClaimError(agentId, msg, null, json, result, steps);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
/** Resolve the claim hash proof from --hash-proof (raw) or --key (derived). */
|
|
293
|
+
function resolveHashProof(options, derivationName) {
|
|
294
|
+
if (options.hashProof) {
|
|
295
|
+
const proof = options.hashProof.trim().toLowerCase();
|
|
296
|
+
if (!FULL_PROOF_RE.test(proof)) {
|
|
297
|
+
throw new Error("--hash-proof must be the full 64-character hex SHA-256 proof.");
|
|
298
|
+
}
|
|
299
|
+
return proof;
|
|
300
|
+
}
|
|
301
|
+
if (options.key)
|
|
302
|
+
return deriveHashProof(options.key, derivationName);
|
|
303
|
+
return undefined;
|
|
304
|
+
}
|
|
305
|
+
/**
|
|
306
|
+
* Build a fatal claim error that also records the failed step and emits the
|
|
307
|
+
* --json envelope before throwing (so a machine caller still gets valid output
|
|
308
|
+
* on the failure path — MNE-442 fail-closed, never a bare hang).
|
|
309
|
+
*/
|
|
310
|
+
function teachingClaimError(agentId, reason, hint, json, result, steps) {
|
|
311
|
+
steps.push({ step: "claim", status: "error", detail: reason });
|
|
312
|
+
if (json)
|
|
313
|
+
console.log(JSON.stringify(result, null, 2));
|
|
314
|
+
const message = hint
|
|
315
|
+
? `Failed to claim ${agentId}: ${reason}\n ${hint}`
|
|
316
|
+
: `Failed to claim ${agentId}: ${reason}`;
|
|
317
|
+
return new Error(message);
|
|
318
|
+
}
|
|
319
|
+
// ── State 4: earn Trust Rating ─────────────────────────────────────────────────
|
|
320
|
+
/**
|
|
321
|
+
* Re-read the agent's integrity and surface it as an HONEST rating: a cold-start
|
|
322
|
+
* agent (0 traces) is `provisional`, never a bare perfect score (MNE-439). The
|
|
323
|
+
* signed Trust Rating remains server-computed; this is the CLI-reachable view.
|
|
324
|
+
*/
|
|
325
|
+
async function earnRating(agentId, steps, say) {
|
|
326
|
+
say();
|
|
327
|
+
say(`${fmt.badge("rating", "cyan")} Reading your Trust Rating…`);
|
|
328
|
+
try {
|
|
329
|
+
const integ = await getIntegrity(agentId);
|
|
330
|
+
const rating = {
|
|
331
|
+
score: integ.integrity_score,
|
|
332
|
+
total_traces: integ.total_traces,
|
|
333
|
+
verified_traces: integ.verified_traces,
|
|
334
|
+
status: integ.total_traces === 0 ? "provisional" : "rated",
|
|
335
|
+
};
|
|
336
|
+
steps.push({
|
|
337
|
+
step: "rating",
|
|
338
|
+
status: "ok",
|
|
339
|
+
detail: `${rating.status} — score ${rating.score.toFixed(2)} (${rating.total_traces} traces)`,
|
|
340
|
+
});
|
|
341
|
+
say(fmt.dim(rating.status === "provisional"
|
|
342
|
+
? ` Provisional — the signed rating lands once the observer pipeline generates traces.`
|
|
343
|
+
: ` Rated — score ${rating.score.toFixed(2)} across ${rating.total_traces} traces.`));
|
|
344
|
+
return rating;
|
|
345
|
+
}
|
|
346
|
+
catch (err) {
|
|
347
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
348
|
+
steps.push({ step: "rating", status: "error", detail: msg });
|
|
349
|
+
say(fmt.warn(`Trust Rating unavailable right now — check \`mnemom status\` later. (${msg})`));
|
|
350
|
+
return null;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
// ── starter alignment card ──────────────────────────────────────────────────────
|
|
354
|
+
/**
|
|
355
|
+
* A starter alignment card in `observe` mode — the honest, minimal declaration
|
|
356
|
+
* of intent for a freshly-onboarded agent. Mirrors the shape `wrap` seeds so the
|
|
357
|
+
* two skill-runners produce a consistent first card.
|
|
358
|
+
*/
|
|
359
|
+
function buildStarterAlignmentCard(agentId) {
|
|
360
|
+
return {
|
|
361
|
+
card_version: "unified/2026-04-26",
|
|
362
|
+
agent_id: agentId,
|
|
363
|
+
autonomy_mode: "observe",
|
|
364
|
+
integrity_mode: "observe",
|
|
365
|
+
principal: { type: "agent", identifier: agentId, relationship: "delegated_authority" },
|
|
366
|
+
values: { declared: ["transparency", "safety", "honesty"] },
|
|
367
|
+
autonomy: {
|
|
368
|
+
bounded_actions: ["respond_to_prompts"],
|
|
369
|
+
forbidden_actions: [],
|
|
370
|
+
escalation_triggers: [],
|
|
371
|
+
},
|
|
372
|
+
audit: { retention_days: 30, queryable: false, trace_format: "otel" },
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
// ── report ──────────────────────────────────────────────────────────────────────
|
|
376
|
+
/** The closing recount for human mode. */
|
|
377
|
+
function renderRecount(result) {
|
|
378
|
+
const L = [fmt.section("Onboarding summary"), ""];
|
|
379
|
+
L.push(fmt.label(" Agent: ", result.agent_id ?? "(unknown)"));
|
|
380
|
+
L.push(fmt.label(" Claimed: ", result.claimed ? "yes" : "no"));
|
|
381
|
+
L.push(fmt.label(" Card: ", result.card_published ? "declared" : "not declared"));
|
|
382
|
+
if (result.rating) {
|
|
383
|
+
L.push(fmt.label(" Rating: ", `${result.rating.status} (score ${result.rating.score.toFixed(2)}, ${result.rating.total_traces} traces)`));
|
|
384
|
+
}
|
|
385
|
+
if (result.badge_url)
|
|
386
|
+
L.push(fmt.label(" Badge: ", result.badge_url));
|
|
387
|
+
L.push("");
|
|
388
|
+
if (result.verdict === "ok") {
|
|
389
|
+
L.push(fmt.dim("You're a claimed, value-declared agent. Traces will appear in `mnemom logs`."));
|
|
390
|
+
}
|
|
391
|
+
else {
|
|
392
|
+
L.push(fmt.dim("Partly done — re-run `mnemom onboard` to finish the remaining step(s)."));
|
|
393
|
+
}
|
|
394
|
+
return L.join("\n");
|
|
395
|
+
}
|
package/dist/commands/org.d.ts
CHANGED
|
@@ -1,3 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `mnemom org use <slug|id>` / `mnemom org use --clear`
|
|
3
|
+
*
|
|
4
|
+
* Record (or forget) the ACTIVE ORG — the durable default that org-scoped
|
|
5
|
+
* commands fall back to when `--org` is omitted. `mnemom login` binds no org
|
|
6
|
+
* (the OAuth token carries only scope), so without this every claim landed
|
|
7
|
+
* silently in the caller's personal org. The value is validated against the
|
|
8
|
+
* caller's live memberships before it is persisted; membership drift after
|
|
9
|
+
* that is caught by the server (403 + teaching list) at use time.
|
|
10
|
+
*/
|
|
11
|
+
export declare function orgUseCommand(slugOrId: string | undefined, opts: {
|
|
12
|
+
clear?: boolean;
|
|
13
|
+
}): Promise<void>;
|
|
1
14
|
/**
|
|
2
15
|
* `mnemom org list`
|
|
3
16
|
*
|
package/dist/commands/org.js
CHANGED
|
@@ -1,6 +1,63 @@
|
|
|
1
1
|
import { listMyOrgs, getMyPersonalOrg } from "../lib/api.js";
|
|
2
2
|
import { requireAuth } from "../lib/auth.js";
|
|
3
|
+
import { getActiveOrg, setActiveOrg } from "../lib/cli-config.js";
|
|
3
4
|
import { fmt } from "../lib/format.js";
|
|
5
|
+
/**
|
|
6
|
+
* `mnemom org use <slug|id>` / `mnemom org use --clear`
|
|
7
|
+
*
|
|
8
|
+
* Record (or forget) the ACTIVE ORG — the durable default that org-scoped
|
|
9
|
+
* commands fall back to when `--org` is omitted. `mnemom login` binds no org
|
|
10
|
+
* (the OAuth token carries only scope), so without this every claim landed
|
|
11
|
+
* silently in the caller's personal org. The value is validated against the
|
|
12
|
+
* caller's live memberships before it is persisted; membership drift after
|
|
13
|
+
* that is caught by the server (403 + teaching list) at use time.
|
|
14
|
+
*/
|
|
15
|
+
export async function orgUseCommand(slugOrId, opts) {
|
|
16
|
+
if (opts.clear) {
|
|
17
|
+
setActiveOrg(null);
|
|
18
|
+
console.log(fmt.success("Active org cleared.") +
|
|
19
|
+
"\n Org-scoped commands now default to your personal org (pass --org to override).\n");
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
if (!slugOrId) {
|
|
23
|
+
// No arg, no --clear → show the current setting (mirrors `git config`).
|
|
24
|
+
const active = getActiveOrg();
|
|
25
|
+
if (active) {
|
|
26
|
+
console.log(fmt.label("Active org:", `${active.name} (${active.slug}, ${active.org_id})`));
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
console.log(fmt.warn("No active org set.") +
|
|
30
|
+
"\n Set one with: mnemom org use <slug> (see: mnemom org list)\n");
|
|
31
|
+
}
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
const cred = await requireAuth();
|
|
35
|
+
if (cred?.type === "api-key") {
|
|
36
|
+
console.log(fmt.warn("API keys are agent-scoped; run mnemom login for org/key management"));
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
let orgs;
|
|
40
|
+
try {
|
|
41
|
+
orgs = await listMyOrgs();
|
|
42
|
+
}
|
|
43
|
+
catch (err) {
|
|
44
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
45
|
+
console.log(fmt.error(`Failed to list orgs: ${msg}`) + "\n");
|
|
46
|
+
process.exit(1);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
const needle = slugOrId.trim();
|
|
50
|
+
const match = orgs.find((o) => o.slug === needle) ?? orgs.find((o) => o.org_id === needle);
|
|
51
|
+
if (!match) {
|
|
52
|
+
const choices = orgs.map((o) => ` ${o.slug.padEnd(24)} ${o.name}`).join("\n");
|
|
53
|
+
console.log(fmt.error(`No org of yours matches '${needle}'. Your organizations:\n${choices}`) + "\n");
|
|
54
|
+
process.exit(1);
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
setActiveOrg({ org_id: match.org_id, slug: match.slug, name: match.name });
|
|
58
|
+
console.log(fmt.success(`Active org set to ${match.name} (${match.slug}).`) +
|
|
59
|
+
"\n Org-scoped commands (e.g. agents claim) now default here. Clear with: mnemom org use --clear\n");
|
|
60
|
+
}
|
|
4
61
|
/**
|
|
5
62
|
* `mnemom org list`
|
|
6
63
|
*
|
|
@@ -41,15 +98,19 @@ export async function orgListCommand(opts) {
|
|
|
41
98
|
const header = "Name".padEnd(nameW) + "ID".padEnd(idW) + "Role".padEnd(roleW) + "Owner".padEnd(ownerW);
|
|
42
99
|
console.log(` ${header}`);
|
|
43
100
|
console.log(` ${"─".repeat(nameW + idW + roleW + ownerW)}`);
|
|
101
|
+
const activeOrgId = getActiveOrg()?.org_id;
|
|
44
102
|
for (const org of orgs) {
|
|
45
|
-
const
|
|
46
|
-
const name = `${org.name}${
|
|
103
|
+
const tags = (org.is_personal ? " (personal)" : "") + (org.org_id === activeOrgId ? " *active" : "");
|
|
104
|
+
const name = `${org.name}${tags}`.slice(0, nameW - 2).padEnd(nameW);
|
|
47
105
|
const id = org.org_id.slice(0, idW - 2).padEnd(idW);
|
|
48
106
|
const role = (org.role ?? "-").padEnd(roleW);
|
|
49
107
|
const owner = (org.is_owner ? "yes" : "no").padEnd(ownerW);
|
|
50
108
|
console.log(` ${name}${id}${role}${owner}`);
|
|
51
109
|
}
|
|
52
110
|
console.log(`\n Total: ${orgs.length} organization(s)\n`);
|
|
111
|
+
if (!activeOrgId) {
|
|
112
|
+
console.log(fmt.dim(" Tip: set a default for org-scoped commands with `mnemom org use <slug>`.") + "\n");
|
|
113
|
+
}
|
|
53
114
|
}
|
|
54
115
|
/**
|
|
55
116
|
* `mnemom org show [<org_id>]` or `mnemom org show --personal`
|
|
@@ -24,3 +24,13 @@ export declare function protectionValidateCommand(file: string, opts?: {
|
|
|
24
24
|
export declare function protectionEditCommand(agentName?: string, options?: {
|
|
25
25
|
idempotencyKey?: string;
|
|
26
26
|
}): Promise<void>;
|
|
27
|
+
/**
|
|
28
|
+
* Compare a committed protection-card snapshot against the live CANONICAL
|
|
29
|
+
* (composed) card. Read-only: never writes, never publishes.
|
|
30
|
+
*
|
|
31
|
+
* Exit 0 = no drift, exit 1 = drift or error, so a scheduled job can gate on it.
|
|
32
|
+
*/
|
|
33
|
+
export declare function protectionDriftCommand(file: string, agentName?: string, opts?: {
|
|
34
|
+
strict?: boolean;
|
|
35
|
+
json?: boolean;
|
|
36
|
+
}): Promise<void>;
|