quoxcli 0.0.0-stage → 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 +35 -0
- package/README.md +144 -2
- package/bin/quox.js +265 -0
- package/bin/quoxon.js +8 -0
- package/lib/api/endpoints/admin.js +151 -0
- package/lib/api/endpoints/aee.js +38 -0
- package/lib/api/endpoints/agent-attach.js +126 -0
- package/lib/api/endpoints/agent-schedules.js +26 -0
- package/lib/api/endpoints/agents.js +63 -0
- package/lib/api/endpoints/assistants.js +62 -0
- package/lib/api/endpoints/audit.js +14 -0
- package/lib/api/endpoints/auth.js +50 -0
- package/lib/api/endpoints/awareness.js +34 -0
- package/lib/api/endpoints/away.js +15 -0
- package/lib/api/endpoints/backup-schedules.js +20 -0
- package/lib/api/endpoints/backups.js +86 -0
- package/lib/api/endpoints/brain.js +140 -0
- package/lib/api/endpoints/budgets.js +62 -0
- package/lib/api/endpoints/calendarq.js +71 -0
- package/lib/api/endpoints/catalog.js +14 -0
- package/lib/api/endpoints/chat.js +61 -0
- package/lib/api/endpoints/consent.js +26 -0
- package/lib/api/endpoints/contactsq.js +68 -0
- package/lib/api/endpoints/conversations.js +46 -0
- package/lib/api/endpoints/custom-roles.js +92 -0
- package/lib/api/endpoints/divisions.js +56 -0
- package/lib/api/endpoints/domains.js +38 -0
- package/lib/api/endpoints/engagement.js +231 -0
- package/lib/api/endpoints/engine.js +11 -0
- package/lib/api/endpoints/entity.js +50 -0
- package/lib/api/endpoints/executions.js +45 -0
- package/lib/api/endpoints/fetchAllowlist.js +27 -0
- package/lib/api/endpoints/file.js +20 -0
- package/lib/api/endpoints/files.js +40 -0
- package/lib/api/endpoints/github.js +21 -0
- package/lib/api/endpoints/governance.js +43 -0
- package/lib/api/endpoints/hat.js +66 -0
- package/lib/api/endpoints/inbox.js +52 -0
- package/lib/api/endpoints/inboxq.js +120 -0
- package/lib/api/endpoints/index.js +242 -0
- package/lib/api/endpoints/invites.js +19 -0
- package/lib/api/endpoints/llm-policy.js +43 -0
- package/lib/api/endpoints/lucid.js +89 -0
- package/lib/api/endpoints/members.js +32 -0
- package/lib/api/endpoints/memory.js +94 -0
- package/lib/api/endpoints/mfa.js +32 -0
- package/lib/api/endpoints/mind.js +13 -0
- package/lib/api/endpoints/mon.js +184 -0
- package/lib/api/endpoints/notesq.js +43 -0
- package/lib/api/endpoints/objectives.js +68 -0
- package/lib/api/endpoints/orgs.js +38 -0
- package/lib/api/endpoints/plugins.js +152 -0
- package/lib/api/endpoints/powers.js +75 -0
- package/lib/api/endpoints/presence.js +33 -0
- package/lib/api/endpoints/prompts.js +69 -0
- package/lib/api/endpoints/qlarity.js +122 -0
- package/lib/api/endpoints/quoxbox.js +55 -0
- package/lib/api/endpoints/quoxchat.js +54 -0
- package/lib/api/endpoints/quoxflow.js +32 -0
- package/lib/api/endpoints/quoxplan.js +121 -0
- package/lib/api/endpoints/quoxseo.js +114 -0
- package/lib/api/endpoints/reach.js +47 -0
- package/lib/api/endpoints/router.js +17 -0
- package/lib/api/endpoints/runs.js +38 -0
- package/lib/api/endpoints/safety.js +53 -0
- package/lib/api/endpoints/scim.js +46 -0
- package/lib/api/endpoints/scopes.js +14 -0
- package/lib/api/endpoints/security.js +69 -0
- package/lib/api/endpoints/setup.js +90 -0
- package/lib/api/endpoints/skills.js +200 -0
- package/lib/api/endpoints/sso.js +67 -0
- package/lib/api/endpoints/stepup.js +20 -0
- package/lib/api/endpoints/tags.js +32 -0
- package/lib/api/endpoints/teams.js +56 -0
- package/lib/api/endpoints/telephony.js +46 -0
- package/lib/api/endpoints/tg.js +56 -0
- package/lib/api/endpoints/tools.js +50 -0
- package/lib/api/endpoints/traces.js +157 -0
- package/lib/api/endpoints/triggers.js +96 -0
- package/lib/api/endpoints/user-library.js +41 -0
- package/lib/api/endpoints/users.js +20 -0
- package/lib/api/endpoints/v1.js +585 -0
- package/lib/api/endpoints/vault.js +192 -0
- package/lib/api/endpoints/voice.js +22 -0
- package/lib/api/endpoints/volt.js +84 -0
- package/lib/api/endpoints/ward.js +47 -0
- package/lib/api/endpoints/webhooks.js +46 -0
- package/lib/api/endpoints/workflows.js +169 -0
- package/lib/api/endpoints/x.js +32 -0
- package/lib/api/endpoints.js +15 -0
- package/lib/commands/actions.js +61 -0
- package/lib/commands/admin.js +375 -0
- package/lib/commands/aee.js +99 -0
- package/lib/commands/agent.js +337 -0
- package/lib/commands/agentAttach.js +1528 -0
- package/lib/commands/approvals.js +119 -0
- package/lib/commands/aside.js +24 -0
- package/lib/commands/ask.js +144 -0
- package/lib/commands/assistant.js +169 -0
- package/lib/commands/auth.js +464 -0
- package/lib/commands/awareness.js +88 -0
- package/lib/commands/backup.js +377 -0
- package/lib/commands/brain.js +1085 -0
- package/lib/commands/budget.js +241 -0
- package/lib/commands/build.js +170 -0
- package/lib/commands/calendar.js +216 -0
- package/lib/commands/capabilities.js +34 -0
- package/lib/commands/chat.js +295 -0
- package/lib/commands/claude.js +380 -0
- package/lib/commands/config-cmd.js +106 -0
- package/lib/commands/consent.js +84 -0
- package/lib/commands/contacts.js +1016 -0
- package/lib/commands/contactsSelftest.js +132 -0
- package/lib/commands/conversation.js +105 -0
- package/lib/commands/dashboard.js +43 -0
- package/lib/commands/department.js +96 -0
- package/lib/commands/dev.js +620 -0
- package/lib/commands/devResident.js +326 -0
- package/lib/commands/devSupervisor.js +1062 -0
- package/lib/commands/discord.js +132 -0
- package/lib/commands/doctor.js +181 -0
- package/lib/commands/domain.js +114 -0
- package/lib/commands/done.js +347 -0
- package/lib/commands/engagement.js +597 -0
- package/lib/commands/entity.js +119 -0
- package/lib/commands/facebook.js +212 -0
- package/lib/commands/fetchAllowlist.js +71 -0
- package/lib/commands/file.js +165 -0
- package/lib/commands/fleet.js +1189 -0
- package/lib/commands/governance.js +647 -0
- package/lib/commands/gripe.js +355 -0
- package/lib/commands/hat.js +374 -0
- package/lib/commands/helpers.js +96 -0
- package/lib/commands/hyperion.js +58 -0
- package/lib/commands/inbox.js +173 -0
- package/lib/commands/instagram.js +264 -0
- package/lib/commands/integration.js +100 -0
- package/lib/commands/inventory.js +115 -0
- package/lib/commands/invites.js +45 -0
- package/lib/commands/licence.js +139 -0
- package/lib/commands/linkedin.js +178 -0
- package/lib/commands/llm-policy.js +117 -0
- package/lib/commands/llm.js +45 -0
- package/lib/commands/lucid.js +597 -0
- package/lib/commands/mail.js +531 -0
- package/lib/commands/memory.js +361 -0
- package/lib/commands/mind.js +71 -0
- package/lib/commands/mon.js +581 -0
- package/lib/commands/monitoring.js +86 -0
- package/lib/commands/motion.js +22 -0
- package/lib/commands/notes.js +1246 -0
- package/lib/commands/notesBatch.js +160 -0
- package/lib/commands/notesBridges.js +56 -0
- package/lib/commands/notesLinks.js +60 -0
- package/lib/commands/notification.js +57 -0
- package/lib/commands/nucleus.js +123 -0
- package/lib/commands/objective.js +240 -0
- package/lib/commands/offload.js +89 -0
- package/lib/commands/onboard.js +329 -0
- package/lib/commands/open.js +56 -0
- package/lib/commands/org.js +276 -0
- package/lib/commands/peek.js +28 -0
- package/lib/commands/plan.js +944 -0
- package/lib/commands/plugin-doctor.js +1091 -0
- package/lib/commands/plugin.js +649 -0
- package/lib/commands/policy.js +78 -0
- package/lib/commands/powers.js +296 -0
- package/lib/commands/presence.js +89 -0
- package/lib/commands/prompt.js +350 -0
- package/lib/commands/provider.js +159 -0
- package/lib/commands/qlarity.js +706 -0
- package/lib/commands/quoxbox.js +270 -0
- package/lib/commands/quoxchat.js +275 -0
- package/lib/commands/quoxseo.js +378 -0
- package/lib/commands/reach.js +159 -0
- package/lib/commands/role.js +286 -0
- package/lib/commands/router.js +77 -0
- package/lib/commands/rows.js +358 -0
- package/lib/commands/run.js +187 -0
- package/lib/commands/safety.js +107 -0
- package/lib/commands/scim.js +88 -0
- package/lib/commands/scope.js +58 -0
- package/lib/commands/security.js +157 -0
- package/lib/commands/server.js +292 -0
- package/lib/commands/service.js +89 -0
- package/lib/commands/settings.js +99 -0
- package/lib/commands/setup.js +569 -0
- package/lib/commands/skillManifest.js +113 -0
- package/lib/commands/skillRightsFormat.js +38 -0
- package/lib/commands/skills.js +687 -0
- package/lib/commands/slot.js +1451 -0
- package/lib/commands/sso.js +229 -0
- package/lib/commands/status.js +137 -0
- package/lib/commands/tag.js +91 -0
- package/lib/commands/team.js +142 -0
- package/lib/commands/tg.js +240 -0
- package/lib/commands/tiktok.js +218 -0
- package/lib/commands/tips.js +23 -0
- package/lib/commands/tool.js +304 -0
- package/lib/commands/trace.js +530 -0
- package/lib/commands/trigger.js +223 -0
- package/lib/commands/update.js +46 -0
- package/lib/commands/vault.js +1447 -0
- package/lib/commands/vm.js +237 -0
- package/lib/commands/voice.js +213 -0
- package/lib/commands/volt.js +570 -0
- package/lib/commands/walkthrough.js +32 -0
- package/lib/commands/watch.js +167 -0
- package/lib/commands/webhook.js +137 -0
- package/lib/commands/welcome.js +14 -0
- package/lib/commands/workflow.js +660 -0
- package/lib/commands/x.js +448 -0
- package/lib/core/actingAs.js +136 -0
- package/lib/core/activeSummary.js +186 -0
- package/lib/core/aee.js +53 -0
- package/lib/core/agentAlias.js +163 -0
- package/lib/core/approvalContext.js +61 -0
- package/lib/core/aside.js +54 -0
- package/lib/core/askDecision.js +60 -0
- package/lib/core/auth.js +501 -0
- package/lib/core/banner.js +143 -0
- package/lib/core/brainPrime.js +90 -0
- package/lib/core/callerSource.js +129 -0
- package/lib/core/claudeLs.js +94 -0
- package/lib/core/cliBuild.js +62 -0
- package/lib/core/client.js +693 -0
- package/lib/core/commandRegistrars.js +121 -0
- package/lib/core/completions.js +163 -0
- package/lib/core/config.js +669 -0
- package/lib/core/confirm.js +37 -0
- package/lib/core/customHats.js +168 -0
- package/lib/core/deeplinks.js +76 -0
- package/lib/core/doneEngineFetch.js +227 -0
- package/lib/core/errors.js +441 -0
- package/lib/core/global-flags.js +190 -0
- package/lib/core/governedSession.js +402 -0
- package/lib/core/hyperion.js +102 -0
- package/lib/core/inboxResolution.js +65 -0
- package/lib/core/lazyCommands.js +122 -0
- package/lib/core/liveSessionsReport.js +112 -0
- package/lib/core/offload.js +504 -0
- package/lib/core/org-override.js +52 -0
- package/lib/core/orgResolve.js +70 -0
- package/lib/core/output.js +239 -0
- package/lib/core/peek.js +126 -0
- package/lib/core/prodEnv.js +7 -0
- package/lib/core/profileUsers.js +51 -0
- package/lib/core/promptCancel.js +18 -0
- package/lib/core/quoxglyph.js +158 -0
- package/lib/core/repoPath.js +11 -0
- package/lib/core/residentAlarm.js +282 -0
- package/lib/core/residentAuth.js +243 -0
- package/lib/core/residentPump.js +229 -0
- package/lib/core/residentSeed.js +106 -0
- 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 +192 -0
- package/lib/core/sessionsOverview.js +224 -0
- package/lib/core/slotKeeper.js +527 -0
- package/lib/core/slotWake.js +120 -0
- package/lib/core/supervisorEngine.js +933 -0
- package/lib/core/systemdInstaller.js +256 -0
- package/lib/core/version.js +87 -0
- 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 +92 -0
- package/lib/lucid/blind.js +71 -0
- package/lib/lucid/cards.js +86 -0
- package/lib/lucid/deck.js +32 -0
- package/lib/lucid/fmt.js +139 -0
- package/lib/lucid/markdown.js +81 -0
- package/lib/lucid/motion.js +56 -0
- package/lib/lucid/sitrep.js +173 -0
- package/lib/plugins/loader.js +93 -0
- package/lib/quoxon/activity.js +116 -0
- package/lib/quoxon/aocl.js +117 -0
- package/lib/quoxon/api.js +952 -0
- package/lib/quoxon/app.js +4032 -0
- 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 +82 -0
- package/lib/quoxon/cells.js +53 -0
- package/lib/quoxon/commandShort.js +112 -0
- package/lib/quoxon/commands.js +479 -0
- package/lib/quoxon/connect.js +654 -0
- package/lib/quoxon/connectCard.js +45 -0
- package/lib/quoxon/consent.js +86 -0
- package/lib/quoxon/context.js +68 -0
- package/lib/quoxon/copy.js +34 -0
- package/lib/quoxon/countdownLine.js +11 -0
- package/lib/quoxon/demo.autoplay.js +29 -0
- package/lib/quoxon/demo.fixtures.js +24 -0
- package/lib/quoxon/demo.js +76 -0
- 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 +36 -0
- package/lib/quoxon/events.js +677 -0
- package/lib/quoxon/feedback.js +50 -0
- package/lib/quoxon/gallery.js +237 -0
- 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 +197 -0
- package/lib/quoxon/intent.js +52 -0
- package/lib/quoxon/keys.js +164 -0
- package/lib/quoxon/launcher.js +103 -0
- package/lib/quoxon/main.js +176 -0
- package/lib/quoxon/markdown.js +341 -0
- 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 +311 -0
- package/lib/quoxon/modelPrefs.js +345 -0
- package/lib/quoxon/motion/clock.js +70 -0
- package/lib/quoxon/motion/color.js +35 -0
- package/lib/quoxon/motion/frame.js +14 -0
- package/lib/quoxon/motion/level.js +91 -0
- package/lib/quoxon/motion/reveal.js +54 -0
- package/lib/quoxon/motion/ring.js +79 -0
- package/lib/quoxon/mouse.js +211 -0
- package/lib/quoxon/noai.js +57 -0
- package/lib/quoxon/objects/agent.js +61 -0
- package/lib/quoxon/objects/away.js +72 -0
- package/lib/quoxon/objects/crystal.js +100 -0
- package/lib/quoxon/objects/fetch.js +182 -0
- package/lib/quoxon/objects/fleet.js +149 -0
- package/lib/quoxon/objects/health.js +186 -0
- package/lib/quoxon/objects/index.js +106 -0
- package/lib/quoxon/objects/ladder.js +71 -0
- package/lib/quoxon/objects/memory.js +44 -0
- package/lib/quoxon/objects/mirror.js +137 -0
- package/lib/quoxon/objects/model.js +118 -0
- package/lib/quoxon/objects/peek.js +17 -0
- package/lib/quoxon/objects/proof.js +110 -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/skill.js +105 -0
- package/lib/quoxon/objects/view.js +87 -0
- package/lib/quoxon/objects/workflow.js +119 -0
- package/lib/quoxon/observers.js +42 -0
- package/lib/quoxon/onboarding/copy.js +263 -0
- package/lib/quoxon/onboarding/credential.js +108 -0
- package/lib/quoxon/onboarding/detect.js +22 -0
- package/lib/quoxon/onboarding/dockerOffer.js +45 -0
- package/lib/quoxon/onboarding/failureCause.js +33 -0
- package/lib/quoxon/onboarding/finish.js +134 -0
- package/lib/quoxon/onboarding/index.js +94 -0
- package/lib/quoxon/onboarding/installRunner.js +284 -0
- package/lib/quoxon/onboarding/intent.js +53 -0
- package/lib/quoxon/onboarding/journey.js +73 -0
- package/lib/quoxon/onboarding/preflight.js +156 -0
- package/lib/quoxon/onboarding/probe.js +65 -0
- package/lib/quoxon/onboarding/runner.js +58 -0
- package/lib/quoxon/onboarding/view.js +682 -0
- 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 +98 -0
- 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 +348 -0
- 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/qglyph.js +143 -0
- package/lib/quoxon/qhello.js +58 -0
- package/lib/quoxon/qmotion.js +132 -0
- package/lib/quoxon/render.js +1167 -0
- package/lib/quoxon/resend.js +72 -0
- package/lib/quoxon/roster.js +307 -0
- package/lib/quoxon/route.js +39 -0
- package/lib/quoxon/rows.js +711 -0
- package/lib/quoxon/rowsStore.js +80 -0
- package/lib/quoxon/rules.js +130 -0
- package/lib/quoxon/screenContext.js +150 -0
- package/lib/quoxon/screenFrames.js +254 -0
- package/lib/quoxon/session.js +238 -0
- package/lib/quoxon/sessionDead.js +17 -0
- package/lib/quoxon/signin.js +125 -0
- package/lib/quoxon/skills.js +30 -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 +90 -0
- 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 +88 -0
- package/lib/quoxon/slash/listView.js +30 -0
- package/lib/quoxon/slash/modelCmd.js +261 -0
- package/lib/quoxon/slash/palette.js +118 -0
- package/lib/quoxon/slash/paletteController.js +169 -0
- package/lib/quoxon/slash/panels.js +116 -0
- package/lib/quoxon/slash/picker.js +56 -0
- package/lib/quoxon/slash/restart.js +202 -0
- package/lib/quoxon/slash/resume.js +59 -0
- package/lib/quoxon/slash/rowCmd.js +157 -0
- package/lib/quoxon/slash/rowWalk.js +175 -0
- package/lib/quoxon/slash/settings.js +60 -0
- 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/index.js +186 -0
- package/lib/quoxon/sound/mapping.js +120 -0
- package/lib/quoxon/sound/output.js +52 -0
- package/lib/quoxon/sound/synth.js +122 -0
- package/lib/quoxon/stray.js +22 -0
- package/lib/quoxon/suggestCommand.js +59 -0
- package/lib/quoxon/term.js +141 -0
- package/lib/quoxon/textBuffer.js +416 -0
- package/lib/quoxon/theme.js +286 -0
- package/lib/quoxon/tips.js +433 -0
- package/lib/quoxon/toolError.js +41 -0
- package/lib/quoxon/transcript.js +188 -0
- package/lib/quoxon/web/qface.js +89 -0
- package/lib/quoxon/welcome.js +52 -0
- 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/app.jsx +289 -0
- package/lib/tui/components/Breadcrumb.jsx +42 -0
- package/lib/tui/components/ChatInput.jsx +68 -0
- package/lib/tui/components/Confirm.jsx +32 -0
- package/lib/tui/components/DataTable.jsx +270 -0
- package/lib/tui/components/DetailPanel.jsx +241 -0
- package/lib/tui/components/HelpOverlay.jsx +67 -0
- package/lib/tui/components/Layout.jsx +129 -0
- package/lib/tui/components/Palette.jsx +141 -0
- package/lib/tui/components/SearchInput.jsx +125 -0
- package/lib/tui/components/Sparkline.jsx +55 -0
- package/lib/tui/components/StatusBar.jsx +133 -0
- package/lib/tui/components/Toast.jsx +52 -0
- package/lib/tui/hooks/useApi.js +44 -0
- package/lib/tui/hooks/useHealth.js +90 -0
- package/lib/tui/hooks/usePolledApi.js +86 -0
- package/lib/tui/hooks/useTerminalSize.js +46 -0
- package/lib/tui/index.js +56 -0
- package/lib/tui/intro/Intro.jsx +332 -0
- package/lib/tui/jsx-loader.js +32 -0
- package/lib/tui/keys.js +87 -0
- package/lib/tui/registry.js +49 -0
- package/lib/tui/theme.js +103 -0
- package/lib/tui/views/AdminView.jsx +398 -0
- package/lib/tui/views/AgentView.jsx +204 -0
- package/lib/tui/views/BackupView.jsx +225 -0
- package/lib/tui/views/BrainView.jsx +223 -0
- package/lib/tui/views/ChatView.jsx +468 -0
- package/lib/tui/views/FilesView.jsx +218 -0
- package/lib/tui/views/FleetView.jsx +234 -0
- package/lib/tui/views/GovernanceView.jsx +453 -0
- package/lib/tui/views/HealthView.jsx +216 -0
- package/lib/tui/views/HyperionDeck.jsx +79 -0
- package/lib/tui/views/InboxView.jsx +228 -0
- package/lib/tui/views/LucidDeck.jsx +719 -0
- package/lib/tui/views/MainMenu.jsx +123 -0
- package/lib/tui/views/MemoryView.jsx +377 -0
- package/lib/tui/views/ObjectivesView.jsx +230 -0
- package/lib/tui/views/OrgView.jsx +229 -0
- package/lib/tui/views/ToolView.jsx +221 -0
- package/lib/tui/views/VaultView.jsx +422 -0
- package/lib/tui/views/WorkflowView.jsx +329 -0
- package/package.json +75 -4
|
@@ -0,0 +1,682 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Getting started: the Ink view bare `quox` shows when no server is configured (FIRST_RUN_JOURNEY.md, C2a).
|
|
3
|
+
* A labelled, scripted setup guide: no model, no account, no network beyond local checks. It is deliberately a
|
|
4
|
+
* separate module from app.js; main.js picks one or the other.
|
|
5
|
+
*
|
|
6
|
+
* Steps: ask -> offer (Y/n) | declined -> preflight -> credential -> key -> install (installStop) ->
|
|
7
|
+
* ownerEmail -> ownerPassword -> finishing -> finale | notyet
|
|
8
|
+
* With no finisher (the dry run) install goes straight to the finale, as in C2a.
|
|
9
|
+
* Every line of copy comes from ./copy.js.
|
|
10
|
+
*/
|
|
11
|
+
import React from 'react';
|
|
12
|
+
import { Box, Static, Text, useStdin, useStdout } from 'ink';
|
|
13
|
+
import { HistoryItem, Row, introAnimates, clampCols, AskCard } from '../render.js';
|
|
14
|
+
import { c, clip } from '../theme.js';
|
|
15
|
+
import { parseChunk, initialKeyState, PASTE_ON, PASTE_OFF } from '../keys.js';
|
|
16
|
+
import { packageVersion } from '../welcome.js';
|
|
17
|
+
import * as T from './copy.js';
|
|
18
|
+
import { matchIntent, yesNo, parseConnect } from './intent.js';
|
|
19
|
+
import { loadJourney, saveJourney, clearJourney, resumeStep } from './journey.js';
|
|
20
|
+
import { pendingRows, runPreflight, preflightPassed, detectOllama, defaultDeps } from './preflight.js';
|
|
21
|
+
import { credentialOptions, KEY_PROVIDERS, normaliseOllamaUrl, nextSelectable } from './credential.js';
|
|
22
|
+
import { needsConsent, providerLabel } from '../consent.js';
|
|
23
|
+
import { HelloIntro, helloIntroMs } from '../qmotion.js';
|
|
24
|
+
import { launchEgg } from '../egg.js';
|
|
25
|
+
import { passwordProblem, emailOk, generatePassword } from './finish.js';
|
|
26
|
+
import { dockerOffer } from './dockerOffer.js';
|
|
27
|
+
|
|
28
|
+
// FIRSTRUN: a number pick means its example ("1" is "watch my servers"); the request is replayed as the first real turn.
|
|
29
|
+
const requestText = (text) => { const n = /^\s*([1-9])\s*$/.exec(String(text ?? '')); return n && T.EXAMPLES[Number(n[1]) - 1] ? T.EXAMPLES[Number(n[1]) - 1] : text; };
|
|
30
|
+
|
|
31
|
+
const h = React.createElement;
|
|
32
|
+
const GLYPH = { ok: '✔', fail: '✖', warn: '⚠', running: '◐', pending: '·' };
|
|
33
|
+
const TONE = { ok: 'ok', fail: 'fail', warn: 'warn', running: 'sky', pending: 'dim' };
|
|
34
|
+
const fmtS = (ms) => `${(Math.max(0, ms) / 1000).toFixed(1)}s`;
|
|
35
|
+
|
|
36
|
+
/** Word wrap to `width` columns; each line is its own <Text>, so every wrapped line keeps its colour. */
|
|
37
|
+
export function wrapWords(text, width) {
|
|
38
|
+
const w = Math.max(8, width);
|
|
39
|
+
const out = [];
|
|
40
|
+
for (const para of String(text ?? '').split('\n')) {
|
|
41
|
+
let line = '';
|
|
42
|
+
for (const word of para.split(/ +/)) {
|
|
43
|
+
let wd = word;
|
|
44
|
+
while ([...wd].length > w) { if (line) { out.push(line); line = ''; } out.push([...wd].slice(0, w).join('')); wd = [...wd].slice(w).join(''); }
|
|
45
|
+
if (!line) line = wd;
|
|
46
|
+
else if ([...line].length + 1 + [...wd].length <= w) line += ' ' + wd;
|
|
47
|
+
else { out.push(line); line = wd; }
|
|
48
|
+
}
|
|
49
|
+
out.push(line);
|
|
50
|
+
}
|
|
51
|
+
return out;
|
|
52
|
+
}
|
|
53
|
+
const Para = ({ text, width, color, bold }) => h(Box, { flexDirection: 'column' }, ...wrapWords(text, width).map((l, i) => h(Text, { key: i, color, bold }, l)));
|
|
54
|
+
|
|
55
|
+
// ---- static items -----------------------------------------------------------------------------
|
|
56
|
+
function Item({ item, cols }) {
|
|
57
|
+
switch (item.kind) {
|
|
58
|
+
case 'intro': return h(HelloIntro, { item, cols, text: T.HELLO });
|
|
59
|
+
case 'welcome': case 'user': return h(HistoryItem, { item, cols });
|
|
60
|
+
case 'header': return h(Box, { flexDirection: 'column', marginTop: 1 },
|
|
61
|
+
h(Text, null, h(Text, { color: c('violet'), bold: true }, T.LABEL), h(Text, { color: c('dim') }, ' ' + '─'.repeat(Math.max(1, cols - T.LABEL.length - 1)))),
|
|
62
|
+
h(Text, { color: c('dim') }, clip(T.LABEL_SUB, cols)));
|
|
63
|
+
case 'ask': return h(AskCard, { text: item.text, cols }); // QUOXON-ASK: the same card as an approval
|
|
64
|
+
case 'guide': return h(Box, { marginTop: item.gap === false ? 0 : 1, paddingLeft: 2 }, h(Para, { width: cols - 2, color: c(item.tone || 'text'), bold: Boolean(item.bold), text: item.text }));
|
|
65
|
+
case 'dim': return h(Box, { paddingLeft: 2 }, h(Para, { width: cols - 2, color: c(item.tone || 'dim'), text: item.text }));
|
|
66
|
+
case 'cmd': return h(Box, { paddingLeft: 4 }, h(Para, { width: cols - 4, color: c('sky'), text: item.text }));
|
|
67
|
+
case 'long': { // a URL or hash that must never be hard-wrapped (copy, click): one line if it fits, else its own line
|
|
68
|
+
if ([...item.text].length <= cols - 2) return h(Box, { marginTop: item.gap === false ? 0 : 1, paddingLeft: 2 }, h(Text, { color: c(item.tone || 'text') }, item.text));
|
|
69
|
+
return h(Box, { flexDirection: 'column', marginTop: item.gap === false ? 0 : 1, paddingLeft: 2 },
|
|
70
|
+
h(Para, { width: cols - 2, color: c(item.tone || 'text'), text: item.lead }),
|
|
71
|
+
h(Box, { width: [...item.value].length + 2 }, h(Text, { color: c('sky'), wrap: 'truncate-end' }, item.value)));
|
|
72
|
+
}
|
|
73
|
+
case 'list': return h(Box, { flexDirection: 'column', paddingLeft: 2 }, ...item.items.map((t, i) => h(Box, { key: i, flexDirection: 'row' },
|
|
74
|
+
h(Text, { color: c('sky') }, item.numbered ? `${i + 1} ` : '· '), h(Para, { width: cols - 5, color: c('text'), text: t }))));
|
|
75
|
+
case 'rows': return h(Box, { flexDirection: 'column', marginTop: 1 }, ...item.rows.map((r, i) => h(CheckRow, { key: i, r, cols })));
|
|
76
|
+
default: return null;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function CheckRow({ r, cols }) {
|
|
81
|
+
return h(Box, { flexDirection: 'column' },
|
|
82
|
+
h(Row, { glyph: GLYPH[r.status] || '·', tone: TONE[r.status] || 'dim', name: r.name, body: r.body, tail: r.tail, cols }),
|
|
83
|
+
r.status === 'fail' && r.fix ? h(Box, { paddingLeft: 6, flexDirection: 'column' },
|
|
84
|
+
h(Para, { width: cols - 6, color: c('placeholder'), text: r.fix }),
|
|
85
|
+
r.link ? h(Text, { color: c('sky') }, clip(r.link, cols - 6)) : null) : null);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function InputLine({ text, placeholder, cols, masked = false }) {
|
|
89
|
+
const shown = masked ? '•'.repeat(Math.min([...text].length, Math.max(4, cols - 20))) : text;
|
|
90
|
+
const room = cols - 4;
|
|
91
|
+
const tail = [...shown].slice(-room).join('');
|
|
92
|
+
return h(Box, { marginTop: 1 },
|
|
93
|
+
h(Text, { color: c('sky'), bold: true }, '› '),
|
|
94
|
+
tail ? h(Text, { color: c('input') }, tail) : null,
|
|
95
|
+
h(Text, { inverse: true }, ' '),
|
|
96
|
+
!tail && placeholder ? h(Text, { color: c('placeholder') }, ' ' + clip(placeholder, room - 2)) : null,
|
|
97
|
+
masked && text ? h(Text, { color: c('dim') }, ` ${[...text].length} chars`) : null);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const Hint = ({ text, cols }) => h(Box, { paddingLeft: 2 }, h(Text, { color: c('dim') }, clip(text, cols - 2)));
|
|
101
|
+
|
|
102
|
+
// ---- the view ---------------------------------------------------------------------------------
|
|
103
|
+
export function GettingStarted({
|
|
104
|
+
columns, version = '0.3.1', cwd = '', welcome = false, animateIntro, earlyInput = null,
|
|
105
|
+
journeyPath, dryRun = false, runner = null, preflightDeps, runChecks = runPreflight, findOllama = detectOllama,
|
|
106
|
+
requestCredential, broker = null, finisher = null, onExit = () => {}, now = () => Date.now()
|
|
107
|
+
}) {
|
|
108
|
+
const { stdin, setRawMode, isRawModeSupported } = useStdin();
|
|
109
|
+
const { stdout } = useStdout();
|
|
110
|
+
const cols = clampCols(columns ?? ((stdout?.columns || 80) - 1));
|
|
111
|
+
const [, force] = React.useState(0);
|
|
112
|
+
const rerender = () => force(n => n + 1);
|
|
113
|
+
const seq = React.useRef(0);
|
|
114
|
+
const nid = () => `g${seq.current++}`;
|
|
115
|
+
|
|
116
|
+
const saved = React.useMemo(() => (journeyPath ? loadJourney(journeyPath) : null), []);
|
|
117
|
+
const resumeAt = resumeStep(saved);
|
|
118
|
+
const introItem = { id: 'intro', kind: 'intro', text: `${version} · Getting started · ${cwd}` };
|
|
119
|
+
const opening = () => {
|
|
120
|
+
const items = [introItem, ...(welcome ? [{ id: 'welcome', kind: 'welcome', version: packageVersion() }] : []), { id: 'hdr', kind: 'header' }];
|
|
121
|
+
if (resumeAt) {
|
|
122
|
+
items.push({ id: 'resume', kind: 'guide', text: T.RESUME });
|
|
123
|
+
if (saved.request) items.push({ id: 'resumeReq', kind: 'dim', text: T.resumeRequest(requestText(saved.request)) });
|
|
124
|
+
} else {
|
|
125
|
+
items.push({ id: 'greet', kind: 'guide', text: T.GREETING_REST }, { id: 'q', kind: 'guide', text: T.QUESTION }, { id: 'ex', kind: 'list', items: T.EXAMPLES, numbered: true });
|
|
126
|
+
}
|
|
127
|
+
return items;
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
const S = React.useRef({
|
|
131
|
+
history: [], step: 'ask', input: '', intent: saved?.intent || null, request: saved?.request ? requestText(saved.request) : null, provider: saved?.provider === 'skip' ? 'skip' : null,
|
|
132
|
+
rows: [], fixNote: null, options: [], sel: 1, flash: null, install: null, logs: [], showLog: false, done: false, secret: null,
|
|
133
|
+
installStage: saved?.installStage || null, installDir: saved?.installDir || null, dashboardUrl: saved?.dashboardUrl || null,
|
|
134
|
+
ownerEmail: saved?.ownerEmail || null, finRows: [], stopping: false, vault: null
|
|
135
|
+
});
|
|
136
|
+
const set = (patch) => { S.current = { ...S.current, ...(typeof patch === 'function' ? patch(S.current) : patch) }; rerender(); };
|
|
137
|
+
const push = (...items) => set(s => ({ history: [...s.history, ...items.map(it => ({ id: nid(), ...it }))] }));
|
|
138
|
+
const persist = (extra = {}) => {
|
|
139
|
+
if (!journeyPath) return;
|
|
140
|
+
const c = S.current;
|
|
141
|
+
saveJourney({ step: c.step, intent: c.intent, request: c.request, provider: c.provider, installStage: c.installStage, installDir: c.installDir, dashboardUrl: c.dashboardUrl, ownerEmail: c.ownerEmail, ...(c.ollamaUrl ? { ollamaUrl: c.ollamaUrl } : {}), ...extra }, journeyPath, () => new Date(now()).toISOString());
|
|
142
|
+
};
|
|
143
|
+
const live = React.useRef(true);
|
|
144
|
+
React.useEffect(() => () => { live.current = false; }, []);
|
|
145
|
+
|
|
146
|
+
// ---- intro (Orbit & Lock), then the opening lines move into <Static> --------------------------
|
|
147
|
+
const animating = animateIntro ?? introAnimates(stdout?.isTTY);
|
|
148
|
+
// Orbit & Lock, then the Q says hello (T.HELLO decodes in beside it); rarely the dragon's googly-eye greeting after that.
|
|
149
|
+
const eye = React.useMemo(() => (animating && animateIntro === undefined ? launchEgg({ tty: Boolean(stdout?.isTTY) }) : false), []);
|
|
150
|
+
const introMs = helloIntroMs({ eye, text: T.HELLO });
|
|
151
|
+
const introStart = React.useRef(Date.now());
|
|
152
|
+
const [, setFrame] = React.useState(0);
|
|
153
|
+
const introDone = React.useRef(false);
|
|
154
|
+
const finishIntro = () => {
|
|
155
|
+
if (introDone.current) return;
|
|
156
|
+
introDone.current = true;
|
|
157
|
+
S.current = { ...S.current, history: [...opening(), ...S.current.history] };
|
|
158
|
+
if (resumeAt === 'offer') offerLines();
|
|
159
|
+
else if (resumeAt === 'preflight') startPreflight();
|
|
160
|
+
else rerender();
|
|
161
|
+
};
|
|
162
|
+
React.useEffect(() => {
|
|
163
|
+
if (!animating) { finishIntro(); return undefined; }
|
|
164
|
+
introStart.current = Date.now();
|
|
165
|
+
const id = setInterval(() => {
|
|
166
|
+
if (Date.now() - introStart.current >= introMs) { clearInterval(id); setTimeout(finishIntro, 0); }
|
|
167
|
+
setFrame(f => f + 1);
|
|
168
|
+
}, 50);
|
|
169
|
+
return () => clearInterval(id);
|
|
170
|
+
}, []);
|
|
171
|
+
|
|
172
|
+
// ---- steps ------------------------------------------------------------------------------------
|
|
173
|
+
const exit = (result) => {
|
|
174
|
+
if (S.current.done) return;
|
|
175
|
+
S.current.secret?.drop?.();
|
|
176
|
+
set({ done: true, secret: null });
|
|
177
|
+
setTimeout(() => onExit(result), 0);
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
function offerLines() {
|
|
181
|
+
const items = [{ kind: 'guide', text: T.OFFER }];
|
|
182
|
+
items.push({ kind: 'ask', text: T.OFFER_ASK }, { kind: 'dim', text: T.ALREADY });
|
|
183
|
+
push(...items);
|
|
184
|
+
set({ step: 'offer' });
|
|
185
|
+
persist();
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function onRequest(text) {
|
|
189
|
+
const m = matchIntent(text);
|
|
190
|
+
if (m.intent === 'empty') return;
|
|
191
|
+
push({ kind: 'user', text });
|
|
192
|
+
if (m.intent === 'connect') { push({ kind: 'guide', text: T.CONNECT_INTENT_LINE }); set({ step: 'ask' }); return; }
|
|
193
|
+
set({ intent: m.intent, request: requestText(text) });
|
|
194
|
+
push({ kind: 'guide', text: m.intent === 'offscript' ? T.DEFERRAL : T.INTENT_LINES[m.intent] });
|
|
195
|
+
offerLines();
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function onSlash(text) {
|
|
199
|
+
const t = text.trim();
|
|
200
|
+
if (/^\/(exit|quit|q)$/i.test(t)) { push({ kind: 'user', text: t }, { kind: 'dim', text: T.BYE }); persist(); exit({ action: 'exit' }); return true; }
|
|
201
|
+
if (/^\/help$/i.test(t)) { push({ kind: 'user', text: t }, { kind: 'list', items: T.HELP.map(([k, v]) => `${k} ${v}`) }); return true; }
|
|
202
|
+
const conn = parseConnect(t);
|
|
203
|
+
if (conn) {
|
|
204
|
+
push({ kind: 'user', text: t });
|
|
205
|
+
if (!conn.ok) { push({ kind: 'dim', tone: 'warn', text: conn.reason === 'url' ? T.connectBadUrl(conn.value) : T.CONNECT_USAGE }); return true; }
|
|
206
|
+
push({ kind: 'guide', text: T.connectChecking(conn.url) });
|
|
207
|
+
exit({ action: 'connect', url: conn.url });
|
|
208
|
+
return true;
|
|
209
|
+
}
|
|
210
|
+
push({ kind: 'user', text: t }, { kind: 'dim', tone: 'warn', text: T.SLASH_UNKNOWN });
|
|
211
|
+
return true;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function submit() {
|
|
215
|
+
const s = S.current;
|
|
216
|
+
const text = s.input.trim();
|
|
217
|
+
set({ input: '' });
|
|
218
|
+
if (text.startsWith('/')) { onSlash(text); return; }
|
|
219
|
+
if (s.step === 'ask') return onRequest(text);
|
|
220
|
+
if (s.step === 'offer' || s.step === 'declined') {
|
|
221
|
+
const yn = yesNo(text);
|
|
222
|
+
if (yn === true) { push({ kind: 'user', text: text || 'yes' }); startPreflight(); return; }
|
|
223
|
+
if (yn === false) { push({ kind: 'user', text }, { kind: 'guide', text: T.DECLINED }, { kind: 'dim', text: T.DECLINED_HINT }); set({ step: 'declined' }); persist(); return; }
|
|
224
|
+
return onRequest(text); // a new request instead of an answer
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
async function startPreflight() {
|
|
229
|
+
set({ step: 'preflight', rows: pendingRows(), fixNote: null });
|
|
230
|
+
persist();
|
|
231
|
+
// resuming an install this guide started: its own ports being busy is expected
|
|
232
|
+
const deps = S.current.installStage ? { ...(preflightDeps || defaultDeps()), ownInstall: true } : preflightDeps;
|
|
233
|
+
const rows = await runChecks(deps, (r) => { if (live.current) set(s => ({ rows: s.rows.map(x => (x.id === r.id ? r : x)) })); });
|
|
234
|
+
if (!live.current || S.current.done) return;
|
|
235
|
+
if (!preflightPassed(rows)) { set({ step: 'preflightFail', rows, fixNote: T.PREFLIGHT_FAIL }); return; }
|
|
236
|
+
push({ kind: 'guide', text: T.PREFLIGHT_TITLE, bold: true }, { kind: 'rows', rows }, { kind: 'guide', tone: 'ok', text: T.PREFLIGHT_OK });
|
|
237
|
+
toCredential();
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
async function toCredential() {
|
|
241
|
+
set({ rows: [] });
|
|
242
|
+
if (S.current.provider === 'skip') { // Z2H-R2B: the owner already chose "Skip for now"; never re-ask on resume (/connect stays open)
|
|
243
|
+
push({ kind: 'guide', tone: 'warn', text: T.CRED_SKIPPED });
|
|
244
|
+
startInstall();
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
const ollama = await findOllama();
|
|
248
|
+
if (!live.current || S.current.done) return;
|
|
249
|
+
const options = credentialOptions({ ollama });
|
|
250
|
+
push({ kind: 'guide', text: T.CRED_TITLE, bold: true });
|
|
251
|
+
set({ step: 'credential', options, sel: Math.max(0, options.findIndex(o => !o.disabled)), flash: null });
|
|
252
|
+
persist();
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
async function choose(opt) {
|
|
256
|
+
if (!opt) return;
|
|
257
|
+
if (opt.disabled) { set({ flash: T.CRED_CHATGPT_SOON }); return; }
|
|
258
|
+
set({ flash: null, provider: opt.id });
|
|
259
|
+
if (KEY_PROVIDERS.has(opt.id)) {
|
|
260
|
+
set({ step: 'key', input: '' });
|
|
261
|
+
const held = await requestCredential(opt.id); // THE hook: K1 secure card once it merges
|
|
262
|
+
if (!live.current || S.current.done) { held?.drop?.(); return; }
|
|
263
|
+
if (!held) { set({ step: 'credential', input: '' }); return; }
|
|
264
|
+
push({ kind: 'user', text: opt.label }, { kind: 'guide', tone: 'ok', text: T.credHeld(opt.label, held.fingerprint) }, ...(needsConsent(opt.id) ? [{ kind: 'guide', text: T.credConsent(opt.label) }] : []));
|
|
265
|
+
set({ secret: held, input: '' });
|
|
266
|
+
if (needsConsent(opt.id)) { set({ step: 'cloudConsent', cloudConsent: null }); return; } // ask once; the answer is the user's own
|
|
267
|
+
|
|
268
|
+
} else if (opt.id === 'ollama' && !opt.detected) {
|
|
269
|
+
set({ step: 'ollamaUrl', input: '' }); // B3: nothing answered locally; ask for the LAN URL
|
|
270
|
+
return;
|
|
271
|
+
} else {
|
|
272
|
+
push({ kind: 'user', text: opt.label }, { kind: 'guide', tone: opt.id === 'skip' ? 'warn' : 'ok', text: opt.id === 'skip' ? T.CRED_SKIPPED : T.CRED_OLLAMA });
|
|
273
|
+
}
|
|
274
|
+
startInstall();
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
function answerCloudConsent(yes) {
|
|
278
|
+
const p = S.current.provider; const who = providerLabel(p);
|
|
279
|
+
push({ kind: 'user', text: yes ? 'yes' : 'no' }, { kind: 'guide', tone: yes ? 'ok' : 'warn', text: yes ? T.credConsentYes(who) : T.credConsentNo(who, p) });
|
|
280
|
+
set({ cloudConsent: yes });
|
|
281
|
+
startInstall();
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function submitOllamaUrl() {
|
|
285
|
+
const url = normaliseOllamaUrl(S.current.input);
|
|
286
|
+
if (!url) { set({ flash: T.OLLAMA_URL_BAD }); return; }
|
|
287
|
+
push({ kind: 'user', text: url }, { kind: 'guide', tone: 'ok', text: T.ollamaChosen(url) });
|
|
288
|
+
set({ input: '', flash: null, ollamaUrl: url });
|
|
289
|
+
startInstall();
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
async function startInstall() {
|
|
293
|
+
if (!runner) { // no runner (should not happen outside tests): say so, and give the installer command
|
|
294
|
+
push({ kind: 'guide', text: T.INSTALL_NOT_YET }, { kind: 'cmd', text: T.INSTALL_COMMAND }, { kind: 'dim', text: T.INSTALL_NOT_YET_AFTER });
|
|
295
|
+
if (S.current.secret) { S.current.secret.drop(); push({ kind: 'dim', tone: 'warn', text: T.INSTALL_KEY_GONE }); }
|
|
296
|
+
set({ step: 'notyet', secret: null });
|
|
297
|
+
persist({ step: 'install' });
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
const dry = runner.kind === 'dry-run';
|
|
301
|
+
const healthOnly = !dry && (S.current.installStage === 'start' || S.current.installStage === 'health');
|
|
302
|
+
if (!dry && runner.installDir) set({ installDir: runner.installDir });
|
|
303
|
+
if (!dry && S.current.install == null) {
|
|
304
|
+
if (runner.installDir) push({ kind: 'dim', text: T.installWhere(runner.installDir) });
|
|
305
|
+
if (healthOnly) push({ kind: 'dim', text: T.INSTALL_HEALTH_ONLY });
|
|
306
|
+
}
|
|
307
|
+
const stages = runner.stages.map(st => ({ id: st.id, name: st.id.toUpperCase(), body: st.label, status: 'pending', startedAt: null, ms: null, earlier: false }));
|
|
308
|
+
set({ step: 'install', install: { stages, kind: runner.kind, failed: null, degraded: false }, logs: [], showLog: false, stopping: false });
|
|
309
|
+
persist();
|
|
310
|
+
const ac = new AbortController();
|
|
311
|
+
S.current.abort = ac;
|
|
312
|
+
const res = await runner.run({
|
|
313
|
+
signal: ac.signal, request: S.current.request,
|
|
314
|
+
resumeFrom: S.current.installStage, resumeDashboardUrl: S.current.dashboardUrl,
|
|
315
|
+
onCheckpoint: (id, extra = {}) => { // the last COMPLETED stage, so the next quox resumes honestly
|
|
316
|
+
set({ installStage: id, ...(extra.dashboardUrl ? { dashboardUrl: extra.dashboardUrl } : {}) });
|
|
317
|
+
persist({ step: 'install' });
|
|
318
|
+
},
|
|
319
|
+
onEvent: (ev) => {
|
|
320
|
+
if (!live.current) return;
|
|
321
|
+
if (ev.type === 'log') set(s => ({ logs: [...s.logs, ev.line].slice(-8) }));
|
|
322
|
+
if (ev.type === 'script') push({ kind: 'long', tone: 'dim', gap: false, text: T.installScript(ev.url, ev.sha256, ev.bytes), lead: T.installScriptLead(ev.url, ev.bytes), value: ev.sha256 });
|
|
323
|
+
if (ev.type === 'stage-label') set(s => ({ install: { ...s.install, stages: s.install.stages.map(x => (x.id === ev.id ? { ...x, body: ev.label } : x)) } }));
|
|
324
|
+
if (ev.type === 'stage') set(s => ({ install: { ...s.install, stages: s.install.stages.map(x => (x.id === ev.id ? { ...x, status: ev.status, startedAt: ev.status === 'running' ? ev.at : x.startedAt, ms: ev.ms ?? x.ms, earlier: Boolean(ev.earlier) } : x)) } }));
|
|
325
|
+
}
|
|
326
|
+
});
|
|
327
|
+
S.current.abort = null;
|
|
328
|
+
if (!live.current || S.current.done) return;
|
|
329
|
+
if (res.interrupted || S.current.stopping) {
|
|
330
|
+
push({ kind: 'dim', text: T.INSTALL_STOPPED }); persist({ step: 'install' }); exit({ action: 'exit' }); return;
|
|
331
|
+
}
|
|
332
|
+
if (res.logPath) set({ installLogPath: res.logPath });
|
|
333
|
+
if (res.dashboardUrl) set({ dashboardUrl: res.dashboardUrl });
|
|
334
|
+
const rows = S.current.install.stages.map(st => ({ ...st, tail: st.earlier ? T.INSTALL_EARLIER : st.ms != null ? fmtS(st.ms) : '' }));
|
|
335
|
+
if (!res.ok) { set(s => ({ step: 'installFail', install: { ...s.install, failed: res.failedStage || '?', degraded: Boolean(res.degraded), error: res.error || null, logPath: res.logPath || null, cause: res.cause?.message || null } })); persist({ step: 'install' }); return; }
|
|
336
|
+
push({ kind: 'guide', text: T.INSTALL_TITLE + (dry ? ` (${T.INSTALL_DRY})` : ''), bold: true }, { kind: 'rows', rows });
|
|
337
|
+
if (dry || !finisher) { finale({ dashboardUrl: res.dashboardUrl, dry }); return; }
|
|
338
|
+
toOwner();
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// ---- owner account (C2c) ----------------------------------------------------------------------
|
|
342
|
+
function toOwner(note = null) {
|
|
343
|
+
if (note) push({ kind: 'guide', tone: 'warn', text: note });
|
|
344
|
+
else push({ kind: 'guide', text: T.OWNER_TITLE, bold: true }, { kind: 'dim', text: T.OWNER_WHY });
|
|
345
|
+
set({ step: 'ownerEmail', input: S.current.ownerEmail || '', flash: null });
|
|
346
|
+
persist({ step: 'owner' });
|
|
347
|
+
}
|
|
348
|
+
function onOwnerEmail() {
|
|
349
|
+
const email = S.current.input.trim();
|
|
350
|
+
if (!emailOk(email)) { set({ flash: T.OWNER_EMAIL_BAD }); return; }
|
|
351
|
+
push({ kind: 'user', text: email });
|
|
352
|
+
set({ ownerEmail: email, step: 'ownerPassword', input: '', flash: null });
|
|
353
|
+
persist({ step: 'owner' });
|
|
354
|
+
}
|
|
355
|
+
function onOwnerPassword() {
|
|
356
|
+
let pw = S.current.input;
|
|
357
|
+
set({ input: '' });
|
|
358
|
+
if (!pw) {
|
|
359
|
+
pw = generatePassword(); // shown once, here, then only the token is kept
|
|
360
|
+
push({ kind: 'guide', tone: 'ok', text: T.ownerPwShownOnce(pw) }, { kind: 'dim', text: T.OWNER_PW_SAVE });
|
|
361
|
+
} else {
|
|
362
|
+
const bad = passwordProblem(pw);
|
|
363
|
+
if (bad) { set({ flash: bad }); return; }
|
|
364
|
+
}
|
|
365
|
+
set({ flash: null });
|
|
366
|
+
runFinish(S.current.ownerEmail, pw);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
async function runFinish(email, password) {
|
|
370
|
+
const F = T.FIN;
|
|
371
|
+
const opt = credentialOptions({ ollama: true }).find(o => o.id === S.current.provider);
|
|
372
|
+
const rows = [
|
|
373
|
+
{ id: 'account', name: F.account.name, status: 'running', body: F.account.running },
|
|
374
|
+
{ id: 'signin', name: F.signin.name, status: 'pending', body: F.signin.running },
|
|
375
|
+
{ id: 'vault', name: F.vault.name, status: 'pending', body: S.current.secret ? F.vault.running(opt?.label || 'key') : F.vault.skipped },
|
|
376
|
+
...(S.current.cloudConsent === true ? [{ id: 'consent', name: F.consent.name, status: 'pending', body: F.consent.running(providerLabel(S.current.provider)) }] : []),
|
|
377
|
+
{ id: 'link', name: F.link.name, status: 'pending', body: F.link.running }
|
|
378
|
+
];
|
|
379
|
+
const row = (id, patch) => set(s => ({ finRows: s.finRows.map(r => (r.id === id ? { ...r, ...patch } : r)) }));
|
|
380
|
+
set({ step: 'finishing', finRows: rows });
|
|
381
|
+
persist({ step: 'finishing' });
|
|
382
|
+
|
|
383
|
+
const made = await finisher.createOwner({ email, password });
|
|
384
|
+
if (!live.current || S.current.done) return;
|
|
385
|
+
if (!made.ok && !made.existed) { set({ finRows: [] }); toOwner(made.error); set({ step: 'ownerPassword', input: '' }); return; }
|
|
386
|
+
row('account', { status: 'ok', body: made.existed ? F.account.existing(email) : F.account.ok(email) });
|
|
387
|
+
|
|
388
|
+
row('signin', { status: 'running' });
|
|
389
|
+
const signed = await finisher.signIn({ email, password });
|
|
390
|
+
password = null; // the password is not needed again; only the token is kept (in the profile)
|
|
391
|
+
if (!live.current || S.current.done) return;
|
|
392
|
+
if (!signed.ok) {
|
|
393
|
+
set({ finRows: [] });
|
|
394
|
+
toOwner(made.existed ? T.OWNER_EXISTS : signed.error);
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
397
|
+
row('signin', { status: 'ok', body: F.signin.ok(signed.org) });
|
|
398
|
+
|
|
399
|
+
let vault = null;
|
|
400
|
+
if (S.current.secret) {
|
|
401
|
+
row('vault', { status: 'running' });
|
|
402
|
+
vault = await finisher.storeCredential(S.current.secret); // drops the key, stored or not
|
|
403
|
+
set({ secret: null });
|
|
404
|
+
if (!live.current || S.current.done) return;
|
|
405
|
+
row('vault', { status: vault.ok ? 'ok' : 'warn', body: String(vault.text || '').replace(/^[✔✖]\s*/, '') }); // the row glyph already says it
|
|
406
|
+
} else {
|
|
407
|
+
row('vault', { status: 'warn', body: S.current.provider === 'ollama' ? F.vault.ollama : F.vault.skipped });
|
|
408
|
+
}
|
|
409
|
+
set({ vault });
|
|
410
|
+
|
|
411
|
+
if (S.current.cloudConsent === true && finisher.grantConsent) { // the owner said yes at the credential step
|
|
412
|
+
row('consent', { status: 'running' });
|
|
413
|
+
const g = await finisher.grantConsent(S.current.provider);
|
|
414
|
+
if (!live.current || S.current.done) return;
|
|
415
|
+
const who = providerLabel(S.current.provider);
|
|
416
|
+
row('consent', g?.ok ? { status: 'ok', body: F.consent.ok(who) } : { status: 'warn', body: F.consent.fail(g?.error || 'unknown error', S.current.provider) });
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
row('link', { status: 'running' });
|
|
420
|
+
const link = await finisher.loginLink(S.current.dashboardUrl);
|
|
421
|
+
if (!live.current || S.current.done) return;
|
|
422
|
+
row('link', link.ok ? { status: 'ok', body: F.link.ok } : { status: 'warn', body: F.link.fail(link.error) });
|
|
423
|
+
|
|
424
|
+
// Z2H-R5 E: only mention adding a host where this install can serve its host installer (unknown = as before).
|
|
425
|
+
let hostsAvailable = true;
|
|
426
|
+
try { const hi = finisher.hostInstaller ? await finisher.hostInstaller() : null; if (hi && hi.ok === false && Number(hi.status) >= 400) hostsAvailable = false; } catch { /* unknown */ }
|
|
427
|
+
if (!live.current || S.current.done) return;
|
|
428
|
+
push({ kind: 'guide', text: T.FINISH_TITLE, bold: true }, { kind: 'rows', rows: S.current.finRows });
|
|
429
|
+
if (vault) push({ kind: 'dim', text: vault.ok ? T.KEY_GONE_AFTER : `${T.KEY_GONE_AFTER} ${T.keyNotStored(vault.provider || S.current.provider)}` });
|
|
430
|
+
set({ finRows: [] });
|
|
431
|
+
finale({ dashboardUrl: S.current.dashboardUrl, link: link.ok ? link.url : null, dry: false, hostsAvailable });
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
function replayText() {
|
|
435
|
+
const { request, intent } = S.current;
|
|
436
|
+
if (!request || intent === 'explore') return T.REPLAY_TOUR;
|
|
437
|
+
return request;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
function finale({ dashboardUrl, link = null, dry, hostsAvailable = true }) {
|
|
441
|
+
const items = [];
|
|
442
|
+
const intent = S.current.intent && T.TIPS[S.current.intent] ? S.current.intent : 'explore';
|
|
443
|
+
if (dry) items.push({ kind: 'guide', tone: 'warn', text: T.FINALE_DRY_BANNER });
|
|
444
|
+
items.push({ kind: 'guide', tone: 'ok', bold: true, text: T.FINALE_RUNNING },
|
|
445
|
+
{ kind: 'guide', text: T.finaleReplay(S.current.request && S.current.intent !== 'explore' ? S.current.request : null) });
|
|
446
|
+
if (dry) items.push({ kind: 'dim', text: T.FINALE_REPLAY_DRY });
|
|
447
|
+
const url = dashboardUrl || 'https://localhost:3000';
|
|
448
|
+
if (dry || link) items.push({ kind: 'long', text: T.finaleDashboard(link || url), lead: T.FINALE_DASHBOARD_LEAD, value: link || url });
|
|
449
|
+
else items.push({ kind: 'long', text: T.finaleDashboardPlain(url), lead: T.FINALE_DASHBOARD_LEAD_PLAIN, value: url });
|
|
450
|
+
items.push({ kind: 'guide', gap: false, text: T.FINALE_PROOF });
|
|
451
|
+
if (!dry) items.push({ kind: 'dim', text: T.FINALE_CERT });
|
|
452
|
+
if (!dry && S.current.installLogPath) items.push({ kind: 'dim', text: T.INSTALL_LOG_PATH(S.current.installLogPath) });
|
|
453
|
+
items.push({ kind: 'guide', text: T.FINALE_TIPS_TITLE }, { kind: 'list', items: dry ? T.FINALE_TIPS : T.TIPS[intent] });
|
|
454
|
+
const noAi = !dry && (S.current.provider === 'skip' || (S.current.vault && !S.current.vault.ok));
|
|
455
|
+
if (noAi && hostsAvailable) items.push({ kind: 'guide', text: T.FINALE_HOSTS_NO_AI });
|
|
456
|
+
if (!dry) items.push({ kind: 'guide', text: noAi ? T.k2NoAi(S.current.provider === 'anthropic' ? 'anthropic' : 'openai') : (!hostsAvailable && intent === 'servers' ? T.K2_SERVERS_NO_INSTALLER : T.K2[intent]) });
|
|
457
|
+
push(...items);
|
|
458
|
+
S.current.secret?.drop?.();
|
|
459
|
+
set({ step: 'finale', secret: null, finaleDry: dry });
|
|
460
|
+
if (!dry && journeyPath) clearJourney(journeyPath);
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
// ---- keys -------------------------------------------------------------------------------------
|
|
464
|
+
const K = React.useRef({ keys: initialKeyState() });
|
|
465
|
+
const onKey = (ev) => {
|
|
466
|
+
const s = S.current;
|
|
467
|
+
if (s.done) return;
|
|
468
|
+
if (ev.type === 'key' && (ev.name === 'ctrl-c' || ev.name === 'ctrl-d') && s.step === 'install') {
|
|
469
|
+
set({ step: 'installStop' }); // never abort a running install on one keypress: ask first
|
|
470
|
+
return;
|
|
471
|
+
}
|
|
472
|
+
if (ev.type === 'key' && (ev.name === 'ctrl-c' || ev.name === 'ctrl-d') && s.step === 'installStop') { stopInstall(); return; }
|
|
473
|
+
if (ev.type === 'key' && (ev.name === 'ctrl-c' || ev.name === 'ctrl-d')) {
|
|
474
|
+
if (s.step === 'key') broker?.cancel?.();
|
|
475
|
+
S.current.abort?.abort?.();
|
|
476
|
+
push({ kind: 'dim', text: T.BYE }); persist(); exit({ action: 'exit' }); return;
|
|
477
|
+
}
|
|
478
|
+
if (!introDone.current) { if (ev.type !== 'key') set({ input: s.input + ev.text.replace(/[\r\n]+/g, ' ') }); return; }
|
|
479
|
+
switch (s.step) {
|
|
480
|
+
case 'ask': case 'offer': case 'declined': return textKey(ev, submit);
|
|
481
|
+
case 'key': return textKey(ev, () => {
|
|
482
|
+
const v = S.current.input;
|
|
483
|
+
if (!v.trim()) { set({ flash: T.CRED_EMPTY }); return; }
|
|
484
|
+
set({ input: '', flash: null });
|
|
485
|
+
broker?.submit?.(v.trim());
|
|
486
|
+
}, () => { set({ input: '', flash: null }); broker?.cancel?.(); });
|
|
487
|
+
case 'ollamaUrl': return textKey(ev, submitOllamaUrl, () => set({ step: 'credential', input: '', flash: null }));
|
|
488
|
+
case 'preflightFail':
|
|
489
|
+
if (ev.type === 'text' && /^r$/i.test(ev.text)) startPreflight();
|
|
490
|
+
else if (ev.type === 'text' && /^c$/i.test(ev.text) && dryRun) {
|
|
491
|
+
push({ kind: 'guide', text: T.PREFLIGHT_TITLE, bold: true }, { kind: 'rows', rows: s.rows }, { kind: 'guide', tone: 'warn', text: T.PREFLIGHT_CONTINUED });
|
|
492
|
+
toCredential();
|
|
493
|
+
}
|
|
494
|
+
else if (ev.type === 'text' && /^q$/i.test(ev.text)) { push({ kind: 'dim', text: T.BYE }); persist({ step: 'preflight' }); exit({ action: 'exit' }); }
|
|
495
|
+
else if (ev.type === 'text' && /^y$/i.test(ev.text) && !dryRun) { // FIRSTRUN: the guide fixes Docker itself
|
|
496
|
+
const offer = dockerOffer(s.rows);
|
|
497
|
+
if (offer) { push({ kind: 'ask', text: offer.ask }, { kind: 'user', text: 'yes' }); persist({ step: 'preflight' }); exit({ action: 'docker', kind: offer.kind }); }
|
|
498
|
+
}
|
|
499
|
+
return;
|
|
500
|
+
case 'credential': {
|
|
501
|
+
const n = s.options.length;
|
|
502
|
+
if (ev.type === 'key' && ev.name === 'up') set({ sel: nextSelectable(s.options, s.sel, -1), flash: null }); // #1288: never land on a disabled row
|
|
503
|
+
else if (ev.type === 'key' && (ev.name === 'down' || ev.name === 'tab')) set({ sel: nextSelectable(s.options, s.sel, 1), flash: null });
|
|
504
|
+
else if (ev.type === 'key' && ev.name === 'enter') choose(s.options[s.sel]);
|
|
505
|
+
else if (ev.type === 'text' && /^[1-9]$/.test(ev.text) && Number(ev.text) <= n) { set({ sel: Number(ev.text) - 1 }); choose(s.options[Number(ev.text) - 1]); }
|
|
506
|
+
return;
|
|
507
|
+
}
|
|
508
|
+
case 'cloudConsent':
|
|
509
|
+
if (ev.type === 'text' && /^y(es)?$/i.test(ev.text.trim())) answerCloudConsent(true);
|
|
510
|
+
else if ((ev.type === 'text' && /^n(o)?$/i.test(ev.text.trim())) || (ev.type === 'key' && (ev.name === 'enter' || ev.name === 'escape'))) answerCloudConsent(false);
|
|
511
|
+
return;
|
|
512
|
+
case 'install': if (ev.type === 'text' && /^v$/i.test(ev.text)) set({ showLog: !s.showLog }); return;
|
|
513
|
+
case 'installStop':
|
|
514
|
+
if (ev.type === 'text' && /^y/i.test(ev.text)) stopInstall();
|
|
515
|
+
else if ((ev.type === 'text' && /^n/i.test(ev.text)) || (ev.type === 'key' && (ev.name === 'enter' || ev.name === 'escape'))) set({ step: 'install' });
|
|
516
|
+
return;
|
|
517
|
+
case 'ownerEmail': return textKey(ev, onOwnerEmail);
|
|
518
|
+
case 'ownerPassword': return textKey(ev, onOwnerPassword, () => set({ step: 'ownerEmail', input: S.current.ownerEmail || '', flash: null }));
|
|
519
|
+
case 'finishing': return;
|
|
520
|
+
case 'installFail':
|
|
521
|
+
if (ev.type === 'text' && /^v$/i.test(ev.text)) set({ showLog: !s.showLog });
|
|
522
|
+
else if (ev.type === 'text' && /^r$/i.test(ev.text)) startInstall();
|
|
523
|
+
return;
|
|
524
|
+
case 'notyet': if (ev.type === 'key' && ev.name === 'enter') exit({ action: 'exit' }); return;
|
|
525
|
+
case 'finale':
|
|
526
|
+
if (ev.type === 'key' && ev.name === 'enter') exit(s.finaleDry || !finisher ? { action: 'exit' } : { action: 'connected', replay: replayText() });
|
|
527
|
+
return;
|
|
528
|
+
default:
|
|
529
|
+
}
|
|
530
|
+
};
|
|
531
|
+
function stopInstall() {
|
|
532
|
+
push({ kind: 'ask', text: T.INSTALL_STOP_ASK }, { kind: 'user', text: 'yes' });
|
|
533
|
+
set({ stopping: true, step: 'install' });
|
|
534
|
+
S.current.abort?.abort?.(); // the runner stops the installer's process group; startInstall then exits
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
function textKey(ev, onEnter, onEscape) {
|
|
538
|
+
const s = S.current;
|
|
539
|
+
const masked = s.step === 'key' || s.step === 'ownerPassword';
|
|
540
|
+
if (ev.type === 'text' || ev.type === 'paste') set({ input: s.input + ev.text.replace(/[\r\n]+/g, masked ? '' : ' '), flash: null });
|
|
541
|
+
else if (ev.name === 'backspace') set({ input: [...s.input].slice(0, -1).join('') });
|
|
542
|
+
else if (ev.name === 'kill-line') set({ input: '' });
|
|
543
|
+
else if (ev.name === 'enter') onEnter();
|
|
544
|
+
else if (ev.name === 'escape' && onEscape) onEscape();
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
React.useEffect(() => {
|
|
548
|
+
if (!stdin) return undefined;
|
|
549
|
+
if (isRawModeSupported) setRawMode(true);
|
|
550
|
+
if (stdout?.isTTY) stdout.write(PASTE_ON);
|
|
551
|
+
const handle = (chunk) => {
|
|
552
|
+
const parsed = parseChunk(chunk, K.current.keys);
|
|
553
|
+
K.current.keys = parsed.state;
|
|
554
|
+
for (const ev of parsed.events) onKey(ev);
|
|
555
|
+
};
|
|
556
|
+
if (earlyInput) { // typed before mount: into the input, never submitted
|
|
557
|
+
earlyInput.detach();
|
|
558
|
+
for (const chunk of earlyInput.chunks.splice(0)) {
|
|
559
|
+
const parsed = parseChunk(chunk, K.current.keys);
|
|
560
|
+
K.current.keys = parsed.state;
|
|
561
|
+
for (const ev of parsed.events) if (ev.type === 'text' || ev.type === 'paste') S.current.input += ev.text.replace(/[\r\n]+/g, ' ');
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
stdin.on('data', handle);
|
|
565
|
+
return () => {
|
|
566
|
+
stdin.off('data', handle);
|
|
567
|
+
if (stdout?.isTTY) stdout.write(PASTE_OFF);
|
|
568
|
+
if (isRawModeSupported) setRawMode(false);
|
|
569
|
+
};
|
|
570
|
+
}, [stdin]);
|
|
571
|
+
|
|
572
|
+
// a ticking clock while a stage runs (elapsed time, never a fake percentage)
|
|
573
|
+
React.useEffect(() => {
|
|
574
|
+
if (S.current.step !== 'install' && S.current.step !== 'installStop') return undefined;
|
|
575
|
+
const id = setInterval(rerender, 250);
|
|
576
|
+
return () => clearInterval(id);
|
|
577
|
+
}, [S.current.step]);
|
|
578
|
+
|
|
579
|
+
// the broker drives the masked card (the K1 secure card replaces it)
|
|
580
|
+
React.useEffect(() => broker?.subscribe?.(() => rerender()), [broker]);
|
|
581
|
+
|
|
582
|
+
// ---- render -----------------------------------------------------------------------------------
|
|
583
|
+
const s = S.current;
|
|
584
|
+
const liveParts = [];
|
|
585
|
+
if (!introDone.current) liveParts.push(h(HelloIntro, { key: 'intro', item: introItem, cols, text: T.HELLO, eye, t: Date.now() - introStart.current }));
|
|
586
|
+
else if (!s.done) {
|
|
587
|
+
switch (s.step) {
|
|
588
|
+
case 'ask':
|
|
589
|
+
liveParts.push(h(InputLine, { key: 'in', text: s.input, placeholder: T.INPUT_PLACEHOLDER, cols }), h(Hint, { key: 'hint', text: 'Enter to send · /help · /exit', cols }));
|
|
590
|
+
break;
|
|
591
|
+
case 'offer': case 'declined':
|
|
592
|
+
liveParts.push(h(InputLine, { key: 'in', text: s.input, placeholder: s.step === 'offer' ? 'Enter for yes, or n' : 'yes, /connect server <url>, or /exit', cols }), h(Hint, { key: 'hint', text: 'Enter to send · /exit', cols }));
|
|
593
|
+
break;
|
|
594
|
+
case 'preflight': case 'preflightFail':
|
|
595
|
+
liveParts.push(h(Box, { key: 'pf', marginTop: 1, paddingLeft: 2 }, h(Text, { bold: true, color: c('text') }, T.PREFLIGHT_TITLE)),
|
|
596
|
+
...s.rows.map((r) => h(CheckRow, { key: r.id, r, cols })));
|
|
597
|
+
if (s.step === 'preflightFail') {
|
|
598
|
+
const offer = dryRun ? null : dockerOffer(s.rows); // FIRSTRUN: offer, never "go and do this"
|
|
599
|
+
if (offer) liveParts.push(h(Box, { key: 'pfo', marginTop: 1 }, h(AskCard, { text: offer.ask, cols })), h(Hint, { key: 'h', text: T.DOCKER_OFFER_HINT, cols }));
|
|
600
|
+
else liveParts.push(h(Box, { key: 'pfn', marginTop: 1, paddingLeft: 2, width: cols }, h(Para, { width: cols - 2, color: c('warn'), text: s.fixNote })), h(Hint, { key: 'h', text: dryRun ? T.PREFLIGHT_RETRY_DRY : T.PREFLIGHT_RETRY, cols }));
|
|
601
|
+
}
|
|
602
|
+
break;
|
|
603
|
+
case 'credential':
|
|
604
|
+
liveParts.push(h(Box, { key: 'opts', flexDirection: 'column', marginTop: 1 }, ...s.options.map((o, i) => {
|
|
605
|
+
const on = i === s.sel;
|
|
606
|
+
return h(Text, { key: o.id },
|
|
607
|
+
h(Text, { color: c(on ? 'sky' : 'dim') }, on ? ' › ' : ' '),
|
|
608
|
+
h(Text, { color: c(o.disabled ? 'dim' : on ? 'input' : 'text'), bold: on && !o.disabled }, `${i + 1} ${o.label}`),
|
|
609
|
+
o.note ? h(Text, { color: c(o.id === 'skip' ? 'warn' : 'dim') }, clip(` ${o.note}`, Math.max(4, cols - o.label.length - 9))) : null);
|
|
610
|
+
})));
|
|
611
|
+
if (s.flash) liveParts.push(h(Box, { key: 'fl', paddingLeft: 2, width: cols }, h(Para, { width: cols - 2, color: c('warn'), text: s.flash })));
|
|
612
|
+
liveParts.push(h(Hint, { key: 'h', text: T.CRED_HINT, cols }));
|
|
613
|
+
break;
|
|
614
|
+
case 'key': {
|
|
615
|
+
const opt = s.options.find(o => o.id === s.provider);
|
|
616
|
+
liveParts.push(h(Box, { key: 'card', flexDirection: 'column', marginTop: 1 },
|
|
617
|
+
h(Text, { color: c('violet') }, '─'.repeat(cols)),
|
|
618
|
+
h(Box, { paddingLeft: 2, width: cols }, h(Para, { width: cols - 2, color: c('text'), text: T.credPrompt(opt?.label || 'API key') })),
|
|
619
|
+
h(InputLine, { text: s.input, cols, masked: true }),
|
|
620
|
+
s.flash ? h(Box, { paddingLeft: 2, width: cols }, h(Para, { width: cols - 2, color: c('warn'), text: s.flash })) : null,
|
|
621
|
+
h(Hint, { text: T.CRED_INPUT_HINT, cols }),
|
|
622
|
+
h(Text, { color: c('violet') }, '─'.repeat(cols))));
|
|
623
|
+
break;
|
|
624
|
+
}
|
|
625
|
+
case 'cloudConsent':
|
|
626
|
+
liveParts.push(h(Box, { key: 'cc', flexDirection: 'column', marginTop: 1 },
|
|
627
|
+
h(AskCard, { text: T.credConsentAsk(providerLabel(s.provider)), cols }),
|
|
628
|
+
h(Hint, { text: T.CRED_CONSENT_HINT, cols })));
|
|
629
|
+
break;
|
|
630
|
+
case 'ollamaUrl':
|
|
631
|
+
liveParts.push(h(Box, { key: 'card', flexDirection: 'column', marginTop: 1 },
|
|
632
|
+
h(Text, { color: c('violet') }, '─'.repeat(cols)),
|
|
633
|
+
h(Box, { paddingLeft: 2, width: cols }, h(Para, { width: cols - 2, color: c('text'), text: T.OLLAMA_URL_PROMPT })),
|
|
634
|
+
h(InputLine, { text: s.input, cols }),
|
|
635
|
+
s.flash ? h(Box, { paddingLeft: 2, width: cols }, h(Para, { width: cols - 2, color: c('warn'), text: s.flash })) : null,
|
|
636
|
+
h(Hint, { text: T.OLLAMA_URL_HINT, cols }),
|
|
637
|
+
h(Text, { color: c('violet') }, '─'.repeat(cols))));
|
|
638
|
+
break;
|
|
639
|
+
case 'ownerEmail': case 'ownerPassword': {
|
|
640
|
+
const pw = s.step === 'ownerPassword';
|
|
641
|
+
if (pw) liveParts.push(h(Box, { key: 'pwp', paddingLeft: 2, width: cols, marginTop: 1 }, h(Para, { width: cols - 2, color: c('text'), text: T.OWNER_PW_PROMPT })));
|
|
642
|
+
liveParts.push(h(InputLine, { key: 'in', text: s.input, placeholder: pw ? '' : T.OWNER_EMAIL_PLACEHOLDER, cols, masked: pw }));
|
|
643
|
+
if (s.flash) liveParts.push(h(Box, { key: 'fl', paddingLeft: 2, width: cols }, h(Para, { width: cols - 2, color: c('warn'), text: s.flash })));
|
|
644
|
+
liveParts.push(h(Hint, { key: 'h', text: pw ? T.OWNER_PW_HINT : 'Enter to continue', cols }));
|
|
645
|
+
break;
|
|
646
|
+
}
|
|
647
|
+
case 'finishing':
|
|
648
|
+
liveParts.push(h(Box, { key: 'ft', marginTop: 1, paddingLeft: 2 }, h(Text, { bold: true, color: c('text') }, T.FINISH_TITLE)),
|
|
649
|
+
...s.finRows.map((r) => h(CheckRow, { key: r.id, r, cols })));
|
|
650
|
+
break;
|
|
651
|
+
case 'install': case 'installFail': case 'installStop': {
|
|
652
|
+
const t = now();
|
|
653
|
+
const dry = s.install.kind === 'dry-run';
|
|
654
|
+
liveParts.push(h(Box, { key: 'it', marginTop: 1, paddingLeft: 2, width: cols }, h(Para, { width: cols - 2, bold: true, color: c('text'), text: T.INSTALL_TITLE + (dry ? ` (${T.INSTALL_DRY})` : '') })));
|
|
655
|
+
for (const st of s.install.stages) {
|
|
656
|
+
const tail = st.earlier ? T.INSTALL_EARLIER : st.status === 'running' && st.startedAt ? fmtS(t - st.startedAt) : st.ms != null ? fmtS(st.ms) : '';
|
|
657
|
+
liveParts.push(h(CheckRow, { key: st.id, r: { ...st, tail }, cols }));
|
|
658
|
+
}
|
|
659
|
+
if (s.showLog) liveParts.push(h(Box, { key: 'log', flexDirection: 'column', paddingLeft: 4, marginTop: 1 }, ...s.logs.map((l, i) => h(Text, { key: i, color: c('dim') }, clip(l, cols - 4)))));
|
|
660
|
+
if (s.step === 'installFail') {
|
|
661
|
+
liveParts.push(h(Box, { key: 'if', paddingLeft: 2, width: cols, marginTop: 1, flexDirection: 'column' },
|
|
662
|
+
h(Para, { width: cols - 2, color: c('fail'), text: T.INSTALL_FAILED(T.STAGES[s.install.failed] || s.install.failed) }),
|
|
663
|
+
s.install.cause ? h(Para, { width: cols - 2, color: c('warn'), text: s.install.cause }) : null, // FIRSTRUN: why, in one line
|
|
664
|
+
s.install.error ? h(Para, { width: cols - 2, color: c('dim'), text: s.install.error }) : null,
|
|
665
|
+
s.install.logPath ? h(Para, { width: cols - 2, color: c('dim'), text: T.INSTALL_LOG_PATH(s.install.logPath) }) : null,
|
|
666
|
+
s.install.logPath ? h(Para, { width: cols - 2, color: c('dim'), text: T.INSTALL_RERUN_HINT }) : null,
|
|
667
|
+
s.install.degraded ? h(Para, { width: cols - 2, color: c('warn'), text: T.INSTALL_DEGRADED }) : null));
|
|
668
|
+
}
|
|
669
|
+
if (s.step === 'installStop') liveParts.push(h(Box, { key: 'stop', width: cols }, h(AskCard, { text: T.INSTALL_STOP_ASK, cols })));
|
|
670
|
+
liveParts.push(h(Hint, { key: 'h', text: s.step === 'installFail' ? T.INSTALL_FAIL_HINT : s.step === 'installStop' ? 'y to stop · n or Enter to carry on' : T.INSTALL_HINT, cols }));
|
|
671
|
+
break;
|
|
672
|
+
}
|
|
673
|
+
case 'notyet': case 'finale':
|
|
674
|
+
liveParts.push(h(Box, { key: 'h', marginTop: 1 }, h(Hint, { text: s.step === 'finale' && !s.finaleDry && finisher ? T.FINALE_EXIT : T.FINALE_EXIT_DRY, cols })));
|
|
675
|
+
break;
|
|
676
|
+
default:
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
return h(Box, { flexDirection: 'column' },
|
|
680
|
+
h(Static, { items: introDone.current ? s.history : [] }, (it) => h(Box, { key: it.id, flexDirection: 'column' }, h(Item, { item: it, cols }))),
|
|
681
|
+
...liveParts);
|
|
682
|
+
}
|