botanary-mcp 0.9.2 → 0.9.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/README.md +91 -1
- package/dist/src/diagnose.d.ts.map +1 -1
- package/dist/src/diagnose.js +17 -4
- package/dist/src/diagnose.js.map +1 -1
- package/dist/src/identity/adopt.d.ts +51 -0
- package/dist/src/identity/adopt.d.ts.map +1 -0
- package/dist/src/identity/adopt.js +167 -0
- package/dist/src/identity/adopt.js.map +1 -0
- package/dist/src/identity/backends/select.d.ts +11 -3
- package/dist/src/identity/backends/select.d.ts.map +1 -1
- package/dist/src/identity/backends/select.js +23 -7
- package/dist/src/identity/backends/select.js.map +1 -1
- package/dist/src/identity/backends/types.d.ts +16 -4
- package/dist/src/identity/backends/types.d.ts.map +1 -1
- package/dist/src/identity/backends/types.js +18 -4
- package/dist/src/identity/backends/types.js.map +1 -1
- package/dist/src/identity/profile.d.ts +107 -0
- package/dist/src/identity/profile.d.ts.map +1 -0
- package/dist/src/identity/profile.js +154 -0
- package/dist/src/identity/profile.js.map +1 -0
- package/dist/src/identity/store.d.ts +17 -0
- package/dist/src/identity/store.d.ts.map +1 -1
- package/dist/src/identity/store.js +23 -0
- package/dist/src/identity/store.js.map +1 -1
- package/dist/src/index.d.ts +9 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +7 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/paths.d.ts +19 -3
- package/dist/src/paths.d.ts.map +1 -1
- package/dist/src/paths.js +34 -5
- package/dist/src/paths.js.map +1 -1
- package/dist/src/runtime.d.ts +23 -0
- package/dist/src/runtime.d.ts.map +1 -1
- package/dist/src/runtime.js +84 -3
- package/dist/src/runtime.js.map +1 -1
- package/dist/src/server.d.ts.map +1 -1
- package/dist/src/server.js +32 -0
- package/dist/src/server.js.map +1 -1
- package/dist/src/tools.d.ts +4 -0
- package/dist/src/tools.d.ts.map +1 -1
- package/dist/src/tools.js +123 -19
- package/dist/src/tools.js.map +1 -1
- package/dist/src/wallet/browser.d.ts +5 -3
- package/dist/src/wallet/browser.d.ts.map +1 -1
- package/dist/src/wallet/browser.js +5 -3
- package/dist/src/wallet/browser.js.map +1 -1
- package/dist/src/wallet/write-routes.manifest.d.ts +2 -0
- package/dist/src/wallet/write-routes.manifest.d.ts.map +1 -1
- package/dist/src/wallet/write-routes.manifest.js +3 -0
- package/dist/src/wallet/write-routes.manifest.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -36,7 +36,7 @@ advertised tool list changes" further down.
|
|
|
36
36
|
|---|---|
|
|
37
37
|
| `diagnose` | START HERE when anything is unclear or an action failed. Single call answers: who is this agent bound to, is the account funded on the grant's chain, is that chain usable, and what - ranked - is blocking the next action. Read-only: never signs or mutates. |
|
|
38
38
|
| `get_identity` | Reports this agent's address/public key/fingerprint. Generates the key on first call. No network call. |
|
|
39
|
-
| `get_pairing_code` / `regenerate_pairing_code` | Shows (or rotates) the short code the owner
|
|
39
|
+
| `get_pairing_code` / `regenerate_pairing_code` | Shows (or rotates) the short code the owner enters under "Connected agents", registers it with the backend so their claim can find it, and **opens their browser** on that screen with the code already filled in - once per code, and never for a code the backend could not register. `open: false` suppresses the browser; `wait: false` returns the code without blocking on the claim. |
|
|
40
40
|
| `pair` | Signs the pairing code and submits the proof to `POST /agents/pair`. |
|
|
41
41
|
| `whoami` | Calls `GET /agents/me` - this agent's own identity, account, and live grant (or an honest `grant: null`). |
|
|
42
42
|
| `get_balance` | Calls `GET /balance` under this agent's session. |
|
|
@@ -384,6 +384,28 @@ This is the product's own promise (Flow 7c/7d), and every tool in this package e
|
|
|
384
384
|
on-chain. A bug or a lie in what any of them reports changes nothing about what this agent can
|
|
385
385
|
actually get away with.
|
|
386
386
|
|
|
387
|
+
## Pairing opens a browser, and why that is not a new power
|
|
388
|
+
|
|
389
|
+
`get_pairing_code` launches the owner's default browser on `<app>/agents?pair=<code>`, the same way
|
|
390
|
+
`wallet_login`, `wallet_send` and `wallet_sign_request` already launch one on the URL they need the
|
|
391
|
+
owner to visit. Pairing was the odd one out: it returned a URL and left the person who had never used
|
|
392
|
+
this package before to click or copy-paste it out of a tool result.
|
|
393
|
+
|
|
394
|
+
Three limits keep that from being a way to point someone's browser somewhere:
|
|
395
|
+
|
|
396
|
+
- **The URL is not model-controlled.** It is the configured API origin (`BOTANARY_API_URL`, mapped to
|
|
397
|
+
the app origin by `deriveAppOrigin`) plus a locally minted code. Nothing a model saw, said or was
|
|
398
|
+
told reaches it.
|
|
399
|
+
- **`openBrowser` refuses anything that is not a parseable absolute `http(s)` URL, and never involves a
|
|
400
|
+
shell** - it passes the URL as a single `execFile` argv element. That check is why a mistyped or
|
|
401
|
+
hostile `BOTANARY_API_URL` cannot turn a deep link into a command.
|
|
402
|
+
- **A code the backend never acknowledged opens nothing.** Registration fails soft, and a window onto a
|
|
403
|
+
form that will reject the code stages a failure rather than a pairing.
|
|
404
|
+
|
|
405
|
+
It also opens **once per code**. `get_pairing_code` is deliberately idempotent - the same code comes
|
|
406
|
+
back until it expires - so a repeated call is the normal case, not a mistake, and each one must not add
|
|
407
|
+
another identical tab.
|
|
408
|
+
|
|
387
409
|
## Why intercepting the pairing code alone grants nothing
|
|
388
410
|
|
|
389
411
|
The short code this server shows (`get_pairing_code`) is built from two parts, and **neither one is a
|
|
@@ -550,6 +572,74 @@ same reasoning as `botanary-fe/tests/api/schema-drift.test.ts`.
|
|
|
550
572
|
ever in use) live - defaults to `~/.botanary-mcp`. It exists mainly for tests and for running this
|
|
551
573
|
server inside a container with an isolated home; a normal install never needs to set it.
|
|
552
574
|
|
|
575
|
+
**Do not reach for it to separate two coding tools.** It moves the metadata file but NOT the keychain
|
|
576
|
+
item, which is keyed by name (`identity/backends/types.ts`) - so a second home used to mint a fresh
|
|
577
|
+
keypair and save it with `security add-generic-password -U`, overwriting the first one's private key in
|
|
578
|
+
place. Use a profile instead; it moves both.
|
|
579
|
+
|
|
580
|
+
## Profiles - one identity per agent, not one per machine
|
|
581
|
+
|
|
582
|
+
Every coding tool that connects to this server gets its own keypair, its own `ConnectedAgent` row on the
|
|
583
|
+
backend, its own mandate and its own revoke button. Before profiles they all shared one address, so the
|
|
584
|
+
backend could not tell Claude Code from Codex even in principle, and disconnecting one disconnected all
|
|
585
|
+
of them.
|
|
586
|
+
|
|
587
|
+
A profile is resolved once, at the MCP `initialize` handshake:
|
|
588
|
+
|
|
589
|
+
| Precedence | Source | Result |
|
|
590
|
+
|---|---|---|
|
|
591
|
+
| 1 | `BOTANARY_AGENT_PROFILE` | that agent name, under the reporting tool |
|
|
592
|
+
| 2 | a pre-profiles `~/.botanary-mcp/identity.json` exists | `default` - the identity this machine already has |
|
|
593
|
+
| 3 | the MCP `clientInfo.name` the client sent | one identity per tool, with no configuration |
|
|
594
|
+
| 4 | nothing | `default` |
|
|
595
|
+
|
|
596
|
+
Storage follows the resolved key. `default` is byte-identical to what shipped before profiles:
|
|
597
|
+
|
|
598
|
+
```
|
|
599
|
+
~/.botanary-mcp/identity.json profile "default"
|
|
600
|
+
~/.botanary-mcp/profiles/codex/identity.json profile "codex"
|
|
601
|
+
|
|
602
|
+
keychain botanary-mcp / agent-identity profile "default"
|
|
603
|
+
keychain botanary-mcp / agent-identity:codex
|
|
604
|
+
keychain botanary-mcp / agent-identity:claude-code.trading
|
|
605
|
+
keychain botanary-mcp / wallet-session ONE, shared by every profile
|
|
606
|
+
```
|
|
607
|
+
|
|
608
|
+
**Step 2 outranks step 3, and that is the whole upgrade story.** A machine that already paired has a
|
|
609
|
+
`ConnectedAgent` on the backend and possibly a live on-chain grant naming its address as
|
|
610
|
+
`delegateeAddress`. Auto-deriving a fresh identity would leave that grant pointing at an address no key
|
|
611
|
+
on the machine can sign for - real, dead, and with no error to explain it. So an existing install stays
|
|
612
|
+
exactly where it is, and splitting it is opt-in.
|
|
613
|
+
|
|
614
|
+
**Several agents per tool** is just several profiles. The tool half comes from `clientInfo`, the agent
|
|
615
|
+
half from the env, so the same name under two tools is two different identities:
|
|
616
|
+
|
|
617
|
+
```jsonc
|
|
618
|
+
// botanary-be/.mcp.json - this repo's Claude Code gets its own agent
|
|
619
|
+
"env": { "BOTANARY_AGENT_PROFILE": "trading" } // -> claude-code.trading
|
|
620
|
+
```
|
|
621
|
+
|
|
622
|
+
Each distinct profile is a fresh address, so it needs its own pairing once. That is the cost, and it is
|
|
623
|
+
the point: the owner authorizes each one deliberately.
|
|
624
|
+
|
|
625
|
+
### What a profile is worth, and what it is not
|
|
626
|
+
|
|
627
|
+
- **Cryptographic:** Codex's grant cannot be spent by Claude Code's key. Different private key, different
|
|
628
|
+
address, and `MandateExecutor` is bound to the delegatee. This holds even against a fully compromised
|
|
629
|
+
backend.
|
|
630
|
+
- **Server-enforced:** a disabled agent cannot mint a session, and disabling revokes its live tokens.
|
|
631
|
+
The client cannot bypass it, because the token comes from the server.
|
|
632
|
+
- **NOT enforced, and not enforceable:** which local process may read which key. The OS keychain is
|
|
633
|
+
per-user, not per-binary - any process that can run `security find-generic-password` can read every
|
|
634
|
+
item this package stores. That is no worse than before (a hostile local process already had the one
|
|
635
|
+
shared identity), but it means the tool an agent reports is **hygiene and honest bookkeeping, never a
|
|
636
|
+
security boundary.** `clientInfo` is self-reported; it selects which local key to load and nothing
|
|
637
|
+
more, and the app renders it as a quoted claim for that reason.
|
|
638
|
+
|
|
639
|
+
`get_identity` and `diagnose` both report the resolved profile and where it came from, so "which
|
|
640
|
+
identity is this process using, and why" never has to be reverse-engineered from which keychain item
|
|
641
|
+
happens to exist.
|
|
642
|
+
|
|
553
643
|
## What's deliberately not here
|
|
554
644
|
|
|
555
645
|
Everything the original design called out as "later work" is now built: the pairing HTTP exchange
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"diagnose.d.ts","sourceRoot":"","sources":["../../src/diagnose.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAa,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"diagnose.d.ts","sourceRoot":"","sources":["../../src/diagnose.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAa,MAAM,cAAc,CAAC;AAI5D,MAAM,MAAM,aAAa,GACrB,QAAQ,GACR,eAAe,GACf,qBAAqB,GACrB,oBAAoB,GACpB,gBAAgB,GAChB,gBAAgB;AAClB;;;gDAGgD;GAC9C,gBAAgB;AAClB;qGACqG;GACnG,yBAAyB,GACzB,UAAU;AACZ,uGAAuG;GACrG,kBAAkB,GAClB,UAAU,GACV,OAAO,CAAC;AAEZ,MAAM,WAAW,OAAO;IACtB;;sBAEkB;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,UAAU,GAAG,SAAS,CAAC;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,aAAa,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,OAAO,EAAE,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC/B,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACnC;AAeD;;;;;;GAMG;AACH,wBAAsB,WAAW,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,cAAc,CAAC,CA+QhF"}
|
package/dist/src/diagnose.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { BotanaryApiError } from './api-client.js';
|
|
2
2
|
import { setupUrl } from './app-link.js';
|
|
3
3
|
import { getFallbackCount } from './hitl/wait.js';
|
|
4
|
+
import { describeProfileSource } from './identity/profile.js';
|
|
4
5
|
const BALANCE_FRESHNESS = 'backend store-backed (GET /balance makes zero external calls) - may lag the chain by a refresh cycle';
|
|
5
6
|
function report(state, summary, blockers, facts = {}, freshness = {}) {
|
|
6
7
|
return { state, summary, blockers, facts, freshness };
|
|
@@ -16,15 +17,26 @@ export async function runDiagnose(runtime) {
|
|
|
16
17
|
const pending = (await runtime.handles.list()).map((h) => ({ id: h.id, kind: h.kind, label: h.label, expiresAt: h.expiresAt }));
|
|
17
18
|
const fallbackCount = getFallbackCount();
|
|
18
19
|
const transport = fallbackCount > 0 ? 'polling (stream unavailable)' : 'stream (or polling as fallback)';
|
|
20
|
+
// Reported in EVERY branch below, including the failure branches, because "which identity is this
|
|
21
|
+
// process even using" is the first thing that is wrong when several coding tools share a machine.
|
|
22
|
+
// Someone whose agent looks unclaimed usually has a claimed one under a different profile; without
|
|
23
|
+
// this they would go re-pair the wrong identity. `profileSource` earns its place next to `profile`:
|
|
24
|
+
// a `legacy` source explains why a machine that expected per-tool identities has one shared one.
|
|
25
|
+
const profile = {
|
|
26
|
+
profile: runtime.profile.key,
|
|
27
|
+
profileSource: describeProfileSource(runtime.profile),
|
|
28
|
+
// What the CLIENT said it was, never verified - see identity/profile.ts. Null when it said nothing.
|
|
29
|
+
reportedClient: runtime.profile.tool,
|
|
30
|
+
};
|
|
19
31
|
if (!(await runtime.store.hasIdentity())) {
|
|
20
32
|
return report('no_key', 'No agent key exists on this machine yet.', [
|
|
21
33
|
{
|
|
22
34
|
code: 'no_key',
|
|
23
35
|
severity: 'blocking',
|
|
24
|
-
detail:
|
|
36
|
+
detail: `Nothing is stored at this machine's identity path for profile "${runtime.profile.key}".`,
|
|
25
37
|
nextAction: 'Call get_pairing_code to create an identity and get a code for the owner to claim.',
|
|
26
38
|
},
|
|
27
|
-
], { pending, transport });
|
|
39
|
+
], { pending, transport, ...profile });
|
|
28
40
|
}
|
|
29
41
|
let self;
|
|
30
42
|
try {
|
|
@@ -39,7 +51,7 @@ export async function runDiagnose(runtime) {
|
|
|
39
51
|
detail: `The backend does not recognise this agent (HTTP ${e.status}).`,
|
|
40
52
|
nextAction: 'Call get_pairing_code and give the code to the owner to enter in Botanary.',
|
|
41
53
|
},
|
|
42
|
-
], { pending, transport });
|
|
54
|
+
], { pending, transport, ...profile });
|
|
43
55
|
}
|
|
44
56
|
return report('backend_unavailable', 'Could not reach the Botanary backend to ask who this agent is.', [
|
|
45
57
|
{
|
|
@@ -48,7 +60,7 @@ export async function runDiagnose(runtime) {
|
|
|
48
60
|
detail: e instanceof Error ? e.message : String(e),
|
|
49
61
|
nextAction: 'Retry once the backend is reachable. Nothing local is wrong.',
|
|
50
62
|
},
|
|
51
|
-
], { pending, transport });
|
|
63
|
+
], { pending, transport, ...profile });
|
|
52
64
|
}
|
|
53
65
|
const facts = {
|
|
54
66
|
agent: { id: self.id, name: self.name, address: self.address, fingerprint: self.fingerprint },
|
|
@@ -56,6 +68,7 @@ export async function runDiagnose(runtime) {
|
|
|
56
68
|
grant: self.grant,
|
|
57
69
|
pending,
|
|
58
70
|
transport,
|
|
71
|
+
...profile,
|
|
59
72
|
};
|
|
60
73
|
/** Name the account by the address the OWNER would recognise, falling back to the opaque row id only
|
|
61
74
|
* when an older backend does not send one. This is the whole point of the diagnosis: "connected to an
|
package/dist/src/diagnose.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"diagnose.js","sourceRoot":"","sources":["../../src/diagnose.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACnD,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAEzC,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"diagnose.js","sourceRoot":"","sources":["../../src/diagnose.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { PublicMetadataFile } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Move this machine's pre-profiles identity into a named profile, KEEPING THE SAME KEY.
|
|
4
|
+
*
|
|
5
|
+
* This is the opt-in half of the migration rule. `resolveProfile` deliberately keeps a legacy install
|
|
6
|
+
* on `default` rather than auto-deriving a per-tool identity, because auto-deriving would mint a fresh
|
|
7
|
+
* keypair and orphan whatever on-chain grant names the old address. That is the safe default, but it
|
|
8
|
+
* means an existing user has no way to join the per-tool world without abandoning their pairing -
|
|
9
|
+
* unless the identity itself can move. It can: same address, same pairing, same grant, new name.
|
|
10
|
+
*
|
|
11
|
+
* WHY THIS IS NOT A COPY. Leaving the key behind under `default` too would mean two profiles able to
|
|
12
|
+
* sign as the same agent, so "disable Codex" would not be a fact about one identity any more. An
|
|
13
|
+
* identity must live in exactly one place, which is why the source is deleted - but only after the
|
|
14
|
+
* destination has been written AND proven to sign.
|
|
15
|
+
*
|
|
16
|
+
* ORDER MATTERS AND IS THE WHOLE SAFETY ARGUMENT:
|
|
17
|
+
* 1. refuse if there is nothing to move, or if the destination already holds an identity;
|
|
18
|
+
* 2. write the secret and metadata to the destination;
|
|
19
|
+
* 3. VERIFY by loading the destination fresh and signing with it - not by trusting the write;
|
|
20
|
+
* 4. only then delete the source.
|
|
21
|
+
* A failure at step 2 or 3 rolls the destination back and leaves the source untouched, so the worst
|
|
22
|
+
* outcome is "nothing happened", never "the key is gone".
|
|
23
|
+
*/
|
|
24
|
+
export declare class ProfileAdoptError extends Error {
|
|
25
|
+
constructor(message: string);
|
|
26
|
+
}
|
|
27
|
+
export interface AdoptResult {
|
|
28
|
+
from: string;
|
|
29
|
+
to: string;
|
|
30
|
+
address: string;
|
|
31
|
+
fingerprint: string;
|
|
32
|
+
}
|
|
33
|
+
export declare function adoptDefaultProfileAs(rawTargetKey: string,
|
|
34
|
+
/**
|
|
35
|
+
* Which platform's backend list to use. Defaults to the real one; overridden ONLY by this package's
|
|
36
|
+
* own tests, which pass a platform with no OS keychain so they exercise the file-fallback backend
|
|
37
|
+
* alone.
|
|
38
|
+
*
|
|
39
|
+
* This parameter is not a convenience. Keychain items are keyed by NAME, not by path, so
|
|
40
|
+
* `BOTANARY_MCP_HOME` does not isolate them - a test that runs this function against the real
|
|
41
|
+
* platform writes to, and then DELETES, the developer's actual `agent-identity` item. That happened
|
|
42
|
+
* while this file was being written, which is why the seam exists and why every test here uses it.
|
|
43
|
+
* `defaultCandidates` already takes a platform for exactly this reason.
|
|
44
|
+
*/
|
|
45
|
+
platform?: NodeJS.Platform): Promise<AdoptResult>;
|
|
46
|
+
/** Every profile with metadata on this machine, so an owner can see what exists before moving anything.
|
|
47
|
+
* Reads metadata only - listing profiles never touches the keychain. */
|
|
48
|
+
export declare function listProfiles(): Promise<Array<PublicMetadataFile & {
|
|
49
|
+
profile: string;
|
|
50
|
+
}>>;
|
|
51
|
+
//# sourceMappingURL=adopt.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"adopt.d.ts","sourceRoot":"","sources":["../../../src/identity/adopt.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAErD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,qBAAa,iBAAkB,SAAQ,KAAK;gBAC9B,OAAO,EAAE,MAAM;CAI5B;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,wBAAsB,qBAAqB,CACzC,YAAY,EAAE,MAAM;AACpB;;;;;;;;;;GAUG;AACH,QAAQ,GAAE,MAAM,CAAC,QAA2B,GAC3C,OAAO,CAAC,WAAW,CAAC,CA2EtB;AAED;yEACyE;AACzE,wBAAsB,YAAY,IAAI,OAAO,CAAC,KAAK,CAAC,kBAAkB,GAAG;IAAE,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC,CA0B7F"}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { readFile, mkdir, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { dirname } from 'node:path';
|
|
3
|
+
import { publicMetadataPath } from '../paths.js';
|
|
4
|
+
import { defaultCandidates } from './backends/select.js';
|
|
5
|
+
import { DEFAULT_PROFILE_KEY, isDefaultProfile, parseExplicitProfile } from './profile.js';
|
|
6
|
+
import { createIdentityStore, IdentityStore } from './store.js';
|
|
7
|
+
/**
|
|
8
|
+
* Move this machine's pre-profiles identity into a named profile, KEEPING THE SAME KEY.
|
|
9
|
+
*
|
|
10
|
+
* This is the opt-in half of the migration rule. `resolveProfile` deliberately keeps a legacy install
|
|
11
|
+
* on `default` rather than auto-deriving a per-tool identity, because auto-deriving would mint a fresh
|
|
12
|
+
* keypair and orphan whatever on-chain grant names the old address. That is the safe default, but it
|
|
13
|
+
* means an existing user has no way to join the per-tool world without abandoning their pairing -
|
|
14
|
+
* unless the identity itself can move. It can: same address, same pairing, same grant, new name.
|
|
15
|
+
*
|
|
16
|
+
* WHY THIS IS NOT A COPY. Leaving the key behind under `default` too would mean two profiles able to
|
|
17
|
+
* sign as the same agent, so "disable Codex" would not be a fact about one identity any more. An
|
|
18
|
+
* identity must live in exactly one place, which is why the source is deleted - but only after the
|
|
19
|
+
* destination has been written AND proven to sign.
|
|
20
|
+
*
|
|
21
|
+
* ORDER MATTERS AND IS THE WHOLE SAFETY ARGUMENT:
|
|
22
|
+
* 1. refuse if there is nothing to move, or if the destination already holds an identity;
|
|
23
|
+
* 2. write the secret and metadata to the destination;
|
|
24
|
+
* 3. VERIFY by loading the destination fresh and signing with it - not by trusting the write;
|
|
25
|
+
* 4. only then delete the source.
|
|
26
|
+
* A failure at step 2 or 3 rolls the destination back and leaves the source untouched, so the worst
|
|
27
|
+
* outcome is "nothing happened", never "the key is gone".
|
|
28
|
+
*/
|
|
29
|
+
export class ProfileAdoptError extends Error {
|
|
30
|
+
constructor(message) {
|
|
31
|
+
super(message);
|
|
32
|
+
this.name = 'ProfileAdoptError';
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
export async function adoptDefaultProfileAs(rawTargetKey,
|
|
36
|
+
/**
|
|
37
|
+
* Which platform's backend list to use. Defaults to the real one; overridden ONLY by this package's
|
|
38
|
+
* own tests, which pass a platform with no OS keychain so they exercise the file-fallback backend
|
|
39
|
+
* alone.
|
|
40
|
+
*
|
|
41
|
+
* This parameter is not a convenience. Keychain items are keyed by NAME, not by path, so
|
|
42
|
+
* `BOTANARY_MCP_HOME` does not isolate them - a test that runs this function against the real
|
|
43
|
+
* platform writes to, and then DELETES, the developer's actual `agent-identity` item. That happened
|
|
44
|
+
* while this file was being written, which is why the seam exists and why every test here uses it.
|
|
45
|
+
* `defaultCandidates` already takes a platform for exactly this reason.
|
|
46
|
+
*/
|
|
47
|
+
platform = process.platform) {
|
|
48
|
+
const targetKey = parseExplicitProfile(rawTargetKey);
|
|
49
|
+
if (isDefaultProfile(targetKey)) {
|
|
50
|
+
throw new ProfileAdoptError('"default" is where the identity already is - name a different profile.');
|
|
51
|
+
}
|
|
52
|
+
const sourceCandidates = defaultCandidates(platform, false, DEFAULT_PROFILE_KEY);
|
|
53
|
+
const sourceMetadataPath = publicMetadataPath(DEFAULT_PROFILE_KEY);
|
|
54
|
+
const source = new IdentityStore(sourceCandidates, { metadataPath: sourceMetadataPath });
|
|
55
|
+
if (!(await source.hasIdentity())) {
|
|
56
|
+
throw new ProfileAdoptError('There is no default identity on this machine to adopt. A fresh install already derives one ' +
|
|
57
|
+
'profile per coding tool - nothing to migrate.');
|
|
58
|
+
}
|
|
59
|
+
const target = createIdentityStore(targetKey, platform);
|
|
60
|
+
if (await target.hasIdentity()) {
|
|
61
|
+
// Overwriting would destroy a key that some agent may already be paired under - the exact class of
|
|
62
|
+
// silent loss profiles were introduced to end.
|
|
63
|
+
throw new ProfileAdoptError(`Profile "${targetKey}" already has an identity. Adopting into it would overwrite that key. ` +
|
|
64
|
+
'Choose another name, or remove that profile first.');
|
|
65
|
+
}
|
|
66
|
+
const metadata = JSON.parse(await readFile(sourceMetadataPath, 'utf8'));
|
|
67
|
+
const secret = await loadRawSecret(sourceCandidates);
|
|
68
|
+
if (secret === null) {
|
|
69
|
+
throw new ProfileAdoptError(`Metadata exists for ${metadata.address} but no backend holds its private key, so there is ` +
|
|
70
|
+
'nothing to move. Run "botanary forget" to clear the stale metadata.');
|
|
71
|
+
}
|
|
72
|
+
const targetCandidates = defaultCandidates(platform, false, targetKey);
|
|
73
|
+
const targetMetadataPath = publicMetadataPath(targetKey);
|
|
74
|
+
let writtenBackendId = null;
|
|
75
|
+
try {
|
|
76
|
+
writtenBackendId = await saveToFirstAvailable(targetCandidates, secret);
|
|
77
|
+
await mkdir(dirname(targetMetadataPath), { recursive: true, mode: 0o700 });
|
|
78
|
+
await writeFile(targetMetadataPath, `${JSON.stringify({ ...metadata, backend: writtenBackendId }, null, 2)}\n`, 'utf8');
|
|
79
|
+
// Prove it, do not assume it. `sign` is the only check that exercises the whole path the agent
|
|
80
|
+
// actually uses - metadata read, secret load from the new item name, and the address-match guard
|
|
81
|
+
// that would otherwise only fail later, at the moment the agent tried to act.
|
|
82
|
+
const verify = createIdentityStore(targetKey, platform);
|
|
83
|
+
const adopted = await verify.ensure();
|
|
84
|
+
if (adopted.address.toLowerCase() !== metadata.address.toLowerCase()) {
|
|
85
|
+
throw new Error(`wrote ${metadata.address} but read back ${adopted.address}`);
|
|
86
|
+
}
|
|
87
|
+
await verify.sign('botanary profile adopt verification');
|
|
88
|
+
}
|
|
89
|
+
catch (e) {
|
|
90
|
+
await rollback(targetCandidates, targetMetadataPath);
|
|
91
|
+
throw new ProfileAdoptError(`Could not move the identity to "${targetKey}": ${e instanceof Error ? e.message : String(e)}. ` +
|
|
92
|
+
'Nothing was changed - the default identity is still intact.');
|
|
93
|
+
}
|
|
94
|
+
// Only now. Everything above is reversible; this is not.
|
|
95
|
+
await source.forget();
|
|
96
|
+
return {
|
|
97
|
+
from: DEFAULT_PROFILE_KEY,
|
|
98
|
+
to: targetKey,
|
|
99
|
+
address: metadata.address,
|
|
100
|
+
fingerprint: metadata.fingerprint,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
/** Every profile with metadata on this machine, so an owner can see what exists before moving anything.
|
|
104
|
+
* Reads metadata only - listing profiles never touches the keychain. */
|
|
105
|
+
export async function listProfiles() {
|
|
106
|
+
const { readdir } = await import('node:fs/promises');
|
|
107
|
+
const { botanaryMcpHome } = await import('../paths.js');
|
|
108
|
+
const { join } = await import('node:path');
|
|
109
|
+
const found = [];
|
|
110
|
+
const readOne = async (profile) => {
|
|
111
|
+
try {
|
|
112
|
+
const raw = await readFile(publicMetadataPath(profile), 'utf8');
|
|
113
|
+
found.push({ ...JSON.parse(raw), profile });
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
// Absent or unreadable: not an error. A profile directory with no metadata is simply not a
|
|
117
|
+
// profile yet, and listing must never fail because one entry is malformed.
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
await readOne(DEFAULT_PROFILE_KEY);
|
|
121
|
+
try {
|
|
122
|
+
for (const entry of await readdir(join(botanaryMcpHome(), 'profiles'), { withFileTypes: true })) {
|
|
123
|
+
if (entry.isDirectory())
|
|
124
|
+
await readOne(entry.name);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
catch {
|
|
128
|
+
// No profiles/ directory: this machine has only the default identity, or none at all.
|
|
129
|
+
}
|
|
130
|
+
return found;
|
|
131
|
+
}
|
|
132
|
+
async function loadRawSecret(candidates) {
|
|
133
|
+
for (const backend of candidates) {
|
|
134
|
+
try {
|
|
135
|
+
const raw = await backend.load();
|
|
136
|
+
if (raw !== null)
|
|
137
|
+
return raw;
|
|
138
|
+
}
|
|
139
|
+
catch {
|
|
140
|
+
// Try the next candidate - a broken backend is not proof the key is absent.
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
async function saveToFirstAvailable(candidates, secret) {
|
|
146
|
+
for (const backend of candidates) {
|
|
147
|
+
if (!(await backend.isAvailable()))
|
|
148
|
+
continue;
|
|
149
|
+
await backend.save(secret);
|
|
150
|
+
return backend.id;
|
|
151
|
+
}
|
|
152
|
+
throw new Error('no storage backend available for the destination profile');
|
|
153
|
+
}
|
|
154
|
+
async function rollback(candidates, metadataPath) {
|
|
155
|
+
const { rm } = await import('node:fs/promises');
|
|
156
|
+
for (const backend of candidates) {
|
|
157
|
+
try {
|
|
158
|
+
await backend.delete();
|
|
159
|
+
}
|
|
160
|
+
catch {
|
|
161
|
+
// Best effort. The source has not been touched at this point, so a leftover destination secret
|
|
162
|
+
// is inert - it has no metadata pointing at it and no profile will load it.
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
await rm(metadataPath, { force: true }).catch(() => { });
|
|
166
|
+
}
|
|
167
|
+
//# sourceMappingURL=adopt.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"adopt.js","sourceRoot":"","sources":["../../../src/identity/adopt.ts"],"names":[],"mappings":"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"}
|
|
@@ -6,10 +6,18 @@ import type { KeyBackend } from './types.js';
|
|
|
6
6
|
* never be empty and a caller can never end up with nowhere to store a key.
|
|
7
7
|
*
|
|
8
8
|
* CRITICAL: the agent's names (SERVICE_NAME / ACCOUNT_NAME) are FROZEN for backward compatibility.
|
|
9
|
-
* Existing installs already have keys stored under these names
|
|
10
|
-
* to prevent
|
|
9
|
+
* Existing installs already have keys stored under these names, which is exactly what
|
|
10
|
+
* `accountNameFor(DEFAULT_PROFILE_KEY)` preserves. Wallet uses a distinct ACCOUNT_NAME to prevent
|
|
11
|
+
* collision.
|
|
12
|
+
*
|
|
13
|
+
* `profileKey` keys the AGENT lane only. THE WALLET LANE IGNORES IT, deliberately: it holds the
|
|
14
|
+
* OWNER's login token, and the owner is one person however many coding tools they run. One
|
|
15
|
+
* `botanary login` is meant to be visible from every tool and from the CLI, and every write that token
|
|
16
|
+
* reaches still terminates in the owner's own browser signature - so there is nothing for a per-tool
|
|
17
|
+
* split to protect. The agent key is the credential that carries delegated authority, and that is the
|
|
18
|
+
* one that gets separated.
|
|
11
19
|
*/
|
|
12
|
-
export declare function defaultCandidates(platform?: NodeJS.Platform, isWallet?: boolean): KeyBackend[];
|
|
20
|
+
export declare function defaultCandidates(platform?: NodeJS.Platform, isWallet?: boolean, profileKey?: string): KeyBackend[];
|
|
13
21
|
/** Pick the first available backend from `candidates`, in order. Used only to decide where a NEW key
|
|
14
22
|
* gets written - see the KeyBackend interface doc for why reads don't use this. */
|
|
15
23
|
export declare function selectAvailable(candidates: KeyBackend[]): Promise<KeyBackend>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"select.d.ts","sourceRoot":"","sources":["../../../../src/identity/backends/select.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"select.d.ts","sourceRoot":"","sources":["../../../../src/identity/backends/select.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAM7C;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,iBAAiB,CAC/B,QAAQ,GAAE,MAAM,CAAC,QAA2B,EAC5C,QAAQ,GAAE,OAAe,EACzB,UAAU,GAAE,MAA4B,GACvC,UAAU,EAAE,CAwBd;AAED;oFACoF;AACpF,wBAAsB,eAAe,CAAC,UAAU,EAAE,UAAU,EAAE,GAAG,OAAO,CAAC,UAAU,CAAC,CAOnF"}
|
|
@@ -2,9 +2,10 @@ import { join } from 'node:path';
|
|
|
2
2
|
import { createFileFallbackBackend } from './file-fallback.js';
|
|
3
3
|
import { createLibsecretBackend } from './libsecret.js';
|
|
4
4
|
import { createMacosKeychainBackend } from './macos-keychain.js';
|
|
5
|
-
import {
|
|
5
|
+
import { accountNameFor, WALLET_ACCOUNT_NAME } from './types.js';
|
|
6
6
|
import { createWindowsDpapiBackend } from './windows-dpapi.js';
|
|
7
7
|
import { fileFallbackSecretPath, botanaryMcpHome } from '../../paths.js';
|
|
8
|
+
import { DEFAULT_PROFILE_KEY, isDefaultProfile } from '../profile.js';
|
|
8
9
|
/**
|
|
9
10
|
* Fixed, platform-derived priority order. The SAME list is used for reads and writes (store.ts): a new
|
|
10
11
|
* key is written to the first available candidate, and every candidate is swept in this order when
|
|
@@ -12,14 +13,29 @@ import { fileFallbackSecretPath, botanaryMcpHome } from '../../paths.js';
|
|
|
12
13
|
* never be empty and a caller can never end up with nowhere to store a key.
|
|
13
14
|
*
|
|
14
15
|
* CRITICAL: the agent's names (SERVICE_NAME / ACCOUNT_NAME) are FROZEN for backward compatibility.
|
|
15
|
-
* Existing installs already have keys stored under these names
|
|
16
|
-
* to prevent
|
|
16
|
+
* Existing installs already have keys stored under these names, which is exactly what
|
|
17
|
+
* `accountNameFor(DEFAULT_PROFILE_KEY)` preserves. Wallet uses a distinct ACCOUNT_NAME to prevent
|
|
18
|
+
* collision.
|
|
19
|
+
*
|
|
20
|
+
* `profileKey` keys the AGENT lane only. THE WALLET LANE IGNORES IT, deliberately: it holds the
|
|
21
|
+
* OWNER's login token, and the owner is one person however many coding tools they run. One
|
|
22
|
+
* `botanary login` is meant to be visible from every tool and from the CLI, and every write that token
|
|
23
|
+
* reaches still terminates in the owner's own browser signature - so there is nothing for a per-tool
|
|
24
|
+
* split to protect. The agent key is the credential that carries delegated authority, and that is the
|
|
25
|
+
* one that gets separated.
|
|
17
26
|
*/
|
|
18
|
-
export function defaultCandidates(platform = process.platform, isWallet = false) {
|
|
19
|
-
const accountName = isWallet ? WALLET_ACCOUNT_NAME :
|
|
27
|
+
export function defaultCandidates(platform = process.platform, isWallet = false, profileKey = DEFAULT_PROFILE_KEY) {
|
|
28
|
+
const accountName = isWallet ? WALLET_ACCOUNT_NAME : accountNameFor(profileKey);
|
|
20
29
|
// Wallet uses distinct file paths so multiple identities never collide
|
|
21
|
-
const walletFileFallbackPath = isWallet ? join(botanaryMcpHome(), 'wallet.key') : fileFallbackSecretPath();
|
|
22
|
-
|
|
30
|
+
const walletFileFallbackPath = isWallet ? join(botanaryMcpHome(), 'wallet.key') : fileFallbackSecretPath(profileKey);
|
|
31
|
+
// The DPAPI ciphertext file is per-profile for the same reason the keychain item is: on Windows this
|
|
32
|
+
// path IS the storage, so two profiles sharing it would overwrite each other exactly as the macOS
|
|
33
|
+
// item name did. `default` keeps the original filename it has always used.
|
|
34
|
+
const walletWindowsDpapiPath = isWallet
|
|
35
|
+
? join(botanaryMcpHome(), 'wallet.dpapi')
|
|
36
|
+
: isDefaultProfile(profileKey)
|
|
37
|
+
? join(botanaryMcpHome(), 'identity.dpapi')
|
|
38
|
+
: join(botanaryMcpHome(), 'profiles', profileKey, 'identity.dpapi');
|
|
23
39
|
switch (platform) {
|
|
24
40
|
case 'darwin':
|
|
25
41
|
return [createMacosKeychainBackend(undefined, accountName), createFileFallbackBackend(walletFileFallbackPath)];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"select.js","sourceRoot":"","sources":["../../../../src/identity/backends/select.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,yBAAyB,EAAE,MAAM,oBAAoB,CAAC;AAC/D,OAAO,EAAE,sBAAsB,EAAE,MAAM,gBAAgB,CAAC;AACxD,OAAO,EAAE,0BAA0B,EAAE,MAAM,qBAAqB,CAAC;AAEjE,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"select.js","sourceRoot":"","sources":["../../../../src/identity/backends/select.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,yBAAyB,EAAE,MAAM,oBAAoB,CAAC;AAC/D,OAAO,EAAE,sBAAsB,EAAE,MAAM,gBAAgB,CAAC;AACxD,OAAO,EAAE,0BAA0B,EAAE,MAAM,qBAAqB,CAAC;AAEjE,OAAO,EAAE,cAAc,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AACjE,OAAO,EAAE,yBAAyB,EAAE,MAAM,oBAAoB,CAAC;AAC/D,OAAO,EAAE,sBAAsB,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACzE,OAAO,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAEtE;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,UAAU,iBAAiB,CAC/B,WAA4B,OAAO,CAAC,QAAQ,EAC5C,WAAoB,KAAK,EACzB,aAAqB,mBAAmB;IAExC,MAAM,WAAW,GAAG,QAAQ,CAAC,CAAC,CAAC,mBAAmB,CAAC,CAAC,CAAC,cAAc,CAAC,UAAU,CAAC,CAAC;IAEhF,uEAAuE;IACvE,MAAM,sBAAsB,GAAG,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,eAAe,EAAE,EAAE,YAAY,CAAC,CAAC,CAAC,CAAC,sBAAsB,CAAC,UAAU,CAAC,CAAC;IACrH,qGAAqG;IACrG,kGAAkG;IAClG,2EAA2E;IAC3E,MAAM,sBAAsB,GAAG,QAAQ;QACrC,CAAC,CAAC,IAAI,CAAC,eAAe,EAAE,EAAE,cAAc,CAAC;QACzC,CAAC,CAAC,gBAAgB,CAAC,UAAU,CAAC;YAC5B,CAAC,CAAC,IAAI,CAAC,eAAe,EAAE,EAAE,gBAAgB,CAAC;YAC3C,CAAC,CAAC,IAAI,CAAC,eAAe,EAAE,EAAE,UAAU,EAAE,UAAU,EAAE,gBAAgB,CAAC,CAAC;IAExE,QAAQ,QAAQ,EAAE,CAAC;QACjB,KAAK,QAAQ;YACX,OAAO,CAAC,0BAA0B,CAAC,SAAS,EAAE,WAAW,CAAC,EAAE,yBAAyB,CAAC,sBAAsB,CAAC,CAAC,CAAC;QACjH,KAAK,OAAO;YACV,OAAO,CAAC,sBAAsB,CAAC,SAAS,EAAE,WAAW,CAAC,EAAE,yBAAyB,CAAC,sBAAsB,CAAC,CAAC,CAAC;QAC7G,KAAK,OAAO;YACV,OAAO,CAAC,yBAAyB,CAAC,SAAS,EAAE,sBAAsB,CAAC,EAAE,yBAAyB,CAAC,sBAAsB,CAAC,CAAC,CAAC;QAC3H;YACE,OAAO,CAAC,yBAAyB,CAAC,sBAAsB,CAAC,CAAC,CAAC;IAC/D,CAAC;AACH,CAAC;AAED;oFACoF;AACpF,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,UAAwB;IAC5D,KAAK,MAAM,OAAO,IAAI,UAAU,EAAE,CAAC;QACjC,IAAI,MAAM,OAAO,CAAC,WAAW,EAAE;YAAE,OAAO,OAAO,CAAC;IAClD,CAAC;IACD,8FAA8F;IAC9F,qGAAqG;IACrG,MAAM,IAAI,KAAK,CAAC,2EAA2E,CAAC,CAAC;AAC/F,CAAC"}
|
|
@@ -31,11 +31,23 @@ export declare class KeyBackendError extends Error {
|
|
|
31
31
|
readonly operation: 'save' | 'load' | 'delete' | 'probe';
|
|
32
32
|
constructor(backend: string, operation: 'save' | 'load' | 'delete' | 'probe', message: string);
|
|
33
33
|
}
|
|
34
|
-
/** Fixed, non-secret names every real backend stores
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
34
|
+
/** Fixed, non-secret names every real backend stores credentials under. These defaults are FROZEN:
|
|
35
|
+
* existing installs already have keys stored here, and `accountNameFor(DEFAULT_PROFILE_KEY)` must
|
|
36
|
+
* keep returning `ACCOUNT_NAME` unchanged forever. The wallet lane uses a different ACCOUNT_NAME to
|
|
37
|
+
* prevent collision with the agent lane. */
|
|
38
38
|
export declare const SERVICE_NAME = "botanary-mcp";
|
|
39
39
|
export declare const ACCOUNT_NAME = "agent-identity";
|
|
40
40
|
export declare const WALLET_ACCOUNT_NAME = "wallet-session";
|
|
41
|
+
/**
|
|
42
|
+
* The keychain/libsecret account name for one profile's agent key.
|
|
43
|
+
*
|
|
44
|
+
* THIS IS THE HALF `BOTANARY_MCP_HOME` NEVER MOVED, and the reason a per-client home was silently
|
|
45
|
+
* destructive before profiles existed: the metadata file followed the home, the keychain item did
|
|
46
|
+
* not, so a second install minted a fresh keypair and `security add-generic-password -U` overwrote
|
|
47
|
+
* the first one's private key in place. Item name and file path now move together, always.
|
|
48
|
+
*
|
|
49
|
+
* `default` returns the bare, frozen name - a pre-profiles install is not migrated or shimmed, it
|
|
50
|
+
* simply IS the default profile and keeps the exact item it already has.
|
|
51
|
+
*/
|
|
52
|
+
export declare function accountNameFor(profileKey: string): string;
|
|
41
53
|
//# sourceMappingURL=types.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../src/identity/backends/types.ts"],"names":[],"mappings":"AAAA;;;8FAG8F;AAC9F,MAAM,WAAW,UAAU;IACzB;kEAC8D;IAC9D,QAAQ,CAAC,EAAE,EAAE,gBAAgB,GAAG,WAAW,GAAG,eAAe,GAAG,eAAe,CAAC;IAChF,wDAAwD;IACxD,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB;;;+EAG2E;IAC3E,WAAW,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;IAChC;oDACgD;IAChD,IAAI,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACpC;;;8FAG0F;IAC1F,IAAI,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC/B;;sCAEkC;IAClC,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACzB;AAED,qBAAa,eAAgB,SAAQ,KAAK;aAEtB,OAAO,EAAE,MAAM;aACf,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,QAAQ,GAAG,OAAO;gBAD/C,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,QAAQ,GAAG,OAAO,EAC/D,OAAO,EAAE,MAAM;CAKlB;AAED;;;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../src/identity/backends/types.ts"],"names":[],"mappings":"AAAA;;;8FAG8F;AAC9F,MAAM,WAAW,UAAU;IACzB;kEAC8D;IAC9D,QAAQ,CAAC,EAAE,EAAE,gBAAgB,GAAG,WAAW,GAAG,eAAe,GAAG,eAAe,CAAC;IAChF,wDAAwD;IACxD,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB;;;+EAG2E;IAC3E,WAAW,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;IAChC;oDACgD;IAChD,IAAI,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACpC;;;8FAG0F;IAC1F,IAAI,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC/B;;sCAEkC;IAClC,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACzB;AAED,qBAAa,eAAgB,SAAQ,KAAK;aAEtB,OAAO,EAAE,MAAM;aACf,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,QAAQ,GAAG,OAAO;gBAD/C,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,QAAQ,GAAG,OAAO,EAC/D,OAAO,EAAE,MAAM;CAKlB;AAED;;;6CAG6C;AAC7C,eAAO,MAAM,YAAY,iBAAiB,CAAC;AAC3C,eAAO,MAAM,YAAY,mBAAmB,CAAC;AAC7C,eAAO,MAAM,mBAAmB,mBAAmB,CAAC;AAEpD;;;;;;;;;;GAUG;AACH,wBAAgB,cAAc,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAEzD"}
|
|
@@ -8,11 +8,25 @@ export class KeyBackendError extends Error {
|
|
|
8
8
|
this.name = 'KeyBackendError';
|
|
9
9
|
}
|
|
10
10
|
}
|
|
11
|
-
/** Fixed, non-secret names every real backend stores
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
11
|
+
/** Fixed, non-secret names every real backend stores credentials under. These defaults are FROZEN:
|
|
12
|
+
* existing installs already have keys stored here, and `accountNameFor(DEFAULT_PROFILE_KEY)` must
|
|
13
|
+
* keep returning `ACCOUNT_NAME` unchanged forever. The wallet lane uses a different ACCOUNT_NAME to
|
|
14
|
+
* prevent collision with the agent lane. */
|
|
15
15
|
export const SERVICE_NAME = 'botanary-mcp';
|
|
16
16
|
export const ACCOUNT_NAME = 'agent-identity';
|
|
17
17
|
export const WALLET_ACCOUNT_NAME = 'wallet-session';
|
|
18
|
+
/**
|
|
19
|
+
* The keychain/libsecret account name for one profile's agent key.
|
|
20
|
+
*
|
|
21
|
+
* THIS IS THE HALF `BOTANARY_MCP_HOME` NEVER MOVED, and the reason a per-client home was silently
|
|
22
|
+
* destructive before profiles existed: the metadata file followed the home, the keychain item did
|
|
23
|
+
* not, so a second install minted a fresh keypair and `security add-generic-password -U` overwrote
|
|
24
|
+
* the first one's private key in place. Item name and file path now move together, always.
|
|
25
|
+
*
|
|
26
|
+
* `default` returns the bare, frozen name - a pre-profiles install is not migrated or shimmed, it
|
|
27
|
+
* simply IS the default profile and keeps the exact item it already has.
|
|
28
|
+
*/
|
|
29
|
+
export function accountNameFor(profileKey) {
|
|
30
|
+
return profileKey === 'default' ? ACCOUNT_NAME : `${ACCOUNT_NAME}:${profileKey}`;
|
|
31
|
+
}
|
|
18
32
|
//# sourceMappingURL=types.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../../src/identity/backends/types.ts"],"names":[],"mappings":"AA6BA,MAAM,OAAO,eAAgB,SAAQ,KAAK;IAEtB;IACA;IAFlB,YACkB,OAAe,EACf,SAA+C,EAC/D,OAAe;QAEf,KAAK,CAAC,IAAI,OAAO,KAAK,SAAS,YAAY,OAAO,EAAE,CAAC,CAAC;QAJtC,YAAO,GAAP,OAAO,CAAQ;QACf,cAAS,GAAT,SAAS,CAAsC;QAI/D,IAAI,CAAC,IAAI,GAAG,iBAAiB,CAAC;IAChC,CAAC;CACF;AAED;;;
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../../src/identity/backends/types.ts"],"names":[],"mappings":"AA6BA,MAAM,OAAO,eAAgB,SAAQ,KAAK;IAEtB;IACA;IAFlB,YACkB,OAAe,EACf,SAA+C,EAC/D,OAAe;QAEf,KAAK,CAAC,IAAI,OAAO,KAAK,SAAS,YAAY,OAAO,EAAE,CAAC,CAAC;QAJtC,YAAO,GAAP,OAAO,CAAQ;QACf,cAAS,GAAT,SAAS,CAAsC;QAI/D,IAAI,CAAC,IAAI,GAAG,iBAAiB,CAAC;IAChC,CAAC;CACF;AAED;;;6CAG6C;AAC7C,MAAM,CAAC,MAAM,YAAY,GAAG,cAAc,CAAC;AAC3C,MAAM,CAAC,MAAM,YAAY,GAAG,gBAAgB,CAAC;AAC7C,MAAM,CAAC,MAAM,mBAAmB,GAAG,gBAAgB,CAAC;AAEpD;;;;;;;;;;GAUG;AACH,MAAM,UAAU,cAAc,CAAC,UAAkB;IAC/C,OAAO,UAAU,KAAK,SAAS,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,GAAG,YAAY,IAAI,UAAU,EAAE,CAAC;AACnF,CAAC"}
|