anyray-connect 0.11.67 → 0.11.68
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/cli.js +96 -97
- package/dist/cli.js.map +1 -1
- package/dist/commands/desktop.js +2 -2
- package/dist/commands/desktop.js.map +1 -1
- package/dist/commands/doctor.js +114 -27
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/history.js +196 -0
- package/dist/commands/history.js.map +1 -0
- package/dist/commands/hook.js +21 -1
- package/dist/commands/hook.js.map +1 -1
- package/dist/commands/managed.js +7 -7
- package/dist/commands/managed.js.map +1 -1
- package/dist/commands/mcpServer.js +115 -2
- package/dist/commands/mcpServer.js.map +1 -1
- package/dist/commands/refreshProxy.js +532 -0
- package/dist/commands/refreshProxy.js.map +1 -0
- package/dist/commands/shared.js +3 -3
- package/dist/commands/shared.js.map +1 -1
- package/dist/commands/ssoGate.js +12 -1
- package/dist/commands/ssoGate.js.map +1 -1
- package/dist/commands/ssoPoll.js +8 -0
- package/dist/commands/ssoPoll.js.map +1 -1
- package/dist/commands/status.js +11 -20
- package/dist/commands/status.js.map +1 -1
- package/dist/target.js +5 -5
- package/dist/target.js.map +1 -1
- package/dist/tools/claudeCodeEnv.js +1 -1
- package/dist/tools/claudeCodeEnv.js.map +1 -1
- package/dist/tools/claudeDesktopNative.js +6 -1
- package/dist/tools/claudeDesktopNative.js.map +1 -1
- package/dist/tools/claudeDesktopProfileNotice.js +44 -0
- package/dist/tools/claudeDesktopProfileNotice.js.map +1 -0
- package/dist/tools/claudeDesktopWindows.js +5 -0
- package/dist/tools/claudeDesktopWindows.js.map +1 -1
- package/dist/tools/codex.js +23 -4
- package/dist/tools/codex.js.map +1 -1
- package/dist/tools/copilotCli.js +1 -1
- package/dist/tools/cursor.js +17 -17
- package/dist/tools/cursor.js.map +1 -1
- package/dist/util/claudeImporter.js +161 -0
- package/dist/util/claudeImporter.js.map +1 -0
- package/dist/util/cliInvocation.js +2 -2
- package/dist/util/codexConfig.js +24 -0
- package/dist/util/codexConfig.js.map +1 -1
- package/dist/util/desktopHistoryRestore.js +116 -0
- package/dist/util/desktopHistoryRestore.js.map +1 -0
- package/dist/util/desktopSessionStore.js +216 -0
- package/dist/util/desktopSessionStore.js.map +1 -0
- package/dist/util/devRefresh.js +227 -32
- package/dist/util/devRefresh.js.map +1 -1
- package/dist/util/fleetBootstrap.js +1 -1
- package/dist/util/historyImport.js +142 -0
- package/dist/util/historyImport.js.map +1 -0
- package/dist/util/historyStore.js +0 -0
- package/dist/util/historyStore.js.map +1 -0
- package/dist/util/loopback.js +20 -20
- package/dist/util/loopback.js.map +1 -1
- package/dist/util/persist.js +77 -2
- package/dist/util/persist.js.map +1 -1
- package/dist/util/profileTarget.js +5 -5
- package/dist/util/profileTarget.js.map +1 -1
- package/dist/util/proxyEnrollmentGate.js +3 -3
- package/dist/util/proxyService.js +58 -25
- package/dist/util/proxyService.js.map +1 -1
- package/dist/util/refreshScheduler.js +12 -4
- package/dist/util/refreshScheduler.js.map +1 -1
- package/dist/util/serviceArtifacts.js +163 -0
- package/dist/util/serviceArtifacts.js.map +1 -0
- package/package.json +1 -1
- package/dist/commands/fallbackProxy.js +0 -718
- package/dist/commands/fallbackProxy.js.map +0 -1
- package/dist/util/fallback.js +0 -542
- package/dist/util/fallback.js.map +0 -1
- package/dist/util/fallbackClient.js +0 -68
- package/dist/util/fallbackClient.js.map +0 -1
- package/dist/util/fallbackStore.js +0 -187
- package/dist/util/fallbackStore.js.map +0 -1
package/dist/cli.js
CHANGED
|
@@ -10,10 +10,9 @@
|
|
|
10
10
|
* personal gateway authentication, and content-free attribution.
|
|
11
11
|
*/
|
|
12
12
|
import { isPersonalClientKey, isInsecureRemoteOrigin, normalizeOrigin, resolveOrigin, resolveTarget, withLocalProxy, } from "./target.js";
|
|
13
|
-
import {
|
|
13
|
+
import { pickLoopbackPort, installProxyService, uninstallProxyService, probeHealth, } from "./util/proxyService.js";
|
|
14
14
|
import { installRefreshScheduler, uninstallRefreshScheduler, } from "./util/refreshScheduler.js";
|
|
15
|
-
import {
|
|
16
|
-
import { runFallbackProxy } from "./commands/fallbackProxy.js";
|
|
15
|
+
import { runRefreshProxy } from "./commands/refreshProxy.js";
|
|
17
16
|
import { loopbackEngaged } from "./util/loopback.js";
|
|
18
17
|
import { runCursorWatch } from "./commands/cursorWatch.js";
|
|
19
18
|
import { runManaged } from "./commands/managed.js";
|
|
@@ -21,18 +20,19 @@ import { runPrintKey } from "./commands/printKey.js";
|
|
|
21
20
|
import { runClaudeDesktopCredential, } from "./commands/claudeDesktopCredential.js";
|
|
22
21
|
import { runDesktop } from "./commands/desktop.js";
|
|
23
22
|
import { runRecallCommand, runRetrieveCommand } from "./commands/retrieval.js";
|
|
23
|
+
import { runHistoryCommand } from "./commands/history.js";
|
|
24
24
|
import { checkGateway } from "./util/preflight.js";
|
|
25
25
|
import { maskKey } from "./util/redeem.js";
|
|
26
26
|
import { parseEnrollLink, isEnrollArg, resolveEnrollLink, } from "./util/enrollLink.js";
|
|
27
27
|
import { canReuseEnrolledCert, enrollAndVerify, enrollTokenHash, GatewayVerifyError, verifyWithGateway, } from "./util/devVerify.js";
|
|
28
28
|
import { detectDisplayName } from "./util/identity.js";
|
|
29
29
|
import { parseSsoLink, ssoEnroll } from "./util/ssoEnroll.js";
|
|
30
|
-
import { refreshIfDue } from "./util/devRefresh.js";
|
|
30
|
+
import { recordMintSuccess, refreshIfDue, reserveMint, } from "./util/devRefresh.js";
|
|
31
31
|
import { REGISTRY, byId } from "./tools/index.js";
|
|
32
32
|
import { staleShellExportsForTargets } from "./tools/shellEnv.js";
|
|
33
33
|
import { effectiveSubscription, metadataHeaderValue } from "./types.js";
|
|
34
34
|
import { bold, cyan, dim, fail, heading, info, note, ok, warn, } from "./util/log.js";
|
|
35
|
-
import { clearDevKey, clearEnrollment, hasEnrollment, loadProfile, recordManagedTools, saveProfile, saveDevKey, loadDevKey, stateDir, ensureStateDir, } from "./util/persist.js";
|
|
35
|
+
import { clearDevKey, clearEnrollment, hasEnrollment, loadProfile, recordManagedTools, saveProfile, saveDevKey, loadDevKey, stateDir, ensureStateDir, purgeRetiredFallbackState, } from "./util/persist.js";
|
|
36
36
|
import { confirm, isInteractive, prompt, resolveDisplayName, resolveTeam, } from "./commands/shared.js";
|
|
37
37
|
import { fetchSavings, formatSavingsLine } from "./util/savings.js";
|
|
38
38
|
import { verifyConnection } from "./util/verify.js";
|
|
@@ -72,7 +72,11 @@ ${bold("Subcommands")}
|
|
|
72
72
|
model retrieve capability);
|
|
73
73
|
--repair re-applies managed tools whose routing drifted
|
|
74
74
|
(only tools a previous apply recorded — a reverted tool
|
|
75
|
-
is never resurrected)
|
|
75
|
+
is never resurrected) and deletes the retired fallback
|
|
76
|
+
lane's cached provider key;
|
|
77
|
+
--security-review prints the allow-list your endpoint-security
|
|
78
|
+
team needs (service names, unit-file paths, loopback-only
|
|
79
|
+
network posture) — use it when an EDR quarantines Anyray
|
|
76
80
|
sync Pull the team's shared skills/instructions into each tool's skills dir
|
|
77
81
|
print-key Print the enrolled dev's short-lived gateway key for helper
|
|
78
82
|
integrations. --json emits the desktop helper contract.
|
|
@@ -82,6 +86,10 @@ ${bold("Subcommands")}
|
|
|
82
86
|
marker. stdout carries the content alone — pipe it anywhere.
|
|
83
87
|
recall <query> Semantic search over your externalized tool outputs; prints
|
|
84
88
|
ranked handle<TAB>score<TAB>preview lines (--k N to limit)
|
|
89
|
+
history Import a Claude data export so past conversations stay
|
|
90
|
+
searchable from a client whose own history is unavailable
|
|
91
|
+
(a third-party Claude Desktop). \`history import <file>\`,
|
|
92
|
+
\`history status\`.
|
|
85
93
|
managed Emit or install a fleet-wide Claude Code managed-settings.json
|
|
86
94
|
(MDM). See \`anyray-connect managed\` for its modes.
|
|
87
95
|
(no subcommand) Apply the connect flow (wizard-driven when interactive)
|
|
@@ -159,14 +167,6 @@ ${bold("Options")}
|
|
|
159
167
|
--force Skip the gateway reachability check before writing
|
|
160
168
|
--skip-verify Skip the post-apply smoke test (one tiny inference request for
|
|
161
169
|
routed tools, or a key-only check for native Cursor mode)
|
|
162
|
-
--fallback Opt in to the local fallback proxy: a small daemon on
|
|
163
|
-
127.0.0.1 your tools point at. It forwards to the gateway,
|
|
164
|
-
and if the gateway is unavailable or returns a transient
|
|
165
|
-
failure it routes the request straight to the original
|
|
166
|
-
provider so you're never blocked — reconciling the usage
|
|
167
|
-
when the gateway is back. Off by default; a plain apply
|
|
168
|
-
points tools straight at the gateway and removes the proxy
|
|
169
|
-
if an earlier run installed it.
|
|
170
170
|
--no-key-refresh Turn OFF the default-on seamless key refresh for
|
|
171
171
|
subscription Claude Code + Codex. Normally a small loopback
|
|
172
172
|
daemon fills a freshly re-minted key on each request so a
|
|
@@ -278,10 +278,8 @@ export const parseArgs = (argv) => {
|
|
|
278
278
|
args.skipVerify = true;
|
|
279
279
|
break;
|
|
280
280
|
case "--fallback":
|
|
281
|
-
args.fallback = true;
|
|
282
|
-
break;
|
|
283
281
|
case "--no-fallback":
|
|
284
|
-
args.
|
|
282
|
+
args.retiredFallbackFlag = true;
|
|
285
283
|
break;
|
|
286
284
|
case "--no-key-refresh":
|
|
287
285
|
args.noKeyRefresh = true;
|
|
@@ -298,6 +296,9 @@ export const parseArgs = (argv) => {
|
|
|
298
296
|
case "--repair":
|
|
299
297
|
args.repair = true;
|
|
300
298
|
break;
|
|
299
|
+
case "--security-review":
|
|
300
|
+
args.securityReview = true;
|
|
301
|
+
break;
|
|
301
302
|
case "--help":
|
|
302
303
|
case "-h":
|
|
303
304
|
args.help = true;
|
|
@@ -448,6 +449,12 @@ export const runApply = async (adapters, target, opts, revert,
|
|
|
448
449
|
* background lanes (refresh, drift repair), which carry none.
|
|
449
450
|
*/
|
|
450
451
|
explicitIds) => {
|
|
452
|
+
// Upgrade cleanup: drop the retired fallback lane's cached ORG PROVIDER KEY.
|
|
453
|
+
// Deliberately here rather than in the interactive command — most upgraded
|
|
454
|
+
// machines never re-run Connect by hand, so the background refresh/repair
|
|
455
|
+
// lanes are what actually reach them. Best-effort, never fails the run.
|
|
456
|
+
if (!opts.dryRun)
|
|
457
|
+
await purgeRetiredFallbackState();
|
|
451
458
|
let changes = 0;
|
|
452
459
|
const manual = [];
|
|
453
460
|
const results = [];
|
|
@@ -628,16 +635,13 @@ const runVerify = async (target, toolIds) => {
|
|
|
628
635
|
* should be pointed at. On health-check success the tools point at the loopback
|
|
629
636
|
* (`viaLocalProxy`); if it never binds we **degrade to the direct gateway
|
|
630
637
|
* target** so the worst case is exactly today's static-key lane — pointing tools
|
|
631
|
-
* at a dead 127.0.0.1 port is never an option.
|
|
632
|
-
* itself picks refresh-only vs fallback from the persisted profile, so this only
|
|
633
|
-
* varies the user-facing copy.
|
|
638
|
+
* at a dead 127.0.0.1 port is never an option.
|
|
634
639
|
*/
|
|
635
|
-
export const setupLoopbackProxy = async (target, port,
|
|
640
|
+
export const setupLoopbackProxy = async (target, port, deps = {}) => {
|
|
636
641
|
const install = deps.install ?? installProxyService;
|
|
637
642
|
const probe = deps.probe ?? probeHealth;
|
|
638
643
|
const pollDelayMs = deps.pollDelayMs ?? 300;
|
|
639
|
-
|
|
640
|
-
heading(isFallback ? "Local fallback proxy" : "Seamless key refresh");
|
|
644
|
+
heading("Seamless key refresh");
|
|
641
645
|
const svc = await install();
|
|
642
646
|
for (const m of svc.messages)
|
|
643
647
|
info(` ${m}`);
|
|
@@ -655,22 +659,17 @@ export const setupLoopbackProxy = async (target, port, mode, deps = {}) => {
|
|
|
655
659
|
await new Promise((r) => setTimeout(r, pollDelayMs));
|
|
656
660
|
}
|
|
657
661
|
if (health) {
|
|
658
|
-
ok(
|
|
659
|
-
? ` Running on 127.0.0.1:${port} → ${target.origin} — tools fail over to the provider automatically.`
|
|
660
|
-
: ` Running on 127.0.0.1:${port} → ${target.origin} — your key refreshes automatically, no restart needed.`);
|
|
662
|
+
ok(` Running on 127.0.0.1:${port} → ${target.origin} — your key refreshes automatically, no restart needed.`);
|
|
661
663
|
return withLocalProxy(target, port);
|
|
662
664
|
}
|
|
663
|
-
warn(
|
|
665
|
+
warn(" Key-refresh proxy did not come up — pointing tools directly at the gateway for now.");
|
|
664
666
|
info(dim(svc.ok
|
|
665
667
|
? // Install succeeded but the port never bound — a transient miss the
|
|
666
668
|
// next run retries against the same supervisor.
|
|
667
669
|
" It will retry on the next run; check the service logs if it persists."
|
|
668
|
-
: // No supervised daemon on this platform (svc.messages said why)
|
|
669
|
-
//
|
|
670
|
-
|
|
671
|
-
isFallback
|
|
672
|
-
? " Local fallback isn't available on this platform."
|
|
673
|
-
: " Your key still refreshes on a schedule — no daemon needed here."));
|
|
670
|
+
: // No supervised daemon on this platform (svc.messages said why); the
|
|
671
|
+
// scheduled ~30-min re-mint still keeps the seat's key fresh.
|
|
672
|
+
" Your key still refreshes on a schedule — no daemon needed here."));
|
|
674
673
|
return target;
|
|
675
674
|
};
|
|
676
675
|
/**
|
|
@@ -813,12 +812,14 @@ export const run = async (argv) => {
|
|
|
813
812
|
return 0;
|
|
814
813
|
}
|
|
815
814
|
// Hidden entrypoint: the OS login service (launchd/systemd) launches
|
|
816
|
-
// `anyray-connect __anyray-fallback-proxy` — the local
|
|
817
|
-
// (commands/
|
|
818
|
-
// gateway,
|
|
819
|
-
//
|
|
815
|
+
// `anyray-connect __anyray-fallback-proxy` — the local key-refresh daemon
|
|
816
|
+
// (commands/refreshProxy.ts). It listens on loopback and forwards to the
|
|
817
|
+
// remote gateway, filling the empty client-key slot with a freshly re-minted
|
|
818
|
+
// key. Runs until the supervisor stops it. The flag keeps its historic
|
|
819
|
+
// `fallback` spelling because it is baked into already-installed launchd
|
|
820
|
+
// plists and systemd units (see PROXY_FLAG in util/proxyService.ts).
|
|
820
821
|
if (argv[0] === "__anyray-fallback-proxy") {
|
|
821
|
-
return
|
|
822
|
+
return runRefreshProxy();
|
|
822
823
|
}
|
|
823
824
|
// Hidden entrypoint: the Cursor adapter spawns this DETACHED when Cursor was
|
|
824
825
|
// running at apply/revert time. It waits for Cursor to quit, completes the
|
|
@@ -877,6 +878,12 @@ export const run = async (argv) => {
|
|
|
877
878
|
if (argv[0] === "recall") {
|
|
878
879
|
return runRecallCommand(argv.slice(1));
|
|
879
880
|
}
|
|
881
|
+
// `history` — import a Claude data export so past conversations stay
|
|
882
|
+
// reachable from a client that can't show them (a third-party Claude Desktop
|
|
883
|
+
// runs in a profile with no account, so its sidebar is empty).
|
|
884
|
+
if (argv[0] === "history") {
|
|
885
|
+
return runHistoryCommand(argv.slice(1));
|
|
886
|
+
}
|
|
880
887
|
if (argv[0] === "login") {
|
|
881
888
|
return runLoginCommand(argv.slice(1));
|
|
882
889
|
}
|
|
@@ -907,6 +914,7 @@ export const run = async (argv) => {
|
|
|
907
914
|
json: parsed.json,
|
|
908
915
|
verify: parsed.verify,
|
|
909
916
|
repair: parsed.repair,
|
|
917
|
+
securityReview: parsed.securityReview,
|
|
910
918
|
});
|
|
911
919
|
if (sub === "sync")
|
|
912
920
|
return runSync({ gateway: parsed.gateway });
|
|
@@ -976,11 +984,12 @@ const runApplyCommand = async (argv, forceWizard) => {
|
|
|
976
984
|
!args.revert) {
|
|
977
985
|
byoUpstream = profile.byoUpstream;
|
|
978
986
|
}
|
|
979
|
-
//
|
|
980
|
-
//
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
987
|
+
// Retired flag: the provider-direct lane is gone. Accepted (not an error) so
|
|
988
|
+
// an old fleet script keeps working, but say so — the seat is on the
|
|
989
|
+
// key-refresh loopback either way, and nothing now caches an org provider key
|
|
990
|
+
// on this machine.
|
|
991
|
+
if (args.retiredFallbackFlag) {
|
|
992
|
+
warn("--fallback/--no-fallback are retired and ignored: the local daemon no longer routes around the gateway to the provider. Seamless key refresh is on by default (--no-key-refresh opts out).");
|
|
984
993
|
}
|
|
985
994
|
// Wizard: explicit `wizard` subcommand, or a bare interactive invocation
|
|
986
995
|
// (no gateway, not a revert/dry-run/scripted run). Non-TTY always falls
|
|
@@ -1140,6 +1149,11 @@ const runApplyCommand = async (argv, forceWizard) => {
|
|
|
1140
1149
|
// A stored cert is only reusable for THIS deployment — when the link
|
|
1141
1150
|
// resolves a deploymentId, a cert bound to a different (e.g. rebuilt)
|
|
1142
1151
|
// deployment must re-enroll rather than be reused.
|
|
1152
|
+
// Claim the mint slot before any network round trip: an interactive enroll
|
|
1153
|
+
// must never race a detached refresh minting against the same cert. The
|
|
1154
|
+
// `recordMintSuccess` below is the other half, and on its own it only closes
|
|
1155
|
+
// the window once we already hold a key.
|
|
1156
|
+
await reserveMint();
|
|
1143
1157
|
const presentedTokenHash = enrollTokenHash(input.token);
|
|
1144
1158
|
const storedValid = canReuseEnrolledCert(profile.enrolledCert, profile.enrolledCertExpiry, input.deploymentId, Date.now(), presentedTokenHash, profile.enrolledTokenHash);
|
|
1145
1159
|
const storedPrivateKey = storedValid ? await loadDevKey() : undefined;
|
|
@@ -1231,6 +1245,7 @@ const runApplyCommand = async (argv, forceWizard) => {
|
|
|
1231
1245
|
// --enroll. The cert + key persist below so the auto-refresher can re-mint
|
|
1232
1246
|
// until the (short) SSO cert expires, after which the dev re-runs --sso.
|
|
1233
1247
|
if (ssoEnrollment) {
|
|
1248
|
+
await reserveMint();
|
|
1234
1249
|
try {
|
|
1235
1250
|
const verification = await verifyWithGateway(origin, ssoEnrollment.cert, ssoEnrollment.privateKeyPem);
|
|
1236
1251
|
clientKey = verification.key;
|
|
@@ -1294,7 +1309,7 @@ const runApplyCommand = async (argv, forceWizard) => {
|
|
|
1294
1309
|
});
|
|
1295
1310
|
const opts = { dryRun: args.dryRun };
|
|
1296
1311
|
// `/v1` authentication is always client-key gated. Stop before confirmation,
|
|
1297
|
-
//
|
|
1312
|
+
// loopback setup, adapter writes, or a misleading placeholder-only manual
|
|
1298
1313
|
// recipe. Dry-run remains read-only and useful: it reports which selected
|
|
1299
1314
|
// installs are present, but does not pretend an unauthenticated config could
|
|
1300
1315
|
// be applied successfully.
|
|
@@ -1369,47 +1384,33 @@ const runApplyCommand = async (argv, forceWizard) => {
|
|
|
1369
1384
|
return 1;
|
|
1370
1385
|
}
|
|
1371
1386
|
}
|
|
1372
|
-
// Local fallback proxy: OFF by default — opt in with --fallback. The provider-
|
|
1373
|
-
// direct fallback leg proved unreliable in the field (the fat daemon could
|
|
1374
|
-
// crash mid-session and strand tools on a dead 127.0.0.1 port), so a plain
|
|
1375
|
-
// apply never enables it. --no-fallback stays accepted and always wins, so
|
|
1376
|
-
// scripts that pass it keep working.
|
|
1377
|
-
const wantFallback = !args.revert && args.fallback === true && args.noFallback !== true;
|
|
1378
1387
|
// Seamless key refresh: ON by default for subscription/passthrough Claude Code
|
|
1379
|
-
// + Codex — the only tools whose traffic actually traverses the loopback
|
|
1380
|
-
//
|
|
1381
|
-
//
|
|
1382
|
-
//
|
|
1383
|
-
// the
|
|
1384
|
-
//
|
|
1385
|
-
//
|
|
1386
|
-
//
|
|
1387
|
-
//
|
|
1388
|
-
//
|
|
1388
|
+
// + Codex — the only tools whose traffic actually traverses the loopback. They
|
|
1389
|
+
// bake a short-TTL client key as a STATIC header read once at launch, so
|
|
1390
|
+
// without the daemon a long session 401s the moment the key lapses mid-flight,
|
|
1391
|
+
// curable only by a re-run + a tool relaunch. The daemon's surface is
|
|
1392
|
+
// deliberately tiny (fill the empty key slot, forward the bytes unchanged) —
|
|
1393
|
+
// that is what lets it default on where the retired provider-direct lane could
|
|
1394
|
+
// not. --no-key-refresh forces the legacy direct static-key lane. Other tools
|
|
1395
|
+
// (native Cursor, the Copilot extension, ACP launchers) rebuild their own
|
|
1396
|
+
// target and bypass the loopback, so they are unaffected either way — see the
|
|
1397
|
+
// scope boundary in commands/refreshProxy.ts.
|
|
1389
1398
|
const keyRefreshSeatSelected = (adapters.some((a) => a.id === "claude-code") &&
|
|
1390
1399
|
effectiveSubscription(target, "anthropic")) ||
|
|
1391
1400
|
(adapters.some((a) => a.id === "codex") &&
|
|
1392
1401
|
effectiveSubscription(target, "openai"));
|
|
1393
1402
|
const wantKeyRefresh = !args.revert && args.noKeyRefresh !== true && keyRefreshSeatSelected;
|
|
1394
|
-
// Either flag engages the loopback daemon; --fallback additionally turns it
|
|
1395
|
-
// into the provider-direct fallback (the daemon derives its mode from
|
|
1396
|
-
// fallbackEnabled at boot, so fallback wins when both are set).
|
|
1397
|
-
const wantLoopback = wantFallback || wantKeyRefresh;
|
|
1398
1403
|
// A scoped apply (`--tools x`) that doesn't itself engage the loopback must
|
|
1399
1404
|
// not tear down the DEFAULT-ON key-refresh lane the tools it did NOT name
|
|
1400
1405
|
// still depend on — the apply-side mirror of the scoped-revert credential rule
|
|
1401
1406
|
// below. Without this, `anyray-connect --tools cursor` after a full connect
|
|
1402
1407
|
// would uninstall the refresh daemon and drop Claude Code / Codex back to the
|
|
1403
|
-
// 401-prone static-key lane.
|
|
1404
|
-
// the opt-in --fallback machinery is NOT preserved here, so the upgrade
|
|
1405
|
-
// cleanup that disables a proxy an earlier release auto-enabled still runs
|
|
1406
|
-
// (re-opt-in with --fallback). An explicit --no-key-refresh still wins, and a
|
|
1408
|
+
// 401-prone static-key lane. An explicit --no-key-refresh still wins, and a
|
|
1407
1409
|
// full, unscoped apply falls through to the normal want-driven teardown below.
|
|
1408
1410
|
const scopedApply = !args.revert && (args.tools?.length ?? 0) > 0;
|
|
1409
1411
|
const preserveKeyRefresh = scopedApply &&
|
|
1410
1412
|
args.noKeyRefresh !== true &&
|
|
1411
1413
|
Boolean(profile.keyRefreshEnabled);
|
|
1412
|
-
const preserveLoopback = preserveKeyRefresh;
|
|
1413
1414
|
// The refresh SCHEDULER is the universal trigger: a per-user launchd/systemd/
|
|
1414
1415
|
// Task Scheduler task that runs `__anyray-refresh` every ~30 min to re-mint
|
|
1415
1416
|
// the short-TTL ark_ key for EVERY configured tool. The loopback daemon above
|
|
@@ -1426,21 +1427,19 @@ const runApplyCommand = async (argv, forceWizard) => {
|
|
|
1426
1427
|
const hasRefreshableCert = !usingEnvironmentKey && Boolean(enrolledCert ?? profile.enrolledCert);
|
|
1427
1428
|
const wantRefreshScheduler = !args.revert && args.noKeyRefresh !== true && hasRefreshableCert;
|
|
1428
1429
|
let applyTarget = target;
|
|
1429
|
-
const resolvedFallbackEnabled = wantFallback;
|
|
1430
1430
|
const resolvedKeyRefreshEnabled = wantKeyRefresh || preserveKeyRefresh;
|
|
1431
|
-
const loopbackMode = resolvedFallbackEnabled
|
|
1432
|
-
? "fallback"
|
|
1433
|
-
: "refresh-only";
|
|
1434
1431
|
// Hold the port when the loopback is wanted now, or preserved for the tools
|
|
1435
1432
|
// this scoped apply didn't name; otherwise clear it so the persisted profile
|
|
1436
1433
|
// and the teardown below agree the daemon is gone.
|
|
1437
|
-
let resolvedFallbackPort =
|
|
1438
|
-
|
|
1439
|
-
|
|
1434
|
+
let resolvedFallbackPort = resolvedKeyRefreshEnabled
|
|
1435
|
+
? profile.fallbackPort
|
|
1436
|
+
: undefined;
|
|
1437
|
+
if (wantKeyRefresh && !args.dryRun) {
|
|
1438
|
+
resolvedFallbackPort = profile.fallbackPort ?? (await pickLoopbackPort());
|
|
1440
1439
|
}
|
|
1441
1440
|
// ACP agents and Claude Desktop's credential helper intentionally persist no
|
|
1442
1441
|
// static key: they load this owner-only profile when the app launches them.
|
|
1443
|
-
// Commit the complete target + enrollment pair before any adapter or
|
|
1442
|
+
// Commit the complete target + enrollment pair before any adapter or loopback
|
|
1444
1443
|
// service can register such a launcher. A persistence failure is fatal here,
|
|
1445
1444
|
// while no tool configuration has changed yet.
|
|
1446
1445
|
if (!args.revert && !args.dryRun) {
|
|
@@ -1479,7 +1478,6 @@ const runApplyCommand = async (argv, forceWizard) => {
|
|
|
1479
1478
|
: undefined)),
|
|
1480
1479
|
authMode: target.authMode,
|
|
1481
1480
|
byoUpstream: target.byoUpstream,
|
|
1482
|
-
fallbackEnabled: resolvedFallbackEnabled,
|
|
1483
1481
|
keyRefreshEnabled: resolvedKeyRefreshEnabled,
|
|
1484
1482
|
refreshSchedulerEnabled: wantRefreshScheduler,
|
|
1485
1483
|
fallbackPort: resolvedFallbackPort,
|
|
@@ -1491,9 +1489,16 @@ const runApplyCommand = async (argv, forceWizard) => {
|
|
|
1491
1489
|
fail("Could not save the owner-only Anyray runtime profile. No tool configuration was changed.");
|
|
1492
1490
|
return 1;
|
|
1493
1491
|
}
|
|
1492
|
+
// This run minted a key (--enroll / --sso). Share that with the
|
|
1493
|
+
// auto-refresher's bookkeeping: with no successful mint on record, the next
|
|
1494
|
+
// detached refresh sees a credential "not rotated in hours", re-mints
|
|
1495
|
+
// immediately, and rotates the key out from under the session just set up.
|
|
1496
|
+
if (clientKeyExpiresAt && !usingEnvironmentKey) {
|
|
1497
|
+
await recordMintSuccess({ expiresAt: clientKeyExpiresAt });
|
|
1498
|
+
}
|
|
1494
1499
|
}
|
|
1495
|
-
if (
|
|
1496
|
-
applyTarget = await setupLoopbackProxy(target, resolvedFallbackPort
|
|
1500
|
+
if (wantKeyRefresh && !args.dryRun) {
|
|
1501
|
+
applyTarget = await setupLoopbackProxy(target, resolvedFallbackPort);
|
|
1497
1502
|
}
|
|
1498
1503
|
// Drop the enrollment credential FIRST, before a single config is unpicked —
|
|
1499
1504
|
// see util/persist `clearEnrollment` for why a revert that leaves it behind
|
|
@@ -1548,27 +1553,23 @@ const runApplyCommand = async (argv, forceWizard) => {
|
|
|
1548
1553
|
if (!args.dryRun) {
|
|
1549
1554
|
await recordManagedTools(results, args.revert, fullRevert).catch(() => undefined);
|
|
1550
1555
|
}
|
|
1551
|
-
// Tear down the loopback daemon
|
|
1552
|
-
//
|
|
1553
|
-
//
|
|
1554
|
-
//
|
|
1555
|
-
//
|
|
1556
|
-
if (!
|
|
1557
|
-
!preserveLoopback &&
|
|
1556
|
+
// Tear down the loopback daemon whenever it isn't wanted but a previous run
|
|
1557
|
+
// left it installed — covers an explicit --revert, --no-key-refresh, or a
|
|
1558
|
+
// subscription→api-key switch that no longer needs the refresh lane. Runs
|
|
1559
|
+
// AFTER the adapters above re-pointed tools straight at the gateway, so
|
|
1560
|
+
// uninstalling the loopback service strands nothing.
|
|
1561
|
+
if (!resolvedKeyRefreshEnabled &&
|
|
1558
1562
|
!args.dryRun &&
|
|
1559
1563
|
(loopbackEngaged(profile) || profile.fallbackPort)) {
|
|
1560
1564
|
heading("Local loopback proxy");
|
|
1561
1565
|
const svc = await uninstallProxyService(profile.fallbackPort);
|
|
1562
1566
|
for (const m of svc.messages)
|
|
1563
1567
|
info(` ${m}`);
|
|
1564
|
-
await clearBundle();
|
|
1565
|
-
await clearQueue();
|
|
1566
1568
|
// Re-read: on a --revert `clearEnrollment` already stripped the credential
|
|
1567
1569
|
// from disk, so spreading the pre-run `profile` snapshot would write it
|
|
1568
1570
|
// straight back. loadProfile() reflects what is actually persisted now.
|
|
1569
1571
|
await saveProfile({
|
|
1570
1572
|
...(await loadProfile()),
|
|
1571
|
-
fallbackEnabled: false,
|
|
1572
1573
|
keyRefreshEnabled: false,
|
|
1573
1574
|
fallbackPort: undefined,
|
|
1574
1575
|
});
|
|
@@ -1582,7 +1583,7 @@ const runApplyCommand = async (argv, forceWizard) => {
|
|
|
1582
1583
|
// Cursor-only seat has the scheduler but never had a daemon.
|
|
1583
1584
|
await reconcileRefreshScheduler({
|
|
1584
1585
|
want: wantRefreshScheduler,
|
|
1585
|
-
announcePrimary: !
|
|
1586
|
+
announcePrimary: !wantKeyRefresh,
|
|
1586
1587
|
scopedRevert: Boolean(args.revert) && !fullRevert,
|
|
1587
1588
|
dryRun: Boolean(args.dryRun),
|
|
1588
1589
|
wasEnabled: Boolean(profile.refreshSchedulerEnabled),
|
|
@@ -1651,12 +1652,10 @@ const runApplyCommand = async (argv, forceWizard) => {
|
|
|
1651
1652
|
}
|
|
1652
1653
|
else {
|
|
1653
1654
|
// MUST probe `target` (the remote gateway), NOT `applyTarget` (the local
|
|
1654
|
-
//
|
|
1655
|
-
//
|
|
1656
|
-
//
|
|
1657
|
-
//
|
|
1658
|
-
// the provider — whose 401 false-positives classifySeatWiring's "gateway
|
|
1659
|
-
// passes your sign-in through" verdict. The daemon hop is health-checked
|
|
1655
|
+
// key-refresh daemon the tools were pointed at). The verify classifiers
|
|
1656
|
+
// sniff response bodies, and a daemon-synthesized 502 ("gateway
|
|
1657
|
+
// unreachable") would confuse them into reporting a routing fault the
|
|
1658
|
+
// gateway itself never returned. The daemon hop is health-checked
|
|
1660
1659
|
// separately at install time (and by `doctor`).
|
|
1661
1660
|
await runVerify(target, adapters.map((a) => a.id));
|
|
1662
1661
|
}
|