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/done.js
CHANGED
|
@@ -55,6 +55,7 @@ import path from 'node:path';
|
|
|
55
55
|
import {
|
|
56
56
|
resolveFetchDestination,
|
|
57
57
|
fetchEngine,
|
|
58
|
+
inspectInstall,
|
|
58
59
|
OPT_GAUGE_DIR,
|
|
59
60
|
userLocalGaugeDir,
|
|
60
61
|
legacyGaugeDir,
|
|
@@ -88,19 +89,21 @@ export function resolveEnginePath({ env = process.env, existsSync = fs.existsSyn
|
|
|
88
89
|
|
|
89
90
|
const ENGINE = resolveEnginePath();
|
|
90
91
|
|
|
91
|
-
function
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
92
|
+
function engineMissingMessage(engine) {
|
|
93
|
+
return `DoneEngine not found at ${engine}\n`
|
|
94
|
+
+ 'Self-heal: `quox done fetch` pulls the engine bundle straight from '
|
|
95
|
+
+ 'the collector, no human in the loop. Otherwise `quox dev enable` '
|
|
96
|
+
+ 'delivers it to /opt/quoxgauge as part of full dev-server '
|
|
97
|
+
+ 'provisioning. Or set QUOX_DONEENGINE_PATH to a checkout.\n';
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function runEngine(args, engine = ENGINE) {
|
|
101
|
+
if (!fs.existsSync(engine)) {
|
|
102
|
+
process.stderr.write(engineMissingMessage(engine));
|
|
100
103
|
// 2, not 1: a missing engine is "cannot tell", not "refused".
|
|
101
104
|
return 2;
|
|
102
105
|
}
|
|
103
|
-
const r = spawnSync(process.execPath, [
|
|
106
|
+
const r = spawnSync(process.execPath, [engine, ...args], { stdio: 'inherit' });
|
|
104
107
|
if (r.error) {
|
|
105
108
|
process.stderr.write(`DoneEngine failed to run: ${r.error.message}\n`);
|
|
106
109
|
return 2;
|
|
@@ -108,6 +111,23 @@ function runEngine(args) {
|
|
|
108
111
|
return r.status ?? 2;
|
|
109
112
|
}
|
|
110
113
|
|
|
114
|
+
/**
|
|
115
|
+
* VISIBILITY GATE (#988 follow-on). Public installs do not ship DoneEngine
|
|
116
|
+
* (its bundle is built from the private quox-gauge repo and delivered to dev
|
|
117
|
+
* servers by `quox dev enable`), so listing `quox done` in their help
|
|
118
|
+
* advertises a command that can only fail. It is listed only when an engine
|
|
119
|
+
* is installed locally, decided from the filesystem alone with the same
|
|
120
|
+
* resolution the runner uses: building help never makes a network call.
|
|
121
|
+
* QUOX_HELP_INCLUDE_GATED=1 lists it regardless; the generators that scrape
|
|
122
|
+
* `--help` into the command trees set it so those trees do not depend on
|
|
123
|
+
* which host ran them.
|
|
124
|
+
*/
|
|
125
|
+
export function isDoneEngineInstalled({ env = process.env, home = os.homedir(), fsOps } = {}) {
|
|
126
|
+
const ops = fsOps || { existsSync: fs.existsSync, readdirSync: fs.readdirSync };
|
|
127
|
+
const engine = resolveEnginePath({ env, existsSync: ops.existsSync, home });
|
|
128
|
+
return inspectInstall(path.dirname(engine), ops).healthy;
|
|
129
|
+
}
|
|
130
|
+
|
|
111
131
|
function defaultExecFn(cmd, args) {
|
|
112
132
|
return spawnSync(cmd, args, { encoding: 'utf8' });
|
|
113
133
|
}
|
|
@@ -140,8 +160,56 @@ function defaultFsOps() {
|
|
|
140
160
|
}
|
|
141
161
|
|
|
142
162
|
export function registerDoneCommands(program, ctx = {}) {
|
|
163
|
+
const env = ctx.env || process.env;
|
|
164
|
+
const home = ctx.home || os.homedir();
|
|
165
|
+
// Tests inject fsOps/env/home; the real CLI keeps the import-time ENGINE.
|
|
166
|
+
const injected = !!(ctx.fsOps || ctx.env || ctx.home);
|
|
167
|
+
const currentEngine = () => (injected
|
|
168
|
+
? resolveEnginePath({ env, existsSync: (ctx.fsOps || fs).existsSync, home })
|
|
169
|
+
: ENGINE);
|
|
170
|
+
const visible = env.QUOX_HELP_INCLUDE_GATED === '1'
|
|
171
|
+
|| isDoneEngineInstalled({ env, home, fsOps: ctx.fsOps });
|
|
172
|
+
|
|
173
|
+
const fetchDeps = () => ({
|
|
174
|
+
execFn: ctx.execFn || defaultExecFn,
|
|
175
|
+
isWritable: ctx.isWritable || defaultIsWritable,
|
|
176
|
+
fsOps: ctx.fsOps || defaultFsOps(),
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Resolve a runnable engine for a subcommand. Present: use it, no network.
|
|
181
|
+
* Absent: try the existing fetch path once. A collector 404
|
|
182
|
+
* (DONEENGINE_NOT_SHIPPED) means this is a public install: refuse with that
|
|
183
|
+
* message and exit 2, having changed nothing. A bundle that IS available
|
|
184
|
+
* (a dev collector) installs and the freshly fetched engine runs.
|
|
185
|
+
*/
|
|
186
|
+
async function ensureEngine() {
|
|
187
|
+
const engine = currentEngine();
|
|
188
|
+
const exists = (ctx.fsOps || fs).existsSync(engine);
|
|
189
|
+
if (exists || !ctx.client || env.QUOX_DONEENGINE_PATH) return { engine };
|
|
190
|
+
const { execFn, isWritable, fsOps } = fetchDeps();
|
|
191
|
+
const { dest, needsSudo } = resolveFetchDestination({ env, isWritable, execFn });
|
|
192
|
+
try {
|
|
193
|
+
await fetchEngine({ dest, needsSudo, force: false, client: ctx.client, execFn, fsOps });
|
|
194
|
+
return { engine: path.join(dest, 'gauge.mjs') };
|
|
195
|
+
} catch (err) {
|
|
196
|
+
if (err.code === 'DONEENGINE_NOT_SHIPPED') {
|
|
197
|
+
process.stderr.write(`quox done: ${err.message}\n`);
|
|
198
|
+
return { code: 2 };
|
|
199
|
+
}
|
|
200
|
+
process.stderr.write(`quox done: engine fetch failed: ${err.message}\n`);
|
|
201
|
+
return { engine };
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
async function runGuarded(args) {
|
|
206
|
+
const r = await ensureEngine();
|
|
207
|
+
if (r.code !== undefined) return r.code;
|
|
208
|
+
return runEngine(args, r.engine);
|
|
209
|
+
}
|
|
210
|
+
|
|
143
211
|
const done = program
|
|
144
|
-
.command('done')
|
|
212
|
+
.command('done', { hidden: !visible })
|
|
145
213
|
.description('Quox DoneEngine: is this actually done, and what is the evidence?');
|
|
146
214
|
|
|
147
215
|
done
|
|
@@ -150,7 +218,7 @@ export function registerDoneCommands(program, ctx = {}) {
|
|
|
150
218
|
.option('--pack <id>', 'rule pack to judge against', 'launch-spine')
|
|
151
219
|
.option('--json', 'structured verdict for agents')
|
|
152
220
|
.option('--root <path>', 'judge <subject> in this repo, reading its own STATUS map with the selected pack (e.g. a plugin with --pack plugin)')
|
|
153
|
-
.action((subject, options) => {
|
|
221
|
+
.action(async (subject, options) => {
|
|
154
222
|
const args = ['--can-ship', subject || 'all', '--pack', options.pack];
|
|
155
223
|
if (options.root) {
|
|
156
224
|
if (!subject) {
|
|
@@ -161,17 +229,17 @@ export function registerDoneCommands(program, ctx = {}) {
|
|
|
161
229
|
args.push('--subject', subject, '--root', path.resolve(options.root));
|
|
162
230
|
}
|
|
163
231
|
if (options.json) args.push('--json');
|
|
164
|
-
process.exitCode =
|
|
232
|
+
process.exitCode = await runGuarded(args);
|
|
165
233
|
});
|
|
166
234
|
|
|
167
235
|
done
|
|
168
236
|
.command('checklist [pack]')
|
|
169
237
|
.description('The builder checklist GENERATED from a pack (every gate the engine judges); --check verifies a doc that embeds it')
|
|
170
238
|
.option('--check <file>', 'exit 1 if the checklist block in this file has drifted from the pack')
|
|
171
|
-
.action((pack, options) => {
|
|
239
|
+
.action(async (pack, options) => {
|
|
172
240
|
const args = ['--checklist', pack || 'plugin'];
|
|
173
241
|
if (options.check) args.push('--check', path.resolve(options.check));
|
|
174
|
-
process.exitCode =
|
|
242
|
+
process.exitCode = await runGuarded(args);
|
|
175
243
|
});
|
|
176
244
|
|
|
177
245
|
done
|
|
@@ -180,12 +248,12 @@ export function registerDoneCommands(program, ctx = {}) {
|
|
|
180
248
|
.option('--json', 'machine-readable, includes raw deploy state')
|
|
181
249
|
.option('--dod', 'per-surface gates, split by evidence class')
|
|
182
250
|
.option('--no-deploy', 'skip docker/git; Deployed Ready becomes unknown, which is not a pass')
|
|
183
|
-
.action((options) => {
|
|
251
|
+
.action(async (options) => {
|
|
184
252
|
const args = [];
|
|
185
253
|
if (options.json) args.push('--json');
|
|
186
254
|
if (options.dod) args.push('--dod');
|
|
187
255
|
if (options.deploy === false) args.push('--no-deploy');
|
|
188
|
-
process.exitCode =
|
|
256
|
+
process.exitCode = await runGuarded(args);
|
|
189
257
|
});
|
|
190
258
|
|
|
191
259
|
done
|
|
@@ -193,25 +261,27 @@ export function registerDoneCommands(program, ctx = {}) {
|
|
|
193
261
|
.description('One rule pack against its subjects: BOTH finish lines, build and deployed')
|
|
194
262
|
.option('--snapshot', 'append this reading to the snapshot history')
|
|
195
263
|
.option('--org <id>', 'measure FOR this org: plan evidence reads as it, the snapshot is stamped with it (default: the pack\'s own org field)')
|
|
196
|
-
.action((pack, options) => {
|
|
264
|
+
.action(async (pack, options) => {
|
|
197
265
|
const args = pack ? ['--pack', pack] : ['--launch'];
|
|
198
266
|
if (options.snapshot) args.push('--snapshot');
|
|
199
267
|
if (options.org) args.push('--org', options.org);
|
|
200
|
-
process.exitCode =
|
|
268
|
+
process.exitCode = await runGuarded(args);
|
|
201
269
|
});
|
|
202
270
|
|
|
203
271
|
done
|
|
204
272
|
.command('history [pack]')
|
|
205
273
|
.description('Movement between snapshots, regressions listed first')
|
|
206
|
-
.action((pack) => {
|
|
207
|
-
process.exitCode =
|
|
274
|
+
.action(async (pack) => {
|
|
275
|
+
process.exitCode = await runGuarded(pack ? ['--history', '--pack', pack] : ['--history']);
|
|
208
276
|
});
|
|
209
277
|
|
|
210
278
|
done
|
|
211
279
|
.command('packs')
|
|
212
280
|
.description('List available rule packs')
|
|
213
|
-
.action(() => {
|
|
214
|
-
const
|
|
281
|
+
.action(async () => {
|
|
282
|
+
const r = await ensureEngine();
|
|
283
|
+
if (r.code !== undefined) { process.exitCode = r.code; return; }
|
|
284
|
+
const dir = path.join(path.dirname(r.engine), 'packs');
|
|
215
285
|
if (!fs.existsSync(dir)) {
|
|
216
286
|
process.stderr.write(
|
|
217
287
|
`No packs directory at ${dir}\n` +
|
|
@@ -246,13 +316,11 @@ export function registerDoneCommands(program, ctx = {}) {
|
|
|
246
316
|
process.exitCode = 2;
|
|
247
317
|
return;
|
|
248
318
|
}
|
|
249
|
-
const execFn
|
|
250
|
-
const isWritable = ctx.isWritable || defaultIsWritable;
|
|
251
|
-
const fsOps = ctx.fsOps || defaultFsOps();
|
|
319
|
+
const { execFn, isWritable, fsOps } = fetchDeps();
|
|
252
320
|
|
|
253
321
|
const { dest, reason, needsSudo } = resolveFetchDestination({
|
|
254
322
|
destOption: options.dest,
|
|
255
|
-
env
|
|
323
|
+
env,
|
|
256
324
|
isWritable,
|
|
257
325
|
execFn,
|
|
258
326
|
});
|
|
@@ -267,7 +335,9 @@ export function registerDoneCommands(program, ctx = {}) {
|
|
|
267
335
|
}
|
|
268
336
|
} catch (err) {
|
|
269
337
|
process.stderr.write(`quox done fetch failed: ${err.message}\n`);
|
|
270
|
-
|
|
338
|
+
// A public install's 404 is "this install has no DoneEngine", not a
|
|
339
|
+
// transient failure: exit 2 like every other "cannot tell" here.
|
|
340
|
+
process.exitCode = err.code === 'DONEENGINE_NOT_SHIPPED' ? 2 : 1;
|
|
271
341
|
}
|
|
272
342
|
});
|
|
273
343
|
|
|
@@ -121,10 +121,13 @@ export function registerEngagementCommands(program, ctx) {
|
|
|
121
121
|
const data = await client.post(ep.path, { service: ep.service, body });
|
|
122
122
|
printSuccess(`Target "${value}" added to engagement ${id}.`);
|
|
123
123
|
if (data.target.verification_token) {
|
|
124
|
+
// #1007: a url target is challenged on its hostname (dns_txt) or at its own origin (well_known_file)
|
|
125
|
+
const stored = data.target.value || value;
|
|
126
|
+
const url = data.target.type === 'url' ? (() => { try { return new URL(stored); } catch { return null; } })() : null;
|
|
124
127
|
if (data.target.verification_method === 'dns_txt') {
|
|
125
|
-
console.log(` Add a DNS TXT record on ${
|
|
128
|
+
console.log(` Add a DNS TXT record on ${url ? url.hostname : stored}: quox-verify=${data.target.verification_token}`);
|
|
126
129
|
} else if (data.target.verification_method === 'well_known_file') {
|
|
127
|
-
console.log(` Serve this content at https://${
|
|
130
|
+
console.log(` Serve this content at ${url ? url.origin : `https://${stored}`}/.well-known/quox-verification: ${data.target.verification_token}`);
|
|
128
131
|
}
|
|
129
132
|
console.log(` Then run: quox engagement verify-target ${id} ${data.target.id}`);
|
|
130
133
|
}
|
|
@@ -172,6 +175,7 @@ export function registerEngagementCommands(program, ctx) {
|
|
|
172
175
|
console.log(JSON.stringify(data.output, null, 2));
|
|
173
176
|
} else {
|
|
174
177
|
console.log(` Failed (allowed: ${data.allowed}): ${data.error}`);
|
|
178
|
+
process.exitCode = 1; // #1007: a refusal is not success, for scripts as much as people
|
|
175
179
|
}
|
|
176
180
|
});
|
|
177
181
|
|
|
@@ -185,6 +189,7 @@ export function registerEngagementCommands(program, ctx) {
|
|
|
185
189
|
const data = await client.post(ep.path, { service: ep.service, body: { target: hostTarget, add: options.add === true } });
|
|
186
190
|
if (!data.success) {
|
|
187
191
|
console.log(` Failed (allowed: ${data.allowed}): ${data.error}`);
|
|
192
|
+
process.exitCode = 1; // #1007: a refusal is not success, for scripts as much as people
|
|
188
193
|
return;
|
|
189
194
|
}
|
|
190
195
|
const discovered = data.discovered || [];
|
|
@@ -234,6 +239,7 @@ export function registerEngagementCommands(program, ctx) {
|
|
|
234
239
|
console.log(` Poll: quox engagement job ${data.job_id}`);
|
|
235
240
|
} else {
|
|
236
241
|
console.log(` Failed (allowed: ${data.allowed}): ${data.error}`);
|
|
242
|
+
process.exitCode = 1; // #1007: a refusal is not success, for scripts as much as people
|
|
237
243
|
}
|
|
238
244
|
});
|
|
239
245
|
|
|
@@ -372,11 +378,16 @@ export function registerEngagementCommands(program, ctx) {
|
|
|
372
378
|
}
|
|
373
379
|
const buf = Buffer.from(await res.arrayBuffer());
|
|
374
380
|
const rootOutput = program.opts().output;
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
381
|
+
// quox#919: the global --json reaches options.output as 'json' via
|
|
382
|
+
// mergeShadowedGlobals; an output-format name is never a file path.
|
|
383
|
+
const asPath = (v) => (v && !KNOWN_OUTPUT_FORMATS.has(v) ? v : null);
|
|
384
|
+
const outFile = asPath(options.output) || asPath(rootOutput) || `quox-security-${id}.pdf`;
|
|
378
385
|
writeFileSync(outFile, buf);
|
|
379
|
-
|
|
386
|
+
if (rootOutput === 'json') {
|
|
387
|
+
console.log(JSON.stringify({ success: true, file: outFile, bytes: buf.length }));
|
|
388
|
+
} else {
|
|
389
|
+
printSuccess(`Report saved to ${outFile} (${buf.length} bytes).`);
|
|
390
|
+
}
|
|
380
391
|
});
|
|
381
392
|
|
|
382
393
|
async function raiseIssue(findingId) {
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `quox fetch allow` — the URL prefixes this org's agents may GET through the
|
|
3
|
+
* governed web_fetch_allowlisted tool (quox#1171). Default-deny: nothing is
|
|
4
|
+
* fetchable until an org admin adds a prefix. Every call goes through the
|
|
5
|
+
* collector; adding and removing need a human org admin (the server refuses
|
|
6
|
+
* agent tokens).
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { resolveEndpoint } from '../api/endpoints.js';
|
|
10
|
+
|
|
11
|
+
async function call(client, id, params = {}, body) {
|
|
12
|
+
const ep = resolveEndpoint(id, params);
|
|
13
|
+
const opts = { service: ep.service };
|
|
14
|
+
if (body) opts.body = body;
|
|
15
|
+
switch (ep.method) {
|
|
16
|
+
case 'POST': return client.post(ep.path, opts);
|
|
17
|
+
case 'DELETE': return client.delete(ep.path, opts);
|
|
18
|
+
default: return client.get(ep.path, opts);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function registerFetchAllowlistCommands(program, ctx) {
|
|
23
|
+
const { client } = ctx;
|
|
24
|
+
|
|
25
|
+
const f = program
|
|
26
|
+
.command('fetch')
|
|
27
|
+
.description('Governed agent fetch: which URLs your agents may GET');
|
|
28
|
+
|
|
29
|
+
const al = f.command('allow')
|
|
30
|
+
.description('The URL prefixes this org allows agents to fetch (default-deny)');
|
|
31
|
+
|
|
32
|
+
al.command('list', { isDefault: true })
|
|
33
|
+
.description('Show the allowed URL prefixes for this org')
|
|
34
|
+
.action(async () => {
|
|
35
|
+
const d = await call(client, 'fetchAllowlist.list');
|
|
36
|
+
if (d.prefixes?.length) {
|
|
37
|
+
process.stdout.write('allowed prefixes:\n');
|
|
38
|
+
for (const e of d.prefixes) process.stdout.write(` ${e.url_prefix}${e.note ? ` (${e.note})` : ''}\n`);
|
|
39
|
+
} else {
|
|
40
|
+
process.stdout.write('none allowed yet: agents cannot fetch anything. Add one with `quox fetch allow add <url-prefix>`\n');
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
al.command('add <url-prefix>')
|
|
45
|
+
.description('Allow agents to GET URLs under a prefix, e.g. https://status.example.com/api/')
|
|
46
|
+
.option('--note <text>', 'why it is allowed, shown alongside it later')
|
|
47
|
+
.action(async (urlPrefix, opts) => {
|
|
48
|
+
try {
|
|
49
|
+
const d = await call(client, 'fetchAllowlist.add', {}, { url_prefix: urlPrefix, note: opts.note });
|
|
50
|
+
process.stdout.write(`allowed ${d.entry.url_prefix}\n`);
|
|
51
|
+
process.stdout.write(` ${d.prefixes.length} prefix(es) now allowed for this org\n`);
|
|
52
|
+
} catch (err) {
|
|
53
|
+
if (err?.status === 400 && err?.message) {
|
|
54
|
+
process.stderr.write(`Not added: ${err.message}\n`);
|
|
55
|
+
process.exitCode = 1;
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
throw err;
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
al.command('remove <url-prefix>')
|
|
63
|
+
.description('Stop allowing a URL prefix')
|
|
64
|
+
.action(async (urlPrefix) => {
|
|
65
|
+
const d = await call(client, 'fetchAllowlist.remove', { prefix: urlPrefix });
|
|
66
|
+
if (d.removed) process.stdout.write(`removed ${urlPrefix}\n`);
|
|
67
|
+
else process.stdout.write(`nothing to remove: ${urlPrefix} was not allowed for this org\n`);
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export default registerFetchAllowlistCommands;
|
package/lib/commands/file.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* File commands: upload, download, list, delete
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import { createWriteStream } from 'node:fs';
|
|
5
|
+
import { createWriteStream, readFileSync } from 'node:fs';
|
|
6
6
|
import { basename } from 'node:path';
|
|
7
7
|
import { formatOutput, formatDetail, printSuccess } from '../core/output.js';
|
|
8
8
|
import { resolveEndpoint } from '../api/endpoints.js';
|
|
@@ -125,4 +125,41 @@ export function registerFileCommands(program, ctx) {
|
|
|
125
125
|
const data = await client.get(resolveEndpoint('files.stats').path, { service: resolveEndpoint('files.stats').service });
|
|
126
126
|
formatDetail(data.stats || data, { format: program.opts().output, title: 'File Storage Stats' });
|
|
127
127
|
});
|
|
128
|
+
|
|
129
|
+
// F01 A': legacy files with no proven owner are hidden from every org until a human instance admin assigns them.
|
|
130
|
+
file
|
|
131
|
+
.command('quarantine')
|
|
132
|
+
.description('List files with no proven owner org (instance admin only)')
|
|
133
|
+
.action(async () => {
|
|
134
|
+
auth.requireAuth();
|
|
135
|
+
const ep = resolveEndpoint('files.quarantine.list');
|
|
136
|
+
const data = await client.get(ep.path, { service: ep.service });
|
|
137
|
+
formatOutput({ columns: ['id', 'name', 'size'], headers: ['ID', 'Name', 'Size'], rows: data.files || [] }, { format: program.opts().output });
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
file
|
|
141
|
+
.command('assign')
|
|
142
|
+
.description('Assign a quarantined file to an org (instance admin only; witnessed): <fileId> --org <orgId>, or --from-report <dry-run.json>')
|
|
143
|
+
.argument('[fileId]', 'quarantined file id')
|
|
144
|
+
.option('--org <orgId>', 'org that owns the file')
|
|
145
|
+
.option('--from-report <path>', 'an owner-approved backfill dry-run report: assigns only rows with a derived owner')
|
|
146
|
+
.action(async (fileId, options) => {
|
|
147
|
+
auth.requireAuth();
|
|
148
|
+
let assignments;
|
|
149
|
+
if (options.fromReport) {
|
|
150
|
+
const report = JSON.parse(readFileSync(options.fromReport, 'utf8'));
|
|
151
|
+
assignments = (report.rows || []).filter((r) => r && r.id && r.orgId).map((r) => ({ file_id: r.id, org_id: r.orgId }));
|
|
152
|
+
if (!assignments.length) throw new Error('The report has no rows with a derived owner; nothing to assign.');
|
|
153
|
+
} else if (fileId) {
|
|
154
|
+
if (!options.org) throw new Error('Give the owning org: quox file assign <fileId> --org <orgId>');
|
|
155
|
+
assignments = [{ file_id: fileId, org_id: options.org }];
|
|
156
|
+
} else {
|
|
157
|
+
throw new Error('Give <fileId> --org <orgId>, or --from-report <dry-run.json>');
|
|
158
|
+
}
|
|
159
|
+
const ep = resolveEndpoint('files.quarantine.assign');
|
|
160
|
+
const data = await client.post(ep.path, { service: ep.service, body: { assignments } });
|
|
161
|
+
formatOutput({ columns: ['file_id', 'org_id', 'ok', 'error'], headers: ['File', 'Org', 'Assigned', 'Error'], rows: data.results || [] }, { format: program.opts().output });
|
|
162
|
+
const failed = (data.results || []).filter((r) => !r.ok).length;
|
|
163
|
+
if (failed) { process.exitCode = 1; } else { printSuccess(`Assigned ${assignments.length} file(s).`); }
|
|
164
|
+
});
|
|
128
165
|
}
|