quoxcli 0.0.0-stage → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +35 -0
- package/README.md +142 -2
- package/bin/quox.js +292 -0
- package/bin/quoxon.js +8 -0
- package/lib/api/endpoints/admin.js +125 -0
- package/lib/api/endpoints/aee.js +38 -0
- package/lib/api/endpoints/agent-attach.js +126 -0
- package/lib/api/endpoints/agents.js +56 -0
- package/lib/api/endpoints/assistants.js +50 -0
- package/lib/api/endpoints/audit.js +14 -0
- package/lib/api/endpoints/auth.js +50 -0
- package/lib/api/endpoints/awareness.js +34 -0
- package/lib/api/endpoints/backup-schedules.js +20 -0
- package/lib/api/endpoints/backups.js +86 -0
- package/lib/api/endpoints/brain.js +140 -0
- package/lib/api/endpoints/budgets.js +62 -0
- package/lib/api/endpoints/calendarq.js +71 -0
- package/lib/api/endpoints/catalog.js +14 -0
- package/lib/api/endpoints/chat.js +54 -0
- package/lib/api/endpoints/consent.js +26 -0
- package/lib/api/endpoints/contactsq.js +66 -0
- package/lib/api/endpoints/conversations.js +32 -0
- package/lib/api/endpoints/custom-roles.js +92 -0
- package/lib/api/endpoints/divisions.js +56 -0
- package/lib/api/endpoints/domains.js +38 -0
- package/lib/api/endpoints/engagement.js +231 -0
- package/lib/api/endpoints/engine.js +11 -0
- package/lib/api/endpoints/entity.js +50 -0
- package/lib/api/endpoints/executions.js +32 -0
- package/lib/api/endpoints/file.js +20 -0
- package/lib/api/endpoints/files.js +26 -0
- package/lib/api/endpoints/github.js +21 -0
- package/lib/api/endpoints/governance.js +43 -0
- package/lib/api/endpoints/hat.js +36 -0
- package/lib/api/endpoints/inbox.js +32 -0
- package/lib/api/endpoints/inboxq.js +120 -0
- package/lib/api/endpoints/index.js +228 -0
- package/lib/api/endpoints/invites.js +19 -0
- package/lib/api/endpoints/llm-policy.js +43 -0
- package/lib/api/endpoints/lucid.js +89 -0
- package/lib/api/endpoints/members.js +32 -0
- package/lib/api/endpoints/memory.js +75 -0
- package/lib/api/endpoints/mfa.js +32 -0
- package/lib/api/endpoints/mon.js +178 -0
- package/lib/api/endpoints/notesq.js +43 -0
- package/lib/api/endpoints/objectives.js +62 -0
- package/lib/api/endpoints/orgs.js +38 -0
- package/lib/api/endpoints/plugins.js +134 -0
- package/lib/api/endpoints/powers.js +75 -0
- package/lib/api/endpoints/prompts.js +69 -0
- package/lib/api/endpoints/qlarity.js +122 -0
- package/lib/api/endpoints/quoxbox.js +55 -0
- package/lib/api/endpoints/quoxchat.js +54 -0
- package/lib/api/endpoints/quoxflow.js +32 -0
- package/lib/api/endpoints/quoxplan.js +114 -0
- package/lib/api/endpoints/quoxseo.js +108 -0
- package/lib/api/endpoints/reach.js +47 -0
- package/lib/api/endpoints/runs.js +38 -0
- package/lib/api/endpoints/safety.js +46 -0
- package/lib/api/endpoints/scim.js +46 -0
- package/lib/api/endpoints/scopes.js +14 -0
- package/lib/api/endpoints/security.js +69 -0
- package/lib/api/endpoints/setup.js +90 -0
- package/lib/api/endpoints/skills.js +180 -0
- package/lib/api/endpoints/sso.js +67 -0
- package/lib/api/endpoints/tags.js +32 -0
- package/lib/api/endpoints/teams.js +56 -0
- package/lib/api/endpoints/telephony.js +46 -0
- package/lib/api/endpoints/tg.js +56 -0
- package/lib/api/endpoints/tools.js +50 -0
- package/lib/api/endpoints/traces.js +157 -0
- package/lib/api/endpoints/triggers.js +70 -0
- package/lib/api/endpoints/user-library.js +41 -0
- package/lib/api/endpoints/users.js +20 -0
- package/lib/api/endpoints/v1.js +499 -0
- package/lib/api/endpoints/vault.js +192 -0
- package/lib/api/endpoints/voice.js +22 -0
- package/lib/api/endpoints/volt.js +84 -0
- package/lib/api/endpoints/ward.js +40 -0
- package/lib/api/endpoints/webhooks.js +46 -0
- package/lib/api/endpoints/workflows.js +162 -0
- package/lib/api/endpoints/x.js +24 -0
- package/lib/api/endpoints.js +15 -0
- package/lib/commands/admin.js +359 -0
- package/lib/commands/aee.js +99 -0
- package/lib/commands/agent.js +201 -0
- package/lib/commands/agentAttach.js +1440 -0
- package/lib/commands/approvals.js +47 -0
- package/lib/commands/assistant.js +114 -0
- package/lib/commands/auth.js +458 -0
- package/lib/commands/awareness.js +88 -0
- package/lib/commands/backup.js +373 -0
- package/lib/commands/brain.js +994 -0
- package/lib/commands/budget.js +241 -0
- package/lib/commands/build.js +156 -0
- package/lib/commands/calendar.js +216 -0
- package/lib/commands/capabilities.js +34 -0
- package/lib/commands/chat.js +240 -0
- package/lib/commands/claude.js +354 -0
- package/lib/commands/config-cmd.js +106 -0
- package/lib/commands/consent.js +84 -0
- package/lib/commands/contacts.js +967 -0
- package/lib/commands/contactsSelftest.js +132 -0
- package/lib/commands/conversation.js +74 -0
- package/lib/commands/department.js +75 -0
- package/lib/commands/dev.js +620 -0
- package/lib/commands/devResident.js +314 -0
- package/lib/commands/devSupervisor.js +1050 -0
- package/lib/commands/discord.js +96 -0
- package/lib/commands/doctor.js +181 -0
- package/lib/commands/domain.js +114 -0
- package/lib/commands/done.js +277 -0
- package/lib/commands/engagement.js +586 -0
- package/lib/commands/entity.js +119 -0
- package/lib/commands/facebook.js +212 -0
- package/lib/commands/file.js +128 -0
- package/lib/commands/fleet.js +882 -0
- package/lib/commands/governance.js +638 -0
- package/lib/commands/gripe.js +324 -0
- package/lib/commands/hat.js +205 -0
- package/lib/commands/helpers.js +79 -0
- package/lib/commands/inbox.js +122 -0
- package/lib/commands/instagram.js +264 -0
- package/lib/commands/integration.js +100 -0
- package/lib/commands/inventory.js +115 -0
- package/lib/commands/invites.js +45 -0
- package/lib/commands/licence.js +280 -0
- package/lib/commands/linkedin.js +178 -0
- package/lib/commands/llm-policy.js +117 -0
- package/lib/commands/llm.js +45 -0
- package/lib/commands/lucid.js +591 -0
- package/lib/commands/mail.js +531 -0
- package/lib/commands/memory.js +284 -0
- package/lib/commands/mon.js +514 -0
- package/lib/commands/monitoring.js +86 -0
- package/lib/commands/motion.js +22 -0
- package/lib/commands/notes.js +1246 -0
- package/lib/commands/notesBatch.js +160 -0
- package/lib/commands/notesBridges.js +56 -0
- package/lib/commands/notesLinks.js +60 -0
- package/lib/commands/notification.js +57 -0
- package/lib/commands/nucleus.js +92 -0
- package/lib/commands/objective.js +229 -0
- package/lib/commands/onboard.js +328 -0
- package/lib/commands/open.js +56 -0
- package/lib/commands/org.js +266 -0
- package/lib/commands/plan.js +906 -0
- package/lib/commands/plugin-doctor.js +1091 -0
- package/lib/commands/plugin.js +539 -0
- package/lib/commands/policy.js +78 -0
- package/lib/commands/powers.js +296 -0
- package/lib/commands/prompt.js +350 -0
- package/lib/commands/qlarity.js +690 -0
- package/lib/commands/quoxbox.js +270 -0
- package/lib/commands/quoxchat.js +266 -0
- package/lib/commands/quoxseo.js +340 -0
- package/lib/commands/reach.js +158 -0
- package/lib/commands/role.js +286 -0
- package/lib/commands/run.js +132 -0
- package/lib/commands/safety.js +107 -0
- package/lib/commands/scim.js +88 -0
- package/lib/commands/scope.js +58 -0
- package/lib/commands/security.js +157 -0
- package/lib/commands/service.js +89 -0
- package/lib/commands/settings.js +99 -0
- package/lib/commands/setup.js +543 -0
- package/lib/commands/skillManifest.js +113 -0
- package/lib/commands/skillRightsFormat.js +38 -0
- package/lib/commands/skills.js +644 -0
- package/lib/commands/slot.js +1198 -0
- package/lib/commands/sso.js +229 -0
- package/lib/commands/status.js +137 -0
- package/lib/commands/tag.js +91 -0
- package/lib/commands/team.js +140 -0
- package/lib/commands/tg.js +240 -0
- package/lib/commands/tiktok.js +218 -0
- package/lib/commands/tool.js +216 -0
- package/lib/commands/trace.js +525 -0
- package/lib/commands/trigger.js +128 -0
- package/lib/commands/vault.js +1424 -0
- package/lib/commands/voice.js +182 -0
- package/lib/commands/volt.js +528 -0
- package/lib/commands/watch.js +115 -0
- package/lib/commands/webhook.js +137 -0
- package/lib/commands/workflow.js +503 -0
- package/lib/commands/x.js +439 -0
- package/lib/core/actingAs.js +136 -0
- package/lib/core/aee.js +53 -0
- package/lib/core/agentAlias.js +163 -0
- package/lib/core/approvalContext.js +61 -0
- package/lib/core/auth.js +350 -0
- package/lib/core/banner.js +143 -0
- package/lib/core/brainPrime.js +90 -0
- package/lib/core/callerSource.js +129 -0
- package/lib/core/claudeLs.js +94 -0
- package/lib/core/client.js +584 -0
- package/lib/core/completions.js +163 -0
- package/lib/core/config.js +551 -0
- package/lib/core/confirm.js +37 -0
- package/lib/core/deeplinks.js +76 -0
- package/lib/core/doneEngineFetch.js +219 -0
- package/lib/core/errors.js +132 -0
- package/lib/core/global-flags.js +90 -0
- package/lib/core/governedSession.js +402 -0
- package/lib/core/lazyCommands.js +119 -0
- package/lib/core/liveSessionsReport.js +112 -0
- package/lib/core/org-override.js +52 -0
- package/lib/core/output.js +239 -0
- package/lib/core/prodEnv.js +7 -0
- package/lib/core/quoxglyph.js +158 -0
- package/lib/core/residentAlarm.js +282 -0
- package/lib/core/residentAuth.js +243 -0
- package/lib/core/residentPump.js +229 -0
- package/lib/core/residentSeed.js +104 -0
- package/lib/core/sessionHeartbeat.js +192 -0
- package/lib/core/sessionsOverview.js +160 -0
- package/lib/core/slotKeeper.js +282 -0
- package/lib/core/supervisorEngine.js +864 -0
- package/lib/core/systemdInstaller.js +256 -0
- package/lib/core/version.js +87 -0
- package/lib/core/websiteSession.js +148 -0
- package/lib/lucid/LucidIntro.jsx +105 -0
- package/lib/lucid/blind.js +71 -0
- package/lib/lucid/deck.js +32 -0
- package/lib/lucid/fmt.js +139 -0
- package/lib/lucid/sitrep.js +173 -0
- package/lib/plugins/loader.js +93 -0
- package/lib/quoxon/activity.js +104 -0
- package/lib/quoxon/api.js +473 -0
- package/lib/quoxon/app.js +1262 -0
- package/lib/quoxon/capabilities.js +75 -0
- package/lib/quoxon/commands.js +154 -0
- package/lib/quoxon/connect.js +467 -0
- package/lib/quoxon/connectCard.js +28 -0
- package/lib/quoxon/context.js +68 -0
- package/lib/quoxon/copy.js +33 -0
- package/lib/quoxon/demo.fixtures.js +24 -0
- package/lib/quoxon/demo.js +35 -0
- package/lib/quoxon/egg.js +36 -0
- package/lib/quoxon/events.js +458 -0
- package/lib/quoxon/gallery.js +223 -0
- package/lib/quoxon/indicator.js +193 -0
- package/lib/quoxon/intent.js +41 -0
- package/lib/quoxon/keys.js +121 -0
- package/lib/quoxon/main.js +244 -0
- package/lib/quoxon/markdown.js +317 -0
- package/lib/quoxon/model.js +205 -0
- package/lib/quoxon/motion/clock.js +69 -0
- package/lib/quoxon/motion/color.js +35 -0
- package/lib/quoxon/motion/frame.js +14 -0
- package/lib/quoxon/motion/reveal.js +54 -0
- package/lib/quoxon/motion/ring.js +79 -0
- package/lib/quoxon/objects/agent.js +61 -0
- package/lib/quoxon/objects/crystal.js +100 -0
- package/lib/quoxon/objects/fetch.js +170 -0
- package/lib/quoxon/objects/index.js +85 -0
- package/lib/quoxon/objects/ladder.js +71 -0
- package/lib/quoxon/objects/memory.js +44 -0
- package/lib/quoxon/objects/model.js +118 -0
- package/lib/quoxon/objects/proof.js +110 -0
- package/lib/quoxon/objects/skill.js +105 -0
- package/lib/quoxon/objects/view.js +76 -0
- package/lib/quoxon/objects/workflow.js +119 -0
- package/lib/quoxon/observers.js +42 -0
- package/lib/quoxon/onboarding/copy.js +235 -0
- package/lib/quoxon/onboarding/credential.js +95 -0
- package/lib/quoxon/onboarding/detect.js +22 -0
- package/lib/quoxon/onboarding/finish.js +119 -0
- package/lib/quoxon/onboarding/index.js +67 -0
- package/lib/quoxon/onboarding/installRunner.js +225 -0
- package/lib/quoxon/onboarding/intent.js +53 -0
- package/lib/quoxon/onboarding/journey.js +73 -0
- package/lib/quoxon/onboarding/preflight.js +142 -0
- package/lib/quoxon/onboarding/runner.js +58 -0
- package/lib/quoxon/onboarding/view.js +625 -0
- package/lib/quoxon/print.js +94 -0
- package/lib/quoxon/qart.js +379 -0
- package/lib/quoxon/qglyph.js +143 -0
- package/lib/quoxon/qhello.js +58 -0
- package/lib/quoxon/qmotion.js +111 -0
- package/lib/quoxon/render.js +648 -0
- package/lib/quoxon/roster.js +221 -0
- package/lib/quoxon/route.js +38 -0
- package/lib/quoxon/rules.js +130 -0
- package/lib/quoxon/session.js +68 -0
- package/lib/quoxon/skills.js +30 -0
- package/lib/quoxon/slash/compact.js +90 -0
- package/lib/quoxon/slash/ledger.js +83 -0
- package/lib/quoxon/slash/listView.js +28 -0
- package/lib/quoxon/slash/palette.js +104 -0
- package/lib/quoxon/slash/paletteController.js +59 -0
- package/lib/quoxon/slash/panels.js +101 -0
- package/lib/quoxon/slash/picker.js +38 -0
- package/lib/quoxon/slash/restart.js +144 -0
- package/lib/quoxon/slash/resume.js +59 -0
- package/lib/quoxon/slash/settings.js +50 -0
- package/lib/quoxon/sound/index.js +186 -0
- package/lib/quoxon/sound/mapping.js +119 -0
- package/lib/quoxon/sound/output.js +52 -0
- package/lib/quoxon/sound/synth.js +122 -0
- package/lib/quoxon/term.js +106 -0
- package/lib/quoxon/textBuffer.js +242 -0
- package/lib/quoxon/theme.js +253 -0
- package/lib/quoxon/transcript.js +150 -0
- package/lib/quoxon/web/qface.js +81 -0
- package/lib/quoxon/welcome.js +52 -0
- package/lib/tui/app.jsx +289 -0
- package/lib/tui/components/Breadcrumb.jsx +42 -0
- package/lib/tui/components/ChatInput.jsx +68 -0
- package/lib/tui/components/Confirm.jsx +32 -0
- package/lib/tui/components/DataTable.jsx +270 -0
- package/lib/tui/components/DetailPanel.jsx +241 -0
- package/lib/tui/components/HelpOverlay.jsx +67 -0
- package/lib/tui/components/Layout.jsx +129 -0
- package/lib/tui/components/Palette.jsx +141 -0
- package/lib/tui/components/SearchInput.jsx +125 -0
- package/lib/tui/components/Sparkline.jsx +55 -0
- package/lib/tui/components/StatusBar.jsx +133 -0
- package/lib/tui/components/Toast.jsx +52 -0
- package/lib/tui/hooks/useApi.js +44 -0
- package/lib/tui/hooks/useHealth.js +90 -0
- package/lib/tui/hooks/usePolledApi.js +86 -0
- package/lib/tui/hooks/useTerminalSize.js +46 -0
- package/lib/tui/index.js +56 -0
- package/lib/tui/intro/Intro.jsx +332 -0
- package/lib/tui/jsx-loader.js +32 -0
- package/lib/tui/keys.js +87 -0
- package/lib/tui/registry.js +49 -0
- package/lib/tui/theme.js +103 -0
- package/lib/tui/views/AdminView.jsx +398 -0
- package/lib/tui/views/AgentView.jsx +204 -0
- package/lib/tui/views/BackupView.jsx +225 -0
- package/lib/tui/views/BrainView.jsx +223 -0
- package/lib/tui/views/ChatView.jsx +468 -0
- package/lib/tui/views/FilesView.jsx +218 -0
- package/lib/tui/views/FleetView.jsx +234 -0
- package/lib/tui/views/GovernanceView.jsx +453 -0
- package/lib/tui/views/HealthView.jsx +216 -0
- package/lib/tui/views/InboxView.jsx +228 -0
- package/lib/tui/views/LucidDeck.jsx +727 -0
- package/lib/tui/views/MainMenu.jsx +123 -0
- package/lib/tui/views/MemoryView.jsx +377 -0
- package/lib/tui/views/ObjectivesView.jsx +230 -0
- package/lib/tui/views/OrgView.jsx +229 -0
- package/lib/tui/views/ToolView.jsx +221 -0
- package/lib/tui/views/VaultView.jsx +422 -0
- package/lib/tui/views/WorkflowView.jsx +329 -0
- package/package.json +72 -4
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Admin commands: config, keys, services, logs, stats, retention
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { formatOutput, formatDetail, printSuccess } from '../core/output.js';
|
|
6
|
+
import { resolveEndpoint } from '../api/endpoints.js';
|
|
7
|
+
|
|
8
|
+
export function registerAdminCommands(program, ctx) {
|
|
9
|
+
const { client, auth } = ctx;
|
|
10
|
+
|
|
11
|
+
const admin = program
|
|
12
|
+
.command('admin')
|
|
13
|
+
.description('Platform administration');
|
|
14
|
+
|
|
15
|
+
// Config
|
|
16
|
+
const cfg = admin
|
|
17
|
+
.command('config')
|
|
18
|
+
.description('Manage platform configuration');
|
|
19
|
+
|
|
20
|
+
cfg
|
|
21
|
+
.command('get')
|
|
22
|
+
.description('Get platform config (all keys, or one key)')
|
|
23
|
+
.argument('[key]', 'show only this key')
|
|
24
|
+
.action(async (key) => {
|
|
25
|
+
auth.requireAuth();
|
|
26
|
+
const data = await client.get(resolveEndpoint('admin.config.list').path, { service: resolveEndpoint('admin.config.list').service });
|
|
27
|
+
const config = data.config || data;
|
|
28
|
+
if (key) {
|
|
29
|
+
// quox#533: `get <key>` used to be rejected outright.
|
|
30
|
+
if (!config || typeof config !== 'object' || !(key in config)) {
|
|
31
|
+
throw new Error(`config key "${key}" not found. Run \`quox admin config get\` to list all keys.`);
|
|
32
|
+
}
|
|
33
|
+
formatDetail({ [key]: config[key] }, { format: program.opts().output, title: 'Platform Config' });
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
formatDetail(config, { format: program.opts().output, title: 'Platform Config' });
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
cfg
|
|
40
|
+
.command('set')
|
|
41
|
+
.description('Set a config value')
|
|
42
|
+
.argument('<key>', 'config key')
|
|
43
|
+
.argument('<value>', 'config value')
|
|
44
|
+
.action(async (key, value) => {
|
|
45
|
+
auth.requireAuth();
|
|
46
|
+
await client.patch(resolveEndpoint('admin.config.update', { key }).path, { service: resolveEndpoint('admin.config.update', { key }).service, body: { value } });
|
|
47
|
+
printSuccess(`Config ${key} updated.`);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
// LICENSE-LAUNCH LL-6b: enforcement posture + break-glass. Parity with the
|
|
51
|
+
// dashboard's Settings → Security → Enforcement panel. In production a
|
|
52
|
+
// security gate switched off in the environment is enforced anyway unless an
|
|
53
|
+
// unexpired break-glass record exists, so an operator needs to see and set
|
|
54
|
+
// those records from the CLI too, not only from the browser.
|
|
55
|
+
const enforcement = admin
|
|
56
|
+
.command('enforcement')
|
|
57
|
+
.description('Security enforcement posture and break-glass windows');
|
|
58
|
+
|
|
59
|
+
enforcement
|
|
60
|
+
.command('list')
|
|
61
|
+
.description('Show every enforcement flag: what the environment asks for and what is actually enforced')
|
|
62
|
+
.action(async () => {
|
|
63
|
+
auth.requireAuth();
|
|
64
|
+
const ep = resolveEndpoint('admin.enforcement.list');
|
|
65
|
+
const data = await client.get(ep.path, { service: ep.service });
|
|
66
|
+
const rows = (data.flags || []).map((f) => ({
|
|
67
|
+
key: f.key,
|
|
68
|
+
env: f.env_enforced ? 'enforce' : 'off',
|
|
69
|
+
effective: f.forced_on ? 'ENFORCED (forced)' : (f.enforced ? 'enforced' : 'off'),
|
|
70
|
+
break_glass: f.break_glass ? `until ${f.break_glass.expires_at}` : '-',
|
|
71
|
+
reason: f.break_glass ? f.break_glass.reason : '',
|
|
72
|
+
}));
|
|
73
|
+
formatOutput({
|
|
74
|
+
columns: ['key', 'env', 'effective', 'break_glass', 'reason'],
|
|
75
|
+
headers: ['Flag', 'Env', 'Effective', 'Break-glass', 'Reason'],
|
|
76
|
+
rows,
|
|
77
|
+
}, { format: program.opts().output });
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
enforcement
|
|
81
|
+
.command('break-glass')
|
|
82
|
+
.description('Open a bounded break-glass window so a gate may stay off in production')
|
|
83
|
+
.argument('<flag>', 'enforcement flag, e.g. COLLECTOR_AUTH_ENFORCE')
|
|
84
|
+
.requiredOption('--reason <reason>', 'why this gate has to be off (recorded against your account)')
|
|
85
|
+
.option('--hours <hours>', 'window length in hours, 72 maximum', '4')
|
|
86
|
+
.action(async (flag, opts) => {
|
|
87
|
+
auth.requireAuth();
|
|
88
|
+
const hours = Number(opts.hours);
|
|
89
|
+
if (!Number.isFinite(hours) || hours <= 0) {
|
|
90
|
+
throw new Error('--hours must be a positive number of hours (72 maximum)');
|
|
91
|
+
}
|
|
92
|
+
const ep = resolveEndpoint('admin.enforcement.break_glass.open');
|
|
93
|
+
const data = await client.post(ep.path, {
|
|
94
|
+
service: ep.service,
|
|
95
|
+
body: {
|
|
96
|
+
flag,
|
|
97
|
+
reason: opts.reason,
|
|
98
|
+
expires_at: new Date(Date.now() + hours * 3600_000).toISOString(),
|
|
99
|
+
},
|
|
100
|
+
});
|
|
101
|
+
printSuccess(`Break-glass open for ${flag} until ${data.break_glass.expires_at}.`);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
enforcement
|
|
105
|
+
.command('clear')
|
|
106
|
+
.description('Close a break-glass window early')
|
|
107
|
+
.argument('<flag>', 'enforcement flag, e.g. COLLECTOR_AUTH_ENFORCE')
|
|
108
|
+
.action(async (flag) => {
|
|
109
|
+
auth.requireAuth();
|
|
110
|
+
const ep = resolveEndpoint('admin.enforcement.break_glass.clear', { flag });
|
|
111
|
+
const data = await client.delete(ep.path, { service: ep.service });
|
|
112
|
+
printSuccess(data.cleared
|
|
113
|
+
? `Break-glass closed for ${flag}.`
|
|
114
|
+
: `No break-glass window was open for ${flag}.`);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
// QUOXPERTICUS P2: governed agent-identity mint (instance-admin only).
|
|
118
|
+
const agentIdentity = admin
|
|
119
|
+
.command('agent-identity')
|
|
120
|
+
.description('Manage governed agent identities (agent-typed user + bound mcp token)');
|
|
121
|
+
|
|
122
|
+
agentIdentity
|
|
123
|
+
.command('mint')
|
|
124
|
+
.description('Mint (or rotate) an agent identity; prints the raw token ONCE')
|
|
125
|
+
.argument('<agentId>', 'agent id, e.g. quoxperticus')
|
|
126
|
+
// NOT requiredOption: --org shadows the root program's global --org
|
|
127
|
+
// <slug> (bin/quox.js). Commander's mandatory-option check runs BEFORE
|
|
128
|
+
// the mergeShadowedGlobals preAction hook, so a requiredOption here
|
|
129
|
+
// always fired "required option '--org' not specified" even when the
|
|
130
|
+
// user supplied it (quox#457 sweep). Validated explicitly below instead.
|
|
131
|
+
.option('--org <orgId>', 'org ULID the agent operates under')
|
|
132
|
+
.option('--rotate', 'revoke the live token and issue a new one')
|
|
133
|
+
.action(async (agentId, opts) => {
|
|
134
|
+
auth.requireAuth();
|
|
135
|
+
if (!opts.org || !String(opts.org).trim()) {
|
|
136
|
+
throw new Error(
|
|
137
|
+
'Missing --org <orgId>. Usage: quox admin agent-identity mint <agentId> --org <orgId>'
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
const ep = resolveEndpoint('admin.agent_identity.mint');
|
|
141
|
+
const data = await client.post(ep.path, {
|
|
142
|
+
service: ep.service,
|
|
143
|
+
body: { agent_id: agentId, org_id: opts.org, rotate: opts.rotate === true },
|
|
144
|
+
});
|
|
145
|
+
printSuccess(`Agent identity ${data.agent_id} ${data.rotated ? 'rotated' : 'minted'} (user ${data.user_id}).`);
|
|
146
|
+
console.log('Raw token (shown ONCE, place it in the supervisor root-only env):');
|
|
147
|
+
console.log(data.token);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
// Email / SMTP (QCPRO-SMTP): instance email onboarding parity
|
|
151
|
+
const email = admin
|
|
152
|
+
.command('email')
|
|
153
|
+
.description('Instance email delivery (SMTP)');
|
|
154
|
+
|
|
155
|
+
email
|
|
156
|
+
.command('status')
|
|
157
|
+
.description('Show email delivery status (configured, from, last test)')
|
|
158
|
+
.action(async () => {
|
|
159
|
+
auth.requireAuth();
|
|
160
|
+
const ep = resolveEndpoint('admin.email.status');
|
|
161
|
+
const data = await client.get(ep.path, { service: ep.service });
|
|
162
|
+
formatDetail(data, { format: program.opts().output, title: 'Email Delivery' });
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
email
|
|
166
|
+
.command('setup')
|
|
167
|
+
.description('Configure instance SMTP (password encrypted at rest; omit --pass to keep the stored one)')
|
|
168
|
+
.requiredOption('--host <host>', 'SMTP host')
|
|
169
|
+
.option('--port <port>', 'SMTP port', '587')
|
|
170
|
+
.option('--user <user>', 'SMTP username')
|
|
171
|
+
.option('--pass <pass>', 'SMTP password')
|
|
172
|
+
.requiredOption('--from <from>', 'From address, e.g. "QuoxCORE <noreply@acme.com>"')
|
|
173
|
+
.option('--secure', 'Use TLS (direct TLS for 465, STARTTLS otherwise)', true)
|
|
174
|
+
.option('--no-secure', 'Disable TLS')
|
|
175
|
+
.action(async (opts) => {
|
|
176
|
+
auth.requireAuth();
|
|
177
|
+
const ep = resolveEndpoint('admin.email.smtp');
|
|
178
|
+
await client.put(ep.path, {
|
|
179
|
+
service: ep.service,
|
|
180
|
+
body: {
|
|
181
|
+
host: opts.host,
|
|
182
|
+
port: Number(opts.port),
|
|
183
|
+
user: opts.user || '',
|
|
184
|
+
pass: opts.pass || '',
|
|
185
|
+
from: opts.from,
|
|
186
|
+
secure: !!opts.secure,
|
|
187
|
+
},
|
|
188
|
+
});
|
|
189
|
+
printSuccess('SMTP configured. Run "quox admin email test" to verify delivery.');
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
email
|
|
193
|
+
.command('test')
|
|
194
|
+
.description('Send a test email and report the honest outcome')
|
|
195
|
+
.option('--to <address>', 'Recipient (defaults to your account email)')
|
|
196
|
+
.action(async (opts) => {
|
|
197
|
+
auth.requireAuth();
|
|
198
|
+
const ep = resolveEndpoint('admin.email.test');
|
|
199
|
+
const result = await client.post(ep.path, { service: ep.service, body: opts.to ? { to: opts.to } : {} });
|
|
200
|
+
if (result.delivered) {
|
|
201
|
+
printSuccess(`Delivered (message id ${result.message_id || 'unknown'}).`);
|
|
202
|
+
} else {
|
|
203
|
+
console.error(`Not delivered: ${result.reason || 'unknown error'}`);
|
|
204
|
+
process.exitCode = 1;
|
|
205
|
+
}
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
// API Keys
|
|
209
|
+
const keys = admin
|
|
210
|
+
.command('key')
|
|
211
|
+
.description('Manage API keys');
|
|
212
|
+
|
|
213
|
+
keys
|
|
214
|
+
.command('list')
|
|
215
|
+
.description('List API keys')
|
|
216
|
+
.action(async () => {
|
|
217
|
+
auth.requireAuth();
|
|
218
|
+
const data = await client.get(resolveEndpoint('admin.keys.list').path, { service: resolveEndpoint('admin.keys.list').service });
|
|
219
|
+
formatOutput({
|
|
220
|
+
// fleet #392: api_keys has key_preview/last_used_at; prefix, role
|
|
221
|
+
// and expires_at don't exist on this row at all.
|
|
222
|
+
columns: ['id', 'name', 'key_preview', 'last_used_at', 'created_at'],
|
|
223
|
+
headers: ['ID', 'Name', 'Preview', 'Last Used', 'Created'],
|
|
224
|
+
rows: data.keys || data,
|
|
225
|
+
}, { format: program.opts().output });
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
keys
|
|
229
|
+
.command('create')
|
|
230
|
+
.description('Create an API key')
|
|
231
|
+
.requiredOption('--name <name>', 'key name')
|
|
232
|
+
.option('--role <role>', 'key role', 'viewer')
|
|
233
|
+
.option('--expires <date>', 'expiry date (ISO)')
|
|
234
|
+
.action(async (options) => {
|
|
235
|
+
auth.requireAuth();
|
|
236
|
+
const body = { name: options.name, role: options.role };
|
|
237
|
+
if (options.expires) body.expires_at = options.expires;
|
|
238
|
+
const data = await client.post(resolveEndpoint('admin.keys.create').path, { service: resolveEndpoint('admin.keys.create').service, body });
|
|
239
|
+
if (data.key || data.api_key) {
|
|
240
|
+
const key = data.key || data.api_key;
|
|
241
|
+
if (program.opts().output === 'json') {
|
|
242
|
+
console.log(JSON.stringify({ key, id: data.id, name: data.name }));
|
|
243
|
+
} else if (program.opts().output === 'quiet') {
|
|
244
|
+
console.log(key);
|
|
245
|
+
} else {
|
|
246
|
+
console.log(`API Key: ${key}`);
|
|
247
|
+
console.log('(Save this key now — it will not be shown again)');
|
|
248
|
+
}
|
|
249
|
+
} else {
|
|
250
|
+
printSuccess('API key created.');
|
|
251
|
+
}
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
keys
|
|
255
|
+
.command('delete')
|
|
256
|
+
.description('Delete an API key')
|
|
257
|
+
.argument('<keyId>', 'key ID')
|
|
258
|
+
.action(async (keyId) => {
|
|
259
|
+
auth.requireAuth();
|
|
260
|
+
await client.delete(resolveEndpoint('admin.keys.delete', { keyId }).path, { service: resolveEndpoint('admin.keys.delete', { keyId }).service });
|
|
261
|
+
printSuccess('API key deleted.');
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
keys
|
|
265
|
+
.command('test')
|
|
266
|
+
.description('Test an API key')
|
|
267
|
+
.argument('<keyId>', 'key ID')
|
|
268
|
+
.action(async (keyId) => {
|
|
269
|
+
auth.requireAuth();
|
|
270
|
+
const data = await client.post(resolveEndpoint('admin.keys.test', { keyId }).path, { service: resolveEndpoint('admin.keys.test', { keyId }).service });
|
|
271
|
+
printSuccess(data.valid ? 'API key is valid.' : 'API key test failed.');
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
// Services
|
|
275
|
+
const services = admin
|
|
276
|
+
.command('service')
|
|
277
|
+
.description('Manage platform services');
|
|
278
|
+
|
|
279
|
+
services
|
|
280
|
+
.command('list')
|
|
281
|
+
.description('List registered services')
|
|
282
|
+
.action(async () => {
|
|
283
|
+
auth.requireAuth();
|
|
284
|
+
const data = await client.get(resolveEndpoint('admin.services.list').path, { service: resolveEndpoint('admin.services.list').service });
|
|
285
|
+
formatOutput({
|
|
286
|
+
// fleet #392: services has health_endpoint/last_check_at, not
|
|
287
|
+
// url/last_check.
|
|
288
|
+
columns: ['id', 'name', 'status', 'health_endpoint', 'last_check_at'],
|
|
289
|
+
headers: ['ID', 'Name', 'Status', 'Health URL', 'Last Check'],
|
|
290
|
+
rows: data.services || data,
|
|
291
|
+
}, { format: program.opts().output });
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
// Logs
|
|
295
|
+
admin
|
|
296
|
+
.command('logs')
|
|
297
|
+
.description('View admin audit logs')
|
|
298
|
+
.option('--action <action>', 'filter by action')
|
|
299
|
+
.option('--limit <n>', 'max results', '50')
|
|
300
|
+
.action(async (options) => {
|
|
301
|
+
auth.requireAuth();
|
|
302
|
+
const query = { limit: options.limit };
|
|
303
|
+
if (options.action) query.action = options.action;
|
|
304
|
+
const data = await client.get(resolveEndpoint('admin.logs.list').path, { service: resolveEndpoint('admin.logs.list').service, query });
|
|
305
|
+
formatOutput({
|
|
306
|
+
// fleet #392: system_logs has { created_at, service, level, message,
|
|
307
|
+
// user_id } — none of timestamp/user_email/action/detail exist, so
|
|
308
|
+
// all four columns were dashes.
|
|
309
|
+
columns: ['created_at', 'service', 'level', 'message', 'user_id'],
|
|
310
|
+
headers: ['Time', 'Service', 'Level', 'Message', 'User'],
|
|
311
|
+
rows: data.logs || data.entries || data,
|
|
312
|
+
}, { format: program.opts().output });
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
// Stats
|
|
316
|
+
admin
|
|
317
|
+
.command('stats')
|
|
318
|
+
.description('Show platform statistics')
|
|
319
|
+
.action(async () => {
|
|
320
|
+
auth.requireAuth();
|
|
321
|
+
const data = await client.get(resolveEndpoint('admin.stats.list').path, { service: resolveEndpoint('admin.stats.list').service });
|
|
322
|
+
formatDetail(data.stats || data, { format: program.opts().output, title: 'Platform Stats' });
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
// Retention
|
|
326
|
+
const retention = admin
|
|
327
|
+
.command('retention')
|
|
328
|
+
.description('Manage data retention');
|
|
329
|
+
|
|
330
|
+
retention
|
|
331
|
+
.command('config')
|
|
332
|
+
.description('View retention config')
|
|
333
|
+
.action(async () => {
|
|
334
|
+
auth.requireAuth();
|
|
335
|
+
const data = await client.get(resolveEndpoint('admin.retention.config.list').path, { service: resolveEndpoint('admin.retention.config.list').service });
|
|
336
|
+
formatDetail(data.config || data, { format: program.opts().output, title: 'Retention Config' });
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
retention
|
|
340
|
+
.command('cleanup')
|
|
341
|
+
.description('Run retention cleanup')
|
|
342
|
+
.option('--dry-run', 'preview without deleting')
|
|
343
|
+
.action(async (options) => {
|
|
344
|
+
auth.requireAuth();
|
|
345
|
+
const body = {};
|
|
346
|
+
if (options.dryRun) body.dry_run = true;
|
|
347
|
+
const data = await client.post(resolveEndpoint('admin.retention.cleanup').path, { service: resolveEndpoint('admin.retention.cleanup').service, body });
|
|
348
|
+
formatDetail(data, { format: program.opts().output, title: 'Cleanup Result' });
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
retention
|
|
352
|
+
.command('stats')
|
|
353
|
+
.description('View retention statistics')
|
|
354
|
+
.action(async () => {
|
|
355
|
+
auth.requireAuth();
|
|
356
|
+
const data = await client.get(resolveEndpoint('admin.retention.stats.list').path, { service: resolveEndpoint('admin.retention.stats.list').service });
|
|
357
|
+
formatDetail(data.stats || data, { format: program.opts().output, title: 'Retention Stats' });
|
|
358
|
+
});
|
|
359
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AEE (Agent Envelope Exchange) commands
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { formatOutput, formatDetail, printSuccess } from '../core/output.js';
|
|
6
|
+
import { parseJSON } from './helpers.js';
|
|
7
|
+
import { resolveEndpoint } from '../api/endpoints.js';
|
|
8
|
+
|
|
9
|
+
export function registerAeeCommands(program, ctx) {
|
|
10
|
+
const { client, auth } = ctx;
|
|
11
|
+
|
|
12
|
+
const aee = program
|
|
13
|
+
.command('aee')
|
|
14
|
+
.description('Agent Envelope Exchange protocol');
|
|
15
|
+
|
|
16
|
+
aee
|
|
17
|
+
.command('store')
|
|
18
|
+
.description('Store an AEE envelope')
|
|
19
|
+
.requiredOption('--envelope <json>', 'envelope data as JSON')
|
|
20
|
+
.action(async (options) => {
|
|
21
|
+
auth.requireAuth();
|
|
22
|
+
const envelope = parseJSON(options.envelope, '--envelope');
|
|
23
|
+
await client.post(resolveEndpoint('aee.store').path, { service: resolveEndpoint('aee.store').service, body: envelope });
|
|
24
|
+
printSuccess('Envelope stored.');
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
aee
|
|
28
|
+
.command('get')
|
|
29
|
+
.description('Get an envelope by ID')
|
|
30
|
+
.argument('<id>', 'envelope ID')
|
|
31
|
+
.action(async (id) => {
|
|
32
|
+
auth.requireAuth();
|
|
33
|
+
const data = await client.get(resolveEndpoint('aee.get', { id }).path, { service: resolveEndpoint('aee.get', { id }).service });
|
|
34
|
+
formatDetail(data.envelope || data, { format: program.opts().output });
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
aee
|
|
38
|
+
.command('query')
|
|
39
|
+
.description('Query envelopes')
|
|
40
|
+
// fleet #386 + adjacent fix: --from/--to were described as dates, but the
|
|
41
|
+
// route matches them against the envelope's "from"/"to" columns — the AEE
|
|
42
|
+
// sender and recipient (from: 'human.adam', to: 'agent.cipher'). Passing a
|
|
43
|
+
// date matched a sender literally named "2026-01-01", i.e. nothing. The
|
|
44
|
+
// route's real date filters are after/before, which the CLI never sent, so
|
|
45
|
+
// date filtering was unreachable. Descriptions now match the protocol, and
|
|
46
|
+
// --after/--before/--intent expose filters the route already supported.
|
|
47
|
+
.option('--from <sender>', 'envelope sender (e.g. human.adam, agent.cipher)')
|
|
48
|
+
.option('--to <recipient>', 'envelope recipient')
|
|
49
|
+
.option('--after <date>', 'only envelopes at/after this ISO timestamp')
|
|
50
|
+
.option('--before <date>', 'only envelopes before this ISO timestamp')
|
|
51
|
+
.option('--intent <intent>', 'intent substring match (e.g. quox.chat)')
|
|
52
|
+
.option('--corr <id>', 'correlation ID — the envelopes of one conversation (see `aee conversations`)')
|
|
53
|
+
.option('--type <type>', 'envelope type')
|
|
54
|
+
.option('--limit <n>', 'max results', '50')
|
|
55
|
+
.action(async (options) => {
|
|
56
|
+
auth.requireAuth();
|
|
57
|
+
const query = { limit: options.limit };
|
|
58
|
+
if (options.from) query.from = options.from;
|
|
59
|
+
if (options.to) query.to = options.to;
|
|
60
|
+
if (options.after) query.after = options.after;
|
|
61
|
+
if (options.before) query.before = options.before;
|
|
62
|
+
if (options.intent) query.intent = options.intent;
|
|
63
|
+
if (options.corr) query.corr = options.corr;
|
|
64
|
+
if (options.type) query.type = options.type;
|
|
65
|
+
const data = await client.get(resolveEndpoint('aee.query.list').path, { service: resolveEndpoint('aee.query.list').service, query });
|
|
66
|
+
formatOutput({
|
|
67
|
+
columns: ['id', 'type', 'from', 'to', 'intent', 'ts'],
|
|
68
|
+
headers: ['ID', 'Type', 'From', 'To', 'Intent', 'Time'],
|
|
69
|
+
rows: data.envelopes || data.results || data,
|
|
70
|
+
}, { format: program.opts().output });
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
aee
|
|
74
|
+
.command('conversations')
|
|
75
|
+
.description('List AEE conversations')
|
|
76
|
+
.option('--limit <n>', 'max results', '20')
|
|
77
|
+
.action(async (options) => {
|
|
78
|
+
auth.requireAuth();
|
|
79
|
+
const data = await client.get(resolveEndpoint('aee.conversations.list').path, { service: resolveEndpoint('aee.conversations.list').service, query: { limit: options.limit } });
|
|
80
|
+
formatOutput({
|
|
81
|
+
// fleet #385: the endpoint returns { corr, first_ts, last_ts, count }
|
|
82
|
+
// (see the GROUP BY corr query in collector/server.js). This declared
|
|
83
|
+
// corr_id/envelope_count, which don't exist in the payload, so two of
|
|
84
|
+
// three columns rendered as dashes with no error.
|
|
85
|
+
columns: ['corr', 'count', 'last_ts'],
|
|
86
|
+
headers: ['Conversation', 'Envelopes', 'Last Activity'],
|
|
87
|
+
rows: data.conversations || data,
|
|
88
|
+
}, { format: program.opts().output });
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
aee
|
|
92
|
+
.command('stats')
|
|
93
|
+
.description('AEE statistics')
|
|
94
|
+
.action(async () => {
|
|
95
|
+
auth.requireAuth();
|
|
96
|
+
const data = await client.get(resolveEndpoint('aee.stats.list').path, { service: resolveEndpoint('aee.stats.list').service });
|
|
97
|
+
formatDetail(data.stats || data, { format: program.opts().output, title: 'AEE Stats' });
|
|
98
|
+
});
|
|
99
|
+
}
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent commands: CRUD, activate/deactivate, tool assignments
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { formatOutput, formatDetail, printSuccess } from '../core/output.js';
|
|
6
|
+
import { resolveOrgId } from './helpers.js';
|
|
7
|
+
import { resolveEndpoint } from '../api/endpoints.js';
|
|
8
|
+
import { registerAgentAttachCommands } from './agentAttach.js';
|
|
9
|
+
|
|
10
|
+
export function registerAgentCommands(program, ctx) {
|
|
11
|
+
const { client, auth } = ctx;
|
|
12
|
+
|
|
13
|
+
const agent = program
|
|
14
|
+
.command('agent')
|
|
15
|
+
.description('Manage agents');
|
|
16
|
+
|
|
17
|
+
agent
|
|
18
|
+
.command('list')
|
|
19
|
+
.description('List agents')
|
|
20
|
+
.option('--status <status>', 'filter by status')
|
|
21
|
+
.option('--level <level>', 'filter by level (0-4)')
|
|
22
|
+
.action(async (options) => {
|
|
23
|
+
auth.requireAuth();
|
|
24
|
+
const orgId = resolveOrgId(auth, program);
|
|
25
|
+
const query = {};
|
|
26
|
+
if (options.status) query.status = options.status;
|
|
27
|
+
if (options.level) query.level = options.level;
|
|
28
|
+
// fleet #392: tool_count is only computed when includeTools=true, which
|
|
29
|
+
// was never sent; and rows carry a `domains` ARRAY, not a `domain`.
|
|
30
|
+
query.includeTools = 'true';
|
|
31
|
+
const data = await client.get(resolveEndpoint('agents.list', { orgId }).path, { service: resolveEndpoint('agents.list', { orgId }).service, query });
|
|
32
|
+
formatOutput({
|
|
33
|
+
columns: ['id', 'agent_id', 'name', 'level', 'status', 'domains', 'tool_count'],
|
|
34
|
+
headers: ['ID', 'Agent ID', 'Name', 'Level', 'Status', 'Domains', 'Tools'],
|
|
35
|
+
rows: (data.agents || data || []).map(a => ({
|
|
36
|
+
...a,
|
|
37
|
+
domains: Array.isArray(a.domains) ? a.domains.join(', ') : (a.domains ?? '-'),
|
|
38
|
+
})),
|
|
39
|
+
}, { format: program.opts().output });
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
agent
|
|
43
|
+
.command('get')
|
|
44
|
+
.description('Get agent details')
|
|
45
|
+
.argument('<agentId>', 'agent ID')
|
|
46
|
+
.action(async (agentId) => {
|
|
47
|
+
auth.requireAuth();
|
|
48
|
+
const orgId = resolveOrgId(auth, program);
|
|
49
|
+
const data = await client.get(resolveEndpoint('agents.get', { orgId, agentId }).path, { service: resolveEndpoint('agents.get', { orgId, agentId }).service });
|
|
50
|
+
formatDetail(data.agent || data, {
|
|
51
|
+
format: program.opts().output,
|
|
52
|
+
title: data.agent?.name || data.name,
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
agent
|
|
57
|
+
.command('create')
|
|
58
|
+
.description('Create a new agent')
|
|
59
|
+
.requiredOption('--name <name>', 'agent display name')
|
|
60
|
+
.option('--agent-id <id>', 'machine identifier')
|
|
61
|
+
.option('--level <level>', 'agent level (0-4)', '2')
|
|
62
|
+
.option('--domain <domain>', 'agent domain')
|
|
63
|
+
.option('--description <desc>', 'description')
|
|
64
|
+
.option('--parent <parentId>', 'parent agent ID')
|
|
65
|
+
.option('--manifest-id <id>', 'manifest ID')
|
|
66
|
+
.action(async (options) => {
|
|
67
|
+
auth.requireAuth();
|
|
68
|
+
const orgId = resolveOrgId(auth, program);
|
|
69
|
+
const body = {
|
|
70
|
+
name: options.name,
|
|
71
|
+
level: parseInt(options.level, 10),
|
|
72
|
+
};
|
|
73
|
+
if (options.agentId) body.agent_id = options.agentId;
|
|
74
|
+
if (options.domain) body.domain = options.domain;
|
|
75
|
+
if (options.description) body.description = options.description;
|
|
76
|
+
if (options.parent) body.parent_id = options.parent;
|
|
77
|
+
if (options.manifestId) body.manifest_id = options.manifestId;
|
|
78
|
+
await client.post(resolveEndpoint('agents.create', { orgId }).path, { service: resolveEndpoint('agents.create', { orgId }).service, body });
|
|
79
|
+
printSuccess(`Agent "${options.name}" created.`);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
agent
|
|
83
|
+
.command('update')
|
|
84
|
+
.description('Update an agent')
|
|
85
|
+
.argument('<agentId>', 'agent ID')
|
|
86
|
+
.option('--name <name>', 'new name')
|
|
87
|
+
.option('--description <desc>', 'new description')
|
|
88
|
+
.option('--level <level>', 'new level')
|
|
89
|
+
.option('--status <status>', 'new status')
|
|
90
|
+
.action(async (agentId, options) => {
|
|
91
|
+
auth.requireAuth();
|
|
92
|
+
const orgId = resolveOrgId(auth, program);
|
|
93
|
+
const body = {};
|
|
94
|
+
if (options.name) body.name = options.name;
|
|
95
|
+
if (options.description) body.description = options.description;
|
|
96
|
+
if (options.level) body.level = parseInt(options.level, 10);
|
|
97
|
+
if (options.status) body.status = options.status;
|
|
98
|
+
await client.patch(resolveEndpoint('agents.update', { orgId, agentId }).path, { service: resolveEndpoint('agents.update', { orgId, agentId }).service, body });
|
|
99
|
+
printSuccess('Agent updated.');
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
agent
|
|
103
|
+
.command('delete')
|
|
104
|
+
.description('Delete an agent')
|
|
105
|
+
.argument('<agentId>', 'agent ID')
|
|
106
|
+
.action(async (agentId) => {
|
|
107
|
+
auth.requireAuth();
|
|
108
|
+
const orgId = resolveOrgId(auth, program);
|
|
109
|
+
await client.delete(resolveEndpoint('agents.delete', { orgId, agentId }).path, { service: resolveEndpoint('agents.delete', { orgId, agentId }).service });
|
|
110
|
+
printSuccess('Agent deleted.');
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
agent
|
|
114
|
+
.command('activate')
|
|
115
|
+
.description('Activate an agent')
|
|
116
|
+
.argument('<agentId>', 'agent ID')
|
|
117
|
+
.action(async (agentId) => {
|
|
118
|
+
auth.requireAuth();
|
|
119
|
+
const orgId = resolveOrgId(auth, program);
|
|
120
|
+
await client.patch(resolveEndpoint('agents.update', { orgId, agentId }).path, {
|
|
121
|
+
service: resolveEndpoint('agents.update', { orgId, agentId }).service,
|
|
122
|
+
body: { status: 'active' },
|
|
123
|
+
});
|
|
124
|
+
printSuccess('Agent activated.');
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
agent
|
|
128
|
+
.command('deactivate')
|
|
129
|
+
.description('Deactivate an agent')
|
|
130
|
+
.argument('<agentId>', 'agent ID')
|
|
131
|
+
.action(async (agentId) => {
|
|
132
|
+
auth.requireAuth();
|
|
133
|
+
const orgId = resolveOrgId(auth, program);
|
|
134
|
+
await client.patch(resolveEndpoint('agents.update', { orgId, agentId }).path, {
|
|
135
|
+
service: resolveEndpoint('agents.update', { orgId, agentId }).service,
|
|
136
|
+
body: { status: 'inactive' },
|
|
137
|
+
});
|
|
138
|
+
printSuccess('Agent deactivated.');
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
// Agent tool management
|
|
142
|
+
const agentTool = agent
|
|
143
|
+
.command('tool')
|
|
144
|
+
.description('Manage agent tool assignments');
|
|
145
|
+
|
|
146
|
+
agentTool
|
|
147
|
+
.command('list')
|
|
148
|
+
.description('List tools assigned to an agent')
|
|
149
|
+
.argument('<agentId>', 'agent ID')
|
|
150
|
+
.action(async (agentId) => {
|
|
151
|
+
auth.requireAuth();
|
|
152
|
+
const orgId = resolveOrgId(auth, program);
|
|
153
|
+
const data = await client.get(resolveEndpoint('agents.tools.list', { orgId, agentId }).path, { service: resolveEndpoint('agents.tools.list', { orgId, agentId }).service });
|
|
154
|
+
formatOutput({
|
|
155
|
+
// fleet #392: assignment rows carry tool_name/category/enabled.
|
|
156
|
+
columns: ['tool_id', 'tool_name', 'category', 'access_mode', 'enabled'],
|
|
157
|
+
headers: ['Tool ID', 'Name', 'Type', 'Access', 'Status'],
|
|
158
|
+
rows: data.assignments || data.tools || data,
|
|
159
|
+
}, { format: program.opts().output });
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
agentTool
|
|
163
|
+
.command('assign')
|
|
164
|
+
.description('Assign a tool to an agent')
|
|
165
|
+
.argument('<agentId>', 'agent ID')
|
|
166
|
+
.requiredOption('--tool <toolId>', 'tool ID')
|
|
167
|
+
.option('--access-mode <mode>', 'access mode', 'full')
|
|
168
|
+
.action(async (agentId, options) => {
|
|
169
|
+
auth.requireAuth();
|
|
170
|
+
const orgId = resolveOrgId(auth, program);
|
|
171
|
+
await client.post(resolveEndpoint('agents.tools.create', { orgId, agentId }).path, {
|
|
172
|
+
service: resolveEndpoint('agents.tools.create', { orgId, agentId }).service,
|
|
173
|
+
body: { tool_id: options.tool, access_mode: options.accessMode },
|
|
174
|
+
});
|
|
175
|
+
printSuccess(`Tool assigned to agent.`);
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
agentTool
|
|
179
|
+
.command('revoke')
|
|
180
|
+
.description('Revoke a tool from an agent')
|
|
181
|
+
.argument('<agentId>', 'agent ID')
|
|
182
|
+
.requiredOption('--tool <toolId>', 'tool ID')
|
|
183
|
+
.action(async (agentId, options) => {
|
|
184
|
+
auth.requireAuth();
|
|
185
|
+
const orgId = resolveOrgId(auth, program);
|
|
186
|
+
// The DELETE route is keyed by assignment id, not tool id — resolve
|
|
187
|
+
// the assignment whose tool matches the given --tool value first.
|
|
188
|
+
const data = await client.get(resolveEndpoint('agents.tools.list', { orgId, agentId }).path, { service: resolveEndpoint('agents.tools.list', { orgId, agentId }).service });
|
|
189
|
+
const assignments = data.assignments || data.tools || data || [];
|
|
190
|
+
const assignment = assignments.find(a => a.tool_id === options.tool || a.id === options.tool);
|
|
191
|
+
if (!assignment) {
|
|
192
|
+
throw new Error(`Tool "${options.tool}" is not assigned to agent ${agentId}.`);
|
|
193
|
+
}
|
|
194
|
+
await client.delete(resolveEndpoint('agents.tools.delete', { orgId, agentId, id: assignment.id }).path, { service: resolveEndpoint('agents.tools.delete', { orgId, agentId, id: assignment.id }).service });
|
|
195
|
+
printSuccess('Tool revoked from agent.');
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
// QuoxIdentity P1 (docs/QUOXIDENTITY_P1_CONTRACT.md): attach/detach/
|
|
199
|
+
// attach-status/attach-heartbeat, registered onto this same `agent` group.
|
|
200
|
+
registerAgentAttachCommands(agent, ctx, program);
|
|
201
|
+
}
|