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,314 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `quox dev resident`, QUOXPERTICUS-REVISIT P3 "Resident lifecycle v1".
|
|
3
|
+
*
|
|
4
|
+
* Deploy/reboot a FULL INTERACTIVE claude session as the resident of a dev
|
|
5
|
+
* host, without anyone ssh-ing, using ONLY existing governed surfaces:
|
|
6
|
+
* - the governed tmux plane (`quox dev sessions`, dev.js/devSessionsProxy.js)
|
|
7
|
+
* - the slots checkout/assign plane (`quox slot`, devSlotsStore.js)
|
|
8
|
+
* - the seed prompt template (lib/core/residentSeed.js), which encodes the
|
|
9
|
+
* proven 2026-09-04 revival recipe: read the revival kit before acting,
|
|
10
|
+
* claim the slot, then say a short hello.
|
|
11
|
+
*
|
|
12
|
+
* deploy: POST tmux new (backend: claude) -> wait for the pane to answer ->
|
|
13
|
+
* inject the seed prompt via the keys route (chunked if it overflows one
|
|
14
|
+
* line's 4096-char cap) -> slot checkout (--create, holder_kind
|
|
15
|
+
* interactive, holder_session = the tmux session id) -> best-effort
|
|
16
|
+
* assigned_to earmark (`v1.slots.assign`) naming the resident identity
|
|
17
|
+
* (`resident:<hostId>`, the same identity buildSelfIdentity() gives the
|
|
18
|
+
* headless supervisor, so both lifecycles name the same logical resident).
|
|
19
|
+
* reboot: release the slot, kill the tmux session, then run deploy again.
|
|
20
|
+
* retire: release the slot and kill the tmux session; no redeploy.
|
|
21
|
+
* status: read-only — tmux presence, a pane tail, and the slot's hold state.
|
|
22
|
+
* seed: render (or --print) the seed prompt with no network call at all.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import { formatDetail, printSuccess, printWarning, printInfo } from '../core/output.js';
|
|
26
|
+
import { resolveEndpoint } from '../api/endpoints.js';
|
|
27
|
+
import { resolveOrgId } from './helpers.js';
|
|
28
|
+
import { loadResidentProfile } from '../core/residentAuth.js';
|
|
29
|
+
import { buildSelfIdentity } from '../core/supervisorEngine.js';
|
|
30
|
+
import { renderSeedPrompt, chunkSeedText } from '../core/residentSeed.js';
|
|
31
|
+
|
|
32
|
+
const PANE_TAIL_LINES = 15;
|
|
33
|
+
|
|
34
|
+
function loadResidentProfileSafely(explicitPath) {
|
|
35
|
+
try {
|
|
36
|
+
return loadResidentProfile(explicitPath);
|
|
37
|
+
} catch {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function resolveHostId(options) {
|
|
43
|
+
const profile = loadResidentProfileSafely(options.residentProfile);
|
|
44
|
+
const hostId = options.host || profile?.host_id || null;
|
|
45
|
+
if (!hostId) {
|
|
46
|
+
throw new Error('No host id: pass --host <hostId>, or provision a resident bootstrap profile with host_id.');
|
|
47
|
+
}
|
|
48
|
+
return hostId;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// A resident's slot defaults to a host-named slot ("one resident slot per
|
|
52
|
+
// host") when neither --slot nor the resident bootstrap profile names one —
|
|
53
|
+
// this never throws, unlike devSupervisor's resolveSlotName, because a
|
|
54
|
+
// resident deploy is meant to work from a bare --host on a fresh box before
|
|
55
|
+
// any profile exists.
|
|
56
|
+
function resolveSlotName(options, hostId) {
|
|
57
|
+
const profile = loadResidentProfileSafely(options.residentProfile);
|
|
58
|
+
return options.slot || profile?.slot || hostId;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function defaultSessionName(slotName) {
|
|
62
|
+
return `resident-${slotName}`;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function registerDevResidentCommands(dev, ctx) {
|
|
66
|
+
const { client, auth, program } = ctx;
|
|
67
|
+
|
|
68
|
+
const resident = dev
|
|
69
|
+
.command('resident')
|
|
70
|
+
.description('Deploy/reboot a full interactive claude session as a dev host\'s resident (governed tmux + slots, no ssh)');
|
|
71
|
+
|
|
72
|
+
const sleepFn = ctx.sleepFn || ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
73
|
+
const waitAttempts = Number.isInteger(ctx.waitAttempts) ? ctx.waitAttempts : 5;
|
|
74
|
+
const waitDelayMs = Number.isInteger(ctx.waitDelayMs) ? ctx.waitDelayMs : 2000;
|
|
75
|
+
|
|
76
|
+
/** Poll the pane until the new session answers, or give up after waitAttempts. */
|
|
77
|
+
async function waitForSession({ hostId, sessionId }) {
|
|
78
|
+
let lastErr = null;
|
|
79
|
+
for (let i = 0; i < waitAttempts; i++) {
|
|
80
|
+
try {
|
|
81
|
+
const ep = resolveEndpoint('v1.dev.sessions.pane', { hostId, sessionId });
|
|
82
|
+
await client.get(ep.path, { service: ep.service });
|
|
83
|
+
return;
|
|
84
|
+
} catch (err) {
|
|
85
|
+
lastErr = err;
|
|
86
|
+
if (i < waitAttempts - 1) await sleepFn(waitDelayMs);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
throw new Error(
|
|
90
|
+
`session ${sessionId} on ${hostId} never answered a pane read after ${waitAttempts} attempt(s)`
|
|
91
|
+
+ (lastErr ? `: ${lastErr.message}` : '')
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** Send the seed prompt, chunked across multiple keys calls if it overflows one line. */
|
|
96
|
+
async function sendSeed({ hostId, sessionId, seedText }) {
|
|
97
|
+
const chunks = chunkSeedText(seedText);
|
|
98
|
+
for (const chunk of chunks) {
|
|
99
|
+
const ep = resolveEndpoint('v1.dev.sessions.keys', { hostId, sessionId });
|
|
100
|
+
await client.post(ep.path, { service: ep.service, body: { keys: chunk } });
|
|
101
|
+
}
|
|
102
|
+
return chunks.length;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
async function deploy(options) {
|
|
106
|
+
auth.requireAuth();
|
|
107
|
+
const orgId = resolveOrgId(auth, program);
|
|
108
|
+
const hostId = resolveHostId(options);
|
|
109
|
+
const slotName = resolveSlotName(options, hostId);
|
|
110
|
+
const sessionName = options.sessionName || defaultSessionName(slotName);
|
|
111
|
+
|
|
112
|
+
const newEp = resolveEndpoint('v1.dev.sessions.new', { hostId });
|
|
113
|
+
const newData = await client.post(newEp.path, {
|
|
114
|
+
service: newEp.service,
|
|
115
|
+
body: { name: sessionName, backend: 'claude' },
|
|
116
|
+
});
|
|
117
|
+
const sessionId = newData && newData.session_id;
|
|
118
|
+
if (!sessionId) {
|
|
119
|
+
throw new Error(`tmux new-session on ${hostId} did not return a session_id (got: ${JSON.stringify(newData)})`);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
await waitForSession({ hostId, sessionId });
|
|
123
|
+
|
|
124
|
+
const seedText = renderSeedPrompt({ hostId, slotName, overrideText: ctx.seedOverrideText });
|
|
125
|
+
const seedChunksSent = await sendSeed({ hostId, sessionId, seedText });
|
|
126
|
+
|
|
127
|
+
const checkoutEp = resolveEndpoint('v1.slots.checkout', { name: slotName });
|
|
128
|
+
const checkoutData = await client.post(checkoutEp.path, {
|
|
129
|
+
service: checkoutEp.service,
|
|
130
|
+
body: {
|
|
131
|
+
holder_session: sessionId, holder_kind: 'interactive', host_id: hostId,
|
|
132
|
+
org_id: orgId, create: true, force: options.force === true,
|
|
133
|
+
},
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
const identity = buildSelfIdentity(hostId);
|
|
137
|
+
let assignedTo = null;
|
|
138
|
+
try {
|
|
139
|
+
const assignEp = resolveEndpoint('v1.slots.assign', { name: slotName });
|
|
140
|
+
await client.post(assignEp.path, { service: assignEp.service, body: { assigned_to: identity, org_id: orgId } });
|
|
141
|
+
assignedTo = identity;
|
|
142
|
+
} catch (err) {
|
|
143
|
+
// The earmark is a nicety (who this slot belongs to when free), never
|
|
144
|
+
// the thing that makes the resident live. A failure here must not
|
|
145
|
+
// undo an already-successful deploy.
|
|
146
|
+
printWarning(`Could not set the assigned_to earmark for ${slotName} (deploy still succeeded): ${err.message}`);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return {
|
|
150
|
+
host_id: hostId,
|
|
151
|
+
slot_name: slotName,
|
|
152
|
+
session_id: sessionId,
|
|
153
|
+
session_name: sessionName,
|
|
154
|
+
seed_chunks_sent: seedChunksSent,
|
|
155
|
+
assigned_to: assignedTo,
|
|
156
|
+
slot: checkoutData && checkoutData.slot || checkoutData,
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/** Release the slot (best-effort) then kill the tmux session (best-effort). Shared by reboot + retire. */
|
|
161
|
+
async function releaseAndKill(options) {
|
|
162
|
+
const orgId = resolveOrgId(auth, program);
|
|
163
|
+
const hostId = resolveHostId(options);
|
|
164
|
+
const slotName = resolveSlotName(options, hostId);
|
|
165
|
+
const sessionName = options.sessionName || defaultSessionName(slotName);
|
|
166
|
+
|
|
167
|
+
try {
|
|
168
|
+
const releaseEp = resolveEndpoint('v1.slots.release', { name: slotName });
|
|
169
|
+
await client.post(releaseEp.path, { service: releaseEp.service, body: { org_id: orgId, force: true } });
|
|
170
|
+
} catch (err) {
|
|
171
|
+
printWarning(`Releasing ${slotName} failed (continuing): ${err.message}`);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
try {
|
|
175
|
+
const killEp = resolveEndpoint('v1.dev.sessions.kill', { hostId, sessionId: sessionName, byName: true });
|
|
176
|
+
await client.delete(killEp.path, { service: killEp.service });
|
|
177
|
+
} catch (err) {
|
|
178
|
+
printWarning(`Killing tmux session ${sessionName} on ${hostId} failed (continuing): ${err.message}`);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return { host_id: hostId, slot_name: slotName, session_name: sessionName };
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
resident
|
|
185
|
+
.command('deploy')
|
|
186
|
+
.description('Deploy a fresh interactive resident: tmux new (claude) -> seed prompt -> slot checkout')
|
|
187
|
+
.option('--host <hostId>', 'target host_id (default: resident bootstrap profile)')
|
|
188
|
+
.option('--slot <name>', 'slot to claim (default: resident bootstrap profile, else the host_id itself)')
|
|
189
|
+
.option('--session-name <n>', 'tmux session name (default: resident-<slot>)')
|
|
190
|
+
.option('--force', 'take the slot even if assigned to another session')
|
|
191
|
+
.option('--resident-profile <path>', 'resident bootstrap profile path')
|
|
192
|
+
.action(async (options) => {
|
|
193
|
+
const report = await deploy(options);
|
|
194
|
+
formatDetail(report, { format: program.opts().output, title: `Resident deployed on ${report.host_id}` });
|
|
195
|
+
printSuccess(
|
|
196
|
+
`Resident live on ${report.host_id}: tmux session ${report.session_id} (${report.session_name}), `
|
|
197
|
+
+ `slot ${report.slot_name} held.`
|
|
198
|
+
);
|
|
199
|
+
printInfo(` quox dev resident status --host ${report.host_id} --slot ${report.slot_name}`);
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
resident
|
|
203
|
+
.command('status')
|
|
204
|
+
.description('Read-only: tmux presence, a pane tail, and the slot hold state')
|
|
205
|
+
.option('--host <hostId>', 'target host_id (default: resident bootstrap profile)')
|
|
206
|
+
.option('--slot <name>', 'slot (default: resident bootstrap profile, else the host_id itself)')
|
|
207
|
+
.option('--session-name <n>', 'tmux session name (default: resident-<slot>)')
|
|
208
|
+
.option('--resident-profile <path>', 'resident bootstrap profile path')
|
|
209
|
+
.action(async (options) => {
|
|
210
|
+
auth.requireAuth();
|
|
211
|
+
const orgId = resolveOrgId(auth, program);
|
|
212
|
+
const hostId = resolveHostId(options);
|
|
213
|
+
const slotName = resolveSlotName(options, hostId);
|
|
214
|
+
const sessionName = options.sessionName || defaultSessionName(slotName);
|
|
215
|
+
|
|
216
|
+
let sessions = [];
|
|
217
|
+
try {
|
|
218
|
+
const listEp = resolveEndpoint('v1.dev.sessions.list', { hostId });
|
|
219
|
+
const data = await client.get(listEp.path, { service: listEp.service });
|
|
220
|
+
sessions = (data && data.sessions) || [];
|
|
221
|
+
} catch (err) {
|
|
222
|
+
printWarning(`Could not list tmux sessions on ${hostId}: ${err.message}`);
|
|
223
|
+
}
|
|
224
|
+
const match = sessions.find((s) => s.name === sessionName) || null;
|
|
225
|
+
|
|
226
|
+
let paneTail = null;
|
|
227
|
+
if (match) {
|
|
228
|
+
try {
|
|
229
|
+
const paneEp = resolveEndpoint('v1.dev.sessions.pane', {
|
|
230
|
+
hostId, sessionId: match.session_id || sessionName, byName: !match.session_id,
|
|
231
|
+
});
|
|
232
|
+
const paneData = await client.get(paneEp.path, { service: paneEp.service });
|
|
233
|
+
const lines = String((paneData && paneData.pane) || '').split('\n');
|
|
234
|
+
paneTail = lines.slice(-PANE_TAIL_LINES).join('\n');
|
|
235
|
+
} catch (err) {
|
|
236
|
+
printWarning(`Could not read pane for ${sessionName} on ${hostId}: ${err.message}`);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
let slotRow = null;
|
|
241
|
+
try {
|
|
242
|
+
const slotsEp = resolveEndpoint('v1.slots.list');
|
|
243
|
+
const slotsData = await client.get(slotsEp.path, { service: slotsEp.service, query: { org_id: orgId } });
|
|
244
|
+
slotRow = ((slotsData && slotsData.slots) || []).find((s) => s.name === slotName) || null;
|
|
245
|
+
} catch (err) {
|
|
246
|
+
printWarning(`Could not read slot ${slotName}: ${err.message}`);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const report = {
|
|
250
|
+
host_id: hostId,
|
|
251
|
+
slot_name: slotName,
|
|
252
|
+
session_name: sessionName,
|
|
253
|
+
session_present: !!match,
|
|
254
|
+
session: match,
|
|
255
|
+
pane_tail: paneTail,
|
|
256
|
+
slot: slotRow,
|
|
257
|
+
};
|
|
258
|
+
formatDetail(report, { format: program.opts().output, title: `Resident status: ${hostId}` });
|
|
259
|
+
|
|
260
|
+
if (match) printSuccess(` tmux session ${sessionName} is live on ${hostId}.`);
|
|
261
|
+
else printWarning(` tmux session ${sessionName} was not found on ${hostId}.`);
|
|
262
|
+
if (slotRow && slotRow.state === 'held') printSuccess(` slot ${slotName} is held by ${slotRow.holder_session}.`);
|
|
263
|
+
else printWarning(` slot ${slotName} is not held.`);
|
|
264
|
+
if (paneTail) printInfo(` pane tail:\n${paneTail}`);
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
resident
|
|
268
|
+
.command('reboot')
|
|
269
|
+
.description('Release the slot, kill the tmux session, then redeploy')
|
|
270
|
+
.option('--host <hostId>', 'target host_id (default: resident bootstrap profile)')
|
|
271
|
+
.option('--slot <name>', 'slot (default: resident bootstrap profile, else the host_id itself)')
|
|
272
|
+
.option('--session-name <n>', 'tmux session name (default: resident-<slot>)')
|
|
273
|
+
.option('--force', 'take the slot even if assigned to another session, on redeploy')
|
|
274
|
+
.option('--resident-profile <path>', 'resident bootstrap profile path')
|
|
275
|
+
.action(async (options) => {
|
|
276
|
+
auth.requireAuth();
|
|
277
|
+
await releaseAndKill(options);
|
|
278
|
+
const report = await deploy(options);
|
|
279
|
+
formatDetail(report, { format: program.opts().output, title: `Resident rebooted on ${report.host_id}` });
|
|
280
|
+
printSuccess(`Resident rebooted on ${report.host_id}: tmux session ${report.session_id}, slot ${report.slot_name} held.`);
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
resident
|
|
284
|
+
.command('retire')
|
|
285
|
+
.description('Release the slot and kill the tmux session; does not redeploy')
|
|
286
|
+
.option('--host <hostId>', 'target host_id (default: resident bootstrap profile)')
|
|
287
|
+
.option('--slot <name>', 'slot (default: resident bootstrap profile, else the host_id itself)')
|
|
288
|
+
.option('--session-name <n>', 'tmux session name (default: resident-<slot>)')
|
|
289
|
+
.option('--resident-profile <path>', 'resident bootstrap profile path')
|
|
290
|
+
.action(async (options) => {
|
|
291
|
+
auth.requireAuth();
|
|
292
|
+
const report = await releaseAndKill(options);
|
|
293
|
+
formatDetail(report, { format: program.opts().output, title: `Resident retired on ${report.host_id}` });
|
|
294
|
+
printSuccess(`Resident retired on ${report.host_id}: slot ${report.slot_name} released, session ${report.session_name} killed.`);
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
resident
|
|
298
|
+
.command('seed')
|
|
299
|
+
.description('Render the seed prompt for a host/slot (no network call)')
|
|
300
|
+
.option('--host <hostId>', 'target host_id (default: resident bootstrap profile)')
|
|
301
|
+
.option('--slot <name>', 'slot (default: resident bootstrap profile, else the host_id itself)')
|
|
302
|
+
.option('--print', 'print the raw seed text (default behavior; explicit for scripting clarity)')
|
|
303
|
+
.option('--resident-profile <path>', 'resident bootstrap profile path')
|
|
304
|
+
.action((options) => {
|
|
305
|
+
const hostId = resolveHostId(options);
|
|
306
|
+
const slotName = resolveSlotName(options, hostId);
|
|
307
|
+
const text = renderSeedPrompt({ hostId, slotName, overrideText: ctx.seedOverrideText });
|
|
308
|
+
if (program.opts().output === 'json' && !options.print) {
|
|
309
|
+
formatDetail({ host_id: hostId, slot_name: slotName, seed: text, chunks: chunkSeedText(text).length }, { format: 'json' });
|
|
310
|
+
} else {
|
|
311
|
+
process.stdout.write(`${text}\n`);
|
|
312
|
+
}
|
|
313
|
+
});
|
|
314
|
+
}
|