quoxcli 0.2.0 → 0.4.0
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/actions.js +15 -0
- package/lib/api/endpoints/admin.js +46 -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/blog.js +16 -0
- package/lib/api/endpoints/chat.js +14 -0
- package/lib/api/endpoints/contactsq.js +6 -0
- package/lib/api/endpoints/conversations.js +14 -0
- package/lib/api/endpoints/devServerRegistry.js +25 -0
- package/lib/api/endpoints/engagement.js +26 -0
- package/lib/api/endpoints/evidence.js +20 -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 +44 -0
- package/lib/api/endpoints/inbox.js +20 -0
- package/lib/api/endpoints/index.js +26 -0
- package/lib/api/endpoints/loops.js +26 -0
- package/lib/api/endpoints/memory.js +19 -0
- package/lib/api/endpoints/mind.js +13 -0
- package/lib/api/endpoints/mon.js +179 -0
- package/lib/api/endpoints/objectives.js +12 -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 +12 -0
- package/lib/api/endpoints/router.js +17 -0
- package/lib/api/endpoints/safety.js +7 -0
- package/lib/api/endpoints/server.js +21 -0
- package/lib/api/endpoints/setup.js +6 -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 +120 -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 +131 -0
- package/lib/commands/admin.js +16 -0
- package/lib/commands/agent.js +170 -27
- package/lib/commands/agentAttach.js +140 -13
- package/lib/commands/approvals.js +78 -1
- package/lib/commands/aside.js +24 -0
- package/lib/commands/ask.js +146 -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/blog.js +110 -0
- package/lib/commands/brain.js +105 -14
- package/lib/commands/build.js +19 -5
- package/lib/commands/chat.js +180 -26
- package/lib/commands/chatCompanion.js +27 -0
- package/lib/commands/chatState.js +45 -0
- package/lib/commands/claude.js +35 -38
- package/lib/commands/contacts.js +134 -4
- package/lib/commands/contactsSendAt.js +21 -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 +107 -6
- package/lib/commands/evidence.js +154 -0
- package/lib/commands/ext.js +23 -0
- package/lib/commands/fetchAllowlist.js +71 -0
- package/lib/commands/file.js +38 -1
- package/lib/commands/fleet.js +336 -4
- package/lib/commands/governance.js +12 -3
- package/lib/commands/gripe.js +44 -13
- package/lib/commands/hat.js +254 -9
- package/lib/commands/helpers.js +18 -1
- package/lib/commands/hyperion.js +58 -0
- package/lib/commands/image.js +68 -0
- package/lib/commands/inbox.js +74 -23
- package/lib/commands/kuma.js +1050 -0
- package/lib/commands/kumaSla.js +174 -0
- package/lib/commands/licence.js +52 -155
- package/lib/commands/loop.js +291 -0
- 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 +59 -1
- package/lib/commands/offload.js +89 -0
- package/lib/commands/onboard.js +2 -1
- package/lib/commands/org.js +30 -3
- 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 +217 -0
- package/lib/commands/qlarity.js +19 -3
- package/lib/commands/quoxchat.js +13 -4
- package/lib/commands/quoxseo.js +69 -0
- package/lib/commands/reach.js +2 -1
- package/lib/commands/research.js +79 -0
- package/lib/commands/router.js +77 -0
- package/lib/commands/rows.js +367 -0
- package/lib/commands/run.js +82 -10
- package/lib/commands/scope.js +2 -2
- package/lib/commands/server.js +405 -0
- package/lib/commands/setup.js +33 -7
- package/lib/commands/shortcut.js +41 -0
- package/lib/commands/skills.js +68 -9
- 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/commands/youtube.js +172 -0
- package/lib/core/actingAs.js +2 -2
- package/lib/core/actionItems.js +115 -0
- package/lib/core/activeSummary.js +186 -0
- package/lib/core/agentAlias.js +2 -0
- package/lib/core/agentFamilies.js +7 -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 +152 -35
- package/lib/core/commandRegistrars.js +130 -0
- package/lib/core/config.js +143 -25
- package/lib/core/customHats.js +168 -0
- package/lib/core/doneEngineFetch.js +8 -0
- package/lib/core/errors.js +329 -20
- package/lib/core/findingReconcile.js +90 -0
- package/lib/core/global-flags.js +105 -5
- package/lib/core/governedSession.js +1 -3
- package/lib/core/hyperion.js +102 -0
- package/lib/core/inboxResolution.js +65 -0
- package/lib/core/lazyCommands.js +4 -1
- package/lib/core/liveSessionsReport.js +1 -1
- package/lib/core/loopGlyph.js +97 -0
- package/lib/core/offload.js +533 -0
- package/lib/core/org-override.js +1 -1
- package/lib/core/orgResolve.js +70 -0
- package/lib/core/output.js +11 -11
- 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/sessionHeartbeat.js +12 -8
- package/lib/core/sessionsOverview.js +64 -0
- package/lib/core/shortcut.js +83 -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 +366 -0
- package/lib/core/vmTui.js +86 -0
- package/lib/core/walkthrough.js +35 -0
- package/lib/dragon/companionView.js +34 -0
- package/lib/dragon/frames.js +118 -0
- package/lib/dragon/jobs.js +123 -0
- package/lib/dragon/renderWorker.js +15 -0
- package/lib/dragon/select.js +72 -0
- package/lib/evidence/bundleExport.js +47 -0
- package/lib/evidence/bundleVerify.js +77 -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/EXTENSIONS.md +23 -0
- package/lib/quoxon/activity.js +14 -2
- package/lib/quoxon/aocl.js +117 -0
- package/lib/quoxon/api.js +690 -25
- package/lib/quoxon/app.js +3586 -250
- package/lib/quoxon/approvalFacts.js +96 -0
- package/lib/quoxon/approvalSentence.js +44 -0
- package/lib/quoxon/approvalsView.js +384 -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 +124 -0
- package/lib/quoxon/commands.js +429 -39
- 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 +20 -0
- package/lib/quoxon/deck.js +30 -0
- package/lib/quoxon/demo.autoplay.js +29 -0
- package/lib/quoxon/demo.fixtures.js +1 -1
- package/lib/quoxon/demo.js +46 -3
- package/lib/quoxon/devObserver.js +20 -0
- package/lib/quoxon/dividerAocl.js +192 -0
- package/lib/quoxon/dividerRule.js +57 -0
- package/lib/quoxon/dragonCompanion.js +255 -0
- package/lib/quoxon/dragonModes.js +151 -0
- package/lib/quoxon/dragonMoments.js +82 -0
- package/lib/quoxon/dragonReads.js +68 -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 +266 -26
- package/lib/quoxon/extensions.js +276 -0
- 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/inboxView.js +79 -0
- package/lib/quoxon/indicator.js +6 -2
- package/lib/quoxon/installCli.js +79 -0
- package/lib/quoxon/intent.js +12 -1
- package/lib/quoxon/keys.js +51 -8
- package/lib/quoxon/launcher.js +103 -0
- package/lib/quoxon/loopCard.js +456 -0
- package/lib/quoxon/loopCards.js +180 -0
- package/lib/quoxon/loopStream.js +87 -0
- package/lib/quoxon/main.js +101 -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 +222 -0
- package/lib/quoxon/noai.js +57 -0
- package/lib/quoxon/objectiveOffer.js +82 -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 +46 -7
- package/lib/quoxon/objects/mirror.js +137 -0
- package/lib/quoxon/objects/model.js +33 -0
- package/lib/quoxon/objects/peek.js +17 -0
- package/lib/quoxon/objects/proof.js +12 -4
- 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/shortcutOffer.js +28 -0
- package/lib/quoxon/onboarding/view.js +76 -19
- package/lib/quoxon/orgLabel.js +10 -0
- package/lib/quoxon/orgSwitch.js +59 -0
- package/lib/quoxon/ownBroadcast.js +22 -0
- package/lib/quoxon/palette.js +43 -0
- package/lib/quoxon/planSteps.js +51 -0
- package/lib/quoxon/print.js +12 -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 +62 -0
- package/lib/quoxon/qdragonScenes.js +282 -0
- package/lib/quoxon/qeyes.js +213 -0
- package/lib/quoxon/qmotion.js +75 -37
- package/lib/quoxon/render.js +740 -124
- package/lib/quoxon/resend.js +72 -0
- package/lib/quoxon/roster.js +117 -17
- package/lib/quoxon/route.js +6 -5
- package/lib/quoxon/rows.js +954 -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/serverChip.js +42 -0
- package/lib/quoxon/session.js +182 -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 +94 -0
- package/lib/quoxon/slash/cliLeaves.json +1023 -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/evidenceCmd.js +34 -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 +22 -8
- 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/qpeCmd.js +35 -0
- package/lib/quoxon/slash/restart.js +95 -13
- package/lib/quoxon/slash/resume.js +18 -1
- 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/todoCmd.js +122 -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 +90 -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/toolCards.js +30 -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 +134 -0
- package/lib/quoxon/windowOrg.js +123 -0
- package/lib/server/devServers.js +137 -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 +5 -1
- package/vendor/dragonlab/SOURCE +4 -0
- package/vendor/dragonlab/design/cipher/v1/cipher-master.svg +46 -0
- package/vendor/dragonlab/design/cipher/v2/character.mjs +49 -0
- package/vendor/dragonlab/design/cipher/v2/review.mjs +33 -0
- package/vendor/dragonlab/design/cipher/v2/watch.mjs +56 -0
- package/vendor/dragonlab/design/drago/v1/character.mjs +19 -0
- package/vendor/dragonlab/design/drago/v1/drago-master.svg +56 -0
- package/vendor/dragonlab/design/drago/v2/character.mjs +87 -0
- package/vendor/dragonlab/design/drago/v4/character.mjs +67 -0
- package/vendor/dragonlab/design/drago/v4/profile.mjs +35 -0
- package/vendor/dragonlab/design/drago/v4/review.mjs +33 -0
- package/vendor/dragonlab/design/drago/v4/watch.mjs +56 -0
- package/vendor/dragonlab/design/hyperion/v1/hyperion-master.svg +56 -0
- package/vendor/dragonlab/design/hyperion/v2/character.mjs +79 -0
- package/vendor/dragonlab/design/hyperion/v2/review.mjs +33 -0
- package/vendor/dragonlab/design/hyperion/v2/watch.mjs +56 -0
- package/vendor/dragonlab/design/lucid/recovery/arrival.mjs +22 -0
- package/vendor/dragonlab/design/lucid/recovery/character.mjs +106 -0
- package/vendor/dragonlab/design/lucid/recovery/layout.mjs +50 -0
- package/vendor/dragonlab/design/lucid/recovery/lucid-master.svg +78 -0
- package/vendor/dragonlab/design/lucid/recovery/motion.mjs +42 -0
- package/vendor/dragonlab/design/lucid/recovery/render.mjs +121 -0
- package/vendor/dragonlab/design/lucid/recovery/review.mjs +68 -0
- package/vendor/dragonlab/design/lucid/recovery/verify.mjs +84 -0
- package/vendor/dragonlab/design/lucid/recovery/watch.mjs +56 -0
- package/vendor/dragonlab/design/seer/v1/seer-master.svg +53 -0
- package/vendor/dragonlab/design/seer/v2/character.mjs +88 -0
- package/vendor/dragonlab/design/seer/v3/character.mjs +33 -0
- package/vendor/dragonlab/design/seer/v4/character.mjs +66 -0
- package/vendor/dragonlab/design/seer/v4/review.mjs +33 -0
- package/vendor/dragonlab/design/seer/v4/watch.mjs +56 -0
- package/vendor/dragonlab/design/stage3/companion-review.mjs +28 -0
- package/vendor/dragonlab/design/stage3/sentinel-art.js +39 -0
- package/vendor/dragonlab/design/stage3/sentinel-braille.js +216 -0
- package/vendor/dragonlab/design/stage3/sentinel-character.js +257 -0
- package/vendor/dragonlab/design/stage3/sentinel-icon.js +103 -0
- package/vendor/dragonlab/design/stage3/sentinel-warden-eyes.js +95 -0
- package/vendor/dragonlab/design/stage3/svg-coverage.js +148 -0
- package/vendor/dragonlab/factory/svg-rig.mjs +63 -0
- package/vendor/dragonlab/integration/companions-v1/characters.mjs +88 -0
- package/vendor/dragonlab/integration/companions-v1/cipher.mjs +2 -0
- package/vendor/dragonlab/integration/companions-v1/companion.mjs +63 -0
- package/vendor/dragonlab/integration/companions-v1/drago.mjs +2 -0
- package/vendor/dragonlab/integration/companions-v1/hyperion.mjs +2 -0
- package/vendor/dragonlab/integration/companions-v1/lucid.mjs +2 -0
- package/vendor/dragonlab/integration/companions-v1/seer.mjs +2 -0
- package/vendor/dragonlab/integration/companions-v1/sentinel.mjs +2 -0
- package/vendor/dragonlab/integration/companions-v1/view.mjs +28 -0
- package/vendor/dragonlab/integration/hyperion-v3/activity.mjs +76 -0
- package/vendor/dragonlab/lib/power-effects.js +294 -0
- package/vendor/dragonlab/lib/power-registry.js +74 -0
- package/vendor/dragonlab/vendor/quox-dragon/qart.js +348 -0
- package/vendor/dragonlab/vendor/quox-dragon/qeyes.js +213 -0
- package/lib/core/websiteSession.js +0 -148
|
@@ -107,6 +107,21 @@ export async function createFinisher({ profileUrl = LOCAL_PROFILE_URL, fetchImpl
|
|
|
107
107
|
}
|
|
108
108
|
},
|
|
109
109
|
|
|
110
|
+
/** QUOXON-Z2H lane R: the owner said yes at the credential step; record it as their own act. -> { ok } | { ok: false, error } */
|
|
111
|
+
async grantConsent(provider) {
|
|
112
|
+
try { await createApi({ client, config, auth }).grantConsent(provider); return { ok: true }; }
|
|
113
|
+
catch (e) { return { ok: false, error: apiError(e, 'consent not recorded') }; }
|
|
114
|
+
},
|
|
115
|
+
|
|
116
|
+
/** Z2H-R5 E: can this server serve its host installer? -> { ok, status } | null (unknown). Reads only. */
|
|
117
|
+
async hostInstaller() {
|
|
118
|
+
try {
|
|
119
|
+
const api = createApi({ client, config, auth });
|
|
120
|
+
const base = await api.hostsBaseUrl();
|
|
121
|
+
return await api.hostInstallCheck(base);
|
|
122
|
+
} catch { return null; }
|
|
123
|
+
},
|
|
124
|
+
|
|
110
125
|
/** -> { ok: true, url, expiresAt, ttlSeconds } | { ok: false, error } */
|
|
111
126
|
async loginLink(dashboardUrl) {
|
|
112
127
|
try {
|
|
@@ -7,6 +7,10 @@ import React from 'react';
|
|
|
7
7
|
import { render } from 'ink';
|
|
8
8
|
import { spawnSync } from 'node:child_process';
|
|
9
9
|
import { fileURLToPath } from 'node:url';
|
|
10
|
+
import { readFileSync, writeFileSync, existsSync } from 'node:fs';
|
|
11
|
+
import { homedir } from 'node:os';
|
|
12
|
+
import { join } from 'node:path';
|
|
13
|
+
import { probeQuoxCore, probeMessage } from './probe.js';
|
|
10
14
|
import { GettingStarted } from './view.js';
|
|
11
15
|
import { inkStdout } from '../term.js';
|
|
12
16
|
import { createCredentialBroker, createRequestCredential } from './credential.js';
|
|
@@ -15,6 +19,7 @@ import { createInstallRunner, defaultInstallDir, probeAuthHealth } from './insta
|
|
|
15
19
|
import { createFinisher } from './finish.js';
|
|
16
20
|
import { journeyPath as defaultJourneyPath, clearJourney, loadJourney } from './journey.js';
|
|
17
21
|
import * as T from './copy.js';
|
|
22
|
+
import { runDockerOffer } from './dockerOffer.js';
|
|
18
23
|
|
|
19
24
|
const QUOX_BIN = fileURLToPath(new URL('../../../bin/quox.js', import.meta.url));
|
|
20
25
|
|
|
@@ -49,19 +54,41 @@ export async function runGettingStarted({ version, cwd, welcome = false, earlyIn
|
|
|
49
54
|
await app.waitUntilExit();
|
|
50
55
|
broker.cancel();
|
|
51
56
|
if (result.action === 'connect') return connectServer(result.url, { demo });
|
|
57
|
+
if (result.action === 'docker' && !demo) return runDockerOffer(result.kind, { bin: QUOX_BIN }); // FIRSTRUN
|
|
52
58
|
return result;
|
|
53
59
|
}
|
|
54
60
|
|
|
55
|
-
/**
|
|
56
|
-
|
|
61
|
+
/**
|
|
62
|
+
* /connect server <url>: check the URL is a real QuoxCORE (#1101), point the active profile at it and run the
|
|
63
|
+
* existing login. Any outcome but a completed sign-in (failure, Ctrl-C at Email or Password) puts the previous
|
|
64
|
+
* URL and the last-good config.json.bak back, so no half-saved profile is left behind.
|
|
65
|
+
*/
|
|
66
|
+
export async function connectServer(url, {
|
|
67
|
+
demo = false, spawn = spawnSync, out = (s) => process.stdout.write(s),
|
|
68
|
+
probe = probeQuoxCore, loadConfigImpl = null, bakFile = join(homedir(), '.config', 'quox', 'config.json.bak'),
|
|
69
|
+
localCertPath = join(defaultInstallDir(), 'quox', 'ssl', 'quox.crt') // the guided install's own certificate, if this is the server
|
|
70
|
+
} = {}) {
|
|
57
71
|
if (demo) { out(`${T.connectHandoff(url)}\n(demo: not connecting)\n`); return { action: 'exit' }; }
|
|
58
|
-
const
|
|
59
|
-
|
|
72
|
+
const p = await probe(url);
|
|
73
|
+
if (!p?.ok) { out(`${probeMessage(url, p, { localCert: existsSync(localCertPath) ? localCertPath : null })}\n`); return { action: 'connect-failed' }; }
|
|
74
|
+
const load = loadConfigImpl || (await import('../../core/config.js')).loadConfig;
|
|
75
|
+
const config = load();
|
|
60
76
|
const prev = config.getActiveProfile()?.url || null;
|
|
77
|
+
let bak = null;
|
|
78
|
+
try { bak = readFileSync(bakFile); } catch { /* no backup yet */ }
|
|
61
79
|
config.setProfileConnection({ url });
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
80
|
+
out(`${T.connectHandoff(url)}\n`);
|
|
81
|
+
// Ctrl-C reaches the whole foreground group: the login child handles it, the parent must live to restore.
|
|
82
|
+
const hold = () => {};
|
|
83
|
+
process.on('SIGINT', hold);
|
|
84
|
+
let r;
|
|
85
|
+
try { r = spawn(process.execPath, [QUOX_BIN, 'login'], { stdio: 'inherit' }); } finally { process.removeListener('SIGINT', hold); }
|
|
86
|
+
if (r?.status === 0) { clearJourney(); out(`${T.CONNECT_DONE}\n`); return { action: 'connected' }; }
|
|
87
|
+
const cfg = load();
|
|
88
|
+
if (prev) cfg.setProfileConnection({ url: prev });
|
|
89
|
+
else { const prof = cfg.getActiveProfile(); if (prof) { delete prof.url; cfg.save(); } }
|
|
90
|
+
if (bak) { try { writeFileSync(bakFile, bak, { mode: 0o600 }); } catch { /* best effort */ } }
|
|
91
|
+
if (r?.status === 130 || r?.signal === 'SIGINT') { out(`\x1b[?25h${T.CONNECT_CANCELLED}\n`); return { action: 'connect-cancelled' }; }
|
|
65
92
|
out(`${T.connectFailed(url)}\n`);
|
|
66
93
|
return { action: 'connect-failed' };
|
|
67
94
|
}
|
|
@@ -22,13 +22,17 @@
|
|
|
22
22
|
*/
|
|
23
23
|
import { spawn as defaultSpawn } from 'node:child_process';
|
|
24
24
|
import { createHash } from 'node:crypto';
|
|
25
|
-
import { mkdtempSync, writeFileSync, rmSync } from 'node:fs';
|
|
25
|
+
import { mkdtempSync, writeFileSync, rmSync, mkdirSync, appendFileSync, openSync, readSync, fstatSync, closeSync } from 'node:fs';
|
|
26
|
+
import { explainInstallFailure } from './failureCause.js';
|
|
26
27
|
import { homedir, tmpdir } from 'node:os';
|
|
27
28
|
import { join } from 'node:path';
|
|
28
29
|
import { STAGE_IDS, stageList } from './runner.js';
|
|
29
30
|
|
|
31
|
+
import { BUILD_LABELS } from './copy.js';
|
|
30
32
|
export const INSTALLER_URL = 'https://get.quox.ai';
|
|
31
33
|
export const defaultInstallDir = (home = homedir()) => join(home, 'quox-install');
|
|
34
|
+
/** Where the full install log lives: the user's quox state dir, ~/.local/state/quox/install-logs. */
|
|
35
|
+
export const defaultInstallLogDir = (home = homedir()) => join(home, '.local', 'state', 'quox', 'install-logs');
|
|
32
36
|
|
|
33
37
|
const ANSI = /\x1b\[[0-9;?]*[ -/]*[@-~]/g;
|
|
34
38
|
const RED = /^\x1b\[(0;)?31m/;
|
|
@@ -44,7 +48,7 @@ const MARKS = [
|
|
|
44
48
|
* push(rawLine) -> { line: cleaned text | null, moves: [[stageId, 'ok'|'running']] }
|
|
45
49
|
*/
|
|
46
50
|
export function createStageParser() {
|
|
47
|
-
const state = { current: 'fetch', ready: false, degraded: false, dashboardUrl: null, lastError: null };
|
|
51
|
+
const state = { current: 'fetch', buildMode: null, ready: false, degraded: false, dashboardUrl: null, lastError: null };
|
|
48
52
|
return {
|
|
49
53
|
state,
|
|
50
54
|
push(raw, { stderr = false } = {}) {
|
|
@@ -61,6 +65,9 @@ export function createStageParser() {
|
|
|
61
65
|
state.current = next;
|
|
62
66
|
break;
|
|
63
67
|
}
|
|
68
|
+
// Z2H-IMAGES: setup.sh either downloads prebuilt images or builds them; the build stage says which.
|
|
69
|
+
if (/Downloading prebuilt images/.test(line)) state.buildMode = 'download';
|
|
70
|
+
else if (/Building images/.test(line)) state.buildMode = 'build';
|
|
64
71
|
if (/QuoxCORE full platform is ready/.test(line)) state.ready = true;
|
|
65
72
|
if (/core shell is up, but platform features are degraded/.test(line)) state.degraded = true;
|
|
66
73
|
const open = /^\s*Open:\s+(https?:\/\/\S+)/.exec(line);
|
|
@@ -88,6 +95,7 @@ export function createInstallRunner({
|
|
|
88
95
|
env = process.env,
|
|
89
96
|
url = env.QUOX_INSTALLER_URL || INSTALLER_URL,
|
|
90
97
|
installDir = defaultInstallDir(),
|
|
98
|
+
logDir = defaultInstallLogDir(),
|
|
91
99
|
fetchImpl = globalThis.fetch,
|
|
92
100
|
spawnImpl = defaultSpawn,
|
|
93
101
|
tmpRoot = tmpdir(),
|
|
@@ -118,7 +126,44 @@ export function createInstallRunner({
|
|
|
118
126
|
installDir,
|
|
119
127
|
stages: stageList(),
|
|
120
128
|
/** Per-run overrides (the view passes the journey's checkpoint): resumeFrom, resumeDashboardUrl, onCheckpoint. */
|
|
121
|
-
|
|
129
|
+
/**
|
|
130
|
+
* Every run also writes the full stage log (each line the installer printed) to a file under the
|
|
131
|
+
* user's quox state dir; the result carries `logPath` so the UI can show it on failure and at the end.
|
|
132
|
+
*/
|
|
133
|
+
async run(opts = {}) {
|
|
134
|
+
const { onEvent = () => {} } = opts;
|
|
135
|
+
let logPath = null;
|
|
136
|
+
try {
|
|
137
|
+
mkdirSync(logDir, { recursive: true, mode: 0o700 });
|
|
138
|
+
logPath = join(logDir, `install-${new Date(now()).toISOString().replace(/[:.]/g, '-')}.log`);
|
|
139
|
+
writeFileSync(logPath, `# quox install log, started ${new Date(now()).toISOString()}\n`, { mode: 0o600 });
|
|
140
|
+
} catch { logPath = null; } // best effort: a log that cannot be written never fails the install
|
|
141
|
+
const recent = []; // FIRSTRUN: the last lines, so a failure can say its cause
|
|
142
|
+
const tee = (e) => {
|
|
143
|
+
if (e.type === 'log') { recent.push(e.line); if (recent.length > 400) recent.shift(); }
|
|
144
|
+
if (logPath) {
|
|
145
|
+
try {
|
|
146
|
+
if (e.type === 'log') appendFileSync(logPath, e.line + '\n');
|
|
147
|
+
else if (e.type === 'stage') appendFileSync(logPath, `-- stage ${e.id} ${e.status}\n`);
|
|
148
|
+
} catch { /* best effort */ }
|
|
149
|
+
}
|
|
150
|
+
onEvent(e);
|
|
151
|
+
};
|
|
152
|
+
const res = await runCore({ ...opts, onEvent: tee });
|
|
153
|
+
if (!res.ok && !res.interrupted) {
|
|
154
|
+
const named = recent.map(l => /Full build log:\s*(\S+)/.exec(l)?.[1]).filter(Boolean).pop();
|
|
155
|
+
const cause = explainInstallFailure(`${recent.join('\n')}\n${res.error || ''}\n${named ? tailOf(named) : ''}`);
|
|
156
|
+
if (cause) res.cause = cause;
|
|
157
|
+
}
|
|
158
|
+
if (!res.ok && !res.interrupted && logPath) {
|
|
159
|
+
// Z2H-R2: a failed run always says where the full log is and what to do next.
|
|
160
|
+
res.hint = `Full log: ${logPath}. It is safe to run the installer again; finished steps are kept and the build is cached.`;
|
|
161
|
+
}
|
|
162
|
+
if (logPath) { try { appendFileSync(logPath, `-- result ${res.ok ? 'ok' : `failed at ${res.failedStage || '?'}: ${res.error || ''}`}\n`); } catch { /* best effort */ } }
|
|
163
|
+
return logPath ? { ...res, logPath } : res;
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
async function runCore({ onEvent = () => {}, signal, resumeFrom: rf = resumeFrom, resumeDashboardUrl: rd = resumeDashboardUrl, onCheckpoint: oc = onCheckpoint } = {}) {
|
|
122
167
|
const resumeAfterStart = rf === 'start' || rf === 'health';
|
|
123
168
|
const started = {};
|
|
124
169
|
const stage = (id, status) => {
|
|
@@ -185,11 +230,13 @@ export function createInstallRunner({
|
|
|
185
230
|
};
|
|
186
231
|
};
|
|
187
232
|
const take = (raw, stderr) => {
|
|
233
|
+
const modeBefore = parser.state.buildMode;
|
|
188
234
|
const { line, moves } = parser.push(raw, { stderr });
|
|
189
235
|
for (const [id, status] of moves) {
|
|
190
236
|
if (status === 'ok') { stage(id, 'ok'); if (id !== 'health') oc(id, {}); }
|
|
191
237
|
else stage(id, 'running');
|
|
192
238
|
}
|
|
239
|
+
if (parser.state.buildMode !== modeBefore) emit({ type: 'stage-label', id: 'build', label: BUILD_LABELS[parser.state.buildMode] });
|
|
193
240
|
if (line) emit({ type: 'log', line });
|
|
194
241
|
};
|
|
195
242
|
child.stdout.on('data', feed(false));
|
|
@@ -220,6 +267,18 @@ export function createInstallRunner({
|
|
|
220
267
|
} finally {
|
|
221
268
|
try { rmSync(dir, { recursive: true, force: true }); } catch { /* temp cleanup only */ }
|
|
222
269
|
}
|
|
223
|
-
|
|
224
|
-
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/** The last `max` bytes of a file, '' when it cannot be read (a cause is best effort, never a new failure). */
|
|
274
|
+
function tailOf(path, max = 256 * 1024) {
|
|
275
|
+
let fd = null;
|
|
276
|
+
try {
|
|
277
|
+
fd = openSync(path, 'r');
|
|
278
|
+
const size = fstatSync(fd).size;
|
|
279
|
+
const len = Math.min(size, max);
|
|
280
|
+
const buf = Buffer.alloc(len);
|
|
281
|
+
readSync(fd, buf, 0, len, size - len);
|
|
282
|
+
return buf.toString('utf8');
|
|
283
|
+
} catch { return ''; } finally { if (fd != null) try { closeSync(fd); } catch { /* closed */ } }
|
|
225
284
|
}
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import { execFile } from 'node:child_process';
|
|
10
10
|
import { statfs } from 'node:fs/promises';
|
|
11
|
-
import { totalmem, homedir } from 'node:os';
|
|
11
|
+
import { totalmem, homedir, userInfo } from 'node:os';
|
|
12
12
|
import { createConnection } from 'node:net';
|
|
13
13
|
import { CHECK } from './copy.js';
|
|
14
14
|
|
|
@@ -55,13 +55,27 @@ export const defaultDeps = () => ({
|
|
|
55
55
|
const gb = (bytes) => Math.floor(bytes / 1024 ** 3);
|
|
56
56
|
const row = (id, status, body, extra = {}) => ({ id, name: CHECK[id].name, status, body, ...extra });
|
|
57
57
|
|
|
58
|
+
const safeUser = () => { try { return userInfo().username; } catch { return ''; } };
|
|
59
|
+
|
|
58
60
|
export async function checkDocker(d) {
|
|
59
61
|
const v = await d.run('docker', ['--version'], 5000);
|
|
60
|
-
|
|
62
|
+
const linux = (d.platform || process.platform) === 'linux'; // FIRSTRUN: the guide can fix these itself (dockerOffer.js)
|
|
63
|
+
const offer = (kind) => (linux ? { offer: kind } : {});
|
|
64
|
+
if (v.missing || v.code !== 0) return row('docker', 'fail', CHECK.docker.missing, { fix: CHECK.docker.missingFix, link: CHECK.docker.missingLink, ...offer('install') });
|
|
61
65
|
const ver = (/version\s+([0-9][\w.\-+]*)/i.exec(v.stdout) || [])[1] || '';
|
|
62
66
|
const info = await d.run('docker', ['info', '--format', '{{.ServerVersion}}'], 10000);
|
|
63
67
|
if (info.code === 0 && info.stdout.trim()) return row('docker', 'ok', CHECK.docker.ok(ver || info.stdout.trim()));
|
|
64
|
-
if (/permission denied/i.test(info.stderr))
|
|
68
|
+
if (/permission denied/i.test(info.stderr)) {
|
|
69
|
+
// Z2H-R5 D: the group was added after this terminal started. `id -nG` shows the groups of THIS process,
|
|
70
|
+
// `id -nG <user>` the account database; in the second but not the first, "press r" can never pass.
|
|
71
|
+
const user = d.user || safeUser();
|
|
72
|
+
if (user) {
|
|
73
|
+
const [mine, db] = await Promise.all([d.run('id', ['-nG'], 3000), d.run('id', ['-nG', user], 3000)]);
|
|
74
|
+
const has = (r) => r.code === 0 && r.stdout.trim().split(/\s+/).includes('docker');
|
|
75
|
+
if (has(db) && !has(mine)) return row('docker', 'fail', CHECK.docker.permStale, { fix: CHECK.docker.permStaleFix, link: CHECK.docker.link, stale: true, ...offer('relaunch') });
|
|
76
|
+
}
|
|
77
|
+
return row('docker', 'fail', CHECK.docker.perm, { fix: CHECK.docker.permFix, link: CHECK.docker.link, ...offer('group') });
|
|
78
|
+
}
|
|
65
79
|
return row('docker', 'fail', CHECK.docker.down, { fix: CHECK.docker.downFix, link: CHECK.docker.link });
|
|
66
80
|
}
|
|
67
81
|
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* #1101: is this URL a real QuoxCORE? One cheap unauthenticated GET of the edge's /health (nginx.conf answers
|
|
3
|
+
* {"status":"ok","service":"dashboard"} on both the HTTP and HTTPS servers), run BEFORE /connect server asks for
|
|
4
|
+
* an email and password. An error body is not data: only a 2xx JSON answer of that exact shape passes.
|
|
5
|
+
*
|
|
6
|
+
* -> { ok: true } | { ok: false, kind: 'url'|'unreachable'|'tls'|'not-quoxcore', code?, status?, detail? }
|
|
7
|
+
*/
|
|
8
|
+
const TLS_CODES = /^(SELF_SIGNED_CERT_IN_CHAIN|DEPTH_ZERO_SELF_SIGNED_CERT|UNABLE_TO_VERIFY_LEAF_SIGNATURE|UNABLE_TO_GET_ISSUER_CERT(_LOCALLY)?|CERT_HAS_EXPIRED|CERT_NOT_YET_VALID|ERR_TLS_.*|ERR_SSL_.*|EPROTO)$/;
|
|
9
|
+
const PLAINTEXT_CODES = new Set(['ERR_SSL_WRONG_VERSION_NUMBER', 'EPROTO']);
|
|
10
|
+
|
|
11
|
+
export async function probeQuoxCore(url, { fetchImpl = globalThis.fetch, timeout = 5000 } = {}) {
|
|
12
|
+
let base;
|
|
13
|
+
try {
|
|
14
|
+
base = new URL(String(url || ''));
|
|
15
|
+
if (base.protocol !== 'http:' && base.protocol !== 'https:') return { ok: false, kind: 'url', detail: 'the address must start with https:// or http://' };
|
|
16
|
+
} catch { return { ok: false, kind: 'url', detail: 'the address must start with https:// or http://' }; }
|
|
17
|
+
const target = `${base.href.replace(/\/+$/, '')}/health`;
|
|
18
|
+
let res;
|
|
19
|
+
try {
|
|
20
|
+
res = await fetchImpl(target, { signal: AbortSignal.timeout(timeout), headers: { accept: 'application/json' } });
|
|
21
|
+
} catch (e) {
|
|
22
|
+
const code = e?.cause?.code || e?.code || null;
|
|
23
|
+
if (code && TLS_CODES.test(code)) return { ok: false, kind: 'tls', code };
|
|
24
|
+
if (e?.name === 'TimeoutError' || e?.name === 'AbortError') return { ok: false, kind: 'unreachable', code: 'TIMEOUT', detail: `no answer within ${Math.round(timeout / 1000)} s` };
|
|
25
|
+
return { ok: false, kind: 'unreachable', code, detail: e?.message || 'no answer' };
|
|
26
|
+
}
|
|
27
|
+
if (res?.status === 400 && base.protocol === 'http:') {
|
|
28
|
+
let t = ''; try { t = await res.text(); } catch { /* no body */ }
|
|
29
|
+
if (/plain HTTP request was sent to HTTPS port/i.test(t)) return { ok: false, kind: 'tls', code: 'HTTP_TO_HTTPS_PORT' };
|
|
30
|
+
}
|
|
31
|
+
if (!res || !(res.status >= 200 && res.status < 300)) return { ok: false, kind: 'not-quoxcore', status: res?.status, detail: `HTTP ${res?.status} from /health` };
|
|
32
|
+
let body;
|
|
33
|
+
try { body = JSON.parse(await res.text()); } catch { return { ok: false, kind: 'not-quoxcore', status: res.status, detail: '/health did not answer JSON' }; }
|
|
34
|
+
if (!body || body.status !== 'ok' || body.service !== 'dashboard') return { ok: false, kind: 'not-quoxcore', status: res.status, detail: '/health answered, but not as QuoxCORE does' };
|
|
35
|
+
return { ok: true };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// A new QuoxCORE serves its own self-signed certificate (setup.sh writes ssl/quox.crt, CA:TRUE, SAN = localhost +
|
|
39
|
+
// the LAN IP). Trusting exactly that file is the sanctioned fix; these are the codes it fixes. An expired or
|
|
40
|
+
// wrong-name certificate is a different problem: trusting a file would not cure it, so it is not suggested.
|
|
41
|
+
const SELF_SIGNED_CODES = new Set(['DEPTH_ZERO_SELF_SIGNED_CERT', 'SELF_SIGNED_CERT_IN_CHAIN', 'UNABLE_TO_VERIFY_LEAF_SIGNATURE', 'UNABLE_TO_GET_ISSUER_CERT', 'UNABLE_TO_GET_ISSUER_CERT_LOCALLY']);
|
|
42
|
+
export const SERVER_CERT_DEFAULT = '~/quox-install/quox/ssl/quox.crt';
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* One plain message per failure kind, naming the address and the next step.
|
|
46
|
+
* opts.localCert: the absolute path of a QuoxCORE certificate on THIS machine (the server is here), if any.
|
|
47
|
+
*/
|
|
48
|
+
export function probeMessage(url, r, { localCert = null } = {}) {
|
|
49
|
+
const again = 'Check the address, then try /connect server <url> again.';
|
|
50
|
+
switch (r?.kind) {
|
|
51
|
+
case 'url': return `"${url}" is not a server address (${r.detail}). Example: /connect server https://quox.example.com`;
|
|
52
|
+
case 'unreachable': return `No answer from ${url}${r.code ? ` (${r.code})` : ''}. Nothing was saved and no password was asked for. ${again}`;
|
|
53
|
+
case 'tls':
|
|
54
|
+
if (r.code === 'HTTP_TO_HTTPS_PORT') return `${url} is an HTTPS port spoken to in plain HTTP. Did you mean ${String(url).replace(/^http:/i, 'https:')} ? Nothing was saved.`;
|
|
55
|
+
if (PLAINTEXT_CODES.has(r.code)) return `${url} answered plain HTTP to an https:// request (${r.code}). Did you mean ${String(url).replace(/^https:/i, 'http:')} ? Nothing was saved.`;
|
|
56
|
+
if (SELF_SIGNED_CODES.has(r.code)) {
|
|
57
|
+
const lead = `${url} answered with its own self-signed certificate (${r.code}), which is normal for a new QuoxCORE. Trust that server's certificate, then start quox again.`;
|
|
58
|
+
const tail = 'Never turn TLS checks off. Nothing was saved.';
|
|
59
|
+
if (localCert) return `${lead} It is on this machine, so run: NODE_EXTRA_CA_CERTS=${localCert} quox ${tail}`;
|
|
60
|
+
return `${lead} Copy it from the server (by default ${SERVER_CERT_DEFAULT}) to this machine, then run: NODE_EXTRA_CA_CERTS=~/quox.crt quox ${tail}`;
|
|
61
|
+
}
|
|
62
|
+
return `${url} answered, but its TLS certificate is not trusted (${r.code}). For a self-signed edge, trust its CA with NODE_EXTRA_CA_CERTS=<ca.pem> and try again; never turn TLS checks off. Nothing was saved.`;
|
|
63
|
+
default: return `${url} answered, but it is not a QuoxCORE server (${r?.detail || 'unexpected answer'}). Nothing was saved and no password was asked for. ${again}`;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* QUOXON-Q: after first-run onboarding succeeds, ONE offer (y/N, default No, asked once) to make `q` run quox.
|
|
3
|
+
* The answer is remembered in the CLI config (shortcutOffer = 'yes' | 'no'). A yes runs the same code as
|
|
4
|
+
* `quox shortcut add`. Nothing is offered when q already resolves to something (ours or not): never shadow.
|
|
5
|
+
*/
|
|
6
|
+
import { askText } from '../ask.js';
|
|
7
|
+
import { addShortcut, whichOnPath, DEFAULT_NAME } from '../../core/shortcut.js';
|
|
8
|
+
|
|
9
|
+
export const SHORTCUT_QUESTION = 'Want a shorter command? Type `q` instead of `quox`.';
|
|
10
|
+
export const shortcutOfferLine = () => askText(SHORTCUT_QUESTION, { keys: 'y/N', who: 'Quox', action: `make \`${DEFAULT_NAME}\` a link to quox in a folder you own (no sudo, no shell edits); undo with: quox shortcut remove` });
|
|
11
|
+
|
|
12
|
+
/** -> true when the offer should be shown now. */
|
|
13
|
+
export function shouldOfferShortcut({ config, env = process.env, which = whichOnPath } = {}) {
|
|
14
|
+
if (config?.get?.('shortcutOffer')) return false; // asked once
|
|
15
|
+
return !which(DEFAULT_NAME, env);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** Runs the offer. ask(line) -> string answer. -> { offered, accepted, result? } */
|
|
19
|
+
export async function offerShortcut({ config, ask, out = (s) => process.stdout.write(s), env = process.env, add = addShortcut, which = whichOnPath } = {}) {
|
|
20
|
+
if (!shouldOfferShortcut({ config, env, which })) return { offered: false };
|
|
21
|
+
const answer = String(await ask(shortcutOfferLine()) ?? '').trim();
|
|
22
|
+
const accepted = /^y(es)?$/i.test(answer);
|
|
23
|
+
config.set('shortcutOffer', accepted ? 'yes' : 'no');
|
|
24
|
+
if (!accepted) return { offered: true, accepted: false };
|
|
25
|
+
const result = add({ name: DEFAULT_NAME, env });
|
|
26
|
+
out(`${result.ok ? '✔' : '✖'} ${result.message}\n`);
|
|
27
|
+
return { offered: true, accepted: true, result };
|
|
28
|
+
}
|