foundr-companion 0.1.1 → 0.1.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/dist/bridge/bridge-auth.d.mts +20 -0
- package/dist/bridge/bridge-auth.d.mts.map +1 -1
- package/dist/bridge/bridge-auth.mjs +18 -0
- package/dist/bridge/bridge-auth.mjs.map +1 -1
- package/dist/bridge/daemon.mjs +130 -1
- package/dist/cli.js +60 -27
- package/dist/cli.js.map +1 -1
- package/dist/pairing-status.d.ts +59 -0
- package/dist/pairing-status.d.ts.map +1 -0
- package/dist/pairing-status.js +84 -0
- package/dist/pairing-status.js.map +1 -0
- package/package.json +1 -1
|
@@ -2,4 +2,24 @@ export function authFilePath(): string;
|
|
|
2
2
|
export function loadBridgeAuth(file?: string): Promise<any>;
|
|
3
3
|
export function saveBridgeAuth(auth: any, file?: string): Promise<void>;
|
|
4
4
|
export function deleteBridgeAuth(file?: string): Promise<void>;
|
|
5
|
+
/**
|
|
6
|
+
* Read the persisted pairing WITHOUT any network call. `paired` mirrors the
|
|
7
|
+
* daemon's own readiness check (a stored agent id — see startCompanionDaemon).
|
|
8
|
+
* Never throws: a missing or corrupt credentials file resolves to
|
|
9
|
+
* `{ paired: false }`.
|
|
10
|
+
*/
|
|
11
|
+
export function readLocalPairing(file?: string): Promise<{
|
|
12
|
+
paired: boolean;
|
|
13
|
+
hasToken: boolean;
|
|
14
|
+
agent?: undefined;
|
|
15
|
+
mcpUrl?: undefined;
|
|
16
|
+
} | {
|
|
17
|
+
paired: boolean;
|
|
18
|
+
agent: {
|
|
19
|
+
id: any;
|
|
20
|
+
name: any;
|
|
21
|
+
};
|
|
22
|
+
mcpUrl: any;
|
|
23
|
+
hasToken: boolean;
|
|
24
|
+
}>;
|
|
5
25
|
//# sourceMappingURL=bridge-auth.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bridge-auth.d.mts","sourceRoot":"","sources":["../../src/bridge/bridge-auth.mjs"],"names":[],"mappings":"AAIA,uCAEC;AAED,4DAGC;AAED,wEAMC;AAED,+DAEC"}
|
|
1
|
+
{"version":3,"file":"bridge-auth.d.mts","sourceRoot":"","sources":["../../src/bridge/bridge-auth.mjs"],"names":[],"mappings":"AAIA,uCAEC;AAED,4DAGC;AAED,wEAMC;AAED,+DAEC;AAED;;;;;GAKG;AACH;;;;;;;;;;;;;GAUC"}
|
|
@@ -22,3 +22,21 @@ export async function saveBridgeAuth(auth, file = authFilePath()) {
|
|
|
22
22
|
export async function deleteBridgeAuth(file = authFilePath()) {
|
|
23
23
|
await fs.rm(file, { force: true })
|
|
24
24
|
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Read the persisted pairing WITHOUT any network call. `paired` mirrors the
|
|
28
|
+
* daemon's own readiness check (a stored agent id — see startCompanionDaemon).
|
|
29
|
+
* Never throws: a missing or corrupt credentials file resolves to
|
|
30
|
+
* `{ paired: false }`.
|
|
31
|
+
*/
|
|
32
|
+
export async function readLocalPairing(file = authFilePath()) {
|
|
33
|
+
const auth = await loadBridgeAuth(file).catch(() => null)
|
|
34
|
+
const hasToken = Boolean(auth?.tokens?.access_token)
|
|
35
|
+
if (!auth?.agent?.id) return { paired: false, hasToken }
|
|
36
|
+
return {
|
|
37
|
+
paired: true,
|
|
38
|
+
agent: { id: auth.agent.id, name: auth.agent.name ?? auth.agent.id },
|
|
39
|
+
mcpUrl: auth.mcpUrl ?? undefined,
|
|
40
|
+
hasToken,
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bridge-auth.mjs","sourceRoot":"","sources":["../../src/bridge/bridge-auth.mjs"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,kBAAkB,CAAA;AACjC,OAAO,EAAE,MAAM,SAAS,CAAA;AACxB,OAAO,IAAI,MAAM,WAAW,CAAA;AAE5B,MAAM,UAAU,YAAY;IAC1B,OAAO,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,eAAe,EAAE,kBAAkB,CAAC,CAAA;AACrE,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,IAAI,GAAG,YAAY,EAAE;IACxD,MAAM,GAAG,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,CAAA;IAC3C,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;AACxB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,IAAI,EAAE,IAAI,GAAG,YAAY,EAAE;IAC9D,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAA;IAC9B,MAAM,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAA;IACrD,MAAM,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,KAAK,CAAC,CAAA;IAC1B,MAAM,EAAE,CAAC,SAAS,CAAC,IAAI,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAA;IAC/E,MAAM,EAAE,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,CAAA;AAC7B,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAC,IAAI,GAAG,YAAY,EAAE;IAC1D,MAAM,EAAE,CAAC,EAAE,CAAC,IAAI,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAA;AACpC,CAAC"}
|
|
1
|
+
{"version":3,"file":"bridge-auth.mjs","sourceRoot":"","sources":["../../src/bridge/bridge-auth.mjs"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,kBAAkB,CAAA;AACjC,OAAO,EAAE,MAAM,SAAS,CAAA;AACxB,OAAO,IAAI,MAAM,WAAW,CAAA;AAE5B,MAAM,UAAU,YAAY;IAC1B,OAAO,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,eAAe,EAAE,kBAAkB,CAAC,CAAA;AACrE,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,IAAI,GAAG,YAAY,EAAE;IACxD,MAAM,GAAG,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,CAAA;IAC3C,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;AACxB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,IAAI,EAAE,IAAI,GAAG,YAAY,EAAE;IAC9D,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAA;IAC9B,MAAM,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAA;IACrD,MAAM,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,KAAK,CAAC,CAAA;IAC1B,MAAM,EAAE,CAAC,SAAS,CAAC,IAAI,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAA;IAC/E,MAAM,EAAE,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,CAAA;AAC7B,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAC,IAAI,GAAG,YAAY,EAAE;IAC1D,MAAM,EAAE,CAAC,EAAE,CAAC,IAAI,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAA;AACpC,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAC,IAAI,GAAG,YAAY,EAAE;;IAC1D,MAAM,IAAI,GAAG,MAAM,cAAc,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAA;IACzD,MAAM,QAAQ,GAAG,OAAO,CAAC,MAAA,IAAI,aAAJ,IAAI,uBAAJ,IAAI,CAAE,MAAM,0CAAE,YAAY,CAAC,CAAA;IACpD,IAAI,CAAC,CAAA,MAAA,IAAI,aAAJ,IAAI,uBAAJ,IAAI,CAAE,KAAK,0CAAE,EAAE,CAAA;QAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAA;IACxD,OAAO;QACL,MAAM,EAAE,IAAI;QACZ,KAAK,EAAE,EAAE,EAAE,EAAE,IAAI,CAAC,KAAK,CAAC,EAAE,EAAE,IAAI,EAAE,MAAA,IAAI,CAAC,KAAK,CAAC,IAAI,mCAAI,IAAI,CAAC,KAAK,CAAC,EAAE,EAAE;QACpE,MAAM,EAAE,MAAA,IAAI,CAAC,MAAM,mCAAI,SAAS;QAChC,QAAQ;KACT,CAAA;AACH,CAAC"}
|
package/dist/bridge/daemon.mjs
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import fs from 'node:fs/promises'
|
|
6
6
|
import os from 'node:os'
|
|
7
7
|
import path from 'node:path'
|
|
8
|
-
import { loadBridgeAuth, saveBridgeAuth, deleteBridgeAuth } from './bridge-auth.mjs'
|
|
8
|
+
import { loadBridgeAuth, saveBridgeAuth, deleteBridgeAuth, readLocalPairing } from './bridge-auth.mjs'
|
|
9
9
|
import { createMcpClient, createOAuthCallbackWaiter, withVercelProtectionBypass } from './mcp-client.mjs'
|
|
10
10
|
import { FileBackedOAuthProvider } from './oauth-provider.mjs'
|
|
11
11
|
import { logoutFoundrBridge } from './logout.mjs'
|
|
@@ -95,6 +95,135 @@ export async function logoutCompanion() {
|
|
|
95
95
|
await logoutFoundrBridge({ loadAuth: loadBridgeAuth, deleteAuth: deleteBridgeAuth })
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
+
// Re-export the no-network local pairing read so the CLI gets it (and its type
|
|
99
|
+
// via daemon.d.mts) from the same bridge surface as the rest of the daemon.
|
|
100
|
+
export { readLocalPairing }
|
|
101
|
+
|
|
102
|
+
/** Classify why a live verification failed, for an actionable status message. */
|
|
103
|
+
export function classifyLiveError(err) {
|
|
104
|
+
const code = err?.code
|
|
105
|
+
const status = err?.status ?? err?.statusCode
|
|
106
|
+
const name = err?.name
|
|
107
|
+
const m = String(err?.message ?? err ?? '').toLowerCase()
|
|
108
|
+
if (
|
|
109
|
+
name === 'UnauthorizedError' ||
|
|
110
|
+
status === 401 || status === 403 ||
|
|
111
|
+
code === 'AUTH_FAILED' ||
|
|
112
|
+
/unauthor|reauthor|invalid_token|invalid_grant|forbidden|access denied/.test(m)
|
|
113
|
+
) {
|
|
114
|
+
return 'auth'
|
|
115
|
+
}
|
|
116
|
+
if (
|
|
117
|
+
name === 'AbortError' ||
|
|
118
|
+
code === 'ENOTFOUND' || code === 'ECONNREFUSED' || code === 'ETIMEDOUT' || code === 'EAI_AGAIN' ||
|
|
119
|
+
/timed out|timeout|fetch failed|network|getaddrinfo|enotfound|econnrefused|socket hang/.test(m)
|
|
120
|
+
) {
|
|
121
|
+
return 'network'
|
|
122
|
+
}
|
|
123
|
+
return 'other'
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Confirm the stored pairing is live by calling the heartbeat tool with the
|
|
128
|
+
* persisted token (the MCP SDK refreshes it transparently). NON-interactive: a
|
|
129
|
+
* dead token surfaces as `live:false` instead of opening a browser, and a hung
|
|
130
|
+
* server is bounded by `timeoutMs`. Always resolves (never rejects) so callers
|
|
131
|
+
* can report a definite state.
|
|
132
|
+
*/
|
|
133
|
+
export async function verifyCompanionPairing({ timeoutMs = 15000 } = {}) {
|
|
134
|
+
const local = await readLocalPairing()
|
|
135
|
+
if (!local.paired) return { ...local, live: false }
|
|
136
|
+
|
|
137
|
+
const auth = await loadBridgeAuth().catch(() => null)
|
|
138
|
+
if (!auth?.agent?.id) {
|
|
139
|
+
return { ...local, live: false, liveError: 'other', reason: 'credentials unreadable' }
|
|
140
|
+
}
|
|
141
|
+
const mcpUrl = auth.mcpUrl || DEFAULT_MCP_URL
|
|
142
|
+
const agentName = auth.agentName || DEFAULT_AGENT_NAME
|
|
143
|
+
|
|
144
|
+
const provider = new FileBackedOAuthProvider({
|
|
145
|
+
mcpUrl,
|
|
146
|
+
agentName,
|
|
147
|
+
auth,
|
|
148
|
+
onChange: async (nextAuth) => {
|
|
149
|
+
await saveBridgeAuth({
|
|
150
|
+
...nextAuth,
|
|
151
|
+
mcpUrl,
|
|
152
|
+
agentName,
|
|
153
|
+
agent: auth.agent,
|
|
154
|
+
updatedAt: new Date().toISOString(),
|
|
155
|
+
})
|
|
156
|
+
},
|
|
157
|
+
onRedirect: () => {}, // a verification must never prompt the user
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
// A callback whose `code` rejects immediately: if the SDK cannot satisfy auth
|
|
161
|
+
// via a refresh it raises UnauthorizedError and createMcpClient awaits this
|
|
162
|
+
// `code` — rejecting turns that into a fast error instead of a hang on a
|
|
163
|
+
// browser round-trip that will never happen during a status check.
|
|
164
|
+
const nonInteractiveCallback = () => {
|
|
165
|
+
const code = Promise.reject(new Error('reauthorization required'))
|
|
166
|
+
code.catch(() => {})
|
|
167
|
+
return { ready: Promise.resolve(0), code, get port() { return 0 }, close() {} }
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
let timedOut = false
|
|
171
|
+
let timer
|
|
172
|
+
let mcp = null
|
|
173
|
+
|
|
174
|
+
// Build the client + heartbeat as a tracked promise (not inlined into the
|
|
175
|
+
// race). Promise.race never settles the LOSING branch, so if the timeout wins
|
|
176
|
+
// and `work` later rejects (a slow server answering after the deadline) it
|
|
177
|
+
// would become an unhandled rejection that crashes the CLI — and a client that
|
|
178
|
+
// connects after the deadline would leak. The always-attached settlement
|
|
179
|
+
// handler below neutralizes both: it swallows a late rejection and closes a
|
|
180
|
+
// late-built client. (A token endpoint that hangs forever can't be actively
|
|
181
|
+
// aborted — the SDK's refresh fetch takes no signal — but the race still
|
|
182
|
+
// returns within timeoutMs and the short-lived CLI process reaps the socket.)
|
|
183
|
+
const work = (async () => {
|
|
184
|
+
mcp = await createMcpClient({
|
|
185
|
+
mcpUrl,
|
|
186
|
+
authProvider: provider,
|
|
187
|
+
oauthCallbackFactory: nonInteractiveCallback,
|
|
188
|
+
})
|
|
189
|
+
return mcp.callTool('heartbeat_private_chat_listener', {
|
|
190
|
+
client_name: CLIENT_NAME,
|
|
191
|
+
client_version: CLIENT_VERSION,
|
|
192
|
+
status: 'stopped',
|
|
193
|
+
})
|
|
194
|
+
})()
|
|
195
|
+
work.then(
|
|
196
|
+
() => { if (timedOut) mcp?.close?.().catch(() => {}) },
|
|
197
|
+
() => { if (timedOut) mcp?.close?.().catch(() => {}) },
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
const timeout = new Promise((_resolve, reject) => {
|
|
201
|
+
timer = setTimeout(() => {
|
|
202
|
+
timedOut = true
|
|
203
|
+
reject(Object.assign(new Error('verification timed out'), { name: 'AbortError' }))
|
|
204
|
+
}, timeoutMs)
|
|
205
|
+
})
|
|
206
|
+
|
|
207
|
+
try {
|
|
208
|
+
const heartbeat = await Promise.race([work, timeout])
|
|
209
|
+
const agent = heartbeat?.agent?.id
|
|
210
|
+
? { id: heartbeat.agent.id, name: heartbeat.agent.name ?? heartbeat.agent.id }
|
|
211
|
+
: local.agent
|
|
212
|
+
return { paired: true, live: true, agent, mcpUrl }
|
|
213
|
+
} catch (err) {
|
|
214
|
+
return {
|
|
215
|
+
...local,
|
|
216
|
+
live: false,
|
|
217
|
+
liveError: classifyLiveError(err),
|
|
218
|
+
reason: err instanceof Error ? err.message : String(err),
|
|
219
|
+
}
|
|
220
|
+
} finally {
|
|
221
|
+
clearTimeout(timer)
|
|
222
|
+
// The timeout path closes the (possibly late) client via work.then above.
|
|
223
|
+
if (!timedOut) await mcp?.close?.().catch(() => {})
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
98
227
|
function isProcessAlive(pid) {
|
|
99
228
|
if (!Number.isInteger(pid) || pid <= 0) return false
|
|
100
229
|
try {
|
package/dist/cli.js
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import fs from 'node:fs/promises';
|
|
3
|
+
import path from 'node:path';
|
|
3
4
|
import { spawn } from 'node:child_process';
|
|
4
5
|
import { createRequire } from 'node:module';
|
|
5
6
|
import { fileURLToPath } from 'node:url';
|
|
6
|
-
import { parseCli, readOption } from './cli-parse.js';
|
|
7
|
+
import { parseCli, readOption, hasFlag } from './cli-parse.js';
|
|
7
8
|
import { companionPaths, LAUNCH_AGENT_LABEL } from './paths.js';
|
|
8
9
|
import { installLaunchAgent, isLaunchAgentLoaded, renderLaunchAgentPlist, restartLaunchAgent, uninstallLaunchAgent, } from './launchd.js';
|
|
9
10
|
import { codexPreflight } from './codex-preflight.js';
|
|
10
|
-
import { DEFAULT_MCP_URL, loginCompanion, logoutCompanion, resolveMcpUrl, startCompanionDaemon, } from './bridge/daemon.mjs';
|
|
11
|
+
import { DEFAULT_MCP_URL, loginCompanion, logoutCompanion, readLocalPairing, resolveMcpUrl, startCompanionDaemon, verifyCompanionPairing, } from './bridge/daemon.mjs';
|
|
12
|
+
import { decideLogin, installPairingLine, pairingStatusLine } from './pairing-status.js';
|
|
11
13
|
const require = createRequire(import.meta.url);
|
|
12
14
|
const VERSION = require('../package.json').version;
|
|
13
15
|
// dist/cli.js at runtime — the absolute path launchd should exec.
|
|
@@ -21,7 +23,8 @@ function usage() {
|
|
|
21
23
|
'Usage: foundr <command>',
|
|
22
24
|
'',
|
|
23
25
|
'Setup:',
|
|
24
|
-
' login Pair this machine with Founder World (opens a browser)',
|
|
26
|
+
' login Pair this machine with Founder World (opens a browser).',
|
|
27
|
+
' Detects an existing pairing; use --force to re-pair.',
|
|
25
28
|
' codex Check your Codex CLI login (Sign in with ChatGPT)',
|
|
26
29
|
' install Install + start the background service (launchd)',
|
|
27
30
|
'',
|
|
@@ -41,6 +44,8 @@ function usage() {
|
|
|
41
44
|
' --url <origin> Founder World origin (default https://www.foundr.world).',
|
|
42
45
|
' Also honored via FOUNDR_URL. Use a *.lab.foundr.world',
|
|
43
46
|
' origin to target a lab deploy.',
|
|
47
|
+
' --force With `login`: re-pair even if already paired.',
|
|
48
|
+
' --local With `status`: skip the online check (on-disk state only).',
|
|
44
49
|
].join('\n');
|
|
45
50
|
}
|
|
46
51
|
function run(cmd, args) {
|
|
@@ -52,7 +57,26 @@ function run(cmd, args) {
|
|
|
52
57
|
}
|
|
53
58
|
async function cmdLogin(args) {
|
|
54
59
|
var _a, _b;
|
|
55
|
-
const
|
|
60
|
+
const force = hasFlag(args, '--force') || hasFlag(args, '--relogin');
|
|
61
|
+
// Detect an existing pairing before re-running OAuth.
|
|
62
|
+
const local = await readLocalPairing();
|
|
63
|
+
// A bare `login` re-verifies the EXISTING pairing's origin (fall back to it),
|
|
64
|
+
// so a lab-paired machine isn't silently re-pointed at the prod default. Only
|
|
65
|
+
// an explicit --url / FOUNDR_URL counts as a deliberate origin change.
|
|
66
|
+
const explicitUrl = (_a = readOption(args, '--url')) !== null && _a !== void 0 ? _a : process.env.FOUNDR_URL;
|
|
67
|
+
const mcpUrl = resolveMcpUrl((_b = explicitUrl !== null && explicitUrl !== void 0 ? explicitUrl : local.mcpUrl) !== null && _b !== void 0 ? _b : DEFAULT_MCP_URL);
|
|
68
|
+
// The live check only short-circuits when targeting the SAME origin we're
|
|
69
|
+
// paired to. If --url points elsewhere (or there's no pairing, or --force),
|
|
70
|
+
// skip the round-trip and let decideLogin route to pairing — otherwise a live
|
|
71
|
+
// heartbeat against the OLD origin would falsely block a deliberate re-point.
|
|
72
|
+
const originChanged = Boolean(explicitUrl && local.paired && local.mcpUrl && resolveMcpUrl(local.mcpUrl) !== mcpUrl);
|
|
73
|
+
const current = force || originChanged || !local.paired
|
|
74
|
+
? Object.assign(Object.assign({}, local), { live: false }) : await verifyCompanionPairing();
|
|
75
|
+
const decision = decideLogin(current, { force, originChanged });
|
|
76
|
+
for (const line of decision.lines)
|
|
77
|
+
console.log(line);
|
|
78
|
+
if (decision.action === 'already-paired')
|
|
79
|
+
return 0;
|
|
56
80
|
const { agent } = await loginCompanion({ mcpUrl });
|
|
57
81
|
console.log(`Paired as ${agent.name} (${agent.id}).`);
|
|
58
82
|
console.log('Next: run `foundr install` to start the background service.');
|
|
@@ -66,14 +90,26 @@ async function cmdCodex() {
|
|
|
66
90
|
console.log(' • ' + g);
|
|
67
91
|
return pf.ok ? 0 : 1;
|
|
68
92
|
}
|
|
69
|
-
async function writePlistAndLoad() {
|
|
93
|
+
async function writePlistAndLoad(urlOverride) {
|
|
70
94
|
const paths = companionPaths();
|
|
71
95
|
await fs.mkdir(paths.logsDir, { recursive: true });
|
|
72
96
|
await fs.mkdir(paths.launchAgentsDir, { recursive: true });
|
|
73
97
|
const env = {};
|
|
74
|
-
|
|
98
|
+
// The listener prefers the origin stored at pairing time (auth.mcpUrl); this
|
|
99
|
+
// plist env is the fallback. Honor an explicit --url so `install --url …`
|
|
100
|
+
// isn't silently ignored, as the global help implies it's supported.
|
|
101
|
+
const url = urlOverride !== null && urlOverride !== void 0 ? urlOverride : process.env.FOUNDR_URL;
|
|
75
102
|
if (url)
|
|
76
103
|
env.FOUNDR_URL = url;
|
|
104
|
+
// launchd starts jobs with a minimal PATH (/usr/bin:/bin:/usr/sbin:/sbin) that
|
|
105
|
+
// omits where `codex` (and node) actually live — Homebrew, nvm, npm-global.
|
|
106
|
+
// `foundr install` runs in the user's shell, whose PATH DID resolve `codex`, so
|
|
107
|
+
// capture it. Without this the daemon's Codex preflight can't find the binary,
|
|
108
|
+
// `foundr run` exits "codex not ready", and the listener never starts — the
|
|
109
|
+
// agent shows offline in-game even though `foundr status` (shell) says ready.
|
|
110
|
+
env.PATH = [path.dirname(process.execPath), process.env.PATH].filter(Boolean).join(':');
|
|
111
|
+
if (process.env.CODEX_HOME)
|
|
112
|
+
env.CODEX_HOME = process.env.CODEX_HOME;
|
|
77
113
|
const plist = renderLaunchAgentPlist({
|
|
78
114
|
label: LAUNCH_AGENT_LABEL,
|
|
79
115
|
nodePath: process.execPath,
|
|
@@ -89,18 +125,20 @@ async function writePlistAndLoad() {
|
|
|
89
125
|
await installLaunchAgent({ plistPath: paths.launchAgentPlist, label: LAUNCH_AGENT_LABEL });
|
|
90
126
|
}
|
|
91
127
|
}
|
|
92
|
-
async function cmdInstall() {
|
|
93
|
-
await writePlistAndLoad();
|
|
128
|
+
async function cmdInstall(args) {
|
|
129
|
+
await writePlistAndLoad(readOption(args, '--url'));
|
|
94
130
|
console.log('Installed and started the Foundr companion (launchd).');
|
|
95
131
|
const pf = await codexPreflight();
|
|
96
132
|
if (!pf.ok)
|
|
97
133
|
for (const g of pf.guidance)
|
|
98
134
|
console.log(' • ' + g);
|
|
99
|
-
|
|
135
|
+
// Report the actual pairing state instead of a blanket "if not paired" hint.
|
|
136
|
+
// Local read only — keep install fast and offline-safe.
|
|
137
|
+
console.log(installPairingLine(await readLocalPairing()));
|
|
100
138
|
return 0;
|
|
101
139
|
}
|
|
102
|
-
async function cmdStart() {
|
|
103
|
-
await writePlistAndLoad();
|
|
140
|
+
async function cmdStart(args) {
|
|
141
|
+
await writePlistAndLoad(readOption(args, '--url'));
|
|
104
142
|
console.log('Started.');
|
|
105
143
|
return 0;
|
|
106
144
|
}
|
|
@@ -127,23 +165,18 @@ async function cmdUninstall() {
|
|
|
127
165
|
console.log('Credentials remain in ~/.foundr-world — run `foundr logout` to clear them.');
|
|
128
166
|
return 0;
|
|
129
167
|
}
|
|
130
|
-
async function cmdStatus() {
|
|
131
|
-
var _a
|
|
132
|
-
const paths = companionPaths();
|
|
168
|
+
async function cmdStatus(args) {
|
|
169
|
+
var _a;
|
|
133
170
|
const loaded = await isLaunchAgentLoaded({ label: LAUNCH_AGENT_LABEL });
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
}
|
|
139
|
-
catch (_c) {
|
|
140
|
-
paired = null;
|
|
141
|
-
}
|
|
171
|
+
// Default to a live check (heartbeat) so `paired:` reflects reality; --local
|
|
172
|
+
// / --no-verify skips the network round-trip and reports the on-disk state.
|
|
173
|
+
const localOnly = hasFlag(args, '--local') || hasFlag(args, '--no-verify');
|
|
174
|
+
const pairing = localOnly ? await readLocalPairing() : await verifyCompanionPairing();
|
|
142
175
|
const pf = await codexPreflight();
|
|
143
176
|
console.log('Foundr companion');
|
|
144
|
-
console.log(` paired: ${
|
|
177
|
+
console.log(` paired: ${pairingStatusLine(pairing)}`);
|
|
145
178
|
console.log(` service: ${loaded ? 'running (launchd)' : 'not installed — run: foundr install'}`);
|
|
146
|
-
console.log(` codex: ${pf.ok ? `ready (${(
|
|
179
|
+
console.log(` codex: ${pf.ok ? `ready (${(_a = pf.authMode) !== null && _a !== void 0 ? _a : 'unknown'})` : 'not ready'}`);
|
|
147
180
|
for (const g of pf.guidance)
|
|
148
181
|
console.log(' • ' + g);
|
|
149
182
|
return 0;
|
|
@@ -202,17 +235,17 @@ async function main() {
|
|
|
202
235
|
case 'codex':
|
|
203
236
|
return cmdCodex();
|
|
204
237
|
case 'install':
|
|
205
|
-
return cmdInstall();
|
|
238
|
+
return cmdInstall(parsed.args);
|
|
206
239
|
case 'uninstall':
|
|
207
240
|
return cmdUninstall();
|
|
208
241
|
case 'start':
|
|
209
|
-
return cmdStart();
|
|
242
|
+
return cmdStart(parsed.args);
|
|
210
243
|
case 'stop':
|
|
211
244
|
return cmdStop();
|
|
212
245
|
case 'restart':
|
|
213
246
|
return cmdRestart();
|
|
214
247
|
case 'status':
|
|
215
|
-
return cmdStatus();
|
|
248
|
+
return cmdStatus(parsed.args);
|
|
216
249
|
case 'update':
|
|
217
250
|
return cmdUpdate();
|
|
218
251
|
case 'run':
|
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,MAAM,kBAAkB,CAAA;AACjC,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAA;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAC3C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AACxC,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAA;
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure rendering + decision logic for the companion's pairing state.
|
|
3
|
+
*
|
|
4
|
+
* Kept free of any I/O so the `foundr login` / `install` / `status` UX is fully
|
|
5
|
+
* testable. The bridge daemon supplies the data (`readLocalPairing` for the
|
|
6
|
+
* no-network check, `verifyCompanionPairing` for the live heartbeat check); this
|
|
7
|
+
* module turns that data into the exact lines the CLI prints and the decision of
|
|
8
|
+
* whether `login` should short-circuit.
|
|
9
|
+
*/
|
|
10
|
+
export interface PairedAgent {
|
|
11
|
+
id: string;
|
|
12
|
+
name: string;
|
|
13
|
+
}
|
|
14
|
+
export interface CompanionPairing {
|
|
15
|
+
/** A pairing exists on disk (a stored agent id) — i.e. `foundr login` has run. */
|
|
16
|
+
paired: boolean;
|
|
17
|
+
agent?: PairedAgent;
|
|
18
|
+
mcpUrl?: string;
|
|
19
|
+
hasToken?: boolean;
|
|
20
|
+
}
|
|
21
|
+
export type LiveErrorKind = 'auth' | 'network' | 'other';
|
|
22
|
+
export interface CompanionPairingVerification extends CompanionPairing {
|
|
23
|
+
/** The stored token actually works right now (heartbeat succeeded). */
|
|
24
|
+
live: boolean;
|
|
25
|
+
/** Why `live` is false while `paired` is true. */
|
|
26
|
+
liveError?: LiveErrorKind;
|
|
27
|
+
reason?: string;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* One-line value for the `paired:` row of `foundr status`. When `live` is
|
|
31
|
+
* `undefined` the caller did a local-only check (`--local`), so we don't claim
|
|
32
|
+
* anything about whether the token still works.
|
|
33
|
+
*/
|
|
34
|
+
export declare function pairingStatusLine(v: CompanionPairing | CompanionPairingVerification): string;
|
|
35
|
+
/** Trailing line for `foundr install`, conditioned on the local pairing. */
|
|
36
|
+
export declare function installPairingLine(p: CompanionPairing): string;
|
|
37
|
+
export type LoginDecision = {
|
|
38
|
+
action: 'already-paired';
|
|
39
|
+
lines: string[];
|
|
40
|
+
} | {
|
|
41
|
+
action: 'pair';
|
|
42
|
+
lines: string[];
|
|
43
|
+
};
|
|
44
|
+
/**
|
|
45
|
+
* Decide whether `foundr login` should short-circuit because the machine is
|
|
46
|
+
* already paired, or proceed with the interactive pairing flow.
|
|
47
|
+
*
|
|
48
|
+
* Re-pairing (which opens a browser) happens ONLY when the user clearly wants
|
|
49
|
+
* it — `--force`, a brand-new machine, or a `--url` pointing at a different
|
|
50
|
+
* origin than the stored pairing. When a pairing exists but the live check
|
|
51
|
+
* didn't confirm it (expired token OR a transient network/other failure) we do
|
|
52
|
+
* NOT silently re-pair: a blip must never surprise the user with a browser or
|
|
53
|
+
* throw away working credentials. We report the state and point at `--force`.
|
|
54
|
+
*/
|
|
55
|
+
export declare function decideLogin(v: CompanionPairingVerification, opts?: {
|
|
56
|
+
force?: boolean;
|
|
57
|
+
originChanged?: boolean;
|
|
58
|
+
}): LoginDecision;
|
|
59
|
+
//# sourceMappingURL=pairing-status.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pairing-status.d.ts","sourceRoot":"","sources":["../src/pairing-status.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,MAAM,CAAA;CACb;AAED,MAAM,WAAW,gBAAgB;IAC/B,kFAAkF;IAClF,MAAM,EAAE,OAAO,CAAA;IACf,KAAK,CAAC,EAAE,WAAW,CAAA;IACnB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,QAAQ,CAAC,EAAE,OAAO,CAAA;CACnB;AAED,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,SAAS,GAAG,OAAO,CAAA;AAExD,MAAM,WAAW,4BAA6B,SAAQ,gBAAgB;IACpE,uEAAuE;IACvE,IAAI,EAAE,OAAO,CAAA;IACb,kDAAkD;IAClD,SAAS,CAAC,EAAE,aAAa,CAAA;IACzB,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;AAMD;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,gBAAgB,GAAG,4BAA4B,GAAG,MAAM,CAU5F;AAED,4EAA4E;AAC5E,wBAAgB,kBAAkB,CAAC,CAAC,EAAE,gBAAgB,GAAG,MAAM,CAI9D;AAED,MAAM,MAAM,aAAa,GACrB;IAAE,MAAM,EAAE,gBAAgB,CAAC;IAAC,KAAK,EAAE,MAAM,EAAE,CAAA;CAAE,GAC7C;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,EAAE,CAAA;CAAE,CAAA;AAEvC;;;;;;;;;;GAUG;AACH,wBAAgB,WAAW,CACzB,CAAC,EAAE,4BAA4B,EAC/B,IAAI,GAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAC;IAAC,aAAa,CAAC,EAAE,OAAO,CAAA;CAAO,GACtD,aAAa,CA+Bf"}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure rendering + decision logic for the companion's pairing state.
|
|
3
|
+
*
|
|
4
|
+
* Kept free of any I/O so the `foundr login` / `install` / `status` UX is fully
|
|
5
|
+
* testable. The bridge daemon supplies the data (`readLocalPairing` for the
|
|
6
|
+
* no-network check, `verifyCompanionPairing` for the live heartbeat check); this
|
|
7
|
+
* module turns that data into the exact lines the CLI prints and the decision of
|
|
8
|
+
* whether `login` should short-circuit.
|
|
9
|
+
*/
|
|
10
|
+
function who(agent) {
|
|
11
|
+
return agent ? `${agent.name} (${agent.id})` : 'this machine';
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* One-line value for the `paired:` row of `foundr status`. When `live` is
|
|
15
|
+
* `undefined` the caller did a local-only check (`--local`), so we don't claim
|
|
16
|
+
* anything about whether the token still works.
|
|
17
|
+
*/
|
|
18
|
+
export function pairingStatusLine(v) {
|
|
19
|
+
if (!v.paired)
|
|
20
|
+
return 'no — run: foundr login';
|
|
21
|
+
const label = who(v.agent);
|
|
22
|
+
const live = v.live;
|
|
23
|
+
if (live === undefined)
|
|
24
|
+
return label;
|
|
25
|
+
if (live)
|
|
26
|
+
return `${label} — verified`;
|
|
27
|
+
const kind = v.liveError;
|
|
28
|
+
if (kind === 'auth')
|
|
29
|
+
return `${label} — sign-in expired, re-pair: foundr login`;
|
|
30
|
+
if (kind === 'network')
|
|
31
|
+
return `${label} — credentials present (couldn't verify — offline?)`;
|
|
32
|
+
return `${label} — credentials present (couldn't verify)`;
|
|
33
|
+
}
|
|
34
|
+
/** Trailing line for `foundr install`, conditioned on the local pairing. */
|
|
35
|
+
export function installPairingLine(p) {
|
|
36
|
+
return p.paired
|
|
37
|
+
? `Paired as ${who(p.agent)}.`
|
|
38
|
+
: 'Not paired yet — run `foundr login` to pair.';
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Decide whether `foundr login` should short-circuit because the machine is
|
|
42
|
+
* already paired, or proceed with the interactive pairing flow.
|
|
43
|
+
*
|
|
44
|
+
* Re-pairing (which opens a browser) happens ONLY when the user clearly wants
|
|
45
|
+
* it — `--force`, a brand-new machine, or a `--url` pointing at a different
|
|
46
|
+
* origin than the stored pairing. When a pairing exists but the live check
|
|
47
|
+
* didn't confirm it (expired token OR a transient network/other failure) we do
|
|
48
|
+
* NOT silently re-pair: a blip must never surprise the user with a browser or
|
|
49
|
+
* throw away working credentials. We report the state and point at `--force`.
|
|
50
|
+
*/
|
|
51
|
+
export function decideLogin(v, opts = {}) {
|
|
52
|
+
const label = who(v.agent);
|
|
53
|
+
if (opts.force) {
|
|
54
|
+
return { action: 'pair', lines: v.paired ? [`Re-pairing ${label}…`] : [] };
|
|
55
|
+
}
|
|
56
|
+
if (opts.originChanged && v.paired) {
|
|
57
|
+
return {
|
|
58
|
+
action: 'pair',
|
|
59
|
+
lines: [`Pairing to a different Founder World origin (this machine was paired as ${label}).`],
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
if (!v.paired)
|
|
63
|
+
return { action: 'pair', lines: [] };
|
|
64
|
+
if (v.live) {
|
|
65
|
+
return {
|
|
66
|
+
action: 'already-paired',
|
|
67
|
+
lines: [`✓ Already paired as ${label}.`, 'Re-pair with: foundr login --force'],
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
if (v.liveError === 'auth') {
|
|
71
|
+
return {
|
|
72
|
+
action: 'already-paired',
|
|
73
|
+
lines: [`Already paired as ${label}, but the saved sign-in has expired.`, 'Re-pair with: foundr login --force'],
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
return {
|
|
77
|
+
action: 'already-paired',
|
|
78
|
+
lines: [
|
|
79
|
+
`Already paired as ${label} (couldn't reach Founder World to verify).`,
|
|
80
|
+
'Re-pair with: foundr login --force',
|
|
81
|
+
],
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
//# sourceMappingURL=pairing-status.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pairing-status.js","sourceRoot":"","sources":["../src/pairing-status.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAyBH,SAAS,GAAG,CAAC,KAAmB;IAC9B,OAAO,KAAK,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,IAAI,KAAK,KAAK,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,cAAc,CAAA;AAC/D,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,iBAAiB,CAAC,CAAkD;IAClF,IAAI,CAAC,CAAC,CAAC,MAAM;QAAE,OAAO,wBAAwB,CAAA;IAC9C,MAAM,KAAK,GAAG,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,CAAA;IAC1B,MAAM,IAAI,GAAI,CAAkC,CAAC,IAAI,CAAA;IACrD,IAAI,IAAI,KAAK,SAAS;QAAE,OAAO,KAAK,CAAA;IACpC,IAAI,IAAI;QAAE,OAAO,GAAG,KAAK,aAAa,CAAA;IACtC,MAAM,IAAI,GAAI,CAAkC,CAAC,SAAS,CAAA;IAC1D,IAAI,IAAI,KAAK,MAAM;QAAE,OAAO,GAAG,KAAK,2CAA2C,CAAA;IAC/E,IAAI,IAAI,KAAK,SAAS;QAAE,OAAO,GAAG,KAAK,qDAAqD,CAAA;IAC5F,OAAO,GAAG,KAAK,0CAA0C,CAAA;AAC3D,CAAC;AAED,4EAA4E;AAC5E,MAAM,UAAU,kBAAkB,CAAC,CAAmB;IACpD,OAAO,CAAC,CAAC,MAAM;QACb,CAAC,CAAC,aAAa,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG;QAC9B,CAAC,CAAC,8CAA8C,CAAA;AACpD,CAAC;AAMD;;;;;;;;;;GAUG;AACH,MAAM,UAAU,WAAW,CACzB,CAA+B,EAC/B,OAAqD,EAAE;IAEvD,MAAM,KAAK,GAAG,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,CAAA;IAC1B,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;QACf,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,cAAc,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,CAAA;IAC5E,CAAC;IACD,IAAI,IAAI,CAAC,aAAa,IAAI,CAAC,CAAC,MAAM,EAAE,CAAC;QACnC,OAAO;YACL,MAAM,EAAE,MAAM;YACd,KAAK,EAAE,CAAC,2EAA2E,KAAK,IAAI,CAAC;SAC9F,CAAA;IACH,CAAC;IACD,IAAI,CAAC,CAAC,CAAC,MAAM;QAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE,EAAE,CAAA;IACnD,IAAI,CAAC,CAAC,IAAI,EAAE,CAAC;QACX,OAAO;YACL,MAAM,EAAE,gBAAgB;YACxB,KAAK,EAAE,CAAC,uBAAuB,KAAK,GAAG,EAAE,oCAAoC,CAAC;SAC/E,CAAA;IACH,CAAC;IACD,IAAI,CAAC,CAAC,SAAS,KAAK,MAAM,EAAE,CAAC;QAC3B,OAAO;YACL,MAAM,EAAE,gBAAgB;YACxB,KAAK,EAAE,CAAC,qBAAqB,KAAK,sCAAsC,EAAE,oCAAoC,CAAC;SAChH,CAAA;IACH,CAAC;IACD,OAAO;QACL,MAAM,EAAE,gBAAgB;QACxB,KAAK,EAAE;YACL,qBAAqB,KAAK,4CAA4C;YACtE,oCAAoC;SACrC;KACF,CAAA;AACH,CAAC"}
|