@onepointfour-packs/flaredeck-agent 0.2.37 → 0.2.39
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/dist/agent.d.ts +42 -3
- package/dist/agent.js +84 -6
- package/dist/agent.js.map +1 -1
- package/dist/automode.js +43 -3
- package/dist/automode.js.map +1 -1
- package/dist/boards.d.ts +72 -0
- package/dist/boards.js +136 -0
- package/dist/boards.js.map +1 -0
- package/dist/branches.d.ts +12 -1
- package/dist/branches.js +13 -3
- package/dist/branches.js.map +1 -1
- package/dist/browser.d.ts +59 -0
- package/dist/browser.js +260 -26
- package/dist/browser.js.map +1 -1
- package/dist/config.d.ts +22 -0
- package/dist/config.js +37 -2
- package/dist/config.js.map +1 -1
- package/dist/credentials.d.ts +217 -0
- package/dist/credentials.js +332 -0
- package/dist/credentials.js.map +1 -0
- package/dist/engine-antigravity.js +2 -0
- package/dist/engine-antigravity.js.map +1 -1
- package/dist/engine-claude.js +14 -2
- package/dist/engine-claude.js.map +1 -1
- package/dist/engine.d.ts +19 -0
- package/dist/engine.js.map +1 -1
- package/dist/environment.d.ts +15 -3
- package/dist/environment.js +79 -9
- package/dist/environment.js.map +1 -1
- package/dist/envtools.d.ts +58 -0
- package/dist/envtools.js +151 -0
- package/dist/envtools.js.map +1 -0
- package/dist/flaredeck-proxy.d.ts +11 -1
- package/dist/flaredeck-proxy.js +27 -1
- package/dist/flaredeck-proxy.js.map +1 -1
- package/dist/flaredeck.d.ts +24 -0
- package/dist/flaredeck.js +20 -0
- package/dist/flaredeck.js.map +1 -1
- package/dist/github.d.ts +2 -0
- package/dist/github.js +4 -0
- package/dist/github.js.map +1 -1
- package/dist/githubauth.d.ts +11 -1
- package/dist/githubauth.js +82 -16
- package/dist/githubauth.js.map +1 -1
- package/dist/hydrate.d.ts +76 -0
- package/dist/hydrate.js +77 -0
- package/dist/hydrate.js.map +1 -0
- package/dist/limits.d.ts +184 -0
- package/dist/limits.js +251 -0
- package/dist/limits.js.map +1 -0
- package/dist/main.js +177 -33
- package/dist/main.js.map +1 -1
- package/dist/picker.d.ts +10 -0
- package/dist/picker.js +150 -0
- package/dist/picker.js.map +1 -0
- package/dist/preflight.d.ts +43 -0
- package/dist/preflight.js +133 -19
- package/dist/preflight.js.map +1 -1
- package/dist/repopicker.js +8 -96
- package/dist/repopicker.js.map +1 -1
- package/dist/runs.d.ts +19 -4
- package/dist/runs.js +74 -9
- package/dist/runs.js.map +1 -1
- package/dist/serve.d.ts +6 -0
- package/dist/serve.js +1152 -138
- package/dist/serve.js.map +1 -1
- package/dist/session.d.ts +51 -0
- package/dist/session.js +145 -0
- package/dist/session.js.map +1 -0
- package/dist/settle.d.ts +18 -0
- package/dist/settle.js +39 -0
- package/dist/settle.js.map +1 -0
- package/dist/signin.d.ts +47 -0
- package/dist/signin.js +331 -0
- package/dist/signin.js.map +1 -0
- package/dist/stream.d.ts +8 -0
- package/dist/stream.js +44 -3
- package/dist/stream.js.map +1 -1
- package/dist/subscriptions.d.ts +115 -0
- package/dist/subscriptions.js +140 -0
- package/dist/subscriptions.js.map +1 -0
- package/dist/team.js +7 -1
- package/dist/team.js.map +1 -1
- package/dist/tmux.d.ts +1 -0
- package/dist/tmux.js +47 -0
- package/dist/tmux.js.map +1 -0
- package/dist/ui.d.ts +13 -0
- package/dist/ui.js +7 -2
- package/dist/ui.js.map +1 -1
- package/dist/usage.d.ts +105 -0
- package/dist/usage.js +348 -0
- package/dist/usage.js.map +1 -0
- package/dist/wall.d.ts +35 -0
- package/dist/wall.js +94 -0
- package/dist/wall.js.map +1 -0
- package/dist/workspace.d.ts +15 -1
- package/dist/workspace.js +25 -1
- package/dist/workspace.js.map +1 -1
- package/dist/worktree.d.ts +19 -0
- package/dist/worktree.js +179 -4
- package/dist/worktree.js.map +1 -1
- package/package.json +1 -1
package/dist/config.js
CHANGED
|
@@ -28,13 +28,15 @@ export function loadConfig(repo = process.cwd()) {
|
|
|
28
28
|
return {
|
|
29
29
|
repo: raw.repo ?? process.cwd(),
|
|
30
30
|
...(raw.repos?.length ? { repos: raw.repos } : {}),
|
|
31
|
+
...(raw.boards?.length ? { boards: raw.boards.filter((b) => b?.key) } : {}),
|
|
31
32
|
...(raw.worktreeLocalFiles?.length ? { worktreeLocalFiles: raw.worktreeLocalFiles } : {}),
|
|
32
33
|
mcpServerName: raw.mcpServerName ?? DEFAULTS.mcpServerName,
|
|
33
34
|
mcpUrl: raw.mcpUrl ?? DEFAULTS.mcpUrl,
|
|
34
35
|
...(raw.tenantId ? { tenantId: raw.tenantId } : {}),
|
|
35
36
|
...(raw.apiKey ? { apiKey: raw.apiKey } : {}),
|
|
36
37
|
...(raw.oauth ? { oauth: raw.oauth } : {}),
|
|
37
|
-
|
|
38
|
+
// The machine's login wins; a repo's own is only what was stored before there was one.
|
|
39
|
+
...((github) => (github ? { github } : {}))(loadGitHub() ?? raw.github),
|
|
38
40
|
...(raw.baseBranch ? { baseBranch: raw.baseBranch } : {}),
|
|
39
41
|
...(raw.runs ? { runs: raw.runs } : {}),
|
|
40
42
|
agentServiceUrl: raw.agentServiceUrl ?? DEFAULTS.agentServiceUrl,
|
|
@@ -63,13 +65,46 @@ function readableConfig(repo) {
|
|
|
63
65
|
export function saveConfig(config) {
|
|
64
66
|
const path = configPath(config.repo);
|
|
65
67
|
mkdirSync(dirname(path), { recursive: true });
|
|
68
|
+
// The GitHub login is the machine's, not the repo's — see githubPath.
|
|
69
|
+
const { github, ...rest } = config;
|
|
70
|
+
if (github)
|
|
71
|
+
saveGitHub(github);
|
|
66
72
|
// 0600: the file holds an API key that can comment on and move cards across the whole tenant.
|
|
67
73
|
// `mode` on writeFileSync only applies when the file is CREATED, so an existing one keeps
|
|
68
74
|
// whatever it had — chmod every time instead of trusting the first write.
|
|
69
|
-
writeFileSync(path, `${JSON.stringify(
|
|
75
|
+
writeFileSync(path, `${JSON.stringify(rest, null, 2)}\n`, { encoding: 'utf8', mode: 0o600 });
|
|
70
76
|
chmodSync(path, 0o600);
|
|
71
77
|
return path;
|
|
72
78
|
}
|
|
79
|
+
/**
|
|
80
|
+
* ONE GitHub login per machine, shared by every repo's agent.
|
|
81
|
+
*
|
|
82
|
+
* It used to be one per repo, and that is what killed them: GitHub keeps ten tokens per person
|
|
83
|
+
* per OAuth app and cancels the oldest when another is issued. Eleven repos meant eleven logins,
|
|
84
|
+
* so every new login silently revoked some other agent's token, which then found out at
|
|
85
|
+
* push_branch with "Invalid username or token" after doing the card's work. One login here
|
|
86
|
+
* uses one slot, and signing in again replaces it for every agent at once.
|
|
87
|
+
*/
|
|
88
|
+
const githubPath = () => join(configDir(), 'github.json');
|
|
89
|
+
export function loadGitHub() {
|
|
90
|
+
try {
|
|
91
|
+
const raw = JSON.parse(readFileSync(githubPath(), 'utf8'));
|
|
92
|
+
return raw?.accessToken ? raw : null;
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
export function saveGitHub(login) {
|
|
99
|
+
const path = githubPath();
|
|
100
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
101
|
+
writeFileSync(path, `${JSON.stringify(login, null, 2)}\n`, { encoding: 'utf8', mode: 0o600 });
|
|
102
|
+
chmodSync(path, 0o600);
|
|
103
|
+
}
|
|
104
|
+
/** Forget the machine's GitHub login, so the next one is a fresh sign-in rather than a reuse. */
|
|
105
|
+
export function forgetGitHub() {
|
|
106
|
+
rmSync(githubPath(), { force: true });
|
|
107
|
+
}
|
|
73
108
|
/** Forget this repo's agent, so the next login is a clean choice of account. */
|
|
74
109
|
/**
|
|
75
110
|
* A PERSON's FlareDeck login on this machine — not any agent's. Kept apart from the repo
|
package/dist/config.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAChG,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Credentials an agent uses, kept in FlareDeck and NOWHERE ELSE.
|
|
3
|
+
*
|
|
4
|
+
* The complaint this answers: setting up an agent meant setting up its credentials, one box at a
|
|
5
|
+
* time. Connect Sceneflo. Connect Cnjure. Connect Jam. Sign in to the site under test. Then do it
|
|
6
|
+
* all again on the next machine, and again when the token expires. At twenty agents that is not a
|
|
7
|
+
* setup cost, it is a standing job.
|
|
8
|
+
*
|
|
9
|
+
* The ones that hurt most are the ones a machine cannot regenerate. A browser sign-in session is
|
|
10
|
+
* minutes of a person's time and sometimes a code from their phone, and it used to live in
|
|
11
|
+
* exactly one `~/.config/flaredeck-agent/browser/` — so the next agent that needed the same site
|
|
12
|
+
* signed in again from scratch, and the one after that did too.
|
|
13
|
+
*
|
|
14
|
+
* NOTHING IS WRITTEN TO THIS MACHINE. Not a copy, not a cache, not a fallback. That is a
|
|
15
|
+
* deliberate reversal of how this module was first built, and the reason is that a cache quietly
|
|
16
|
+
* undoes the entire point: twenty machines each holding a copy of a credential is the situation
|
|
17
|
+
* being escaped, and a "fallback" is just that situation with a nicer name. It also means
|
|
18
|
+
* revocation is honest — one write in FlareDeck and the next read anywhere returns nothing, with
|
|
19
|
+
* no window during which some disconnected box is still using what it had.
|
|
20
|
+
*
|
|
21
|
+
* The price is paid openly: an agent that cannot reach FlareDeck cannot open a signed-in page,
|
|
22
|
+
* and must say so rather than carrying on with something stale. `get` therefore distinguishes
|
|
23
|
+
* "there is no session" from "I could not ask", because those call for different behaviour — the
|
|
24
|
+
* first means request a sign-in, the second means stop and report.
|
|
25
|
+
*
|
|
26
|
+
* The other thing this module refuses to do is throw. A credential store that throws turns "I
|
|
27
|
+
* could not reach FlareDeck" into a failed card. Every path here resolves to an outcome.
|
|
28
|
+
*/
|
|
29
|
+
export type CredentialKind = 'browser-session' | 'mcp-server' | 'token' | 'oauth'
|
|
30
|
+
/**
|
|
31
|
+
* A central secret that is NEVER handed to an agent — a GitHub App's private key, say. It
|
|
32
|
+
* exists so FlareDeck can MINT short-lived, narrowly scoped credentials from it on request.
|
|
33
|
+
* The server refuses to serve one of these through the ordinary read path, which is the point:
|
|
34
|
+
* see "the two patterns" in docs/credential-store.md.
|
|
35
|
+
*/
|
|
36
|
+
| 'app-key';
|
|
37
|
+
export type CredentialMeta = {
|
|
38
|
+
id: string;
|
|
39
|
+
kind: CredentialKind;
|
|
40
|
+
label: string;
|
|
41
|
+
version: number;
|
|
42
|
+
state: 'active' | 'revoked';
|
|
43
|
+
bytes: number;
|
|
44
|
+
};
|
|
45
|
+
export type PutOptions = {
|
|
46
|
+
kind: CredentialKind;
|
|
47
|
+
/** What a person sees in the FlareDeck UI. Never a value, and never anything secret. */
|
|
48
|
+
label?: string;
|
|
49
|
+
};
|
|
50
|
+
/**
|
|
51
|
+
* The outcome of a read.
|
|
52
|
+
*
|
|
53
|
+
* `{ ok: true, value: null }` and `{ ok: false }` are emphatically not the same thing and the
|
|
54
|
+
* caller must not collapse them. The first means FlareDeck was asked and holds no session for
|
|
55
|
+
* this site, which is the normal case and the trigger for requesting a sign-in. The second means
|
|
56
|
+
* FlareDeck could not be asked, and the only correct response is to stop and say so — there is
|
|
57
|
+
* nothing on disk to fall back to, by design.
|
|
58
|
+
*/
|
|
59
|
+
export type Read = {
|
|
60
|
+
ok: true;
|
|
61
|
+
value: Buffer | null;
|
|
62
|
+
} | {
|
|
63
|
+
ok: false;
|
|
64
|
+
why: string;
|
|
65
|
+
};
|
|
66
|
+
export type CredentialStore = {
|
|
67
|
+
get(id: string): Promise<Read>;
|
|
68
|
+
put(id: string, value: Buffer, opts: PutOptions): Promise<{
|
|
69
|
+
ok: boolean;
|
|
70
|
+
message?: string;
|
|
71
|
+
}>;
|
|
72
|
+
/** Metadata for everything this agent may read. Never a value. */
|
|
73
|
+
list(): Promise<CredentialMeta[]>;
|
|
74
|
+
};
|
|
75
|
+
/**
|
|
76
|
+
* What a signed-in site IS, centrally.
|
|
77
|
+
*
|
|
78
|
+
* The on-disk key was `<repo basename>/<part>` — `"App/ui"` — and the reasoning behind it is
|
|
79
|
+
* sound: every worktree serves the app on a different port, so a URL-keyed memory is empty on
|
|
80
|
+
* every card, while a given repo's `ui` is the same place on Tuesday as it was on Monday. What is
|
|
81
|
+
* wrong with it centrally is the subject, not the logic. "App/ui" on this machine and "App/ui" on
|
|
82
|
+
* another are the same project but say nothing about which SITE was signed in to, and a real
|
|
83
|
+
* site — Sceneflo, Cnjure, a staging box — is the same site from every machine.
|
|
84
|
+
*
|
|
85
|
+
* So both halves are kept and the subject changes:
|
|
86
|
+
*
|
|
87
|
+
* · A real site is its HOST. `session:host:app.sceneflo.ai`. The port is dropped because cookies
|
|
88
|
+
* ignore ports and a port is a deployment accident; the full host is kept because a session is
|
|
89
|
+
* scoped to a host. Two agents on two machines that both need Sceneflo now land on one key,
|
|
90
|
+
* which is the entire request.
|
|
91
|
+
* · A local site is its PROJECT. `session:repo:App:ui` for anything on loopback or a private
|
|
92
|
+
* address. There is no identity in `localhost:4301`; the identity is "this project's UI".
|
|
93
|
+
*
|
|
94
|
+
* The separator in the project form is a colon and not the slash the on-disk key used, because a
|
|
95
|
+
* credential id is a Firestore document id and a slash in one silently creates a subcollection
|
|
96
|
+
* somewhere nobody is looking. The server refuses it outright (`VALID_ID` in
|
|
97
|
+
* flaredeck-mcp/src/vault/store.ts), which is how this was caught: every local session wrote fine
|
|
98
|
+
* and came back 400 from FlareDeck.
|
|
99
|
+
*
|
|
100
|
+
* The tenant is not in the key — it is in the Firestore path and in the request URL, so putting
|
|
101
|
+
* it here would only create a second place for it to be wrong.
|
|
102
|
+
*/
|
|
103
|
+
export declare function siteCredentialId(repo: string, part: string, url?: string): string;
|
|
104
|
+
/**
|
|
105
|
+
* Which site a saved session actually belongs to, read off the session itself.
|
|
106
|
+
*
|
|
107
|
+
* This exists so that `rememberSignIn` needs no new argument: its two callers are in `main.ts`
|
|
108
|
+
* and `serve.ts`, and those belong to somebody else. The session already knows where it came
|
|
109
|
+
* from, so asking it is both less code and harder to get wrong than threading a URL through.
|
|
110
|
+
*
|
|
111
|
+
* `_ss` first because `captureSessionStorage` records the origin of the page as it was when the
|
|
112
|
+
* session was taken — i.e. the app, after the sign-in redirect chain finished. `origins` is the
|
|
113
|
+
* fallback and needs the identity providers filtered out, because a Google sign-in leaves
|
|
114
|
+
* `accounts.google.com` in there and that is emphatically not the site being remembered.
|
|
115
|
+
*
|
|
116
|
+
* Returning undefined is a normal outcome, not a failure: the caller then keys by project.
|
|
117
|
+
*/
|
|
118
|
+
export declare function sessionOrigin(session: {
|
|
119
|
+
origins?: unknown[];
|
|
120
|
+
_ss?: {
|
|
121
|
+
origin?: string;
|
|
122
|
+
}[];
|
|
123
|
+
} | null | undefined): string | undefined;
|
|
124
|
+
export type FlaredeckStoreConfig = {
|
|
125
|
+
tenantId: string;
|
|
126
|
+
/** The agent's own access token. A function because it is refreshed as it expires. */
|
|
127
|
+
auth: () => Promise<string>;
|
|
128
|
+
baseUrl?: string;
|
|
129
|
+
/** The board being worked, when there is one — for board-scoped credentials and the audit row. */
|
|
130
|
+
board?: () => string | undefined;
|
|
131
|
+
};
|
|
132
|
+
export type RemoteRead = {
|
|
133
|
+
kind: 'value';
|
|
134
|
+
value: Buffer;
|
|
135
|
+
meta: CredentialMeta;
|
|
136
|
+
} | {
|
|
137
|
+
kind: 'missing';
|
|
138
|
+
} | {
|
|
139
|
+
kind: 'withheld';
|
|
140
|
+
message: string;
|
|
141
|
+
} | {
|
|
142
|
+
kind: 'revoked';
|
|
143
|
+
message: string;
|
|
144
|
+
} | {
|
|
145
|
+
kind: 'unreachable';
|
|
146
|
+
message: string;
|
|
147
|
+
};
|
|
148
|
+
/**
|
|
149
|
+
* The vault over HTTP.
|
|
150
|
+
*
|
|
151
|
+
* Returns a tagged outcome rather than a value-or-null because the failures are not the same
|
|
152
|
+
* thing and the caller treats them differently: `missing` and `revoked` mean request a sign-in,
|
|
153
|
+
* `withheld` means somebody deliberately said no, `unreachable` means stop.
|
|
154
|
+
*/
|
|
155
|
+
export declare class FlaredeckCredentials {
|
|
156
|
+
private readonly cfg;
|
|
157
|
+
constructor(cfg: FlaredeckStoreConfig);
|
|
158
|
+
private get base();
|
|
159
|
+
private url;
|
|
160
|
+
read(id: string): Promise<RemoteRead>;
|
|
161
|
+
write(id: string, value: Buffer, opts: PutOptions): Promise<{
|
|
162
|
+
ok: boolean;
|
|
163
|
+
message?: string;
|
|
164
|
+
}>;
|
|
165
|
+
list(): Promise<CredentialMeta[]>;
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* FlareDeck, and only FlareDeck.
|
|
169
|
+
*
|
|
170
|
+
* How many times this is called matters, because there is no cache to absorb it: once per
|
|
171
|
+
* `open_page`, which is a handful of times per card. That is deliberately cheaper than a cache
|
|
172
|
+
* would be to reason about — and it is what makes a rotation or a revoke take effect on the very
|
|
173
|
+
* next page an agent opens, anywhere, with nothing told to forget anything.
|
|
174
|
+
*/
|
|
175
|
+
export declare function vaultStore(remote: FlaredeckCredentials, onNote?: (text: string) => void): CredentialStore;
|
|
176
|
+
/**
|
|
177
|
+
* The store for an agent with no FlareDeck login.
|
|
178
|
+
*
|
|
179
|
+
* Says why, every time, instead of silently behaving like an empty vault. The difference matters:
|
|
180
|
+
* an empty vault means "sign in to the site", while this means "this agent is not set up", and
|
|
181
|
+
* sending a person to sign in to a site over the second one wastes their time.
|
|
182
|
+
*/
|
|
183
|
+
export declare function unavailable(why: string): CredentialStore;
|
|
184
|
+
/**
|
|
185
|
+
* The store for this checkout's agent, built from what is already on disk.
|
|
186
|
+
*
|
|
187
|
+
* Takes a repo path and nothing else on purpose. The browser tools are constructed from a
|
|
188
|
+
* `BrowserDeps` that has no FlareDeck client in it, and the places that build that object are
|
|
189
|
+
* several and belong to other work; threading a client through all of them to reach one call site
|
|
190
|
+
* would be a far bigger change than this feature deserves. The agent's login is in its config
|
|
191
|
+
* file, which is where every other command gets it from.
|
|
192
|
+
*/
|
|
193
|
+
export declare function credentialsFor(repo: string, onNote?: (text: string) => void, board?: () => string | undefined): CredentialStore;
|
|
194
|
+
export declare function getJson<T>(store: CredentialStore, id: string): Promise<{
|
|
195
|
+
ok: true;
|
|
196
|
+
value: T | null;
|
|
197
|
+
} | {
|
|
198
|
+
ok: false;
|
|
199
|
+
why: string;
|
|
200
|
+
}>;
|
|
201
|
+
export declare function putJson(store: CredentialStore, id: string, value: unknown, opts: PutOptions): Promise<{
|
|
202
|
+
ok: boolean;
|
|
203
|
+
message?: string;
|
|
204
|
+
}>;
|
|
205
|
+
/**
|
|
206
|
+
* Write a credential that cannot be regenerated cheaply, with retries.
|
|
207
|
+
*
|
|
208
|
+
* A captured sign-in has nowhere else to go — there is no local copy any more — so losing it to
|
|
209
|
+
* one network blip would cost a person another trip through a sign-in form, possibly with a code
|
|
210
|
+
* from their phone. Three attempts with a widening gap, then it gives up and says so loudly; the
|
|
211
|
+
* card then hits the sign-in wall again on its next look and the existing machinery asks for a
|
|
212
|
+
* fresh one, which is the same path an expired session takes.
|
|
213
|
+
*/
|
|
214
|
+
export declare function putInsistently(store: CredentialStore, id: string, value: Buffer, opts: PutOptions, onNote?: (text: string) => void, attempts?: number): Promise<{
|
|
215
|
+
ok: boolean;
|
|
216
|
+
message?: string;
|
|
217
|
+
}>;
|
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
import { loadConfig, saveConfig } from './config.js';
|
|
2
|
+
import { freshAccessToken } from './oauth.js';
|
|
3
|
+
/**
|
|
4
|
+
* How long to wait on FlareDeck.
|
|
5
|
+
*
|
|
6
|
+
* Short on read: this is in the path of opening a page, and an agent waiting half a minute to be
|
|
7
|
+
* told "no" is worse than being told quickly. Long on write: a captured sign-in has nowhere else
|
|
8
|
+
* to go, so it is worth being patient for.
|
|
9
|
+
*/
|
|
10
|
+
const READ_TIMEOUT_MS = 10_000;
|
|
11
|
+
const WRITE_TIMEOUT_MS = 60_000;
|
|
12
|
+
// ---------------------------------------------------------------------------------------------
|
|
13
|
+
// Identity
|
|
14
|
+
const PRIVATE_HOST = /^(localhost|127\.\d+\.\d+\.\d+|0\.0\.0\.0|\[?::1\]?|10\.\d+\.\d+\.\d+|192\.168\.\d+\.\d+|172\.(1[6-9]|2\d|3[01])\.\d+\.\d+|169\.254\.\d+\.\d+)$/i;
|
|
15
|
+
const PRIVATE_SUFFIX = /\.(local|localhost|internal|test|lan|home)$/i;
|
|
16
|
+
/**
|
|
17
|
+
* What a signed-in site IS, centrally.
|
|
18
|
+
*
|
|
19
|
+
* The on-disk key was `<repo basename>/<part>` — `"App/ui"` — and the reasoning behind it is
|
|
20
|
+
* sound: every worktree serves the app on a different port, so a URL-keyed memory is empty on
|
|
21
|
+
* every card, while a given repo's `ui` is the same place on Tuesday as it was on Monday. What is
|
|
22
|
+
* wrong with it centrally is the subject, not the logic. "App/ui" on this machine and "App/ui" on
|
|
23
|
+
* another are the same project but say nothing about which SITE was signed in to, and a real
|
|
24
|
+
* site — Sceneflo, Cnjure, a staging box — is the same site from every machine.
|
|
25
|
+
*
|
|
26
|
+
* So both halves are kept and the subject changes:
|
|
27
|
+
*
|
|
28
|
+
* · A real site is its HOST. `session:host:app.sceneflo.ai`. The port is dropped because cookies
|
|
29
|
+
* ignore ports and a port is a deployment accident; the full host is kept because a session is
|
|
30
|
+
* scoped to a host. Two agents on two machines that both need Sceneflo now land on one key,
|
|
31
|
+
* which is the entire request.
|
|
32
|
+
* · A local site is its PROJECT. `session:repo:App:ui` for anything on loopback or a private
|
|
33
|
+
* address. There is no identity in `localhost:4301`; the identity is "this project's UI".
|
|
34
|
+
*
|
|
35
|
+
* The separator in the project form is a colon and not the slash the on-disk key used, because a
|
|
36
|
+
* credential id is a Firestore document id and a slash in one silently creates a subcollection
|
|
37
|
+
* somewhere nobody is looking. The server refuses it outright (`VALID_ID` in
|
|
38
|
+
* flaredeck-mcp/src/vault/store.ts), which is how this was caught: every local session wrote fine
|
|
39
|
+
* and came back 400 from FlareDeck.
|
|
40
|
+
*
|
|
41
|
+
* The tenant is not in the key — it is in the Firestore path and in the request URL, so putting
|
|
42
|
+
* it here would only create a second place for it to be wrong.
|
|
43
|
+
*/
|
|
44
|
+
export function siteCredentialId(repo, part, url) {
|
|
45
|
+
const host = (() => {
|
|
46
|
+
if (!url)
|
|
47
|
+
return null;
|
|
48
|
+
try {
|
|
49
|
+
const h = new URL(url).hostname.replace(/^\[|\]$/g, '');
|
|
50
|
+
return PRIVATE_HOST.test(h) || PRIVATE_SUFFIX.test(h) ? null : h.toLowerCase();
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
})();
|
|
56
|
+
if (host)
|
|
57
|
+
return `session:host:${host}`;
|
|
58
|
+
const project = repo.replace(/\/+$/, '').split('/').pop() || repo;
|
|
59
|
+
return `session:repo:${project}:${part || 'ui'}`;
|
|
60
|
+
}
|
|
61
|
+
/** Well-known identity providers, which are never the site that was signed IN to. */
|
|
62
|
+
const IDP_HOST = /(^|\.)(accounts\.google\.com|login\.microsoftonline\.com|login\.live\.com|github\.com|auth0\.com|okta\.com|appleid\.apple\.com)$/i;
|
|
63
|
+
/**
|
|
64
|
+
* Which site a saved session actually belongs to, read off the session itself.
|
|
65
|
+
*
|
|
66
|
+
* This exists so that `rememberSignIn` needs no new argument: its two callers are in `main.ts`
|
|
67
|
+
* and `serve.ts`, and those belong to somebody else. The session already knows where it came
|
|
68
|
+
* from, so asking it is both less code and harder to get wrong than threading a URL through.
|
|
69
|
+
*
|
|
70
|
+
* `_ss` first because `captureSessionStorage` records the origin of the page as it was when the
|
|
71
|
+
* session was taken — i.e. the app, after the sign-in redirect chain finished. `origins` is the
|
|
72
|
+
* fallback and needs the identity providers filtered out, because a Google sign-in leaves
|
|
73
|
+
* `accounts.google.com` in there and that is emphatically not the site being remembered.
|
|
74
|
+
*
|
|
75
|
+
* Returning undefined is a normal outcome, not a failure: the caller then keys by project.
|
|
76
|
+
*/
|
|
77
|
+
export function sessionOrigin(session) {
|
|
78
|
+
const ss = session?._ss?.find((o) => typeof o?.origin === 'string' && o.origin);
|
|
79
|
+
if (ss?.origin)
|
|
80
|
+
return ss.origin;
|
|
81
|
+
const origins = (session?.origins ?? [])
|
|
82
|
+
.map((o) => o?.origin)
|
|
83
|
+
.filter((o) => typeof o === 'string' && !!o)
|
|
84
|
+
.filter((o) => { try {
|
|
85
|
+
return !IDP_HOST.test(new URL(o).hostname);
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
return false;
|
|
89
|
+
} });
|
|
90
|
+
// Only when it is unambiguous. Two non-IdP origins means a guess, and a guess here splits one
|
|
91
|
+
// site's session across two keys, which is the exact problem this is meant to end.
|
|
92
|
+
return origins.length === 1 ? origins[0] : undefined;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* The vault over HTTP.
|
|
96
|
+
*
|
|
97
|
+
* Returns a tagged outcome rather than a value-or-null because the failures are not the same
|
|
98
|
+
* thing and the caller treats them differently: `missing` and `revoked` mean request a sign-in,
|
|
99
|
+
* `withheld` means somebody deliberately said no, `unreachable` means stop.
|
|
100
|
+
*/
|
|
101
|
+
export class FlaredeckCredentials {
|
|
102
|
+
cfg;
|
|
103
|
+
constructor(cfg) {
|
|
104
|
+
this.cfg = cfg;
|
|
105
|
+
}
|
|
106
|
+
get base() {
|
|
107
|
+
return (this.cfg.baseUrl ?? 'https://mcp.flaredeck.app/api').replace(/\/+$/, '');
|
|
108
|
+
}
|
|
109
|
+
url(id) {
|
|
110
|
+
const board = this.cfg.board?.();
|
|
111
|
+
return `${this.base}/v1/${encodeURIComponent(this.cfg.tenantId)}/credentials/${encodeURIComponent(id)}${board ? `?board=${encodeURIComponent(board)}` : ''}`;
|
|
112
|
+
}
|
|
113
|
+
async read(id) {
|
|
114
|
+
let res;
|
|
115
|
+
try {
|
|
116
|
+
res = await fetch(this.url(id), {
|
|
117
|
+
headers: { Authorization: `Bearer ${await this.cfg.auth()}` },
|
|
118
|
+
signal: AbortSignal.timeout(READ_TIMEOUT_MS),
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
catch (e) {
|
|
122
|
+
// A token refresh that failed lands here too, and it is the same situation from the agent's
|
|
123
|
+
// point of view: FlareDeck cannot be asked right now.
|
|
124
|
+
return { kind: 'unreachable', message: e instanceof Error ? e.message : String(e) };
|
|
125
|
+
}
|
|
126
|
+
const message = async () => {
|
|
127
|
+
const body = await res.json().catch(() => ({}));
|
|
128
|
+
return String(body?.message || `HTTP ${res.status}`);
|
|
129
|
+
};
|
|
130
|
+
if (res.status === 404)
|
|
131
|
+
return { kind: 'missing' };
|
|
132
|
+
if (res.status === 410)
|
|
133
|
+
return { kind: 'revoked', message: await message() };
|
|
134
|
+
if (res.status === 403)
|
|
135
|
+
return { kind: 'withheld', message: await message() };
|
|
136
|
+
if (!res.ok)
|
|
137
|
+
return { kind: 'unreachable', message: await message() };
|
|
138
|
+
const body = (await res.json().catch(() => null));
|
|
139
|
+
const value = Buffer.from(String(body?.value ?? ''), 'base64');
|
|
140
|
+
// An empty value is not a credential. Treating it as one would hand an empty session to a
|
|
141
|
+
// browser, which looks exactly like being signed out but without the sign-in being requested.
|
|
142
|
+
if (!value.length)
|
|
143
|
+
return { kind: 'unreachable', message: 'FlareDeck returned an empty credential' };
|
|
144
|
+
return {
|
|
145
|
+
kind: 'value',
|
|
146
|
+
value,
|
|
147
|
+
meta: {
|
|
148
|
+
id,
|
|
149
|
+
kind: body.kind ?? 'token',
|
|
150
|
+
label: String(body.label ?? id),
|
|
151
|
+
version: Number(body.version ?? 1),
|
|
152
|
+
state: body.state === 'revoked' ? 'revoked' : 'active',
|
|
153
|
+
bytes: Number(body.bytes ?? value.length),
|
|
154
|
+
},
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
async write(id, value, opts) {
|
|
158
|
+
try {
|
|
159
|
+
const res = await fetch(this.url(id), {
|
|
160
|
+
method: 'PUT',
|
|
161
|
+
headers: { Authorization: `Bearer ${await this.cfg.auth()}`, 'Content-Type': 'application/json' },
|
|
162
|
+
body: JSON.stringify({
|
|
163
|
+
kind: opts.kind,
|
|
164
|
+
...(opts.label ? { label: opts.label } : {}),
|
|
165
|
+
value: value.toString('base64'),
|
|
166
|
+
}),
|
|
167
|
+
signal: AbortSignal.timeout(WRITE_TIMEOUT_MS),
|
|
168
|
+
});
|
|
169
|
+
if (res.ok)
|
|
170
|
+
return { ok: true };
|
|
171
|
+
const body = await res.json().catch(() => ({}));
|
|
172
|
+
return { ok: false, message: String(body?.message || `HTTP ${res.status}`) };
|
|
173
|
+
}
|
|
174
|
+
catch (e) {
|
|
175
|
+
return { ok: false, message: e instanceof Error ? e.message : String(e) };
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
async list() {
|
|
179
|
+
try {
|
|
180
|
+
const res = await fetch(`${this.base}/v1/${encodeURIComponent(this.cfg.tenantId)}/credentials`, {
|
|
181
|
+
headers: { Authorization: `Bearer ${await this.cfg.auth()}` },
|
|
182
|
+
signal: AbortSignal.timeout(READ_TIMEOUT_MS),
|
|
183
|
+
});
|
|
184
|
+
if (!res.ok)
|
|
185
|
+
return [];
|
|
186
|
+
const body = (await res.json());
|
|
187
|
+
return (body.credentials ?? []).map((c) => ({
|
|
188
|
+
id: String(c.id),
|
|
189
|
+
kind: c.kind ?? 'token',
|
|
190
|
+
label: String(c.label ?? c.id),
|
|
191
|
+
version: Number(c.version ?? 1),
|
|
192
|
+
state: c.state === 'revoked' ? 'revoked' : 'active',
|
|
193
|
+
bytes: Number(c.bytes ?? 0),
|
|
194
|
+
}));
|
|
195
|
+
}
|
|
196
|
+
catch {
|
|
197
|
+
return [];
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
// ---------------------------------------------------------------------------------------------
|
|
202
|
+
// The store
|
|
203
|
+
/**
|
|
204
|
+
* FlareDeck, and only FlareDeck.
|
|
205
|
+
*
|
|
206
|
+
* How many times this is called matters, because there is no cache to absorb it: once per
|
|
207
|
+
* `open_page`, which is a handful of times per card. That is deliberately cheaper than a cache
|
|
208
|
+
* would be to reason about — and it is what makes a rotation or a revoke take effect on the very
|
|
209
|
+
* next page an agent opens, anywhere, with nothing told to forget anything.
|
|
210
|
+
*/
|
|
211
|
+
export function vaultStore(remote, onNote = () => { }) {
|
|
212
|
+
return {
|
|
213
|
+
async get(id) {
|
|
214
|
+
const read = await remote.read(id);
|
|
215
|
+
switch (read.kind) {
|
|
216
|
+
case 'value':
|
|
217
|
+
return { ok: true, value: read.value };
|
|
218
|
+
case 'missing':
|
|
219
|
+
// Normal, and the trigger for requesting a sign-in. Not worth a note: it is what every
|
|
220
|
+
// first visit to a site looks like.
|
|
221
|
+
return { ok: true, value: null };
|
|
222
|
+
case 'revoked':
|
|
223
|
+
onNote(`${id} was revoked in FlareDeck (${read.message}) — a fresh sign-in is needed.`);
|
|
224
|
+
return { ok: true, value: null };
|
|
225
|
+
case 'withheld':
|
|
226
|
+
onNote(`${id} is in FlareDeck but not for this agent: ${read.message}`);
|
|
227
|
+
return { ok: true, value: null };
|
|
228
|
+
case 'unreachable':
|
|
229
|
+
return { ok: false, why: read.message };
|
|
230
|
+
}
|
|
231
|
+
},
|
|
232
|
+
put: (id, value, opts) => remote.write(id, value, opts),
|
|
233
|
+
list: () => remote.list(),
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* The store for an agent with no FlareDeck login.
|
|
238
|
+
*
|
|
239
|
+
* Says why, every time, instead of silently behaving like an empty vault. The difference matters:
|
|
240
|
+
* an empty vault means "sign in to the site", while this means "this agent is not set up", and
|
|
241
|
+
* sending a person to sign in to a site over the second one wastes their time.
|
|
242
|
+
*/
|
|
243
|
+
export function unavailable(why) {
|
|
244
|
+
return {
|
|
245
|
+
async get() { return { ok: false, why }; },
|
|
246
|
+
async put() { return { ok: false, message: why }; },
|
|
247
|
+
async list() { return []; },
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
// ---------------------------------------------------------------------------------------------
|
|
251
|
+
// Wiring
|
|
252
|
+
/**
|
|
253
|
+
* The store for this checkout's agent, built from what is already on disk.
|
|
254
|
+
*
|
|
255
|
+
* Takes a repo path and nothing else on purpose. The browser tools are constructed from a
|
|
256
|
+
* `BrowserDeps` that has no FlareDeck client in it, and the places that build that object are
|
|
257
|
+
* several and belong to other work; threading a client through all of them to reach one call site
|
|
258
|
+
* would be a far bigger change than this feature deserves. The agent's login is in its config
|
|
259
|
+
* file, which is where every other command gets it from.
|
|
260
|
+
*/
|
|
261
|
+
export function credentialsFor(repo, onNote = () => { }, board) {
|
|
262
|
+
const config = loadConfig(repo);
|
|
263
|
+
const tenantId = config ? tenantOfConfig(config) : null;
|
|
264
|
+
if (!config?.oauth || !tenantId) {
|
|
265
|
+
return unavailable('this agent has no FlareDeck login, and credentials are only ever read from FlareDeck — run: flaredeck-agent login');
|
|
266
|
+
}
|
|
267
|
+
const origin = new URL(config.mcpUrl).origin;
|
|
268
|
+
return vaultStore(new FlaredeckCredentials({
|
|
269
|
+
tenantId,
|
|
270
|
+
baseUrl: `${origin}/api`,
|
|
271
|
+
auth: () => freshAccessToken(origin, config.oauth, (next) => {
|
|
272
|
+
config.oauth = next;
|
|
273
|
+
saveConfig(config);
|
|
274
|
+
}),
|
|
275
|
+
...(board ? { board } : {}),
|
|
276
|
+
}), onNote);
|
|
277
|
+
}
|
|
278
|
+
/** The tenant the agent's own token is for. Same read as main.ts's `tenantOf`. */
|
|
279
|
+
function tenantOfConfig(config) {
|
|
280
|
+
if (!config.oauth)
|
|
281
|
+
return config.tenantId ?? null;
|
|
282
|
+
try {
|
|
283
|
+
const part = config.oauth.accessToken.split('.')[1] ?? '';
|
|
284
|
+
return JSON.parse(Buffer.from(part, 'base64url').toString('utf8')).tenantId ?? config.tenantId ?? null;
|
|
285
|
+
}
|
|
286
|
+
catch {
|
|
287
|
+
return config.tenantId ?? null;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
// ---------------------------------------------------------------------------------------------
|
|
291
|
+
// JSON, which is what everything stored here happens to be
|
|
292
|
+
export async function getJson(store, id) {
|
|
293
|
+
const read = await store.get(id);
|
|
294
|
+
if (!read.ok)
|
|
295
|
+
return read;
|
|
296
|
+
if (!read.value)
|
|
297
|
+
return { ok: true, value: null };
|
|
298
|
+
try {
|
|
299
|
+
return { ok: true, value: JSON.parse(read.value.toString('utf8')) };
|
|
300
|
+
}
|
|
301
|
+
catch {
|
|
302
|
+
// A corrupt or truncated credential reads as "no credential", never as a throw: the caller's
|
|
303
|
+
// only sensible response is to sign in again, and that is also its response to not having one.
|
|
304
|
+
return { ok: true, value: null };
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
export function putJson(store, id, value, opts) {
|
|
308
|
+
return store.put(id, Buffer.from(JSON.stringify(value), 'utf8'), opts);
|
|
309
|
+
}
|
|
310
|
+
/**
|
|
311
|
+
* Write a credential that cannot be regenerated cheaply, with retries.
|
|
312
|
+
*
|
|
313
|
+
* A captured sign-in has nowhere else to go — there is no local copy any more — so losing it to
|
|
314
|
+
* one network blip would cost a person another trip through a sign-in form, possibly with a code
|
|
315
|
+
* from their phone. Three attempts with a widening gap, then it gives up and says so loudly; the
|
|
316
|
+
* card then hits the sign-in wall again on its next look and the existing machinery asks for a
|
|
317
|
+
* fresh one, which is the same path an expired session takes.
|
|
318
|
+
*/
|
|
319
|
+
export async function putInsistently(store, id, value, opts, onNote = () => { }, attempts = 3) {
|
|
320
|
+
let last = { ok: false, message: 'not attempted' };
|
|
321
|
+
for (let n = 1; n <= attempts; n++) {
|
|
322
|
+
last = await store.put(id, value, opts);
|
|
323
|
+
if (last.ok)
|
|
324
|
+
return last;
|
|
325
|
+
if (n < attempts) {
|
|
326
|
+
onNote(`could not save ${id} to FlareDeck (${last.message}) — retrying (${n}/${attempts - 1}).`);
|
|
327
|
+
await new Promise((r) => setTimeout(r, 1000 * 2 ** (n - 1)));
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
return last;
|
|
331
|
+
}
|
|
332
|
+
//# sourceMappingURL=credentials.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"credentials.js","sourceRoot":"","sources":["../src/credentials.ts"],"names":[],"mappings":"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"}
|