quoxcli 0.2.0 → 0.3.1
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/LICENSE +1 -1
- package/README.md +2 -0
- package/bin/quox.js +99 -126
- package/lib/api/endpoints/admin.js +26 -0
- package/lib/api/endpoints/agent-schedules.js +26 -0
- package/lib/api/endpoints/agents.js +7 -0
- package/lib/api/endpoints/assistants.js +12 -0
- package/lib/api/endpoints/away.js +15 -0
- package/lib/api/endpoints/chat.js +7 -0
- package/lib/api/endpoints/contactsq.js +2 -0
- package/lib/api/endpoints/conversations.js +14 -0
- package/lib/api/endpoints/executions.js +13 -0
- package/lib/api/endpoints/fetchAllowlist.js +27 -0
- package/lib/api/endpoints/files.js +14 -0
- package/lib/api/endpoints/hat.js +30 -0
- package/lib/api/endpoints/inbox.js +20 -0
- package/lib/api/endpoints/index.js +14 -0
- package/lib/api/endpoints/memory.js +19 -0
- package/lib/api/endpoints/mind.js +13 -0
- package/lib/api/endpoints/mon.js +6 -0
- package/lib/api/endpoints/objectives.js +6 -0
- package/lib/api/endpoints/plugins.js +18 -0
- package/lib/api/endpoints/presence.js +33 -0
- package/lib/api/endpoints/quoxplan.js +7 -0
- package/lib/api/endpoints/quoxseo.js +6 -0
- package/lib/api/endpoints/router.js +17 -0
- package/lib/api/endpoints/safety.js +7 -0
- package/lib/api/endpoints/skills.js +20 -0
- package/lib/api/endpoints/stepup.js +20 -0
- package/lib/api/endpoints/teams.js +2 -2
- package/lib/api/endpoints/triggers.js +26 -0
- package/lib/api/endpoints/v1.js +86 -0
- package/lib/api/endpoints/ward.js +7 -0
- package/lib/api/endpoints/workflows.js +7 -0
- package/lib/api/endpoints/x.js +8 -0
- package/lib/commands/actions.js +61 -0
- package/lib/commands/admin.js +16 -0
- package/lib/commands/agent.js +163 -27
- package/lib/commands/agentAttach.js +94 -6
- package/lib/commands/approvals.js +73 -1
- package/lib/commands/aside.js +24 -0
- package/lib/commands/ask.js +144 -0
- package/lib/commands/assistant.js +59 -4
- package/lib/commands/auth.js +8 -2
- package/lib/commands/backup.js +8 -4
- package/lib/commands/brain.js +105 -14
- package/lib/commands/build.js +19 -5
- package/lib/commands/chat.js +57 -2
- package/lib/commands/claude.js +34 -8
- package/lib/commands/contacts.js +49 -0
- package/lib/commands/conversation.js +35 -4
- package/lib/commands/dashboard.js +43 -0
- package/lib/commands/department.js +28 -7
- package/lib/commands/devResident.js +15 -3
- package/lib/commands/devSupervisor.js +13 -1
- package/lib/commands/discord.js +75 -39
- package/lib/commands/done.js +98 -28
- package/lib/commands/engagement.js +17 -6
- package/lib/commands/fetchAllowlist.js +71 -0
- package/lib/commands/file.js +38 -1
- package/lib/commands/fleet.js +311 -4
- package/lib/commands/governance.js +12 -3
- package/lib/commands/gripe.js +44 -13
- package/lib/commands/hat.js +178 -9
- package/lib/commands/helpers.js +18 -1
- package/lib/commands/hyperion.js +58 -0
- package/lib/commands/inbox.js +74 -23
- package/lib/commands/licence.js +14 -155
- package/lib/commands/lucid.js +10 -4
- package/lib/commands/memory.js +85 -8
- package/lib/commands/mind.js +71 -0
- package/lib/commands/mon.js +69 -2
- package/lib/commands/monitoring.js +1 -1
- package/lib/commands/motion.js +2 -2
- package/lib/commands/nucleus.js +48 -17
- package/lib/commands/objective.js +11 -0
- package/lib/commands/offload.js +89 -0
- package/lib/commands/onboard.js +2 -1
- package/lib/commands/org.js +11 -1
- package/lib/commands/peek.js +28 -0
- package/lib/commands/plan.js +38 -0
- package/lib/commands/plugin.js +110 -0
- package/lib/commands/presence.js +89 -0
- package/lib/commands/provider.js +159 -0
- package/lib/commands/qlarity.js +19 -3
- package/lib/commands/quoxchat.js +13 -4
- package/lib/commands/quoxseo.js +38 -0
- package/lib/commands/reach.js +2 -1
- package/lib/commands/router.js +77 -0
- package/lib/commands/rows.js +358 -0
- package/lib/commands/run.js +64 -9
- package/lib/commands/server.js +292 -0
- package/lib/commands/setup.js +33 -7
- package/lib/commands/skills.js +48 -5
- package/lib/commands/slot.js +260 -7
- package/lib/commands/status.js +1 -1
- package/lib/commands/team.js +3 -1
- package/lib/commands/tips.js +23 -0
- package/lib/commands/tool.js +133 -45
- package/lib/commands/trace.js +12 -7
- package/lib/commands/trigger.js +95 -0
- package/lib/commands/update.js +46 -0
- package/lib/commands/vault.js +31 -8
- package/lib/commands/vm.js +237 -0
- package/lib/commands/voice.js +34 -3
- package/lib/commands/volt.js +45 -3
- package/lib/commands/walkthrough.js +32 -0
- package/lib/commands/watch.js +73 -21
- package/lib/commands/welcome.js +14 -0
- package/lib/commands/workflow.js +189 -32
- package/lib/commands/x.js +13 -4
- package/lib/core/actingAs.js +2 -2
- package/lib/core/activeSummary.js +186 -0
- package/lib/core/aside.js +54 -0
- package/lib/core/askDecision.js +60 -0
- package/lib/core/auth.js +164 -13
- package/lib/core/callerSource.js +1 -1
- package/lib/core/cliBuild.js +62 -0
- package/lib/core/client.js +144 -35
- package/lib/core/commandRegistrars.js +121 -0
- package/lib/core/config.js +142 -24
- package/lib/core/customHats.js +168 -0
- package/lib/core/doneEngineFetch.js +8 -0
- package/lib/core/errors.js +329 -20
- package/lib/core/global-flags.js +105 -5
- package/lib/core/hyperion.js +102 -0
- package/lib/core/inboxResolution.js +65 -0
- package/lib/core/lazyCommands.js +4 -1
- package/lib/core/offload.js +504 -0
- package/lib/core/org-override.js +1 -1
- package/lib/core/orgResolve.js +70 -0
- package/lib/core/output.js +4 -4
- package/lib/core/peek.js +126 -0
- package/lib/core/profileUsers.js +51 -0
- package/lib/core/promptCancel.js +18 -0
- package/lib/core/repoPath.js +11 -0
- package/lib/core/residentSeed.js +3 -1
- package/lib/core/rootOptions.js +39 -0
- package/lib/core/router.js +168 -0
- package/lib/core/selfUpdate.js +188 -0
- package/lib/core/sessionsOverview.js +64 -0
- package/lib/core/slotKeeper.js +248 -3
- package/lib/core/slotWake.js +120 -0
- package/lib/core/supervisorEngine.js +71 -2
- package/lib/core/version.js +1 -1
- package/lib/core/vm.js +361 -0
- package/lib/core/vmTui.js +86 -0
- package/lib/core/walkthrough.js +35 -0
- package/lib/hyperion/deck.js +29 -0
- package/lib/lucid/LucidCard.jsx +68 -0
- package/lib/lucid/LucidIntro.jsx +45 -58
- package/lib/lucid/cards.js +86 -0
- package/lib/lucid/markdown.js +81 -0
- package/lib/lucid/motion.js +56 -0
- package/lib/quoxon/activity.js +14 -2
- package/lib/quoxon/aocl.js +117 -0
- package/lib/quoxon/api.js +504 -25
- package/lib/quoxon/app.js +3000 -230
- package/lib/quoxon/approvalFacts.js +94 -0
- package/lib/quoxon/approvalsView.js +325 -0
- package/lib/quoxon/asideView.js +40 -0
- package/lib/quoxon/ask.js +121 -0
- package/lib/quoxon/away.js +52 -0
- package/lib/quoxon/bang.js +224 -0
- package/lib/quoxon/bulkRisk.js +17 -0
- package/lib/quoxon/capabilities.js +13 -6
- package/lib/quoxon/cells.js +53 -0
- package/lib/quoxon/commandShort.js +112 -0
- package/lib/quoxon/commands.js +363 -38
- package/lib/quoxon/connect.js +210 -23
- package/lib/quoxon/connectCard.js +19 -2
- package/lib/quoxon/consent.js +86 -0
- package/lib/quoxon/copy.js +4 -3
- package/lib/quoxon/countdownLine.js +11 -0
- package/lib/quoxon/demo.autoplay.js +29 -0
- package/lib/quoxon/demo.fixtures.js +1 -1
- package/lib/quoxon/demo.js +44 -3
- package/lib/quoxon/devObserver.js +20 -0
- package/lib/quoxon/dividerAocl.js +191 -0
- package/lib/quoxon/dividerRule.js +57 -0
- package/lib/quoxon/dragonModes.js +150 -0
- package/lib/quoxon/dragonView.js +41 -0
- package/lib/quoxon/earlyInput.js +40 -0
- package/lib/quoxon/echo.js +15 -0
- package/lib/quoxon/egg.js +2 -2
- package/lib/quoxon/events.js +244 -25
- package/lib/quoxon/feedback.js +50 -0
- package/lib/quoxon/gallery.js +21 -7
- package/lib/quoxon/handoffOffer.js +78 -0
- package/lib/quoxon/hatSwitch.js +37 -0
- package/lib/quoxon/headless.js +161 -0
- package/lib/quoxon/hostUrls.js +41 -0
- package/lib/quoxon/indicator.js +6 -2
- package/lib/quoxon/intent.js +12 -1
- package/lib/quoxon/keys.js +49 -6
- package/lib/quoxon/launcher.js +103 -0
- package/lib/quoxon/main.js +92 -160
- package/lib/quoxon/markdown.js +45 -21
- package/lib/quoxon/markdownSettle.js +19 -0
- package/lib/quoxon/matrixDomains.json +46 -0
- package/lib/quoxon/mentions.js +90 -0
- package/lib/quoxon/mind.js +103 -0
- package/lib/quoxon/mode.js +46 -0
- package/lib/quoxon/model.js +132 -26
- package/lib/quoxon/modelPrefs.js +345 -0
- package/lib/quoxon/motion/clock.js +3 -2
- package/lib/quoxon/motion/level.js +91 -0
- package/lib/quoxon/mouse.js +211 -0
- package/lib/quoxon/noai.js +57 -0
- package/lib/quoxon/objects/away.js +72 -0
- package/lib/quoxon/objects/fetch.js +17 -5
- package/lib/quoxon/objects/fleet.js +149 -0
- package/lib/quoxon/objects/health.js +186 -0
- package/lib/quoxon/objects/index.js +24 -3
- package/lib/quoxon/objects/mirror.js +137 -0
- package/lib/quoxon/objects/peek.js +17 -0
- package/lib/quoxon/objects/prove.js +130 -0
- package/lib/quoxon/objects/replay.js +125 -0
- package/lib/quoxon/objects/sitrep.js +58 -0
- package/lib/quoxon/objects/view.js +11 -0
- package/lib/quoxon/onboarding/copy.js +32 -4
- package/lib/quoxon/onboarding/credential.js +17 -4
- package/lib/quoxon/onboarding/dockerOffer.js +45 -0
- package/lib/quoxon/onboarding/failureCause.js +33 -0
- package/lib/quoxon/onboarding/finish.js +15 -0
- package/lib/quoxon/onboarding/index.js +34 -7
- package/lib/quoxon/onboarding/installRunner.js +64 -5
- package/lib/quoxon/onboarding/preflight.js +17 -3
- package/lib/quoxon/onboarding/probe.js +65 -0
- package/lib/quoxon/onboarding/view.js +76 -19
- package/lib/quoxon/orgLabel.js +10 -0
- package/lib/quoxon/orgSwitch.js +33 -0
- package/lib/quoxon/ownBroadcast.js +22 -0
- package/lib/quoxon/palette.js +43 -0
- package/lib/quoxon/print.js +9 -5
- package/lib/quoxon/profileSwitch.js +53 -0
- package/lib/quoxon/promptHistory.js +41 -0
- package/lib/quoxon/providers.js +48 -0
- package/lib/quoxon/qart.js +20 -51
- package/lib/quoxon/qdragon.js +410 -0
- package/lib/quoxon/qdragonFly.js +61 -0
- package/lib/quoxon/qdragonScenes.js +282 -0
- package/lib/quoxon/qeyes.js +213 -0
- package/lib/quoxon/qmotion.js +58 -37
- package/lib/quoxon/render.js +638 -119
- package/lib/quoxon/resend.js +72 -0
- package/lib/quoxon/roster.js +95 -9
- package/lib/quoxon/route.js +6 -5
- package/lib/quoxon/rows.js +711 -0
- package/lib/quoxon/rowsStore.js +80 -0
- package/lib/quoxon/screenContext.js +150 -0
- package/lib/quoxon/screenFrames.js +254 -0
- package/lib/quoxon/session.js +172 -2
- package/lib/quoxon/sessionDead.js +17 -0
- package/lib/quoxon/signin.js +125 -0
- package/lib/quoxon/slash/agents.js +112 -0
- package/lib/quoxon/slash/argSources.js +65 -0
- package/lib/quoxon/slash/args.js +65 -0
- package/lib/quoxon/slash/automate.js +89 -0
- package/lib/quoxon/slash/broadcast.js +140 -0
- package/lib/quoxon/slash/broadcastRun.js +108 -0
- package/lib/quoxon/slash/card.js +30 -0
- package/lib/quoxon/slash/challenge.js +114 -0
- package/lib/quoxon/slash/cliCommand.js +53 -0
- package/lib/quoxon/slash/cliLeaves.json +948 -0
- package/lib/quoxon/slash/compact.js +1 -1
- package/lib/quoxon/slash/confirm.js +30 -0
- package/lib/quoxon/slash/crystallize.js +69 -0
- package/lib/quoxon/slash/dashboard.js +136 -0
- package/lib/quoxon/slash/draft.js +54 -0
- package/lib/quoxon/slash/hatWalk.js +206 -0
- package/lib/quoxon/slash/inverses.js +77 -0
- package/lib/quoxon/slash/killer.js +24 -0
- package/lib/quoxon/slash/killerApi.js +16 -0
- package/lib/quoxon/slash/ledger.js +13 -8
- package/lib/quoxon/slash/listView.js +3 -1
- package/lib/quoxon/slash/modelCmd.js +261 -0
- package/lib/quoxon/slash/palette.js +21 -7
- package/lib/quoxon/slash/paletteController.js +117 -7
- package/lib/quoxon/slash/panels.js +21 -6
- package/lib/quoxon/slash/picker.js +20 -2
- package/lib/quoxon/slash/restart.js +71 -13
- package/lib/quoxon/slash/rowCmd.js +157 -0
- package/lib/quoxon/slash/rowWalk.js +175 -0
- package/lib/quoxon/slash/settings.js +16 -6
- package/lib/quoxon/slash/spawn.js +177 -0
- package/lib/quoxon/slash/stepUp.js +274 -0
- package/lib/quoxon/slash/stepUpCard.js +60 -0
- package/lib/quoxon/slash/stepUpCmds.js +78 -0
- package/lib/quoxon/slash/undoApi.js +38 -0
- package/lib/quoxon/slash/undoCmd.js +49 -0
- package/lib/quoxon/slash/undoWish.js +15 -0
- package/lib/quoxon/slash/watch.js +164 -0
- package/lib/quoxon/slash/wish.js +23 -0
- package/lib/quoxon/slash/wishCmd.js +32 -0
- package/lib/quoxon/slotChat.js +300 -0
- package/lib/quoxon/slotFormat.js +195 -0
- package/lib/quoxon/slotRun.js +131 -0
- package/lib/quoxon/sound/mapping.js +2 -1
- package/lib/quoxon/stray.js +22 -0
- package/lib/quoxon/suggestCommand.js +59 -0
- package/lib/quoxon/term.js +46 -11
- package/lib/quoxon/textBuffer.js +176 -2
- package/lib/quoxon/theme.js +58 -25
- package/lib/quoxon/tips.js +433 -0
- package/lib/quoxon/toolError.js +41 -0
- package/lib/quoxon/transcript.js +42 -4
- package/lib/quoxon/web/qface.js +18 -10
- package/lib/quoxon/welcome.js +1 -1
- package/lib/quoxon/whatsnew.js +136 -0
- package/lib/quoxon/whatsnew.jsonl +89 -0
- package/lib/quoxon/windowOrg.js +113 -0
- package/lib/server/diag.js +129 -0
- package/lib/server/doctor.js +71 -0
- package/lib/server/install.js +149 -0
- package/lib/server/pluginEnable.js +161 -0
- package/lib/server/skew.js +56 -0
- package/lib/server/snapshot.js +131 -0
- package/lib/server/status.js +128 -0
- package/lib/server/sys.js +65 -0
- package/lib/server/uninstall.js +63 -0
- package/lib/server/update.js +234 -0
- package/lib/toolOutputShape.js +315 -0
- package/lib/tui/views/HyperionDeck.jsx +79 -0
- package/lib/tui/views/LucidDeck.jsx +209 -217
- package/package.json +4 -1
- package/lib/core/websiteSession.js +0 -148
package/lib/core/client.js
CHANGED
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
import { resolveActing, resolveActingToken } from './actingAs.js';
|
|
13
13
|
import { detectCallerSource } from './callerSource.js';
|
|
14
14
|
import { QuoxApiError, QuoxNetworkError, QuoxAuthError, QuoxTransientAuthError } from './errors.js';
|
|
15
|
+
import { joinServiceUrl, isLocalServiceTarget } from './config.js';
|
|
15
16
|
|
|
16
17
|
const RETRY_STATUSES = new Set([429, 502, 503]);
|
|
17
18
|
const MAX_RETRIES = 3;
|
|
@@ -33,8 +34,9 @@ export function describeNetworkError(service, baseUrl, err) {
|
|
|
33
34
|
const code = err?.cause?.code || err?.code;
|
|
34
35
|
let msg = `Could not connect to ${service} service at ${baseUrl}: ${err.message}`;
|
|
35
36
|
if (code && TLS_TRUST_CODES.has(code)) {
|
|
36
|
-
msg += `\n TLS: certificate not trusted (${code}).
|
|
37
|
-
+ `\n
|
|
37
|
+
msg += `\n TLS: certificate not trusted (${code}). A new QuoxCORE uses its own self-signed certificate; trust that file`
|
|
38
|
+
+ `\n (on the server, by default ~/quox-install/quox/ssl/quox.crt; copy it here if the server is another machine):`
|
|
39
|
+
+ `\n export NODE_EXTRA_CA_CERTS=<path to quox.crt>`
|
|
38
40
|
+ `\n Never disable TLS verification (NODE_TLS_REJECT_UNAUTHORIZED=0) instead.`;
|
|
39
41
|
} else if (code === 'ERR_SSL_WRONG_VERSION_NUMBER' || code === 'EPROTO') {
|
|
40
42
|
// quox#532 defect 1: the profile says https:// but the service answered
|
|
@@ -128,10 +130,13 @@ export function createClient(config, auth) {
|
|
|
128
130
|
noAuth = false,
|
|
129
131
|
noActAs = false,
|
|
130
132
|
sensitive = false,
|
|
133
|
+
// HYPERION #23: an outside-caller request (trigger webhook-fire) also drops the caller labels
|
|
134
|
+
// (X-Quox-Session / X-Quox-Profile): the receiving route may store every header in an org-readable record.
|
|
135
|
+
anonymous = false,
|
|
131
136
|
} = options;
|
|
132
137
|
|
|
133
138
|
const baseUrl = config.getServiceUrl(service);
|
|
134
|
-
const url =
|
|
139
|
+
const url = joinServiceUrl(baseUrl, path);
|
|
135
140
|
|
|
136
141
|
if (query) {
|
|
137
142
|
for (const [key, value] of Object.entries(query)) {
|
|
@@ -142,7 +147,7 @@ export function createClient(config, auth) {
|
|
|
142
147
|
const fetchHeaders = {
|
|
143
148
|
'X-Quox-Correlation': correlationId,
|
|
144
149
|
'X-Quox-Client': 'cli',
|
|
145
|
-
...callerContextHeaders(config),
|
|
150
|
+
...(anonymous ? {} : callerContextHeaders(config)),
|
|
146
151
|
...extraHeaders,
|
|
147
152
|
};
|
|
148
153
|
|
|
@@ -177,7 +182,7 @@ export function createClient(config, auth) {
|
|
|
177
182
|
let usingServiceKey = false;
|
|
178
183
|
// quox#772: opt-in acting identity (--as / QUOX_AS). Replaces the bearer
|
|
179
184
|
// with the agent's attach token; throws (no request sent) if not attached.
|
|
180
|
-
const acting = !noAuth && !noActAs ? await resolveActing() : { name: null };
|
|
185
|
+
const acting = !noAuth && !noActAs ? await resolveActing({ explicitProfile: Boolean(config._explicitProfile) }) : { name: null };
|
|
181
186
|
const actingAs = acting.name;
|
|
182
187
|
if (actingAs) {
|
|
183
188
|
const heartbeat = {
|
|
@@ -195,7 +200,10 @@ export function createClient(config, auth) {
|
|
|
195
200
|
usingServiceKey = true;
|
|
196
201
|
} else if (tokens?.access_token) {
|
|
197
202
|
fetchHeaders['Authorization'] = `Bearer ${tokens.access_token}`;
|
|
198
|
-
} else if (serviceKey) {
|
|
203
|
+
} else if (serviceKey && isLocalServiceTarget(url.href)) {
|
|
204
|
+
// QUOXON-Z2H lane R: the implicit fallback is THIS box's key; never
|
|
205
|
+
// offer it to another host (a remote profile with no login). A caller
|
|
206
|
+
// that owns a remote key says so with QUOX_FORCE_SERVICE_KEY.
|
|
199
207
|
fetchHeaders['X-Service-Key'] = serviceKey;
|
|
200
208
|
usingServiceKey = true;
|
|
201
209
|
}
|
|
@@ -218,6 +226,10 @@ export function createClient(config, auth) {
|
|
|
218
226
|
}
|
|
219
227
|
|
|
220
228
|
let lastError;
|
|
229
|
+
let staleReloaded = false;
|
|
230
|
+
// REL-424: one refresh per request, on whichever attempt the 401 lands. A token that expires while the
|
|
231
|
+
// client sleeps between 503 retries gets its 401 on attempt 1+, and gating refresh on attempt 0 surfaced it.
|
|
232
|
+
let refreshedThisRequest = false;
|
|
221
233
|
|
|
222
234
|
for (let attempt = 0; attempt <= retries; attempt++) {
|
|
223
235
|
try {
|
|
@@ -255,10 +267,32 @@ export function createClient(config, auth) {
|
|
|
255
267
|
// profile over what is really just "wrong token for this route".
|
|
256
268
|
// In that case, skip the refresh and fall through to the normal
|
|
257
269
|
// error handling below so the original 401/403 is surfaced as-is.
|
|
258
|
-
|
|
270
|
+
let looksExpired = typeof auth.isExpired === 'function' ? auth.isExpired() : true;
|
|
271
|
+
// The SERVER saying "invalid or expired token" outranks the local clock. expires_at is
|
|
272
|
+
// stamped after the refresh response lands, so for a moment the server rejects a token
|
|
273
|
+
// the client still thinks is good. 2026-10-14 07:32:03: three windows hit that gap
|
|
274
|
+
// together, skipped the refresh and declared the profile signed out. Refresh is safe
|
|
275
|
+
// here: a dead refresh token is still judged by auth.refresh() itself.
|
|
276
|
+
if (res.status === 401 && !looksExpired && !noAuth && !actingAs && !usingServiceKey) {
|
|
277
|
+
try { looksExpired = (await res.clone().json())?.error === 'invalid_token'; } catch { /* non-JSON body */ }
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// Stale sign-in: a sibling window or `quox login` may have written a newer token for this profile.
|
|
281
|
+
// Adopt it and retry this request ONCE before any refresh (which would spend our stale refresh token).
|
|
282
|
+
if (res.status === 401 && !noAuth && !actingAs && !usingServiceKey && !staleReloaded
|
|
283
|
+
&& typeof config.reloadAuth === 'function' && config.reloadAuth()) {
|
|
284
|
+
staleReloaded = true;
|
|
285
|
+
const fresh = auth.getTokens?.();
|
|
286
|
+
if (fresh?.access_token) {
|
|
287
|
+
fetchHeaders['Authorization'] = `Bearer ${fresh.access_token}`;
|
|
288
|
+
attempt--; // this retry is free: it does not spend the caller's retry budget
|
|
289
|
+
continue;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
259
292
|
|
|
260
293
|
const refreshCoolingDown = refreshFailedAt && (Date.now() - refreshFailedAt < REFRESH_RETRY_COOLDOWN_MS);
|
|
261
|
-
if (res.status === 401 && !noAuth && !actingAs &&
|
|
294
|
+
if (res.status === 401 && !noAuth && !actingAs && !refreshedThisRequest && !refreshCoolingDown && looksExpired) {
|
|
295
|
+
refreshedThisRequest = true;
|
|
262
296
|
try {
|
|
263
297
|
if (!refreshing) {
|
|
264
298
|
const serviceUrl = config.getServiceUrl('auth');
|
|
@@ -288,16 +322,25 @@ export function createClient(config, auth) {
|
|
|
288
322
|
if (refreshErr && refreshErr.transient) {
|
|
289
323
|
// Auth is only temporarily unavailable (429 / restart): no
|
|
290
324
|
// cooldown, and keep the type so a listener can back off.
|
|
291
|
-
throw new QuoxTransientAuthError(`
|
|
325
|
+
throw new QuoxTransientAuthError(`Quox server is slow or unreachable, retrying. ${refreshErr.message}`, refreshErr.retryAfterMs ?? null);
|
|
292
326
|
}
|
|
293
327
|
refreshFailedAt = Date.now();
|
|
294
328
|
const why = refreshErr?.message ? ` ${refreshErr.message}` : '';
|
|
295
|
-
|
|
329
|
+
const dead = new QuoxAuthError(`Session expired. Run \`quox login\` to re-authenticate.${why}`);
|
|
330
|
+
if (refreshErr?.code) dead.code = refreshErr.code;
|
|
331
|
+
if (refreshErr?.reason) dead.reason = refreshErr.reason;
|
|
332
|
+
throw dead;
|
|
296
333
|
}
|
|
297
334
|
}
|
|
298
335
|
|
|
299
336
|
// Retry transient errors
|
|
337
|
+
// quox#882: the server can say a failure won't clear by itself (a downstream refusing
|
|
338
|
+
// connections): body retryable:false skips the backoff retries.
|
|
339
|
+
let noRetry = false;
|
|
300
340
|
if (RETRY_STATUSES.has(res.status) && attempt < retries) {
|
|
341
|
+
try { noRetry = (await res.clone().json())?.retryable === false; } catch { /* not JSON */ }
|
|
342
|
+
}
|
|
343
|
+
if (RETRY_STATUSES.has(res.status) && attempt < retries && !noRetry) {
|
|
301
344
|
const delay = RETRY_DELAYS[attempt] || 9000;
|
|
302
345
|
if (isDebug) {
|
|
303
346
|
console.error(` Retrying in ${delay}ms (attempt ${attempt + 1}/${retries})...`);
|
|
@@ -315,7 +358,8 @@ export function createClient(config, auth) {
|
|
|
315
358
|
try {
|
|
316
359
|
data = await res.json();
|
|
317
360
|
} catch {
|
|
318
|
-
|
|
361
|
+
// CLI-ERRORS: a proxy error page, not a Quox body; named so the renderer can say so
|
|
362
|
+
data = { error: 'non_json_response', message: res.statusText };
|
|
319
363
|
}
|
|
320
364
|
// A permission error while we silently fell back to the SERVICE
|
|
321
365
|
// KEY is almost never a permissions problem: the service key
|
|
@@ -327,7 +371,10 @@ export function createClient(config, auth) {
|
|
|
327
371
|
// theory about who could grant it. He had held instance-admin
|
|
328
372
|
// since March. His session had simply expired and the CLI had
|
|
329
373
|
// quietly downgraded the request to a key with no user attached.
|
|
330
|
-
|
|
374
|
+
// CLI-ERRORS: a body with only a prose `error` ("Agent not found") leads with that, never
|
|
375
|
+
// "Request failed with status N" (the real reason used to sit on the dim Status line).
|
|
376
|
+
const proseError = !data.message && typeof data.error === 'string' && /\s/.test(data.error);
|
|
377
|
+
let message = data.message || (proseError ? data.error : `Request failed with status ${res.status}`);
|
|
331
378
|
// quox#617: surface the server's machine-readable `reason`. A vault
|
|
332
379
|
// update returns 403 with `reason: 'mfa_required'` for an MFA-enabled
|
|
333
380
|
// owner (a step-up gate) vs `reason: 'role_<r>_lacks_capability_<cap>'`
|
|
@@ -344,18 +391,38 @@ export function createClient(config, auth) {
|
|
|
344
391
|
+ `The service key has no user identity, so anything gated on WHO you are will refuse it regardless of your real permissions. `
|
|
345
392
|
+ `Run \`quox login\` (or \`quox -p <profile> login\`) and retry before treating this as a permissions problem.`;
|
|
346
393
|
}
|
|
394
|
+
// #1012: the paid backends (QuoxPlan, QuoxSEO, QLarity, QuoxSignal) are not part of the public
|
|
395
|
+
// install, so a licensed customer got a bare 502 from the collector proxy. This client cannot
|
|
396
|
+
// tell "not installed" from "installed but down", so it says both, and exits 1 (not 8).
|
|
397
|
+
const pluginDown = (res.status === 502 || res.status === 503) && typeof data.error === 'string'
|
|
398
|
+
&& /^(QuoxPlan|QuoxSEO|QLarity|QuoxSignal) service unavailable$/i.exec(data.error);
|
|
399
|
+
if (pluginDown) {
|
|
400
|
+
const err = new QuoxApiError(
|
|
401
|
+
res.status,
|
|
402
|
+
'PLUGIN_SERVICE_UNAVAILABLE',
|
|
403
|
+
`${pluginDown[1]} isn't reachable on this server. It isn't part of the public install (it ships separately), `
|
|
404
|
+
+ 'or it is installed but down: run `quox health`. Nothing was changed.',
|
|
405
|
+
data.detail || data.details,
|
|
406
|
+
data
|
|
407
|
+
);
|
|
408
|
+
err.exitCode = 1;
|
|
409
|
+
throw err;
|
|
410
|
+
}
|
|
347
411
|
// LL-3d: carry the WHOLE body, not just message/detail. Endpoints
|
|
348
412
|
// that answer with structured context (the licensing 409's
|
|
349
413
|
// bound_instance_name / bound[] / max_servers, the 402's plugin)
|
|
350
414
|
// had it flattened away here, so command handlers could only print
|
|
351
415
|
// "Request failed with status 409".
|
|
352
|
-
|
|
416
|
+
const apiErr = new QuoxApiError(
|
|
353
417
|
res.status,
|
|
354
418
|
data.error || 'api_error',
|
|
355
419
|
message,
|
|
356
420
|
data.detail || data.details,
|
|
357
421
|
data
|
|
358
422
|
);
|
|
423
|
+
if (proseError) apiErr.reasonFromError = true;
|
|
424
|
+
if (usingServiceKey && (res.status === 403 || res.status === 401)) apiErr.serviceKeyFallback = true;
|
|
425
|
+
throw apiErr;
|
|
359
426
|
}
|
|
360
427
|
|
|
361
428
|
// Handle empty responses (204 No Content)
|
|
@@ -431,7 +498,7 @@ export function createClient(config, auth) {
|
|
|
431
498
|
} = options;
|
|
432
499
|
|
|
433
500
|
const baseUrl = config.getServiceUrl(service);
|
|
434
|
-
const url =
|
|
501
|
+
const url = joinServiceUrl(baseUrl, path);
|
|
435
502
|
|
|
436
503
|
if (query) {
|
|
437
504
|
for (const [key, value] of Object.entries(query)) {
|
|
@@ -449,7 +516,7 @@ export function createClient(config, auth) {
|
|
|
449
516
|
// Same precedence as request() above, including the QUOX_FORCE_SERVICE_KEY
|
|
450
517
|
// override for daemons that own their key and must not borrow the shared
|
|
451
518
|
// config's stored session.
|
|
452
|
-
const streamActingR = await resolveActing();
|
|
519
|
+
const streamActingR = await resolveActing({ explicitProfile: Boolean(config._explicitProfile) });
|
|
453
520
|
const streamActing = streamActingR.name;
|
|
454
521
|
const streamTokens = auth.getTokens();
|
|
455
522
|
let streamServiceKey = null;
|
|
@@ -462,7 +529,7 @@ export function createClient(config, auth) {
|
|
|
462
529
|
fetchHeaders['X-Service-Key'] = streamServiceKey;
|
|
463
530
|
} else if (streamTokens?.access_token) {
|
|
464
531
|
fetchHeaders['Authorization'] = `Bearer ${streamTokens.access_token}`;
|
|
465
|
-
} else if (process.env.QUOX_SERVICE_KEY) {
|
|
532
|
+
} else if (process.env.QUOX_SERVICE_KEY && isLocalServiceTarget(url.href)) {
|
|
466
533
|
streamServiceKey = process.env.QUOX_SERVICE_KEY;
|
|
467
534
|
fetchHeaders['X-Service-Key'] = streamServiceKey;
|
|
468
535
|
}
|
|
@@ -472,22 +539,61 @@ export function createClient(config, auth) {
|
|
|
472
539
|
console.error(`-> ${streamBody !== undefined ? 'POST' : 'GET'}(stream) ${url.toString()}`);
|
|
473
540
|
}
|
|
474
541
|
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
542
|
+
const openStream = async () => {
|
|
543
|
+
try {
|
|
544
|
+
return await fetch(url.toString(), {
|
|
545
|
+
method: streamBody !== undefined ? 'POST' : 'GET',
|
|
546
|
+
headers: fetchHeaders,
|
|
547
|
+
...(streamBody !== undefined ? { body: JSON.stringify(streamBody) } : {}),
|
|
548
|
+
signal: externalSignal,
|
|
549
|
+
// Same redirect protection as the non-streaming path above: never let
|
|
550
|
+
// a 3xx resend X-Service-Key to a host we did not choose.
|
|
551
|
+
...(streamServiceKey ? { redirect: 'manual' } : {}),
|
|
552
|
+
});
|
|
553
|
+
} catch (err) {
|
|
554
|
+
throw new QuoxNetworkError(
|
|
555
|
+
describeNetworkError(service, baseUrl, err),
|
|
556
|
+
err
|
|
557
|
+
);
|
|
558
|
+
}
|
|
559
|
+
};
|
|
560
|
+
let res = await openStream();
|
|
561
|
+
|
|
562
|
+
// 2026-10-16: request() refreshed an expired session on a 401, the stream (the chat path) did not,
|
|
563
|
+
// so a chat turn right after the access token expired read "Not signed in" on a signed-in profile.
|
|
564
|
+
// Same rule as request(): one refresh for a user token that looks expired (locally, or the server
|
|
565
|
+
// says invalid_token), shared with any refresh already in flight, then one retry.
|
|
566
|
+
const usesUserToken = !streamActing && !streamServiceKey && !!streamTokens?.access_token;
|
|
567
|
+
if (res.status === 401 && usesUserToken && typeof config.reloadAuth === 'function' && config.reloadAuth()) {
|
|
568
|
+
// Stale sign-in: a sibling window or `quox login` wrote a newer token. Adopt it, retry once.
|
|
569
|
+
const fresh = auth.getTokens?.();
|
|
570
|
+
if (fresh?.access_token) { fetchHeaders['Authorization'] = `Bearer ${fresh.access_token}`; res = await openStream(); }
|
|
571
|
+
}
|
|
572
|
+
if (res.status === 401 && usesUserToken) {
|
|
573
|
+
let looksExpired = typeof auth.isExpired === 'function' ? auth.isExpired() : true;
|
|
574
|
+
if (!looksExpired) {
|
|
575
|
+
try { looksExpired = (await res.clone().json())?.error === 'invalid_token'; } catch { /* non-JSON body */ }
|
|
576
|
+
}
|
|
577
|
+
const refreshCoolingDown = refreshFailedAt && (Date.now() - refreshFailedAt < REFRESH_RETRY_COOLDOWN_MS);
|
|
578
|
+
if (looksExpired && !refreshCoolingDown) {
|
|
579
|
+
try {
|
|
580
|
+
if (!refreshing) {
|
|
581
|
+
refreshing = auth.refresh(config.getServiceUrl('auth')).finally(() => { refreshing = null; });
|
|
582
|
+
}
|
|
583
|
+
await refreshing;
|
|
584
|
+
} catch (refreshErr) {
|
|
585
|
+
if (refreshErr && refreshErr.transient) {
|
|
586
|
+
throw new QuoxTransientAuthError(`Quox server is slow or unreachable, retry shortly. ${refreshErr.message}`, refreshErr.retryAfterMs ?? null);
|
|
587
|
+
}
|
|
588
|
+
refreshFailedAt = Date.now();
|
|
589
|
+
const dead = new QuoxAuthError(`Session expired. Run \`quox login\` to re-authenticate.${refreshErr?.message ? ` ${refreshErr.message}` : ''}`);
|
|
590
|
+
dead.status = 401;
|
|
591
|
+
throw dead;
|
|
592
|
+
}
|
|
593
|
+
const fresh = auth.getTokens();
|
|
594
|
+
if (fresh?.access_token) fetchHeaders['Authorization'] = `Bearer ${fresh.access_token}`;
|
|
595
|
+
res = await openStream();
|
|
596
|
+
}
|
|
491
597
|
}
|
|
492
598
|
|
|
493
599
|
if (!res.ok) {
|
|
@@ -495,15 +601,18 @@ export function createClient(config, auth) {
|
|
|
495
601
|
try {
|
|
496
602
|
data = await res.json();
|
|
497
603
|
} catch {
|
|
498
|
-
data = { error: '
|
|
604
|
+
data = { error: 'non_json_response', message: res.statusText };
|
|
499
605
|
}
|
|
500
|
-
|
|
606
|
+
const proseError = !data.message && typeof data.error === 'string' && /\s/.test(data.error);
|
|
607
|
+
const streamErr = new QuoxApiError(
|
|
501
608
|
res.status,
|
|
502
609
|
data.error || 'api_error',
|
|
503
|
-
data.message || `Stream request failed with status ${res.status}
|
|
610
|
+
data.message || (proseError ? data.error : `Stream request failed with status ${res.status}`),
|
|
504
611
|
data.detail || data.details,
|
|
505
612
|
data
|
|
506
613
|
);
|
|
614
|
+
if (proseError) streamErr.reasonFromError = true;
|
|
615
|
+
throw streamErr;
|
|
507
616
|
}
|
|
508
617
|
|
|
509
618
|
if (!res.body) {
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The CLI's command groups, in registration order (moved verbatim from bin/quox.js so
|
|
3
|
+
* one list feeds both the binary and cli/scripts/export-leaf-tree.mjs, which exports the
|
|
4
|
+
* machine-readable leaf tree the collector's quox_cli tool validates against).
|
|
5
|
+
* Paths are relative to THIS file; registerCommandGroups resolves them against the
|
|
6
|
+
* baseUrl it is given (COMMAND_REGISTRARS_BASE).
|
|
7
|
+
*/
|
|
8
|
+
export const COMMAND_REGISTRARS = [
|
|
9
|
+
['../commands/auth.js', 'registerAuthCommands'],
|
|
10
|
+
['../commands/service.js', 'registerServiceCommands'],
|
|
11
|
+
['../commands/status.js', 'registerStatusCommand'],
|
|
12
|
+
['../commands/doctor.js', 'registerDoctorCommand'],
|
|
13
|
+
['../commands/server.js', 'registerServerCommands'],
|
|
14
|
+
['../commands/update.js', 'registerUpdateCommand'],
|
|
15
|
+
['../commands/open.js', 'registerOpenCommand'],
|
|
16
|
+
['../commands/peek.js', 'registerPeekCommand'],
|
|
17
|
+
['../commands/aside.js', 'registerAsideCommand'],
|
|
18
|
+
['../commands/motion.js', 'registerMotionCommand'],
|
|
19
|
+
['../commands/welcome.js', 'registerWelcomeCommand'],
|
|
20
|
+
['../commands/config-cmd.js', 'registerConfigCommands'],
|
|
21
|
+
['../commands/org.js', 'registerOrgCommands'],
|
|
22
|
+
['../commands/invites.js', 'registerInvitesCommands'],
|
|
23
|
+
['../commands/team.js', 'registerTeamCommands'],
|
|
24
|
+
['../commands/department.js', 'registerDepartmentCommands'],
|
|
25
|
+
['../commands/agent.js', 'registerAgentCommands'],
|
|
26
|
+
['../commands/tool.js', 'registerToolCommands'],
|
|
27
|
+
['../commands/workflow.js', 'registerWorkflowCommands'],
|
|
28
|
+
['../commands/objective.js', 'registerObjectiveCommands'],
|
|
29
|
+
['../commands/assistant.js', 'registerAssistantCommands'],
|
|
30
|
+
['../commands/fleet.js', 'registerFleetCommands'],
|
|
31
|
+
['../commands/chat.js', 'registerChatCommands'],
|
|
32
|
+
['../commands/memory.js', 'registerMemoryCommands'],
|
|
33
|
+
['../commands/actions.js', 'registerActionsCommands'],
|
|
34
|
+
['../commands/brain.js', 'registerBrainCommands'],
|
|
35
|
+
['../commands/entity.js', 'registerEntityCommands'],
|
|
36
|
+
['../commands/backup.js', 'registerBackupCommands'],
|
|
37
|
+
['../commands/inbox.js', 'registerInboxCommands'],
|
|
38
|
+
['../commands/approvals.js', 'registerApprovalsCommands'],
|
|
39
|
+
['../commands/vm.js', 'registerVmCommands'],
|
|
40
|
+
['../commands/router.js', 'registerRouterCommands'],
|
|
41
|
+
['../commands/mail.js', 'registerMailCommands'],
|
|
42
|
+
['../commands/calendar.js', 'registerCalendarCommands'],
|
|
43
|
+
['../commands/notes.js', 'registerNotesCommands'],
|
|
44
|
+
['../commands/contacts.js', 'registerContactsCommands'],
|
|
45
|
+
['../commands/licence.js', 'registerLicenceCommands'],
|
|
46
|
+
['../commands/run.js', 'registerRunCommands'],
|
|
47
|
+
['../commands/file.js', 'registerFileCommands'],
|
|
48
|
+
['../commands/admin.js', 'registerAdminCommands'],
|
|
49
|
+
['../commands/aee.js', 'registerAeeCommands'],
|
|
50
|
+
['../commands/integration.js', 'registerIntegrationCommands'],
|
|
51
|
+
['../commands/consent.js', 'registerConsentCommands'],
|
|
52
|
+
['../commands/setup.js', 'registerSetupCommands'],
|
|
53
|
+
['../commands/watch.js', 'registerWatchCommands'],
|
|
54
|
+
['../commands/tag.js', 'registerTagCommands'],
|
|
55
|
+
['../commands/scope.js', 'registerScopeCommands'],
|
|
56
|
+
['../commands/notification.js', 'registerNotificationCommands'],
|
|
57
|
+
['../commands/prompt.js', 'registerPromptCommands'],
|
|
58
|
+
['../commands/plugin.js', 'registerPluginCommands'],
|
|
59
|
+
['../commands/powers.js', 'registerPowersCommands'],
|
|
60
|
+
['../commands/conversation.js', 'registerConversationCommands'],
|
|
61
|
+
['../commands/vault.js', 'registerVaultCommands'],
|
|
62
|
+
['../commands/volt.js', 'registerVoltCommands'],
|
|
63
|
+
['../commands/governance.js', 'registerGovernanceCommands'],
|
|
64
|
+
['../commands/skills.js', 'registerSkillsCommands'],
|
|
65
|
+
['../commands/claude.js', 'registerClaudeCommands'],
|
|
66
|
+
['../commands/monitoring.js', 'registerMonitoringCommands'],
|
|
67
|
+
['../commands/trigger.js', 'registerTriggerCommands'],
|
|
68
|
+
['../commands/webhook.js', 'registerWebhookCommands'],
|
|
69
|
+
['../commands/sso.js', 'registerSsoCommands'],
|
|
70
|
+
['../commands/role.js', 'registerRoleCommands'],
|
|
71
|
+
['../commands/domain.js', 'registerDomainCommands'],
|
|
72
|
+
['../commands/security.js', 'registerSecurityCommands'],
|
|
73
|
+
['../commands/safety.js', 'registerSafetyCommands'],
|
|
74
|
+
['../commands/scim.js', 'registerScimCommands'],
|
|
75
|
+
['../commands/llm-policy.js', 'registerLlmPolicyCommands'],
|
|
76
|
+
['../commands/hat.js', 'registerHatCommands'],
|
|
77
|
+
['../commands/rows.js', 'registerRowsCommands'],
|
|
78
|
+
['../commands/settings.js', 'registerSettingsCommands'],
|
|
79
|
+
['../commands/llm.js', 'registerLlmCommands'],
|
|
80
|
+
['../commands/capabilities.js', 'registerCapabilitiesCommands'],
|
|
81
|
+
['../commands/provider.js', 'registerProviderCommands'],
|
|
82
|
+
['../commands/tips.js', 'registerTipsCommands'],
|
|
83
|
+
['../commands/quoxchat.js', 'registerQuoxchatCommands'],
|
|
84
|
+
['../commands/budget.js', 'registerBudgetCommands'],
|
|
85
|
+
['../commands/trace.js', 'registerTraceCommands'],
|
|
86
|
+
['../commands/mon.js', 'registerMonCommands'],
|
|
87
|
+
['../commands/quoxbox.js', 'registerQuoxboxCommands'],
|
|
88
|
+
['../commands/plan.js', 'registerPlanCommands'],
|
|
89
|
+
['../commands/engagement.js', 'registerEngagementCommands'],
|
|
90
|
+
['../commands/done.js', 'registerDoneCommands'],
|
|
91
|
+
['../commands/discord.js', 'registerDiscordCommands'],
|
|
92
|
+
['../commands/qlarity.js', 'registerQlarityCommands'],
|
|
93
|
+
['../commands/fetchAllowlist.js', 'registerFetchAllowlistCommands'],
|
|
94
|
+
['../commands/reach.js', 'registerReachCommands'],
|
|
95
|
+
['../commands/inventory.js', 'registerInventoryCommands'],
|
|
96
|
+
['../commands/policy.js', 'registerPolicyCommands'],
|
|
97
|
+
['../commands/quoxseo.js', 'registerQuoxseoCommands'],
|
|
98
|
+
['../commands/voice.js', 'registerVoiceCommands'],
|
|
99
|
+
['../commands/awareness.js', 'registerAwarenessCommands'],
|
|
100
|
+
['../commands/mind.js', 'registerMindCommands'],
|
|
101
|
+
['../commands/lucid.js', 'registerLucidCommands'],
|
|
102
|
+
['../commands/hyperion.js', 'registerHyperionCommands'],
|
|
103
|
+
['../commands/tg.js', 'registerTgCommands'],
|
|
104
|
+
['../commands/dev.js', 'registerDevCommands'],
|
|
105
|
+
['../commands/build.js', 'registerBuildCommands'],
|
|
106
|
+
['../commands/slot.js', 'registerSlotCommands'],
|
|
107
|
+
['../commands/gripe.js', 'registerGripeCommands'],
|
|
108
|
+
['../commands/ask.js', 'registerAskCommands'],
|
|
109
|
+
['../commands/presence.js', 'registerPresenceCommands'],
|
|
110
|
+
['../commands/dashboard.js', 'registerDashboardCommands'],
|
|
111
|
+
['../commands/nucleus.js', 'registerNucleusCommands'],
|
|
112
|
+
['../commands/onboard.js', 'registerOnboardCommands'],
|
|
113
|
+
['../commands/x.js', 'registerXCommands'],
|
|
114
|
+
['../commands/linkedin.js', 'registerLinkedInCommands'],
|
|
115
|
+
['../commands/facebook.js', 'registerFacebookCommands'],
|
|
116
|
+
['../commands/instagram.js', 'registerInstagramCommands'],
|
|
117
|
+
['../commands/tiktok.js', 'registerTikTokCommands'],
|
|
118
|
+
['../commands/offload.js', 'registerOffloadCommands'],
|
|
119
|
+
];
|
|
120
|
+
|
|
121
|
+
export const COMMAND_REGISTRARS_BASE = import.meta.url;
|