quoxcli 0.3.1 → 0.5.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/CHANGELOG.md +61 -0
- package/README.md +2 -2
- package/bin/quox.js +19 -1
- package/bin/quoxon.js +6 -1
- package/lib/api/endpoints/actions.js +15 -0
- package/lib/api/endpoints/admin.js +27 -0
- package/lib/api/endpoints/agent-attach.js +7 -0
- package/lib/api/endpoints/blog.js +16 -0
- package/lib/api/endpoints/chat.js +7 -0
- package/lib/api/endpoints/compliance.js +25 -0
- package/lib/api/endpoints/contactsq.js +4 -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/hat.js +14 -0
- package/lib/api/endpoints/inbox.js +6 -0
- package/lib/api/endpoints/inboxq.js +19 -0
- package/lib/api/endpoints/index.js +14 -0
- package/lib/api/endpoints/loops.js +26 -0
- package/lib/api/endpoints/mon.js +295 -0
- package/lib/api/endpoints/objectives.js +6 -0
- package/lib/api/endpoints/quoxseo.js +6 -0
- package/lib/api/endpoints/server.js +21 -0
- package/lib/api/endpoints/setup.js +6 -0
- package/lib/api/endpoints/tools.js +13 -0
- package/lib/api/endpoints/v1.js +47 -0
- package/lib/commands/actions.js +81 -11
- package/lib/commands/admin.js +35 -0
- package/lib/commands/agent.js +11 -4
- package/lib/commands/agentAttach.js +61 -7
- package/lib/commands/approvals.js +198 -3
- package/lib/commands/ask.js +4 -2
- package/lib/commands/auth.js +64 -11
- package/lib/commands/backup.js +11 -4
- package/lib/commands/blog.js +110 -0
- package/lib/commands/capabilities.js +3 -2
- package/lib/commands/chat.js +142 -28
- package/lib/commands/chatCompanion.js +27 -0
- package/lib/commands/chatState.js +45 -0
- package/lib/commands/claude.js +7 -33
- package/lib/commands/compliance.js +109 -0
- package/lib/commands/connect.js +69 -0
- package/lib/commands/contacts.js +85 -4
- package/lib/commands/contactsSendAt.js +21 -0
- package/lib/commands/dev.js +6 -8
- package/lib/commands/doctor.js +22 -3
- package/lib/commands/engagement.js +90 -0
- package/lib/commands/evidence.js +155 -0
- package/lib/commands/ext.js +23 -0
- package/lib/commands/fleet.js +25 -0
- package/lib/commands/gripe.js +24 -13
- package/lib/commands/hat.js +76 -0
- package/lib/commands/image.js +68 -0
- package/lib/commands/inbox.js +37 -3
- package/lib/commands/inboxq.js +147 -0
- package/lib/commands/kuma.js +1123 -0
- package/lib/commands/kumaSla.js +174 -0
- package/lib/commands/licence.js +39 -1
- package/lib/commands/licenceLinkView.js +311 -0
- package/lib/commands/linkedin.js +19 -0
- package/lib/commands/loop.js +318 -0
- package/lib/commands/lucid.js +1 -0
- package/lib/commands/mail.js +161 -20
- package/lib/commands/memory.js +68 -4
- package/lib/commands/memorySync.js +158 -0
- package/lib/commands/mon.js +55 -144
- package/lib/commands/notes.js +29 -7
- package/lib/commands/notesSeal.js +259 -0
- package/lib/commands/objective.js +53 -3
- package/lib/commands/offload.js +2 -3
- package/lib/commands/org.js +21 -4
- package/lib/commands/plan.js +17 -0
- package/lib/commands/provider.js +87 -8
- package/lib/commands/qlarity.js +32 -1
- package/lib/commands/quoxseo.js +31 -0
- package/lib/commands/research.js +79 -0
- package/lib/commands/rows.js +9 -0
- package/lib/commands/run.js +20 -2
- package/lib/commands/scope.js +2 -2
- package/lib/commands/server.js +150 -0
- package/lib/commands/setup.js +127 -75
- package/lib/commands/shortcut.js +41 -0
- package/lib/commands/skills.js +24 -5
- package/lib/commands/slot.js +49 -9
- package/lib/commands/status.js +92 -44
- package/lib/commands/tool.js +30 -0
- package/lib/commands/vault.js +6 -3
- package/lib/commands/vm.js +2 -2
- package/lib/commands/walkthrough.js +14 -2
- package/lib/commands/watch.js +11 -2
- package/lib/commands/wazuh.js +824 -0
- package/lib/commands/wazuhIntake.js +317 -0
- package/lib/commands/wazuhReport.js +192 -0
- package/lib/commands/workflow.js +47 -4
- package/lib/commands/x.js +50 -0
- package/lib/commands/youtube.js +172 -0
- package/lib/core/actionItems.js +115 -0
- package/lib/core/agentAlias.js +2 -0
- package/lib/core/agentFamilies.js +7 -0
- package/lib/core/announce.js +86 -0
- package/lib/core/auth.js +20 -2
- package/lib/core/callMe.js +118 -0
- package/lib/core/client.js +37 -3
- package/lib/core/commandRegistrars.js +17 -0
- package/lib/core/config.js +29 -3
- package/lib/core/confirm.js +68 -7
- package/lib/core/crossOrgApprovals.js +69 -0
- package/lib/core/errors.js +52 -1
- package/lib/core/findingReconcile.js +90 -0
- package/lib/core/global-flags.js +1 -1
- package/lib/core/governedSession.js +136 -8
- package/lib/core/inboxResolution.js +3 -2
- package/lib/core/liveSessionsReport.js +1 -1
- package/lib/core/loginGuard.js +73 -0
- package/lib/core/loopGlyph.js +97 -0
- package/lib/core/offload.js +73 -7
- package/lib/core/org-override.js +5 -1
- package/lib/core/output.js +8 -8
- package/lib/core/profileTrust.js +182 -0
- package/lib/core/quoxglyph.js +22 -1
- package/lib/core/router.js +14 -0
- package/lib/core/sessionHeartbeat.js +12 -8
- package/lib/core/shortcut.js +83 -0
- package/lib/core/stepUpPrompt.js +99 -0
- package/lib/core/version.js +1 -1
- package/lib/core/vm.js +5 -0
- package/lib/core/walkthrough.js +9 -5
- 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/quoxon/EXTENSIONS.md +146 -0
- package/lib/quoxon/api.js +364 -27
- package/lib/quoxon/app.js +1629 -183
- package/lib/quoxon/approvalBatch.js +117 -0
- package/lib/quoxon/approvalFacts.js +64 -8
- package/lib/quoxon/approvalSentence.js +48 -0
- package/lib/quoxon/approvalsView.js +124 -7
- package/lib/quoxon/autoOpen.js +94 -0
- package/lib/quoxon/backgroundWork.js +59 -0
- package/lib/quoxon/bang.js +1 -1
- package/lib/quoxon/capabilities.js +7 -2
- package/lib/quoxon/commandShort.js +22 -3
- package/lib/quoxon/commands.js +110 -15
- package/lib/quoxon/connect.js +5 -3
- package/lib/quoxon/countdownLine.js +10 -1
- package/lib/quoxon/deck.js +30 -0
- package/lib/quoxon/deckLazy.js +58 -0
- package/lib/quoxon/demo.autoplay.js +1 -1
- package/lib/quoxon/demo.js +3 -1
- package/lib/quoxon/destructive.js +35 -0
- package/lib/quoxon/dividerAocl.js +4 -3
- package/lib/quoxon/dividerRule.js +3 -3
- package/lib/quoxon/dnd.js +71 -0
- package/lib/quoxon/dragonCompanion.js +285 -0
- package/lib/quoxon/dragonModes.js +1 -0
- package/lib/quoxon/dragonMoments.js +160 -0
- package/lib/quoxon/dragonReads.js +68 -0
- package/lib/quoxon/dragonView.js +2 -2
- package/lib/quoxon/echo.js +1 -0
- package/lib/quoxon/events.js +67 -10
- package/lib/quoxon/extensions.js +636 -0
- package/lib/quoxon/gripeQueue.js +135 -0
- package/lib/quoxon/inboxView.js +182 -0
- package/lib/quoxon/installCli.js +79 -0
- package/lib/quoxon/keys.js +4 -4
- package/lib/quoxon/loopCard.js +460 -0
- package/lib/quoxon/loopCards.js +189 -0
- package/lib/quoxon/loopStream.js +98 -0
- package/lib/quoxon/mailView.js +71 -0
- package/lib/quoxon/main.js +29 -5
- package/lib/quoxon/markdown.js +1 -1
- package/lib/quoxon/model.js +15 -0
- package/lib/quoxon/mouse.js +12 -1
- package/lib/quoxon/neverAnswered.js +20 -0
- package/lib/quoxon/objectiveOffer.js +82 -0
- package/lib/quoxon/objects/away.js +43 -5
- package/lib/quoxon/objects/index.js +22 -4
- package/lib/quoxon/objects/model.js +33 -0
- package/lib/quoxon/objects/proof.js +12 -4
- package/lib/quoxon/onboarding/connectServer.js +162 -0
- package/lib/quoxon/onboarding/copy.js +6 -0
- package/lib/quoxon/onboarding/index.js +5 -43
- package/lib/quoxon/onboarding/probe.js +20 -1
- package/lib/quoxon/onboarding/shortcutOffer.js +28 -0
- package/lib/quoxon/onboarding/view.js +16 -3
- package/lib/quoxon/orgSwitch.js +49 -1
- package/lib/quoxon/planSteps.js +51 -0
- package/lib/quoxon/print.js +8 -3
- package/lib/quoxon/profileSwitch.js +7 -0
- package/lib/quoxon/promptHistory.js +3 -1
- package/lib/quoxon/qdragonFly.js +7 -6
- package/lib/quoxon/qmotion.js +17 -0
- package/lib/quoxon/render.js +187 -41
- package/lib/quoxon/roster.js +36 -17
- package/lib/quoxon/rosterSessions.js +44 -0
- package/lib/quoxon/rows.js +244 -1
- package/lib/quoxon/screenContext.js +1 -1
- package/lib/quoxon/secretShape.js +86 -0
- package/lib/quoxon/serverChip.js +42 -0
- package/lib/quoxon/session.js +10 -0
- package/lib/quoxon/slash/agents.js +2 -1
- package/lib/quoxon/slash/argSources.js +9 -2
- package/lib/quoxon/slash/args.js +4 -2
- package/lib/quoxon/slash/broadcast.js +18 -5
- package/lib/quoxon/slash/broadcastRun.js +13 -2
- package/lib/quoxon/slash/cliCommand.js +42 -1
- package/lib/quoxon/slash/cliLeaves.json +139 -2
- package/lib/quoxon/slash/evidenceCmd.js +34 -0
- package/lib/quoxon/slash/hatWalk.js +1 -1
- package/lib/quoxon/slash/palette.js +23 -2
- package/lib/quoxon/slash/paletteController.js +10 -4
- package/lib/quoxon/slash/qpeCmd.js +35 -0
- package/lib/quoxon/slash/restart.js +31 -4
- package/lib/quoxon/slash/resume.js +18 -1
- package/lib/quoxon/slash/stepUp.js +5 -5
- package/lib/quoxon/slash/stepUpCmds.js +74 -1
- package/lib/quoxon/slash/todoCmd.js +122 -0
- package/lib/quoxon/slashSpinner.js +35 -0
- package/lib/quoxon/slotAttach.js +8 -0
- package/lib/quoxon/slotChat.js +17 -0
- package/lib/quoxon/slotFormat.js +1 -1
- package/lib/quoxon/slotOutbox.js +113 -0
- package/lib/quoxon/suggestCommand.js +34 -3
- package/lib/quoxon/term.js +25 -1
- package/lib/quoxon/toolCards.js +30 -0
- package/lib/quoxon/transcript.js +2 -1
- package/lib/quoxon/welcome.js +1 -1
- package/lib/quoxon/whatsnew.js +1 -1
- package/lib/quoxon/whatsnew.jsonl +81 -0
- package/lib/quoxon/windowOrg.js +11 -1
- package/lib/server/devServers.js +137 -0
- package/lib/server/installFlow.js +168 -0
- package/lib/server/status.js +15 -1
- package/lib/toolOutputShape.js +55 -0
- package/package.json +4 -2
- 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/quoxperticus/v2/quoxperticus-master.svg +59 -0
- package/vendor/dragonlab/design/quoxperticus/v3/character.mjs +43 -0
- package/vendor/dragonlab/design/quoxperticus/v4/character.mjs +57 -0
- package/vendor/dragonlab/design/quoxperticus/v4/review.mjs +35 -0
- package/vendor/dragonlab/design/quoxperticus/v4/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/design/vulcan/v1/character.mjs +34 -0
- package/vendor/dragonlab/design/vulcan/v1/vulcan-master.svg +57 -0
- package/vendor/dragonlab/design/vulcan/v2/character.mjs +39 -0
- package/vendor/dragonlab/design/vulcan/v3/character.mjs +41 -0
- package/vendor/dragonlab/design/vulcan/v3/review.mjs +35 -0
- package/vendor/dragonlab/design/vulcan/v3/watch.mjs +56 -0
- package/vendor/dragonlab/factory/svg-rig-contour.mjs +64 -0
- package/vendor/dragonlab/factory/svg-rig.mjs +63 -0
- package/vendor/dragonlab/integration/companions-v1/characters.mjs +108 -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/quoxperticus.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/companions-v1/vulcan.mjs +2 -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
|
@@ -18,6 +18,7 @@ import * as T from './copy.js';
|
|
|
18
18
|
import { matchIntent, yesNo, parseConnect } from './intent.js';
|
|
19
19
|
import { loadJourney, saveJourney, clearJourney, resumeStep } from './journey.js';
|
|
20
20
|
import { pendingRows, runPreflight, preflightPassed, detectOllama, defaultDeps } from './preflight.js';
|
|
21
|
+
import { probeLocalQuox } from './probe.js';
|
|
21
22
|
import { credentialOptions, KEY_PROVIDERS, normaliseOllamaUrl, nextSelectable } from './credential.js';
|
|
22
23
|
import { needsConsent, providerLabel } from '../consent.js';
|
|
23
24
|
import { HelloIntro, helloIntroMs } from '../qmotion.js';
|
|
@@ -101,8 +102,8 @@ const Hint = ({ text, cols }) => h(Box, { paddingLeft: 2 }, h(Text, { color: c('
|
|
|
101
102
|
|
|
102
103
|
// ---- the view ---------------------------------------------------------------------------------
|
|
103
104
|
export function GettingStarted({
|
|
104
|
-
columns, version = '0.
|
|
105
|
-
journeyPath, dryRun = false, runner = null, preflightDeps, runChecks = runPreflight, findOllama = detectOllama,
|
|
105
|
+
columns, version = '0.5.0', cwd = '', welcome = false, animateIntro, earlyInput = null,
|
|
106
|
+
journeyPath, dryRun = false, runner = null, preflightDeps, runChecks = runPreflight, findLocal = probeLocalQuox, findOllama = detectOllama,
|
|
106
107
|
requestCredential, broker = null, finisher = null, onExit = () => {}, now = () => Date.now()
|
|
107
108
|
}) {
|
|
108
109
|
const { stdin, setRawMode, isRawModeSupported } = useStdin();
|
|
@@ -179,7 +180,7 @@ export function GettingStarted({
|
|
|
179
180
|
|
|
180
181
|
function offerLines() {
|
|
181
182
|
const items = [{ kind: 'guide', text: T.OFFER }];
|
|
182
|
-
items.push({ kind: 'ask', text: T.OFFER_ASK }, { kind: 'dim', text: T.ALREADY });
|
|
183
|
+
items.push({ kind: 'ask', text: T.OFFER_ASK }, { kind: 'dim', text: T.CHOICES }, { kind: 'dim', text: T.ALREADY });
|
|
183
184
|
push(...items);
|
|
184
185
|
set({ step: 'offer' });
|
|
185
186
|
persist();
|
|
@@ -218,6 +219,10 @@ export function GettingStarted({
|
|
|
218
219
|
if (text.startsWith('/')) { onSlash(text); return; }
|
|
219
220
|
if (s.step === 'ask') return onRequest(text);
|
|
220
221
|
if (s.step === 'offer' || s.step === 'declined') {
|
|
222
|
+
const pick = text.toLowerCase();
|
|
223
|
+
if (pick === 'a') { push({ kind: 'user', text }); startPreflight(); return; } // install here: the same flow as yes
|
|
224
|
+
if (pick === 'b') { push({ kind: 'user', text }, { kind: 'dim', text: T.LOCAL_CHECKING }); useLocal(); return; }
|
|
225
|
+
if (pick === 'c') { push({ kind: 'user', text }, { kind: 'guide', text: T.CONNECT_ELSEWHERE }); return; }
|
|
221
226
|
const yn = yesNo(text);
|
|
222
227
|
if (yn === true) { push({ kind: 'user', text: text || 'yes' }); startPreflight(); return; }
|
|
223
228
|
if (yn === false) { push({ kind: 'user', text }, { kind: 'guide', text: T.DECLINED }, { kind: 'dim', text: T.DECLINED_HINT }); set({ step: 'declined' }); persist(); return; }
|
|
@@ -225,6 +230,14 @@ export function GettingStarted({
|
|
|
225
230
|
}
|
|
226
231
|
}
|
|
227
232
|
|
|
233
|
+
async function useLocal() {
|
|
234
|
+
const f = await findLocal();
|
|
235
|
+
if (!live.current || S.current.done) return;
|
|
236
|
+
if (!f.found) { push({ kind: 'guide', tone: 'warn', text: T.LOCAL_NONE }); return; }
|
|
237
|
+
push({ kind: 'guide', tone: 'ok', text: T.localFound(f.url) }, { kind: 'guide', text: T.connectChecking(f.url) });
|
|
238
|
+
exit({ action: 'connect', url: f.url });
|
|
239
|
+
}
|
|
240
|
+
|
|
228
241
|
async function startPreflight() {
|
|
229
242
|
set({ step: 'preflight', rows: pendingRows(), fixNote: null });
|
|
230
243
|
persist();
|
package/lib/quoxon/orgSwitch.js
CHANGED
|
@@ -5,10 +5,32 @@ export const orgRows = (list) => (Array.isArray(list) ? list : []).map((o) => ({
|
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Resolve a typed name or id against the user's orgs. Exact id, exact slug/name, then every word of the
|
|
8
|
-
* query found in the name or slug (so "
|
|
8
|
+
* query found in the name or slug (so "acme widgets" finds "Acme Widgets Ltd"). Never guesses between ties.
|
|
9
9
|
* -> { org } | { ambiguous: [orgs] } | { none: true }
|
|
10
10
|
*/
|
|
11
|
+
const VERBS = new Set(['switch', 'swith', 'swich', 'sw', 'to', 'go', 'use']);
|
|
12
|
+
/** Drop ONE leading verb-ish token ("swith quox dev" -> "quox dev"); a lone verb is kept (it may be the name). */
|
|
13
|
+
export function stripOrgVerb(query) {
|
|
14
|
+
const m = /^(\S+)\s+(\S[\s\S]*)$/.exec(String(query ?? '').trim());
|
|
15
|
+
return m && VERBS.has(m[1].toLowerCase()) ? m[2].trim() : String(query ?? '').trim();
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** Orgs for a picker when nothing matched uniquely: those hitting the most query words (verb stripped), at least one. */
|
|
19
|
+
export function orgCandidates(query, list) {
|
|
20
|
+
const words = norm(stripOrgVerb(query)).split(' ').filter(Boolean);
|
|
21
|
+
const scored = orgRows(list).map((o) => { const hay = `${norm(o.name)} ${norm(o.slug)}`; return { o, n: words.filter((w) => hay.includes(w)).length }; });
|
|
22
|
+
const best = Math.max(0, ...scored.map((x) => x.n));
|
|
23
|
+
return best ? scored.filter((x) => x.n === best).map((x) => x.o) : [];
|
|
24
|
+
}
|
|
25
|
+
|
|
11
26
|
export function matchOrg(query, list) {
|
|
27
|
+
const first = matchOrgExact(query, list);
|
|
28
|
+
if (!first.none) return first;
|
|
29
|
+
const stripped = stripOrgVerb(query);
|
|
30
|
+
return stripped !== String(query ?? '').trim() ? matchOrgExact(stripped, list) : first;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function matchOrgExact(query, list) {
|
|
12
34
|
const rows = orgRows(list);
|
|
13
35
|
const q = norm(query);
|
|
14
36
|
if (!q) return { none: true };
|
|
@@ -31,3 +53,29 @@ export function orgLines(list, currentId) {
|
|
|
31
53
|
const w = Math.min(36, Math.max(4, ...rows.map((o) => [...o.name].length)));
|
|
32
54
|
return rows.map((o) => `${o.id === currentId ? '* ' : ' '}${o.name.padEnd(w)} ${o.slug || o.id}${o.role ? ` (${o.role})` : ''}`);
|
|
33
55
|
}
|
|
56
|
+
|
|
57
|
+
/** `quox org switch` with no argument: the same list, numbered from 1, current org marked. */
|
|
58
|
+
export function numberedOrgLines(list, currentId) {
|
|
59
|
+
const rows = orgRows(list);
|
|
60
|
+
const w = Math.min(36, Math.max(4, ...rows.map((o) => [...o.name].length)));
|
|
61
|
+
return rows.map((o, i) => `${o.id === currentId ? '*' : ' '} ${String(i + 1).padStart(String(rows.length).length)}) ${o.name.padEnd(w)} ${o.slug || o.id}${o.role ? ` (${o.role})` : ''}`);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** A typed answer to the numbered list: an exact slug/name/id first (so a numeric slug is never shadowed), then a 1-based number. */
|
|
65
|
+
export function pickOrgByInput(input, list) {
|
|
66
|
+
const rows = orgRows(list);
|
|
67
|
+
const t = String(input ?? '').trim();
|
|
68
|
+
if (!t) return { none: true };
|
|
69
|
+
const m = matchOrg(t, rows);
|
|
70
|
+
const exactly = m.org && (m.org.slug === t || String(m.org.id).toLowerCase() === t.toLowerCase() || norm(m.org.name) === norm(t));
|
|
71
|
+
if (exactly) return m;
|
|
72
|
+
if (/^\d+$/.test(t)) { const n = Number(t); return n >= 1 && n <= rows.length ? { org: rows[n - 1] } : { none: true }; }
|
|
73
|
+
return m;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** After a profile-wide `quox org switch`: how the other windows on that profile pick the change up. */
|
|
77
|
+
export function sharedSwitchHint({ name, pids }) {
|
|
78
|
+
const n = pids.length;
|
|
79
|
+
return `${n} other quox session${n === 1 ? '' : 's'} ${n === 1 ? 'uses' : 'use'} profile "${name}" (pid ${pids.join(', ')}). `
|
|
80
|
+
+ 'They pick this org up on their next token refresh; run /restart in a window to pick it up now. /org <name> moves one window only.';
|
|
81
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// QUOXON plan approval card (owner 2026-10-26 18:05: "i would deny based on the fact it's not even presenting a plan"):
|
|
2
|
+
// an objective's "approve plan" item must list the plan's steps, and show the org the item really lives in.
|
|
3
|
+
// Pure helpers plus one async enrich step; the card renders what this returns and nothing else.
|
|
4
|
+
|
|
5
|
+
export const PLAN_STEPS_MAX = 8;
|
|
6
|
+
/** Verbs that change things. Anything else reads as read-only (inspect, list, audit, report). */
|
|
7
|
+
const CHANGES = /\b(upgrad\w*|updat\w*|restart\w*|redeploy\w*|deploy\w*|writ\w*|delet\w*|remov\w*|install\w*|migrat\w*|patch\w*|rollback|roll back|reboot\w*|creat\w*|apply|applies|rotat\w*|scal\w*|provision\w*|stop|start|kill|drop|reset|modify|modifies|change\w*|configur\w*)\b/i;
|
|
8
|
+
export const changesThings = (title) => CHANGES.test(String(title || ''));
|
|
9
|
+
|
|
10
|
+
/** Does this inbox item ask for approval of an objective's plan? */
|
|
11
|
+
export const isPlanApproval = (it) => it?.payload?.source === 'objective_checkpoint'
|
|
12
|
+
&& it?.payload?.checkpoint_type === 'plan_approval' && typeof it?.payload?.objective_id === 'string';
|
|
13
|
+
|
|
14
|
+
/** Steps from an objective read: [{ n, title, agent, writes }]. Agent display name from `names` (id -> name), else the id. */
|
|
15
|
+
export function stepsOf(objective, names = new Map()) {
|
|
16
|
+
const tasks = Array.isArray(objective?.tasks) ? objective.tasks : [];
|
|
17
|
+
return tasks.filter(t => t && t.title).map((t, i) => ({
|
|
18
|
+
n: i + 1, title: String(t.title), agent: t.agent_id ? String(names.get(t.agent_id) || t.agent_id) : null, writes: changesThings(t.title)
|
|
19
|
+
}));
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Rows for the card: numbered lines plus "+N more" past the cap. Each row is a plain string. */
|
|
23
|
+
export function planLines(steps, max = PLAN_STEPS_MAX) {
|
|
24
|
+
const list = Array.isArray(steps) ? steps : [];
|
|
25
|
+
const lines = list.slice(0, max).map(s => `${s.n}. ${s.writes ? '[changes]' : '[read-only]'} ${s.title}${s.agent ? ` · ${s.agent}` : ''}`);
|
|
26
|
+
if (list.length > max) lines.push(`+${list.length - max} more`);
|
|
27
|
+
return lines;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Attach { steps, orgId, orgName } as item._plan for a plan-approval item. Reads the objective once (cached by objective id
|
|
32
|
+
* in `cache`). Never throws: when the read fails the item is returned as it came and the card says the plan could not be loaded.
|
|
33
|
+
*/
|
|
34
|
+
export async function enrichPlanItem(item, { objectiveGet, agents = null, orgs = null, cache = new Map() } = {}) {
|
|
35
|
+
if (!isPlanApproval(item) || typeof objectiveGet !== 'function') return item;
|
|
36
|
+
const id = item.payload.objective_id;
|
|
37
|
+
if (!cache.has(id)) {
|
|
38
|
+
cache.set(id, (async () => {
|
|
39
|
+
try {
|
|
40
|
+
const o = await objectiveGet(id);
|
|
41
|
+
let names = new Map();
|
|
42
|
+
try { for (const a of (agents ? await agents() : []) || []) if (a?.agent_id && a.name) names.set(a.agent_id, a.name); } catch { /* ids stand in */ }
|
|
43
|
+
const orgId = item.org_id || o?.org_id || null;
|
|
44
|
+
let orgName = null;
|
|
45
|
+
try { orgName = orgId && orgs ? ((await orgs()) || []).find(x => x?.id === orgId)?.name || null : null; } catch { /* the short id stands in */ }
|
|
46
|
+
return { steps: stepsOf(o, names), orgId, orgName, loaded: true };
|
|
47
|
+
} catch (e) { cache.delete(id); return { steps: [], orgId: item.org_id || null, orgName: null, loaded: false, error: e?.message || 'failed' }; }
|
|
48
|
+
})());
|
|
49
|
+
}
|
|
50
|
+
return { ...item, _plan: await cache.get(id) };
|
|
51
|
+
}
|
package/lib/quoxon/print.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* Exit codes: 0 completed, 2 held for approval, 3 interrupted, 4 auth, 5 unconfirmed
|
|
5
5
|
* (stream closed without turn.finished), 1 other.
|
|
6
6
|
*/
|
|
7
|
+
import { eventLine } from '../core/announce.js';
|
|
7
8
|
import { textGate } from './events.js';
|
|
8
9
|
import { streamFields, fallbackNote } from './model.js';
|
|
9
10
|
import { connectOfferFor } from './connect.js';
|
|
@@ -46,7 +47,7 @@ export async function runPrint({ api, prompt, agent = 'quox', output = 'text', c
|
|
|
46
47
|
const sum = { tools: 0, approvals: 0, receipts: [] };
|
|
47
48
|
let convo = conversation, doneSession = null, status = null, text = '', sawApproval = false, errorMsg = null, authFail = false;
|
|
48
49
|
const seen = new Set();
|
|
49
|
-
let offer = null, handoff = null;
|
|
50
|
+
let offer = null, handoff = null, objective = null;
|
|
50
51
|
let gate = null;
|
|
51
52
|
try {
|
|
52
53
|
for await (const ev of api.stream({ turnId, message: prompt, agent: agent || 'quox', conversationId: conversation, context, ...streamFields(choice), ...(promptProfile ? { promptProfile } : {}), ...(timeoutS > 0 ? { turnBudgetMs: timeoutS * 1000 } : {}) }, ctrl.signal)) {
|
|
@@ -60,8 +61,10 @@ export async function runPrint({ api, prompt, agent = 'quox', output = 'text', c
|
|
|
60
61
|
else if (ev.type === 'done' && !text && ev.full_response) text = ev.full_response;
|
|
61
62
|
else if (ev.type === 'tool.started' && !seen.has(ev.call_id)) { seen.add(ev.call_id); sum.tools++; }
|
|
62
63
|
else if (ev.type === 'tool.finished') { if (!seen.has(ev.call_id)) { seen.add(ev.call_id); sum.tools++; } if (ev.receipt?.ward_seq != null) sum.receipts.push(ev.receipt.ward_seq); }
|
|
64
|
+
else if (ev.type === 'quox_event') { if (output !== 'json') { const q = eventLine(ev); if (q) err(`${q}\n`); } } // QUOX-ANNOUNCE A2: stdout stays the answer
|
|
63
65
|
else if (ev.type === 'approval.requested') { sawApproval = true; sum.approvals++; }
|
|
64
66
|
else if (ev.type === 'approval.resolved') sawApproval = false;
|
|
67
|
+
else if (ev.type === 'turn.objective_suggested' && ev.objective_suggested?.goal) objective = ev.objective_suggested; // QUOXON objective offer
|
|
65
68
|
else if (ev.type === 'turn.handoff_suggested' && ev.handoff_suggested?.agent) handoff = ev.handoff_suggested; // QUOXON handoff offer
|
|
66
69
|
else if (ev.type === 'turn.finished') status = ev.status || 'completed';
|
|
67
70
|
else if (ev.type === 'error') errorMsg = isConsentError(ev) ? consentPrintMessage(ev, profile) : (ev.error || 'stream error');
|
|
@@ -85,7 +88,7 @@ export async function runPrint({ api, prompt, agent = 'quox', output = 'text', c
|
|
|
85
88
|
if (!status) status = authFail ? 'auth' : 'failed';
|
|
86
89
|
|
|
87
90
|
if (output === 'json') {
|
|
88
|
-
out(JSON.stringify({ type: 'summary', status, turn_id: turnId, conversation_id: convo, duration_ms: Date.now() - t0, server_stopped: serverStopped, ...sum, ...(handoff ? { handoff_suggested: handoff } : {}) }) + '\n');
|
|
91
|
+
out(JSON.stringify({ type: 'summary', status, turn_id: turnId, conversation_id: convo, duration_ms: Date.now() - t0, server_stopped: serverStopped, ...sum, ...(handoff ? { handoff_suggested: handoff } : {}), ...(objective ? { objective_suggested: objective } : {}) }) + '\n');
|
|
89
92
|
} else if (text) out(text.endsWith('\n') ? text : text + '\n');
|
|
90
93
|
if (authFail) err(`Not signed in for chat. Run: quox login -p <profile>\n`);
|
|
91
94
|
else if (code === EXIT.approval) err('Held for approval: approve it in the dashboard or with `quox` /approvals.\n');
|
|
@@ -93,6 +96,8 @@ export async function runPrint({ api, prompt, agent = 'quox', output = 'text', c
|
|
|
93
96
|
else if (errorMsg) err(`quox: ${errorMsg}\n`);
|
|
94
97
|
if (offer && code !== EXIT.ok) err(`${offer}\n`);
|
|
95
98
|
// QUOXON handoff offer: no y/N here, so name the exact one-turn command (same question, same conversation).
|
|
96
|
-
|
|
99
|
+
// QUOXON objective offer: no y/N here, so name the exact command that makes it an objective.
|
|
100
|
+
if (objective && output !== 'json' && code === EXIT.ok) err(`quox: ${objective.reason ? `${String(objective.reason).replace(/\s+$/, '')} ` : ''}Run it until done: quox objective create ${JSON.stringify(objective.goal)} --checkpoint\n`);
|
|
101
|
+
else if (handoff && output !== 'json' && code === EXIT.ok) err(`quox: ${handoff.reason ? `${String(handoff.reason).replace(/\s+$/, '')} ` : ''}${handoff.name || handoff.agent} can answer it: quox --print --agent ${handoff.agent}${convo ? ` --conversation ${convo}` : ''} ${JSON.stringify(prompt)}\n`);
|
|
97
102
|
return code;
|
|
98
103
|
}
|
|
@@ -23,6 +23,13 @@ export const switchAskLine = (profile, human) => askText('Switch?', {
|
|
|
23
23
|
action: `switch this window to your login '${human}' (same conversation, attach, slot and groups)`,
|
|
24
24
|
});
|
|
25
25
|
|
|
26
|
+
/** The startup offer on an agent login (owner 2026-10-28): restart as the human profile; /profile lists the rest. */
|
|
27
|
+
export const startupSwitchAskLine = (profile, human, others = 0) => askText(`Restart as you (${human})?`, {
|
|
28
|
+
title: `This window would run as the agent '${profile}'.`,
|
|
29
|
+
action: `restart this window as '${human}' (same directory, nothing else changes)`,
|
|
30
|
+
reason: others > 0 ? `${others} other signed-in profile${others === 1 ? '' : 's'}: /profile lists them` : '',
|
|
31
|
+
});
|
|
32
|
+
|
|
26
33
|
/** The ask card for a /slot name that does not exist (the near misses are said on the line above). */
|
|
27
34
|
export const slotCreateAskLine = (nm) => askText('Create it?', { title: `No slot ${nm}.`, action: `create and hold slot ${nm}` });
|
|
28
35
|
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
import { readFileSync, appendFileSync, writeFileSync, mkdirSync, statSync } from 'node:fs';
|
|
7
7
|
import { homedir } from 'node:os';
|
|
8
8
|
import { join, dirname } from 'node:path';
|
|
9
|
+
import { redactSecrets } from './secretShape.js';
|
|
9
10
|
|
|
10
11
|
export const HISTORY_CAP = 500;
|
|
11
12
|
export const defaultHistoryPath = (home = homedir()) => join(home, '.quox', 'history.jsonl');
|
|
@@ -22,7 +23,8 @@ export function fileHistory(path = defaultHistoryPath(), { cap = HISTORY_CAP } =
|
|
|
22
23
|
return out.slice(-cap);
|
|
23
24
|
} catch { return []; }
|
|
24
25
|
};
|
|
25
|
-
const add = (
|
|
26
|
+
const add = (text0) => {
|
|
27
|
+
const text = redactSecrets(text0); // SECRET-GUARD: a value never reaches the file
|
|
26
28
|
try {
|
|
27
29
|
mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
|
|
28
30
|
appendFileSync(path, JSON.stringify({ t: text }) + '\n', { mode: 0o600 });
|
package/lib/quoxon/qdragonFly.js
CHANGED
|
@@ -37,17 +37,18 @@ export function flyPose(ms) {
|
|
|
37
37
|
/** The frame at `ms` since the pass began. still: the one fixed frame (mid-sky, wings raised). mono: shapes only. */
|
|
38
38
|
/** The sky's size for a terminal `termRows` tall: the mid Q, or the small Q (headroom scaled to match) when the mid sky would
|
|
39
39
|
* not leave 10 rows for the transcript and composer. Never taller than max(12, termRows - 10). */
|
|
40
|
-
export function flySky(termRows) {
|
|
41
|
-
const room = Math.max(12, (termRows || 40) - 10);
|
|
40
|
+
export function flySky(termRows, chromeH = null) {
|
|
41
|
+
const room = chromeH == null ? Math.max(12, (termRows || 40) - 10) : (termRows || 40) - chromeH - 2; // measured chrome (app.js): the sky + margin + chrome must stay under the terminal height or Ink wipes the screen
|
|
42
42
|
const fits = (size, top) => dragonLayout(120, { size, lm: 0, ty: top }).rows <= room;
|
|
43
|
-
if (fits(SIZE, TOP)) return { size: SIZE, top: TOP, bob: BOB };
|
|
43
|
+
if (fits(SIZE, TOP)) return { size: SIZE, top: TOP, bob: BOB, fits: true };
|
|
44
44
|
const k = DSIZES.small / DSIZES[SIZE];
|
|
45
|
-
|
|
45
|
+
const top = Math.round(TOP * k);
|
|
46
|
+
return { size: 'small', top, bob: Math.max(1, Math.round(BOB * k)), fits: chromeH == null || dragonLayout(120, { size: 'small', lm: 0, ty: top }).rows <= room };
|
|
46
47
|
}
|
|
47
48
|
|
|
48
|
-
export function flyFrame(ms, cols, { still = false, mono = false, theme = 'dark', termRows } = {}) {
|
|
49
|
+
export function flyFrame(ms, cols, { still = false, mono = false, theme = 'dark', termRows, chromeH = null } = {}) {
|
|
49
50
|
const t = still ? 0 : Math.max(0, Math.min(ms, FLY_PASS_MS));
|
|
50
|
-
const { size: SIZE, top: TOP, bob: BOB } = flySky(termRows);
|
|
51
|
+
const { size: SIZE, top: TOP, bob: BOB } = flySky(termRows, chromeH);
|
|
51
52
|
const probe = dragonLayout(cols, { size: SIZE, lm: 0, ty: TOP });
|
|
52
53
|
const rows = probe.rows;
|
|
53
54
|
const xc = still ? cols / 2 : -SPAN + (cols + 2 * SPAN) * kf(t, [[0, 0], [FLY_PASS_MS, 1]]);
|
package/lib/quoxon/qmotion.js
CHANGED
|
@@ -11,6 +11,7 @@ import { helloCells, helloGradient, helloMs } from './qhello.js';
|
|
|
11
11
|
import { mono, c, clip, isLight } from './theme.js';
|
|
12
12
|
import { CLIPS, clipCells, clipCaption, eggCells, EGG_MS } from './qdragonScenes.js';
|
|
13
13
|
import { reducedMotion } from './motion/clock.js';
|
|
14
|
+
import { flySky } from './qdragonFly.js';
|
|
14
15
|
import { FrameContext, useFrame } from './motion/frame.js';
|
|
15
16
|
const isQuiet = () => reducedMotion(); // motion only (NO_COLOR is colour only; QGlyph falls back to the circled Q itself)
|
|
16
17
|
import { termRows } from './term.js';
|
|
@@ -130,3 +131,19 @@ export function DragonLive({ cols, rows }) {
|
|
|
130
131
|
...cells.map((row, k) => h(Text, { key: k, wrap: 'truncate' }, ...cellNodes(row, `d${k}-`))),
|
|
131
132
|
h(Text, { color: c('dim') }, clip(caption, Math.max(10, cols - 2))));
|
|
132
133
|
}
|
|
134
|
+
|
|
135
|
+
/** Rows the 15-row dragon clips need besides the chrome: the clip, its caption and the margin above it. */
|
|
136
|
+
export const CLIP_ROWS = 17;
|
|
137
|
+
/** Does an explicit /dragon <clip> fit? { ok } or { ok:false, note } (one line, never silent). Unknown size (null rows) is ok. */
|
|
138
|
+
export function clipRoom(clipName, rows, chromeH = 10) {
|
|
139
|
+
if (!rows) return { ok: true };
|
|
140
|
+
const need = CLIP_ROWS + chromeH + 2;
|
|
141
|
+
return rows >= need ? { ok: true } : { ok: false, note: `${clipName} needs ${need} rows, you have ${rows}. Make the window taller.` };
|
|
142
|
+
}
|
|
143
|
+
/** Does /dragon-fly fit? Same shape as clipRoom; the smallest sky is 12 rows. */
|
|
144
|
+
export function flyRoom(rows, chromeH = 10) {
|
|
145
|
+
if (!rows) return { ok: true };
|
|
146
|
+
if (flySky(rows, chromeH).fits) return { ok: true };
|
|
147
|
+
const need = 12 + 1 + chromeH + 2;
|
|
148
|
+
return { ok: false, note: `fly needs ${need} rows, you have ${rows}. Make the window taller.` };
|
|
149
|
+
}
|