quoxcli 0.2.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/README.md +2 -0
- package/bin/quox.js +99 -126
- package/lib/api/endpoints/admin.js +26 -0
- package/lib/api/endpoints/agent-schedules.js +26 -0
- package/lib/api/endpoints/agents.js +7 -0
- package/lib/api/endpoints/assistants.js +12 -0
- package/lib/api/endpoints/away.js +15 -0
- package/lib/api/endpoints/chat.js +7 -0
- package/lib/api/endpoints/contactsq.js +2 -0
- package/lib/api/endpoints/conversations.js +14 -0
- package/lib/api/endpoints/executions.js +13 -0
- package/lib/api/endpoints/fetchAllowlist.js +27 -0
- package/lib/api/endpoints/files.js +14 -0
- package/lib/api/endpoints/hat.js +30 -0
- package/lib/api/endpoints/inbox.js +20 -0
- package/lib/api/endpoints/index.js +14 -0
- package/lib/api/endpoints/memory.js +19 -0
- package/lib/api/endpoints/mind.js +13 -0
- package/lib/api/endpoints/mon.js +6 -0
- package/lib/api/endpoints/objectives.js +6 -0
- package/lib/api/endpoints/plugins.js +18 -0
- package/lib/api/endpoints/presence.js +33 -0
- package/lib/api/endpoints/quoxplan.js +7 -0
- package/lib/api/endpoints/quoxseo.js +6 -0
- package/lib/api/endpoints/router.js +17 -0
- package/lib/api/endpoints/safety.js +7 -0
- package/lib/api/endpoints/skills.js +20 -0
- package/lib/api/endpoints/stepup.js +20 -0
- package/lib/api/endpoints/teams.js +2 -2
- package/lib/api/endpoints/triggers.js +26 -0
- package/lib/api/endpoints/v1.js +86 -0
- package/lib/api/endpoints/ward.js +7 -0
- package/lib/api/endpoints/workflows.js +7 -0
- package/lib/api/endpoints/x.js +8 -0
- package/lib/commands/actions.js +61 -0
- package/lib/commands/admin.js +16 -0
- package/lib/commands/agent.js +163 -27
- package/lib/commands/agentAttach.js +94 -6
- package/lib/commands/approvals.js +73 -1
- package/lib/commands/aside.js +24 -0
- package/lib/commands/ask.js +144 -0
- package/lib/commands/assistant.js +59 -4
- package/lib/commands/auth.js +8 -2
- package/lib/commands/backup.js +8 -4
- package/lib/commands/brain.js +105 -14
- package/lib/commands/build.js +19 -5
- package/lib/commands/chat.js +57 -2
- package/lib/commands/claude.js +34 -8
- package/lib/commands/contacts.js +49 -0
- package/lib/commands/conversation.js +35 -4
- package/lib/commands/dashboard.js +43 -0
- package/lib/commands/department.js +28 -7
- package/lib/commands/devResident.js +15 -3
- package/lib/commands/devSupervisor.js +13 -1
- package/lib/commands/discord.js +75 -39
- package/lib/commands/done.js +98 -28
- package/lib/commands/engagement.js +17 -6
- package/lib/commands/fetchAllowlist.js +71 -0
- package/lib/commands/file.js +38 -1
- package/lib/commands/fleet.js +311 -4
- package/lib/commands/governance.js +12 -3
- package/lib/commands/gripe.js +44 -13
- package/lib/commands/hat.js +178 -9
- package/lib/commands/helpers.js +18 -1
- package/lib/commands/hyperion.js +58 -0
- package/lib/commands/inbox.js +74 -23
- package/lib/commands/licence.js +14 -155
- package/lib/commands/lucid.js +10 -4
- package/lib/commands/memory.js +85 -8
- package/lib/commands/mind.js +71 -0
- package/lib/commands/mon.js +69 -2
- package/lib/commands/monitoring.js +1 -1
- package/lib/commands/motion.js +2 -2
- package/lib/commands/nucleus.js +48 -17
- package/lib/commands/objective.js +11 -0
- package/lib/commands/offload.js +89 -0
- package/lib/commands/onboard.js +2 -1
- package/lib/commands/org.js +11 -1
- package/lib/commands/peek.js +28 -0
- package/lib/commands/plan.js +38 -0
- package/lib/commands/plugin.js +110 -0
- package/lib/commands/presence.js +89 -0
- package/lib/commands/provider.js +159 -0
- package/lib/commands/qlarity.js +19 -3
- package/lib/commands/quoxchat.js +13 -4
- package/lib/commands/quoxseo.js +38 -0
- package/lib/commands/reach.js +2 -1
- package/lib/commands/router.js +77 -0
- package/lib/commands/rows.js +358 -0
- package/lib/commands/run.js +64 -9
- package/lib/commands/server.js +292 -0
- package/lib/commands/setup.js +33 -7
- package/lib/commands/skills.js +48 -5
- package/lib/commands/slot.js +260 -7
- package/lib/commands/status.js +1 -1
- package/lib/commands/team.js +3 -1
- package/lib/commands/tips.js +23 -0
- package/lib/commands/tool.js +133 -45
- package/lib/commands/trace.js +12 -7
- package/lib/commands/trigger.js +95 -0
- package/lib/commands/update.js +46 -0
- package/lib/commands/vault.js +31 -8
- package/lib/commands/vm.js +237 -0
- package/lib/commands/voice.js +34 -3
- package/lib/commands/volt.js +45 -3
- package/lib/commands/walkthrough.js +32 -0
- package/lib/commands/watch.js +73 -21
- package/lib/commands/welcome.js +14 -0
- package/lib/commands/workflow.js +189 -32
- package/lib/commands/x.js +13 -4
- package/lib/core/actingAs.js +2 -2
- package/lib/core/activeSummary.js +186 -0
- package/lib/core/aside.js +54 -0
- package/lib/core/askDecision.js +60 -0
- package/lib/core/auth.js +164 -13
- package/lib/core/callerSource.js +1 -1
- package/lib/core/cliBuild.js +62 -0
- package/lib/core/client.js +144 -35
- package/lib/core/commandRegistrars.js +121 -0
- package/lib/core/config.js +142 -24
- package/lib/core/customHats.js +168 -0
- package/lib/core/doneEngineFetch.js +8 -0
- package/lib/core/errors.js +329 -20
- package/lib/core/global-flags.js +105 -5
- package/lib/core/hyperion.js +102 -0
- package/lib/core/inboxResolution.js +65 -0
- package/lib/core/lazyCommands.js +4 -1
- package/lib/core/offload.js +504 -0
- package/lib/core/org-override.js +1 -1
- package/lib/core/orgResolve.js +70 -0
- package/lib/core/output.js +4 -4
- package/lib/core/peek.js +126 -0
- package/lib/core/profileUsers.js +51 -0
- package/lib/core/promptCancel.js +18 -0
- package/lib/core/repoPath.js +11 -0
- package/lib/core/residentSeed.js +3 -1
- package/lib/core/rootOptions.js +39 -0
- package/lib/core/router.js +168 -0
- package/lib/core/selfUpdate.js +188 -0
- package/lib/core/sessionsOverview.js +64 -0
- package/lib/core/slotKeeper.js +248 -3
- package/lib/core/slotWake.js +120 -0
- package/lib/core/supervisorEngine.js +71 -2
- package/lib/core/version.js +1 -1
- package/lib/core/vm.js +361 -0
- package/lib/core/vmTui.js +86 -0
- package/lib/core/walkthrough.js +35 -0
- package/lib/hyperion/deck.js +29 -0
- package/lib/lucid/LucidCard.jsx +68 -0
- package/lib/lucid/LucidIntro.jsx +45 -58
- package/lib/lucid/cards.js +86 -0
- package/lib/lucid/markdown.js +81 -0
- package/lib/lucid/motion.js +56 -0
- package/lib/quoxon/activity.js +14 -2
- package/lib/quoxon/aocl.js +117 -0
- package/lib/quoxon/api.js +504 -25
- package/lib/quoxon/app.js +3000 -230
- package/lib/quoxon/approvalFacts.js +94 -0
- package/lib/quoxon/approvalsView.js +325 -0
- package/lib/quoxon/asideView.js +40 -0
- package/lib/quoxon/ask.js +121 -0
- package/lib/quoxon/away.js +52 -0
- package/lib/quoxon/bang.js +224 -0
- package/lib/quoxon/bulkRisk.js +17 -0
- package/lib/quoxon/capabilities.js +13 -6
- package/lib/quoxon/cells.js +53 -0
- package/lib/quoxon/commandShort.js +112 -0
- package/lib/quoxon/commands.js +363 -38
- package/lib/quoxon/connect.js +210 -23
- package/lib/quoxon/connectCard.js +19 -2
- package/lib/quoxon/consent.js +86 -0
- package/lib/quoxon/copy.js +4 -3
- package/lib/quoxon/countdownLine.js +11 -0
- package/lib/quoxon/demo.autoplay.js +29 -0
- package/lib/quoxon/demo.fixtures.js +1 -1
- package/lib/quoxon/demo.js +44 -3
- package/lib/quoxon/devObserver.js +20 -0
- package/lib/quoxon/dividerAocl.js +191 -0
- package/lib/quoxon/dividerRule.js +57 -0
- package/lib/quoxon/dragonModes.js +150 -0
- package/lib/quoxon/dragonView.js +41 -0
- package/lib/quoxon/earlyInput.js +40 -0
- package/lib/quoxon/echo.js +15 -0
- package/lib/quoxon/egg.js +2 -2
- package/lib/quoxon/events.js +244 -25
- package/lib/quoxon/feedback.js +50 -0
- package/lib/quoxon/gallery.js +21 -7
- package/lib/quoxon/handoffOffer.js +78 -0
- package/lib/quoxon/hatSwitch.js +37 -0
- package/lib/quoxon/headless.js +161 -0
- package/lib/quoxon/hostUrls.js +41 -0
- package/lib/quoxon/indicator.js +6 -2
- package/lib/quoxon/intent.js +12 -1
- package/lib/quoxon/keys.js +49 -6
- package/lib/quoxon/launcher.js +103 -0
- package/lib/quoxon/main.js +92 -160
- package/lib/quoxon/markdown.js +45 -21
- package/lib/quoxon/markdownSettle.js +19 -0
- package/lib/quoxon/matrixDomains.json +46 -0
- package/lib/quoxon/mentions.js +90 -0
- package/lib/quoxon/mind.js +103 -0
- package/lib/quoxon/mode.js +46 -0
- package/lib/quoxon/model.js +132 -26
- package/lib/quoxon/modelPrefs.js +345 -0
- package/lib/quoxon/motion/clock.js +3 -2
- package/lib/quoxon/motion/level.js +91 -0
- package/lib/quoxon/mouse.js +211 -0
- package/lib/quoxon/noai.js +57 -0
- package/lib/quoxon/objects/away.js +72 -0
- package/lib/quoxon/objects/fetch.js +17 -5
- package/lib/quoxon/objects/fleet.js +149 -0
- package/lib/quoxon/objects/health.js +186 -0
- package/lib/quoxon/objects/index.js +24 -3
- package/lib/quoxon/objects/mirror.js +137 -0
- package/lib/quoxon/objects/peek.js +17 -0
- package/lib/quoxon/objects/prove.js +130 -0
- package/lib/quoxon/objects/replay.js +125 -0
- package/lib/quoxon/objects/sitrep.js +58 -0
- package/lib/quoxon/objects/view.js +11 -0
- package/lib/quoxon/onboarding/copy.js +32 -4
- package/lib/quoxon/onboarding/credential.js +17 -4
- package/lib/quoxon/onboarding/dockerOffer.js +45 -0
- package/lib/quoxon/onboarding/failureCause.js +33 -0
- package/lib/quoxon/onboarding/finish.js +15 -0
- package/lib/quoxon/onboarding/index.js +34 -7
- package/lib/quoxon/onboarding/installRunner.js +64 -5
- package/lib/quoxon/onboarding/preflight.js +17 -3
- package/lib/quoxon/onboarding/probe.js +65 -0
- package/lib/quoxon/onboarding/view.js +76 -19
- package/lib/quoxon/orgLabel.js +10 -0
- package/lib/quoxon/orgSwitch.js +33 -0
- package/lib/quoxon/ownBroadcast.js +22 -0
- package/lib/quoxon/palette.js +43 -0
- package/lib/quoxon/print.js +9 -5
- package/lib/quoxon/profileSwitch.js +53 -0
- package/lib/quoxon/promptHistory.js +41 -0
- package/lib/quoxon/providers.js +48 -0
- package/lib/quoxon/qart.js +20 -51
- package/lib/quoxon/qdragon.js +410 -0
- package/lib/quoxon/qdragonFly.js +61 -0
- package/lib/quoxon/qdragonScenes.js +282 -0
- package/lib/quoxon/qeyes.js +213 -0
- package/lib/quoxon/qmotion.js +58 -37
- package/lib/quoxon/render.js +638 -119
- package/lib/quoxon/resend.js +72 -0
- package/lib/quoxon/roster.js +95 -9
- package/lib/quoxon/route.js +6 -5
- package/lib/quoxon/rows.js +711 -0
- package/lib/quoxon/rowsStore.js +80 -0
- package/lib/quoxon/screenContext.js +150 -0
- package/lib/quoxon/screenFrames.js +254 -0
- package/lib/quoxon/session.js +172 -2
- package/lib/quoxon/sessionDead.js +17 -0
- package/lib/quoxon/signin.js +125 -0
- package/lib/quoxon/slash/agents.js +112 -0
- package/lib/quoxon/slash/argSources.js +65 -0
- package/lib/quoxon/slash/args.js +65 -0
- package/lib/quoxon/slash/automate.js +89 -0
- package/lib/quoxon/slash/broadcast.js +140 -0
- package/lib/quoxon/slash/broadcastRun.js +108 -0
- package/lib/quoxon/slash/card.js +30 -0
- package/lib/quoxon/slash/challenge.js +114 -0
- package/lib/quoxon/slash/cliCommand.js +53 -0
- package/lib/quoxon/slash/cliLeaves.json +948 -0
- package/lib/quoxon/slash/compact.js +1 -1
- package/lib/quoxon/slash/confirm.js +30 -0
- package/lib/quoxon/slash/crystallize.js +69 -0
- package/lib/quoxon/slash/dashboard.js +136 -0
- package/lib/quoxon/slash/draft.js +54 -0
- package/lib/quoxon/slash/hatWalk.js +206 -0
- package/lib/quoxon/slash/inverses.js +77 -0
- package/lib/quoxon/slash/killer.js +24 -0
- package/lib/quoxon/slash/killerApi.js +16 -0
- package/lib/quoxon/slash/ledger.js +13 -8
- package/lib/quoxon/slash/listView.js +3 -1
- package/lib/quoxon/slash/modelCmd.js +261 -0
- package/lib/quoxon/slash/palette.js +21 -7
- package/lib/quoxon/slash/paletteController.js +117 -7
- package/lib/quoxon/slash/panels.js +21 -6
- package/lib/quoxon/slash/picker.js +20 -2
- package/lib/quoxon/slash/restart.js +71 -13
- package/lib/quoxon/slash/rowCmd.js +157 -0
- package/lib/quoxon/slash/rowWalk.js +175 -0
- package/lib/quoxon/slash/settings.js +16 -6
- package/lib/quoxon/slash/spawn.js +177 -0
- package/lib/quoxon/slash/stepUp.js +274 -0
- package/lib/quoxon/slash/stepUpCard.js +60 -0
- package/lib/quoxon/slash/stepUpCmds.js +78 -0
- package/lib/quoxon/slash/undoApi.js +38 -0
- package/lib/quoxon/slash/undoCmd.js +49 -0
- package/lib/quoxon/slash/undoWish.js +15 -0
- package/lib/quoxon/slash/watch.js +164 -0
- package/lib/quoxon/slash/wish.js +23 -0
- package/lib/quoxon/slash/wishCmd.js +32 -0
- package/lib/quoxon/slotChat.js +300 -0
- package/lib/quoxon/slotFormat.js +195 -0
- package/lib/quoxon/slotRun.js +131 -0
- package/lib/quoxon/sound/mapping.js +2 -1
- package/lib/quoxon/stray.js +22 -0
- package/lib/quoxon/suggestCommand.js +59 -0
- package/lib/quoxon/term.js +46 -11
- package/lib/quoxon/textBuffer.js +176 -2
- package/lib/quoxon/theme.js +58 -25
- package/lib/quoxon/tips.js +433 -0
- package/lib/quoxon/toolError.js +41 -0
- package/lib/quoxon/transcript.js +42 -4
- package/lib/quoxon/web/qface.js +18 -10
- package/lib/quoxon/welcome.js +1 -1
- package/lib/quoxon/whatsnew.js +136 -0
- package/lib/quoxon/whatsnew.jsonl +89 -0
- package/lib/quoxon/windowOrg.js +113 -0
- package/lib/server/diag.js +129 -0
- package/lib/server/doctor.js +71 -0
- package/lib/server/install.js +149 -0
- package/lib/server/pluginEnable.js +161 -0
- package/lib/server/skew.js +56 -0
- package/lib/server/snapshot.js +131 -0
- package/lib/server/status.js +128 -0
- package/lib/server/sys.js +65 -0
- package/lib/server/uninstall.js +63 -0
- package/lib/server/update.js +234 -0
- package/lib/toolOutputShape.js +315 -0
- package/lib/tui/views/HyperionDeck.jsx +79 -0
- package/lib/tui/views/LucidDeck.jsx +209 -217
- package/package.json +4 -1
- package/lib/core/websiteSession.js +0 -148
package/lib/commands/lucid.js
CHANGED
|
@@ -102,6 +102,7 @@ export function registerLucidCommands(program, ctx) {
|
|
|
102
102
|
if (o.json) { console.log(JSON.stringify(data, null, 2)); return; }
|
|
103
103
|
if (o.agent) { console.log(data.text_agent ?? data.text ?? ''); return; }
|
|
104
104
|
if (o.spoken) { console.log(data.spoken ?? data.text ?? ''); return; }
|
|
105
|
+
if (o.markdown) { const { renderSitrepMarkdown } = await import('../lucid/markdown.js'); console.log(renderSitrepMarkdown(data)); return; }
|
|
105
106
|
const { renderSitrep } = await import('../lucid/sitrep.js');
|
|
106
107
|
const tty = Boolean(process.stdout.isTTY);
|
|
107
108
|
process.stdout.write(renderSitrep(data, {
|
|
@@ -118,14 +119,15 @@ export function registerLucidCommands(program, ctx) {
|
|
|
118
119
|
const sitrepOpts = (c) => c
|
|
119
120
|
.option('--agent', 'compact, id-rich form for agents')
|
|
120
121
|
.option('--json', 'the full structured situation')
|
|
121
|
-
.option('--spoken', 'sentences only, as read on the phone')
|
|
122
|
+
.option('--spoken', 'sentences only, as read on the phone')
|
|
123
|
+
.option('--markdown', 'chat-ready Markdown for Discord, Matrix and slots (fits one message)');
|
|
122
124
|
|
|
123
125
|
sitrepOpts(lu
|
|
124
126
|
.command('sitrep')
|
|
125
127
|
.description('One-shot formatted snapshot (areas, what needs you, what changed, what I cannot see)'))
|
|
126
128
|
.action(async (o) => showSitrep(o));
|
|
127
129
|
|
|
128
|
-
// Top-level shortcut so any human or agent can ask for a sitrep: `quox sitrep [--agent|--json|--spoken]`.
|
|
130
|
+
// Top-level shortcut so any human or agent can ask for a sitrep: `quox sitrep [--agent|--json|--spoken|--markdown]`.
|
|
129
131
|
sitrepOpts(program
|
|
130
132
|
.command('sitrep')
|
|
131
133
|
.description('LUCID situation report: what needs you, every area, what changed, what LUCID cannot see'))
|
|
@@ -393,9 +395,13 @@ export function registerLucidCommands(program, ctx) {
|
|
|
393
395
|
if (wantJson(cmd)) { console.log(JSON.stringify(data, null, 2)); return; }
|
|
394
396
|
if (data.status === 'filed') {
|
|
395
397
|
printSuccess(`Filed${data.approval_id ? ` (approval ${data.approval_id})` : ''}. Approve it in Approvals.`);
|
|
396
|
-
} else if (data.status === 'manual') {
|
|
398
|
+
} else if (data.status === 'manual' && data.command) {
|
|
397
399
|
console.log('This one is manual. Run this yourself:');
|
|
398
|
-
console.log(` ${data.command
|
|
400
|
+
console.log(` ${data.command}`);
|
|
401
|
+
} else if (data.status === 'manual') {
|
|
402
|
+
// #992: a 'say' measure has no command; print what it says and how to ask, never a blank instruction.
|
|
403
|
+
console.log(data.say || (m && m.label) || 'There is nothing to run for this one.');
|
|
404
|
+
console.log(` quox lucid ask "${((m && m.label) || 'Look into it').replace(/"/g, "'")} (${ref})"`);
|
|
399
405
|
} else if (data.status === 'opened') {
|
|
400
406
|
console.log(`Opened: ${data.url ?? ''}`);
|
|
401
407
|
} else {
|
package/lib/commands/memory.js
CHANGED
|
@@ -26,6 +26,7 @@ import { resolveEndpoint } from '../api/endpoints.js';
|
|
|
26
26
|
import { QuoxError } from '../core/errors.js';
|
|
27
27
|
import { resolveOrgId } from './helpers.js';
|
|
28
28
|
import { resolveMyAttach } from './agentAttach.js';
|
|
29
|
+
import { hasExplicitProfile } from '../core/actingAs.js';
|
|
29
30
|
|
|
30
31
|
// QUOXIDENTITY P9: which agent this session acts as is resolved session-scoped
|
|
31
32
|
// (QUOX_T subtree / QUOX_ATTACH_AGENT override / single-attach fallback), so
|
|
@@ -46,7 +47,12 @@ async function callMemoryTool({ client, auth, program }, toolName, input) {
|
|
|
46
47
|
const body = { tool_name: toolName, input };
|
|
47
48
|
const requestOptions = { service: ep.service, body };
|
|
48
49
|
|
|
49
|
-
|
|
50
|
+
// An explicit -p/--profile (or QUOX_PROFILE) is the caller saying "as me":
|
|
51
|
+
// it wins over the session's attach lease, same precedence as
|
|
52
|
+
// core/actingAs. Before this, `quox -p moo memory search` inside an attached
|
|
53
|
+
// session went out on the lease token and 401'd (proof engine 2026-10-14).
|
|
54
|
+
const argv = (program.rawArgs || process.argv).slice(2);
|
|
55
|
+
const lease = hasExplicitProfile(argv) ? null : detectSoleAttachLease();
|
|
50
56
|
if (lease) {
|
|
51
57
|
requestOptions.noAuth = true;
|
|
52
58
|
requestOptions.headers = { Authorization: `Bearer ${lease.token}` };
|
|
@@ -260,16 +266,87 @@ export function registerMemoryCommands(program, ctx) {
|
|
|
260
266
|
}, { format: program.opts().output });
|
|
261
267
|
});
|
|
262
268
|
|
|
269
|
+
// QUOXMIND #1111: the old view counted only rows under YOUR user id, so an
|
|
270
|
+
// org holding hundreds of agent-written memories looked nearly empty. Default
|
|
271
|
+
// is now the org view (per agent, per writer), falling back honestly.
|
|
263
272
|
memory
|
|
264
273
|
.command('stats')
|
|
265
|
-
.description('Show memory statistics')
|
|
266
|
-
.
|
|
274
|
+
.description('Show memory statistics: the whole org by agent and writer (org admins), else your own')
|
|
275
|
+
.option('--mine', 'only memories written under your own login')
|
|
276
|
+
.action(async (options) => {
|
|
277
|
+
auth.requireAuth();
|
|
278
|
+
const ep = resolveEndpoint('memory.stats.list');
|
|
279
|
+
let data;
|
|
280
|
+
let note = '';
|
|
281
|
+
if (options.mine) {
|
|
282
|
+
data = await client.get(ep.path, { service: ep.service, query: {} });
|
|
283
|
+
} else {
|
|
284
|
+
try {
|
|
285
|
+
data = await client.get(ep.path, { service: ep.service, query: { scope: 'org' } });
|
|
286
|
+
} catch (err) {
|
|
287
|
+
if (err?.status !== 403) throw err;
|
|
288
|
+
data = await client.get(ep.path, { service: ep.service, query: {} });
|
|
289
|
+
note = 'Org-wide counts need an org admin or owner. Showing only memories written under your own login; agents write under their own identities, so the org holds more.';
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
const format = program.opts().output;
|
|
293
|
+
if (format === 'json') { console.log(JSON.stringify(note ? { ...data, note } : data, null, 2)); return; }
|
|
294
|
+
if (data?.scope !== 'org') {
|
|
295
|
+
formatDetail(data.stats || data, { format, title: 'Memory Stats (your login only)' });
|
|
296
|
+
console.log(note || 'Counts only memories written under your own login. `quox memory stats` without --mine shows the whole org (org admins).');
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
const m = data.memories || {};
|
|
300
|
+
console.log(`Memory Stats (org): ${m.total ?? 0} valid memories, ${m.not_valid ?? 0} superseded or deleted (kept for history), ${data.pinned ?? 0} pinned, last write ${data.last_write_at || 'never'}`);
|
|
301
|
+
if (m.by_type) console.log(`By type: ${Object.entries(m.by_type).map(([k, v]) => `${k} ${v}`).join(', ')}`);
|
|
302
|
+
formatOutput({
|
|
303
|
+
columns: ['agent', 'count'], headers: ['Agent', 'Memories'],
|
|
304
|
+
rows: Object.entries(data.by_agent || {}).map(([agent, count]) => ({ agent, count })),
|
|
305
|
+
}, { format });
|
|
306
|
+
formatOutput({
|
|
307
|
+
columns: ['user_id', 'kind', 'count', 'agents'], headers: ['Writer', 'Kind', 'Memories', 'Agents'],
|
|
308
|
+
rows: (data.by_user || []).map((u) => ({ ...u, agents: (u.agents || []).join(',') })),
|
|
309
|
+
}, { format });
|
|
310
|
+
console.log('Valid = not deleted and not superseded. A writer is the login a memory was saved under: your own, an agent identity, service/system (internal agents), or org-shared beliefs.');
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
memory
|
|
314
|
+
.command('core')
|
|
315
|
+
.description('Show the core set: memories loaded into every chat turn (pinned, plus auto-promoted decisions and facts rated high or critical)')
|
|
316
|
+
.option('--limit <n>', 'max memories', '20')
|
|
317
|
+
.action(async (options) => {
|
|
267
318
|
auth.requireAuth();
|
|
268
|
-
const
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
319
|
+
const ep = resolveEndpoint('memory.core-set');
|
|
320
|
+
const data = await client.get(ep.path, { service: ep.service, query: { limit: options.limit } });
|
|
321
|
+
formatOutput({
|
|
322
|
+
columns: ['id', 'core_reason', 'agent_id', 'memory_type', 'content'],
|
|
323
|
+
headers: ['ID', 'Why', 'Agent', 'Type', 'Content'],
|
|
324
|
+
rows: (data.memories || []).map((m) => ({ ...m, content: (m.content || '').slice(0, 80) })),
|
|
325
|
+
}, { format: program.opts().output, raw: data });
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
memory
|
|
329
|
+
.command('pin')
|
|
330
|
+
.description('Pin a memory into the core set so every chat turn carries it')
|
|
331
|
+
.argument('<id>', 'memory ID')
|
|
332
|
+
.action(async (id) => {
|
|
333
|
+
auth.requireAuth();
|
|
334
|
+
const ep = resolveEndpoint('memory.pin', { id });
|
|
335
|
+
const data = await client.post(ep.path, { service: ep.service, body: {} });
|
|
336
|
+
if (program.opts().output === 'json') { console.log(JSON.stringify(data, null, 2)); return; }
|
|
337
|
+
printSuccess(`Pinned ${id} into the core set.`);
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
memory
|
|
341
|
+
.command('unpin')
|
|
342
|
+
.description('Take a memory out of the core set (also stops it being auto-promoted)')
|
|
343
|
+
.argument('<id>', 'memory ID')
|
|
344
|
+
.action(async (id) => {
|
|
345
|
+
auth.requireAuth();
|
|
346
|
+
const ep = resolveEndpoint('memory.unpin', { id });
|
|
347
|
+
const data = await client.delete(ep.path, { service: ep.service });
|
|
348
|
+
if (program.opts().output === 'json') { console.log(JSON.stringify(data, null, 2)); return; }
|
|
349
|
+
printSuccess(`Unpinned ${id}: it stays a memory, but is no longer in the core set.`);
|
|
273
350
|
});
|
|
274
351
|
|
|
275
352
|
memory
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `quox mind implants|implant <id>|health`: QuoxMind cognitive implants (read-only).
|
|
3
|
+
* Collector routes under /api/v1/mind (org-scoped). Implants with no recorded
|
|
4
|
+
* activity say so; nothing is invented.
|
|
5
|
+
*/
|
|
6
|
+
import { formatOutput } from '../core/output.js';
|
|
7
|
+
import { resolveEndpoint } from '../api/endpoints.js';
|
|
8
|
+
import { resolveOrgId } from './helpers.js';
|
|
9
|
+
|
|
10
|
+
export function registerMindCommands(program, ctx) {
|
|
11
|
+
const { client, auth } = ctx;
|
|
12
|
+
const get = async (name, params) => {
|
|
13
|
+
auth.requireAuth();
|
|
14
|
+
const ep = resolveEndpoint(name, params);
|
|
15
|
+
return client.get(ep.path, { service: ep.service, query: { org_id: resolveOrgId(auth, program) } });
|
|
16
|
+
};
|
|
17
|
+
const isJson = () => program.opts().output === 'json';
|
|
18
|
+
|
|
19
|
+
const mind = program.command('mind').description('QuoxMind: the cognitive implants (read-only)');
|
|
20
|
+
|
|
21
|
+
mind
|
|
22
|
+
.command('implants')
|
|
23
|
+
.description('List the cognitive implants: purpose, status, last activity')
|
|
24
|
+
.action(async () => {
|
|
25
|
+
const data = await get('mind.implants');
|
|
26
|
+
const rows = data.implants || [];
|
|
27
|
+
formatOutput(rows.map((i) => ({
|
|
28
|
+
ID: i.short,
|
|
29
|
+
NAME: i.name,
|
|
30
|
+
PURPOSE: i.purpose,
|
|
31
|
+
STATUS: i.status,
|
|
32
|
+
'LAST ACTIVITY': i.last_activity || 'no activity recorded',
|
|
33
|
+
// QUOXMIND-PROOF: firing evidence from the implant's own table (null = not tracked).
|
|
34
|
+
'LAST FIRED': i.activity_error ? 'unknown' : (typeof i.fired_7d === 'number' ? (i.last_fired || 'never') : 'not tracked'),
|
|
35
|
+
'7D': typeof i.fired_7d === 'number' && !i.activity_error ? i.fired_7d : '-',
|
|
36
|
+
})), {
|
|
37
|
+
format: program.opts().output,
|
|
38
|
+
columns: ['ID', 'NAME', 'STATUS', 'LAST FIRED', '7D', 'PURPOSE'],
|
|
39
|
+
emptyMessage: 'No implants registered.',
|
|
40
|
+
raw: rows,
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
mind
|
|
45
|
+
.command('implant <id>')
|
|
46
|
+
.description('One implant in detail (e.g. predict, reflect, repobrain, qortex)')
|
|
47
|
+
.action(async (id) => {
|
|
48
|
+
const d = await get('mind.implant', { id });
|
|
49
|
+
if (isJson()) { console.log(JSON.stringify(d, null, 2)); return; }
|
|
50
|
+
console.log(`${d.name} (${d.short}) · ${d.status}${d.tier ? ` · ${d.tier}` : ''}`);
|
|
51
|
+
console.log(d.description || d.purpose);
|
|
52
|
+
if (d.inspired_by) console.log(`Inspired by: ${d.inspired_by}`);
|
|
53
|
+
if (d.summary) console.log(`Now: ${d.summary}`);
|
|
54
|
+
console.log(d.activity);
|
|
55
|
+
if (typeof d.fired_7d === 'number' && !d.activity_error) console.log(`Fired: ${d.last_fired || 'never'} · ${d.fired_7d} in the last 7 days${d.activity_source ? ` (from ${d.activity_source})` : ''}`);
|
|
56
|
+
else if (d.activity_error) console.log(`Fired: unknown (${d.activity_error})`);
|
|
57
|
+
for (const [k, v] of Object.entries(d.metrics || {})) if (v !== null && typeof v !== 'object') console.log(` ${k}: ${v}`);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
mind
|
|
61
|
+
.command('health')
|
|
62
|
+
.description('Summary of QuoxMind health across all implants')
|
|
63
|
+
.action(async () => {
|
|
64
|
+
const d = await get('mind.health');
|
|
65
|
+
if (isJson()) { console.log(JSON.stringify(d, null, 2)); return; }
|
|
66
|
+
console.log(`${d.total} implants: ${Object.entries(d.by_status).map(([k, v]) => `${v} ${k}`).join(', ')}`);
|
|
67
|
+
console.log(`${d.with_activity} with recorded activity, ${d.no_activity} with no activity recorded`);
|
|
68
|
+
if (typeof d.fired_7d_total === 'number') console.log(`Last 7 days: ${d.fired_7d_implants} implants fired (${d.fired_7d_total} firings), ${d.silent_7d} silent, ${d.activity_unknown} not tracked`);
|
|
69
|
+
console.log(d.average_health === null ? 'Average health: not enough data' : `Average health: ${d.average_health} (over ${d.scored} scored)`);
|
|
70
|
+
});
|
|
71
|
+
}
|
package/lib/commands/mon.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Monitoring commands: Docker fleet, Wazuh security monitoring, AGH-chain
|
|
3
3
|
* agent honesty leaderboard, Prometheus metrics, and monitoring config.
|
|
4
4
|
*
|
|
5
|
-
* quox mon docker fleet|containers|container|logs|stats|stacks|df|action
|
|
5
|
+
* quox mon docker fleet|containers|container|inspect|logs|stats|stacks|df|action
|
|
6
6
|
* quox mon wazuh status|agents|alerts|vulns|deploy-history|deploy-agent|deploy-bulk|active-response
|
|
7
7
|
* quox mon agh-chain leaderboard|agent|events
|
|
8
8
|
* quox mon metrics
|
|
@@ -36,6 +36,47 @@ async function confirmOrSkip(program, options, message) {
|
|
|
36
36
|
return ans.confirm;
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
+
|
|
40
|
+
/** One readable line per fact from the inspect health block (null when the container has no healthcheck). */
|
|
41
|
+
function healthRows(h) {
|
|
42
|
+
if (!h) return null;
|
|
43
|
+
const rows = [];
|
|
44
|
+
const streak = h.failing_streak ? ` (failing ${h.failing_streak} times)` : '';
|
|
45
|
+
rows.push(`health: ${h.status || 'unknown'}${streak}`);
|
|
46
|
+
if (h.test) {
|
|
47
|
+
const parts = [h.interval && `every ${h.interval}`, h.timeout && `timeout ${h.timeout}`, h.retries != null && `retries ${h.retries}`].filter(Boolean);
|
|
48
|
+
rows.push(`probe: ${h.test}${parts.length ? ' ' + parts.join(', ') : ''}`);
|
|
49
|
+
}
|
|
50
|
+
for (const p of h.last_probes || []) {
|
|
51
|
+
const o = String(p.output || '').replace(/\s+/g, ' ').trim();
|
|
52
|
+
rows.push(`last: exit ${p.exit_code}${o ? ` '${o}'` : ''}${p.end ? ` at ${p.end}` : ''}`);
|
|
53
|
+
}
|
|
54
|
+
return rows;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* DEVFLEET-DOCKER: an error body is not data. If the collector reports a refused/failed Docker
|
|
59
|
+
* read, say so (stderr) and set a non-zero exit instead of printing an empty list. Partial fleets
|
|
60
|
+
* print the failed hosts as warnings. Returns true when the whole read failed (caller stops).
|
|
61
|
+
*/
|
|
62
|
+
function reportDockerReadFailures(data) {
|
|
63
|
+
if (!data) return false;
|
|
64
|
+
const failedHosts = Array.isArray(data.hosts_failed) ? data.hosts_failed
|
|
65
|
+
: (data.success === false && Array.isArray(data.hosts) ? data.hosts.filter((h) => h && h.ok === false) : []);
|
|
66
|
+
if (data.success === false) {
|
|
67
|
+
const tag = data.refused ? ' (refused, error_code ' + (data.error_code || 'approval_required') + ')' : '';
|
|
68
|
+
console.error('Docker read failed' + tag + ': ' + (data.error || 'unknown error'));
|
|
69
|
+
for (const h of failedHosts) console.error(' ' + h.host + ': ' + (h.error || 'failed'));
|
|
70
|
+
process.exitCode = 1;
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
73
|
+
if (data.partial) {
|
|
74
|
+
console.error('Partial result: ' + failedHosts.length + ' host(s) could not be read; the list below is incomplete.');
|
|
75
|
+
for (const h of failedHosts) console.error(' ' + h.host + ': ' + (h.error || 'failed'));
|
|
76
|
+
}
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
|
|
39
80
|
export function registerMonCommands(program, ctx) {
|
|
40
81
|
const { client, auth } = ctx;
|
|
41
82
|
|
|
@@ -59,6 +100,7 @@ export function registerMonCommands(program, ctx) {
|
|
|
59
100
|
auth.requireAuth();
|
|
60
101
|
const ep = resolveEndpoint('mon.docker.fleet.list');
|
|
61
102
|
const data = await client.get(ep.path, { service: ep.service, query: options.refresh ? { refresh: 'true' } : undefined });
|
|
103
|
+
if (reportDockerReadFailures(data)) return;
|
|
62
104
|
const hosts = data.hosts || [];
|
|
63
105
|
formatOutput({
|
|
64
106
|
// fleet #392: docker fleet rows are id/containers_total/docker_version; ip and status don't exist.
|
|
@@ -82,6 +124,7 @@ export function registerMonCommands(program, ctx) {
|
|
|
82
124
|
if (options.all === false) query.all = 'false';
|
|
83
125
|
if (options.refresh) query.refresh = 'true';
|
|
84
126
|
const data = await client.get(ep.path, { service: ep.service, query });
|
|
127
|
+
if (reportDockerReadFailures(data)) return;
|
|
85
128
|
formatOutput({
|
|
86
129
|
columns: ['host_id', 'id', 'name', 'image', 'state', 'status'],
|
|
87
130
|
headers: ['Host', 'ID', 'Name', 'Image', 'State', 'Status'],
|
|
@@ -98,6 +141,9 @@ export function registerMonCommands(program, ctx) {
|
|
|
98
141
|
auth.requireAuth();
|
|
99
142
|
const ep = resolveEndpoint('mon.docker.containers.get', { id });
|
|
100
143
|
const data = await client.get(ep.path, { service: ep.service, query: { host: options.host } });
|
|
144
|
+
// quox#1001: a 200 with success:false (e.g. "Bastion not configured") is a failure, not an
|
|
145
|
+
// empty container. Surface the error on stderr + exit non-zero instead of rendering a blank card.
|
|
146
|
+
if (reportDockerReadFailures(data)) return;
|
|
101
147
|
formatDetail(data, {
|
|
102
148
|
format: program.opts().output,
|
|
103
149
|
title: `Container: ${id} (${options.host})`,
|
|
@@ -106,6 +152,24 @@ export function registerMonCommands(program, ctx) {
|
|
|
106
152
|
});
|
|
107
153
|
});
|
|
108
154
|
|
|
155
|
+
docker
|
|
156
|
+
.command('inspect')
|
|
157
|
+
.description('Show container state and health (failing probe command and last results)')
|
|
158
|
+
.argument('<id>', 'container ID')
|
|
159
|
+
.requiredOption('--host <hostId>', 'host the container lives on')
|
|
160
|
+
.action(async (id, options) => {
|
|
161
|
+
auth.requireAuth();
|
|
162
|
+
const ep = resolveEndpoint('mon.docker.containers.inspect', { id });
|
|
163
|
+
const data = await client.get(ep.path, { service: ep.service, query: { host: options.host } });
|
|
164
|
+
if (reportDockerReadFailures(data)) return;
|
|
165
|
+
if (program.opts().output === 'json') { console.log(JSON.stringify(data, null, 2)); return; }
|
|
166
|
+
const d = data.details || {};
|
|
167
|
+
console.log(`Container: ${id} (${options.host})`);
|
|
168
|
+
console.log(`state: ${d.status || 'unknown'} · image: ${d.image || 'unknown'}`);
|
|
169
|
+
const rows = healthRows(d.health);
|
|
170
|
+
console.log(rows ? rows.join('\n') : 'health: no healthcheck configured');
|
|
171
|
+
});
|
|
172
|
+
|
|
109
173
|
docker
|
|
110
174
|
.command('logs')
|
|
111
175
|
.description('Show container logs')
|
|
@@ -143,6 +207,7 @@ export function registerMonCommands(program, ctx) {
|
|
|
143
207
|
auth.requireAuth();
|
|
144
208
|
const ep = resolveEndpoint('mon.docker.stacks.list');
|
|
145
209
|
const data = await client.get(ep.path, { service: ep.service, query: options.host ? { host: options.host } : undefined });
|
|
210
|
+
if (reportDockerReadFailures(data)) return;
|
|
146
211
|
formatOutput({
|
|
147
212
|
columns: ['host_id', 'name', 'status', 'services'],
|
|
148
213
|
headers: ['Host', 'Stack', 'Status', 'Services'],
|
|
@@ -181,7 +246,9 @@ export function registerMonCommands(program, ctx) {
|
|
|
181
246
|
const ep = resolveEndpoint('mon.docker.containers.action', { id, action });
|
|
182
247
|
const data = await client.post(ep.path, { service: ep.service, body: { host: options.host } });
|
|
183
248
|
if (data.success === false) {
|
|
184
|
-
|
|
249
|
+
// quox#1001: surface the real server error and exit non-zero (was exit 0 = success on failure).
|
|
250
|
+
printWarning(`Action failed: ${data.error || data.message || 'unknown error'}`);
|
|
251
|
+
process.exitCode = 1;
|
|
185
252
|
return;
|
|
186
253
|
}
|
|
187
254
|
printSuccess(`Container ${id}: ${action} succeeded.`);
|
|
@@ -35,7 +35,7 @@ export function registerMonitoringCommands(program, ctx) {
|
|
|
35
35
|
// nothing is stored); logged-in now actually sends the token.
|
|
36
36
|
const data = await client.get(resolveEndpoint('v1.health.aggregate.list').path, {
|
|
37
37
|
service: resolveEndpoint('v1.health.aggregate.list').service,
|
|
38
|
-
timeout:
|
|
38
|
+
timeout: 15000, // #930: aggregate fan-out, 5s too tight under load
|
|
39
39
|
});
|
|
40
40
|
formatDetail(data, { format: program.opts().output, title: 'Platform Health' });
|
|
41
41
|
});
|
package/lib/commands/motion.js
CHANGED
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
export function registerMotionCommand(program) {
|
|
6
6
|
program
|
|
7
7
|
.command('motion')
|
|
8
|
-
.description('Play the Quox motion gallery: the Q intro, portal, particle, the Dragon\'s Eye, the status Q, ring and
|
|
9
|
-
.argument('[scenes...]', 'only these: orbit portal particle eye hello perpetual ring fx')
|
|
8
|
+
.description('Play the Quox motion gallery: the Q intro, portal, particle, the Dragon\'s Eye, the status Q, ring, reveals and the living dragon (no server needed)')
|
|
9
|
+
.argument('[scenes...]', 'only these: orbit portal particle eye hello perpetual ring fx dragon dragon-fire dragon-moods')
|
|
10
10
|
.option('--narrow', 'use the 40-column layout (the compact Q)')
|
|
11
11
|
.option('--slow', 'play three times slower')
|
|
12
12
|
.option('--mono', 'no colour (also honoured: NO_COLOR)')
|
package/lib/commands/nucleus.js
CHANGED
|
@@ -23,6 +23,36 @@ import { line as glyphLine, printEvent } from '../core/quoxglyph.js';
|
|
|
23
23
|
import { resolveEndpoint } from '../api/endpoints.js';
|
|
24
24
|
import { resolveOrgId } from './helpers.js';
|
|
25
25
|
|
|
26
|
+
// codebase_query's schema (collector agentTools.js): question (required),
|
|
27
|
+
// target, max_snippets. quox#836: the CLI used to send query/repo/limit,
|
|
28
|
+
// every call failed "Missing required parameter: question", and the failure
|
|
29
|
+
// printed as "0 hits".
|
|
30
|
+
export function buildNucleusInput(question, options = {}) {
|
|
31
|
+
const input = { question };
|
|
32
|
+
if (options.target) input.target = options.target;
|
|
33
|
+
const n = parseInt(options.limit, 10);
|
|
34
|
+
if (n > 0) input.max_snippets = n;
|
|
35
|
+
return input;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// The tool result shape varies by engine version, so normalise rather than
|
|
39
|
+
// assuming: an unexpected shape must still print SOMETHING true. An error
|
|
40
|
+
// body is an error, never an empty result.
|
|
41
|
+
export function readNucleusResult(data) {
|
|
42
|
+
if (data && data.success === false) return { error: data.error || data.message || 'tool call failed' };
|
|
43
|
+
// The collector's codebase_query returns agent-friendly text with
|
|
44
|
+
// file:line citations ({ success, output, resultCount }), not an array.
|
|
45
|
+
if (typeof data?.output === 'string') return { text: data.output, count: data.resultCount ?? null, result: data };
|
|
46
|
+
const result = data && (data.result || data.output || data);
|
|
47
|
+
if (result && result.success === false) return { error: result.error || result.message || 'tool call failed' };
|
|
48
|
+
if (typeof result?.output === 'string') return { text: result.output, count: result.resultCount ?? null, result };
|
|
49
|
+
const results = Array.isArray(result?.results) ? result.results
|
|
50
|
+
: Array.isArray(result?.snippets) ? result.snippets
|
|
51
|
+
: Array.isArray(result) ? result
|
|
52
|
+
: null;
|
|
53
|
+
return { results, result };
|
|
54
|
+
}
|
|
55
|
+
|
|
26
56
|
export function registerNucleusCommands(program, { client, auth }) {
|
|
27
57
|
const nucleus = program
|
|
28
58
|
.command('nucleus')
|
|
@@ -43,33 +73,34 @@ export function registerNucleusCommands(program, { client, auth }) {
|
|
|
43
73
|
const started = Date.now();
|
|
44
74
|
|
|
45
75
|
const ep = resolveEndpoint('v1.tools.execute');
|
|
46
|
-
const body = {
|
|
47
|
-
tool_name: 'codebase_query',
|
|
48
|
-
input: {
|
|
49
|
-
query: question,
|
|
50
|
-
...(options.target ? { repo: options.target } : {}),
|
|
51
|
-
...(options.limit ? { limit: parseInt(options.limit, 10) || 8 } : {}),
|
|
52
|
-
},
|
|
53
|
-
};
|
|
76
|
+
const body = { tool_name: 'codebase_query', input: buildNucleusInput(question, options) };
|
|
54
77
|
const orgId = resolveOrgId(auth, program);
|
|
55
78
|
if (orgId) body.org_id = orgId;
|
|
56
79
|
|
|
57
80
|
const data = await client.post(ep.path, { service: ep.service, body, timeout: 60000 });
|
|
58
81
|
const ms = Date.now() - started;
|
|
59
|
-
|
|
60
|
-
// The tool result shape varies by engine version, so normalise rather
|
|
61
|
-
// than assuming: an unexpected shape must still print SOMETHING true.
|
|
62
|
-
const result = data && (data.result || data.output || data);
|
|
63
|
-
const results = Array.isArray(result?.results) ? result.results
|
|
64
|
-
: Array.isArray(result?.snippets) ? result.snippets
|
|
65
|
-
: Array.isArray(result) ? result
|
|
66
|
-
: null;
|
|
82
|
+
const { error, results, result, text, count } = readNucleusResult(data);
|
|
67
83
|
|
|
68
84
|
if (program.opts().output === 'json') {
|
|
69
85
|
formatDetail(data, { format: 'json' });
|
|
70
86
|
return;
|
|
71
87
|
}
|
|
72
88
|
|
|
89
|
+
if (error) {
|
|
90
|
+
printEvent(glyphLine('repobrain', `query "${question}"`, { result: 'failed', ms }));
|
|
91
|
+
printWarning(`The query failed: ${error}`);
|
|
92
|
+
process.exitCode = 1;
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (typeof text === 'string') {
|
|
97
|
+
const hits = count === null ? 'answered' : `${count} hits`;
|
|
98
|
+
printEvent(glyphLine('repobrain', `query "${question}"`, { result: count === 0 ? '0 hits' : hits, ms }));
|
|
99
|
+
console.log('');
|
|
100
|
+
console.log(text.split('\n').map((l) => ` ${l}`).join('\n'));
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
|
|
73
104
|
if (!results || results.length === 0) {
|
|
74
105
|
printEvent(glyphLine('repobrain', `query "${question}"`, { result: '0 hits', ms }));
|
|
75
106
|
printWarning('Nothing matched. Try naming a concrete symbol, filename or behaviour rather than a general description.');
|
|
@@ -85,7 +116,7 @@ export function registerNucleusCommands(program, { client, auth }) {
|
|
|
85
116
|
const line = r.line || r.start_line || null;
|
|
86
117
|
console.log('');
|
|
87
118
|
console.log(` ${loc}${line ? `:${line}` : ''}`);
|
|
88
|
-
const snippet = r.snippet || r.content || r.text || '';
|
|
119
|
+
const snippet = r.snippet || r.code || r.content || r.text || '';
|
|
89
120
|
for (const l of String(snippet).split('\n').slice(0, 20)) console.log(` ${l}`);
|
|
90
121
|
}
|
|
91
122
|
});
|
|
@@ -209,6 +209,17 @@ export function registerObjectiveCommands(program, ctx) {
|
|
|
209
209
|
printSuccess(`Checkpoint ${checkpointId}: ${data.status}${data.objective_status ? ` (objective ${data.objective_status})` : ''}`);
|
|
210
210
|
});
|
|
211
211
|
|
|
212
|
+
objective
|
|
213
|
+
.command('cancel')
|
|
214
|
+
.description('Cancel an objective (pending ones included, e.g. proof leftovers that never started); its open tasks are cancelled too. For a RUNNING loop prefer `halt`, which also kills the live session.')
|
|
215
|
+
.argument('<objectiveId>', 'objective ID')
|
|
216
|
+
.action(async (objectiveId) => {
|
|
217
|
+
auth.requireAuth();
|
|
218
|
+
const ep = resolveEndpoint('objectives.cancel', { id: objectiveId });
|
|
219
|
+
const data = await client.delete(ep.path, { service: ep.service });
|
|
220
|
+
printSuccess(`Objective ${objectiveId}: ${data?.status || 'cancelled'}`);
|
|
221
|
+
});
|
|
222
|
+
|
|
212
223
|
objective
|
|
213
224
|
.command('halt')
|
|
214
225
|
.description('Halt a running QLoop loop ON DEMAND (unconditional human stop; no pending gate needed). Fires the same signed-kill as a gate deny.')
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `quox offload` : run a heavy command on a pool worker VM and behave like running it locally.
|
|
3
|
+
*
|
|
4
|
+
* quox offload [--dir <path>] [--fresh] [--keep] [--cores N --memory-gb N --disk-gb N] -- <command...>
|
|
5
|
+
* quox offload status
|
|
6
|
+
* quox offload release
|
|
7
|
+
*
|
|
8
|
+
* Engine: lib/core/offload.js. The worker is leased through the human pool REST API
|
|
9
|
+
* (same as `quox vm pool lease`) with a dedicated key at ~/.config/quox/offload_ed25519.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import path from 'node:path';
|
|
13
|
+
import { printInfo } from '../core/output.js';
|
|
14
|
+
import { makePoolRest } from '../core/vm.js';
|
|
15
|
+
import { OffloadError, realDeps, runOffload, releaseWorker, workerStatus, gitToplevel, WORKER_NAME_RE } from '../core/offload.js';
|
|
16
|
+
|
|
17
|
+
export function registerOffloadCommands(program, ctx, { deps: injected } = {}) {
|
|
18
|
+
const { client, auth } = ctx;
|
|
19
|
+
const json = () => program.opts().output === 'json';
|
|
20
|
+
const base = () => injected || realDeps({ rest: makePoolRest({ client }) });
|
|
21
|
+
// --worker <name> / QUOX_OFFLOAD_WORKER: your own worker (state, lease, size), not the box's shared one.
|
|
22
|
+
const mk = (options = {}) => {
|
|
23
|
+
const d = base();
|
|
24
|
+
const name = options.worker ?? d.env?.QUOX_OFFLOAD_WORKER;
|
|
25
|
+
if (name !== undefined && name !== '' && !WORKER_NAME_RE.test(String(name))) {
|
|
26
|
+
throw new OffloadError(`worker name must be lowercase letters, digits and dashes (up to 32), got ${JSON.stringify(name)}`);
|
|
27
|
+
}
|
|
28
|
+
return name ? Object.assign(Object.create(Object.getPrototypeOf(d)), d, { worker: String(name) }) : d;
|
|
29
|
+
};
|
|
30
|
+
const WORKER_HELP = 'use your own named worker (its own lease and size) instead of the shared one; or set QUOX_OFFLOAD_WORKER';
|
|
31
|
+
|
|
32
|
+
const off = program
|
|
33
|
+
.command('offload')
|
|
34
|
+
.description('Run a heavy command on a pool worker VM (synced dir, cached npm ci, live output, same exit code)')
|
|
35
|
+
.argument('[command...]', 'command to run remotely (put it after --)')
|
|
36
|
+
.option('--dir <path>', 'directory to sync (default: git toplevel of the current directory)')
|
|
37
|
+
.option('--fresh', 'release the current worker, lease a new one and reinstall dependencies')
|
|
38
|
+
.option('--keep', 'leave the worker leased for reuse (the default; kept for compatibility)')
|
|
39
|
+
.option('--cores <n>', 'vCPU cores for a newly leased worker')
|
|
40
|
+
.option('--memory-gb <n>', 'memory in GB for a newly leased worker')
|
|
41
|
+
.option('--disk-gb <n>', 'disk in GB for a newly leased worker')
|
|
42
|
+
.option('--worker <name>', WORKER_HELP)
|
|
43
|
+
.option('--docker', 'also give the worker Docker (user hyp in the docker group) and build the quox-sandbox images from the synced repo when they are missing or changed')
|
|
44
|
+
.allowExcessArguments(true)
|
|
45
|
+
.action(async (command, options) => {
|
|
46
|
+
auth.requireAuth();
|
|
47
|
+
const cwd = process.cwd();
|
|
48
|
+
const dir = path.resolve(options.dir || gitToplevel(cwd));
|
|
49
|
+
try {
|
|
50
|
+
const code = await runOffload(mk(options), {
|
|
51
|
+
dir, cwd, command, fresh: !!options.fresh, docker: !!options.docker,
|
|
52
|
+
cores: options.cores, memoryGb: options.memoryGb, diskGb: options.diskGb,
|
|
53
|
+
});
|
|
54
|
+
process.exitCode = code;
|
|
55
|
+
} catch (e) {
|
|
56
|
+
if (!(e instanceof OffloadError)) throw e;
|
|
57
|
+
process.stderr.write(`offload: ${e.message}\n`);
|
|
58
|
+
process.exitCode = 1;
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
off.command('status')
|
|
63
|
+
.description('Show your offload worker: vmid, ip, lease expiry, synced dirs')
|
|
64
|
+
.option('--worker <name>', WORKER_HELP)
|
|
65
|
+
.action(async (options) => {
|
|
66
|
+
auth.requireAuth();
|
|
67
|
+
const s = await workerStatus(mk(options));
|
|
68
|
+
if (json()) { console.log(JSON.stringify(s, null, 2)); return; }
|
|
69
|
+
if (!s.worker) { printInfo('no offload worker leased (it is leased on the next `quox offload -- <command>`)'); return; }
|
|
70
|
+
const w = s.worker;
|
|
71
|
+
console.log(`worker ${w.vmid} ${w.ip || '?'} ${w.state} lessee ${w.lessee} lease expires ${w.lease_expires_at || '?'}`);
|
|
72
|
+
for (const d of s.dirs) console.log(` synced ${d.local} -> ${d.remote} ${d.synced_at || ''}`);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
off.command('release')
|
|
76
|
+
.description('Release (destroy) your offload worker via the pool')
|
|
77
|
+
.option('--worker <name>', WORKER_HELP)
|
|
78
|
+
.action(async (options) => {
|
|
79
|
+
auth.requireAuth();
|
|
80
|
+
try {
|
|
81
|
+
const vmid = await releaseWorker(mk(options));
|
|
82
|
+
if (json()) console.log(JSON.stringify({ released: vmid }));
|
|
83
|
+
else printInfo(vmid ? `released worker ${vmid}` : 'no offload worker to release');
|
|
84
|
+
} catch (e) {
|
|
85
|
+
process.stderr.write(`offload: release failed: ${e.message}\n`);
|
|
86
|
+
process.exitCode = 1;
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
}
|
package/lib/commands/onboard.js
CHANGED
|
@@ -64,7 +64,8 @@ export function inferTargetType(value) {
|
|
|
64
64
|
/** curl install command for a host that is not yet a fleet-enrolled dev server. */
|
|
65
65
|
export function buildOnboardInstallCommand(collectorBase, host) {
|
|
66
66
|
const base = String(collectorBase || '').replace(/\/+$/, '');
|
|
67
|
-
|
|
67
|
+
// REL-423: fail loud, never `curl | sudo bash` (that pipe reports success when the download fails).
|
|
68
|
+
return `( f=$(mktemp) && trap 'rm -f "$f"' EXIT && curl -fsSL ${base}/install -o "$f" && sudo bash "$f" --host-id ${host} )`;
|
|
68
69
|
}
|
|
69
70
|
|
|
70
71
|
// ── the wizard ──────────────────────────────────────────────────────────────
|
package/lib/commands/org.js
CHANGED
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import { QuoxError } from '../core/errors.js';
|
|
6
|
-
import { formatOutput, formatDetail, printSuccess } from '../core/output.js';
|
|
6
|
+
import { formatOutput, formatDetail, printSuccess, printWarning } from '../core/output.js';
|
|
7
|
+
import { otherProfileUsers, listProcs } from '../core/profileUsers.js';
|
|
7
8
|
import { resolveOrgId } from './helpers.js';
|
|
8
9
|
import { resolveEndpoint } from '../api/endpoints.js';
|
|
9
10
|
|
|
@@ -120,6 +121,15 @@ export function registerOrgCommands(program, ctx) {
|
|
|
120
121
|
config.setAuth(authData);
|
|
121
122
|
|
|
122
123
|
printSuccess(`Switched to ${data.org?.name || orgId}`);
|
|
124
|
+
// #1034: this changed the SHARED profile. Name the other live sessions that will follow it.
|
|
125
|
+
try {
|
|
126
|
+
const name = config.getActiveProfileName?.();
|
|
127
|
+
const others = name ? otherProfileUsers({ profile: name, defaultProfile: config._data?.activeProfile, procs: listProcs(), selfPids: [process.pid, process.ppid] }) : [];
|
|
128
|
+
if (others.length) {
|
|
129
|
+
printWarning(`${others.length} other quox session${others.length === 1 ? '' : 's'} use profile "${name}" (pid ${others.map(p => p.pid).join(', ')}). `
|
|
130
|
+
+ 'They follow this org on their next token refresh or restart. To move one window only, use /org switch inside it.');
|
|
131
|
+
}
|
|
132
|
+
} catch { /* best effort: never fail the switch over the warning */ }
|
|
123
133
|
});
|
|
124
134
|
|
|
125
135
|
// Members subcommands
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* quox peek <id>
|
|
3
|
+
*
|
|
4
|
+
* Look up an id without knowing what it is: an AEE envelope, inbox (approval) item, objective, workflow
|
|
5
|
+
* run, WARD receipt or agent. The id's shape picks what to ask; every lookup is an existing read
|
|
6
|
+
* endpoint; a miss names each kind tried and what it answered. The TUI's /peek prints the same card.
|
|
7
|
+
*/
|
|
8
|
+
import { createPeekFetchers, peekId, peekLines } from '../core/peek.js';
|
|
9
|
+
import { resolveOrgId } from './helpers.js';
|
|
10
|
+
|
|
11
|
+
export function registerPeekCommand(program, ctx) {
|
|
12
|
+
const { client, auth } = ctx;
|
|
13
|
+
program
|
|
14
|
+
.command('peek')
|
|
15
|
+
.description('Look up an envelope, inbox item, objective, run, WARD receipt or agent by id (read only)')
|
|
16
|
+
.argument('<id>', 'the id, e.g. a ULID, a WARD entry UUID or an agent name')
|
|
17
|
+
.action(async (id) => {
|
|
18
|
+
auth.requireAuth();
|
|
19
|
+
const orgId = resolveOrgId(auth, program);
|
|
20
|
+
const r = await peekId(id, createPeekFetchers({ client, orgId: () => orgId }));
|
|
21
|
+
if (program.opts().output === 'json') {
|
|
22
|
+
console.log(JSON.stringify({ id: r.id, shape: r.shape, kind: r.hit?.kind || null, data: r.hit?.data || null, also: r.also, tried: r.tried }, null, 2));
|
|
23
|
+
} else {
|
|
24
|
+
for (const l of peekLines(r)) console.log(l.text);
|
|
25
|
+
}
|
|
26
|
+
if (!r.hit) process.exitCode = 5; // not found (the inbox verbs use 5 for not_found too)
|
|
27
|
+
});
|
|
28
|
+
}
|