@b00dea/cockpit 0.1.0 → 0.1.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/CHANGELOG.md +99 -0
- package/README.md +3 -0
- package/cockpit.schema.json +73 -3
- package/dist/autonomy.d.ts +3 -2
- package/dist/autonomy.js +30 -13
- package/dist/browser/commands.d.ts +57 -2
- package/dist/browser/commands.js +114 -10
- package/dist/browser/runtime.d.ts +1 -0
- package/dist/browser/runtime.js +4 -1
- package/dist/browser/surfaces.d.ts +7 -7
- package/dist/browser/surfaces.js +5 -0
- package/dist/canonical-json.d.ts +2 -0
- package/dist/canonical-json.js +5 -0
- package/dist/cli-main.d.ts +40 -3
- package/dist/cli-main.js +241 -62
- package/dist/command-table.d.ts +19 -11
- package/dist/command-table.js +190 -38
- package/dist/config/load.d.ts +16 -4
- package/dist/config/load.js +25 -3
- package/dist/config/unconfirmed-toml.d.ts +9 -0
- package/dist/config/unconfirmed-toml.js +29 -0
- package/dist/diagnostics.js +10 -1
- package/dist/docs/check.d.ts +16 -0
- package/dist/docs/check.js +197 -0
- package/dist/docs/decision-record.d.ts +34 -0
- package/dist/docs/decision-record.js +135 -0
- package/dist/docs/decisions.d.ts +16 -0
- package/dist/docs/decisions.js +103 -0
- package/dist/docs/markdown.d.ts +36 -0
- package/dist/docs/markdown.js +88 -0
- package/dist/docs/references.d.ts +32 -0
- package/dist/docs/references.js +148 -0
- package/dist/environment/application/confirm.d.ts +35 -0
- package/dist/environment/application/confirm.js +122 -0
- package/dist/environment/application/destructive.js +6 -2
- package/dist/environment/application/doctor.d.ts +12 -7
- package/dist/environment/application/doctor.js +20 -8
- package/dist/environment/application/guard.d.ts +12 -6
- package/dist/environment/application/guard.js +87 -32
- package/dist/environment/application/health.d.ts +14 -5
- package/dist/environment/application/health.js +8 -2
- package/dist/environment/application/lifecycle-context.d.ts +10 -3
- package/dist/environment/application/lifecycle-state.js +4 -3
- package/dist/environment/application/lifecycle.d.ts +2 -1
- package/dist/environment/application/lifecycle.js +11 -3
- package/dist/environment/application/services.d.ts +60 -0
- package/dist/environment/application/services.js +89 -0
- package/dist/environment/application/siblings.d.ts +43 -0
- package/dist/environment/application/siblings.js +269 -0
- package/dist/environment/claims.d.ts +10 -10
- package/dist/environment/claims.js +3 -11
- package/dist/environment/compose-topology.d.ts +15 -7
- package/dist/environment/compose-topology.js +100 -23
- package/dist/environment/contract.d.ts +42 -9
- package/dist/environment/contract.js +33 -0
- package/dist/environment/executable.d.ts +12 -0
- package/dist/environment/executable.js +46 -23
- package/dist/environment/generated-application/check.js +2 -0
- package/dist/environment/generated-application/prepare.js +2 -0
- package/dist/environment/generated-application/shared.d.ts +10 -2
- package/dist/environment/generated-application/shared.js +6 -1
- package/dist/environment/generated-application/surface-outputs.js +23 -12
- package/dist/environment/generated-application/surfaces.js +16 -11
- package/dist/environment/generated-application/sync.js +19 -4
- package/dist/environment/git-observation.d.ts +25 -6
- package/dist/environment/git-observation.js +37 -28
- package/dist/environment/health-composition.d.ts +3 -2
- package/dist/environment/health-composition.js +19 -2
- package/dist/environment/init-application.d.ts +39 -14
- package/dist/environment/init-application.js +39 -10
- package/dist/environment/lifecycle-composition.d.ts +4 -2
- package/dist/environment/lifecycle-composition.js +84 -17
- package/dist/environment/lifecycle-stage.d.ts +8 -3
- package/dist/environment/lifecycle-stage.js +11 -1
- package/dist/environment/logs.d.ts +1 -1
- package/dist/environment/map-limit.d.ts +2 -0
- package/dist/environment/map-limit.js +13 -0
- package/dist/environment/mcp-observation.d.ts +9 -0
- package/dist/environment/mcp-observation.js +46 -0
- package/dist/environment/observations.d.ts +1 -1
- package/dist/environment/observations.js +2 -0
- package/dist/environment/operator-application.d.ts +35 -6
- package/dist/environment/operator-application.js +42 -0
- package/dist/environment/presence.d.ts +4 -0
- package/dist/environment/presence.js +26 -0
- package/dist/environment/read-composition.d.ts +8 -3
- package/dist/environment/read-composition.js +53 -8
- package/dist/environment/readiness-composition.d.ts +20 -1
- package/dist/environment/readiness-composition.js +23 -2
- package/dist/environment/reclaim.d.ts +56 -19
- package/dist/environment/reclaim.js +147 -42
- package/dist/environment/runtime.d.ts +64 -18
- package/dist/environment/runtime.js +73 -3
- package/dist/environment/served-checkout-grants.d.ts +53 -0
- package/dist/environment/served-checkout-grants.js +182 -0
- package/dist/environment/sibling-checkouts.d.ts +119 -0
- package/dist/environment/sibling-checkouts.js +254 -0
- package/dist/environment/sibling-observation.d.ts +27 -0
- package/dist/environment/sibling-observation.js +45 -0
- package/dist/environment/sibling-pull.d.ts +23 -0
- package/dist/environment/sibling-pull.js +95 -0
- package/dist/environment/stack-inputs.d.ts +18 -0
- package/dist/environment/stack-inputs.js +210 -0
- package/dist/environment/stack-inventory-prune.d.ts +31 -0
- package/dist/environment/stack-inventory-prune.js +129 -0
- package/dist/environment/stack-inventory-report.d.ts +44 -0
- package/dist/environment/stack-inventory-report.js +78 -0
- package/dist/environment/stack-inventory.d.ts +94 -0
- package/dist/environment/stack-inventory.js +418 -0
- package/dist/environment/state.d.ts +48 -9
- package/dist/environment/state.js +142 -2
- package/dist/environment/tool-registration.js +3 -7
- package/dist/environment-exec/execute.d.ts +6 -3
- package/dist/environment-exec/execute.js +4 -0
- package/dist/fix-catalogue.d.ts +1 -1
- package/dist/generated-environment/adopt.js +4 -8
- package/dist/guards/answer-confirmation.d.ts +41 -0
- package/dist/guards/answer-confirmation.js +109 -0
- package/dist/guards/evaluator.d.ts +20 -3
- package/dist/guards/evaluator.js +39 -7
- package/dist/guards/local-identity.d.ts +6 -2
- package/dist/guards/local-identity.js +10 -4
- package/dist/guards/policy-change.d.ts +43 -0
- package/dist/guards/policy-change.js +159 -0
- package/dist/guards/policy-pin.d.ts +1 -1
- package/dist/guards/policy-pin.js +14 -12
- package/dist/init/command.d.ts +4 -3
- package/dist/init/command.js +36 -16
- package/dist/init/derive.d.ts +8 -6
- package/dist/init/derive.js +140 -36
- package/dist/init/draft.d.ts +13 -11
- package/dist/init/draft.js +54 -31
- package/dist/init/render.d.ts +18 -6
- package/dist/init/render.js +31 -9
- package/dist/repository-path.d.ts +9 -0
- package/dist/repository-path.js +16 -0
- package/dist/schema.d.ts +42 -75
- package/dist/schema.js +62 -11
- package/dist/surfaces/harness-registry.d.ts +80 -0
- package/dist/surfaces/harness-registry.js +29 -0
- package/dist/surfaces/mcp.d.ts +67 -17
- package/dist/surfaces/mcp.js +194 -31
- package/dist/surfaces/projection.d.ts +7 -7
- package/dist/surfaces/projection.js +41 -6
- package/dist/surfaces/render.d.ts +5 -44
- package/dist/surfaces/render.js +5 -2
- package/dist/team-metadata.js +2 -1
- package/dist/testing/test-protection.d.ts +34 -0
- package/dist/testing/test-protection.js +137 -0
- package/dist/testing/weakening.d.ts +15 -0
- package/dist/testing/weakening.js +30 -0
- package/dist/work-orders/application-closure.d.ts +7 -0
- package/dist/work-orders/application-closure.js +76 -0
- package/dist/work-orders/application-context.d.ts +162 -0
- package/dist/work-orders/application-context.js +137 -0
- package/dist/work-orders/application-decisions.d.ts +27 -0
- package/dist/work-orders/application-decisions.js +121 -0
- package/dist/work-orders/application-grading.d.ts +11 -0
- package/dist/work-orders/application-grading.js +212 -0
- package/dist/work-orders/application.d.ts +4 -97
- package/dist/work-orders/application.js +94 -254
- package/dist/work-orders/composition.d.ts +35 -58
- package/dist/work-orders/composition.js +216 -24
- package/dist/work-orders/contract.d.ts +22 -6
- package/dist/work-orders/contract.js +34 -16
- package/dist/work-orders/decisions.d.ts +23 -0
- package/dist/work-orders/decisions.js +55 -0
- package/dist/work-orders/store.d.ts +54 -5
- package/dist/work-orders/store.js +118 -24
- package/docs/README.md +6 -3
- package/docs/capability-matrix.md +17 -9
- package/docs/cli-reference.md +209 -1
- package/docs/command-list.md +12 -1
- package/docs/concepts.md +6 -4
- package/docs/configuration-essentials.md +7 -2
- package/docs/configuration-reference.md +18 -3
- package/docs/docs-check.md +76 -0
- package/docs/environment.md +194 -6
- package/docs/faq.md +5 -3
- package/docs/getting-started.md +109 -25
- package/docs/harness-surfaces.md +69 -2
- package/docs/safety-and-autonomy.md +56 -12
- package/docs/troubleshooting.md +29 -8
- package/docs/tutorial.md +3 -2
- package/docs/work-and-browser.md +180 -14
- package/libexec/browser-operation.cjs +86 -2
- package/package.json +2 -2
- package/surfaces/skills/cockpit-browser.skill.md +7 -2
- package/surfaces/skills/cockpit-explain.skill.md +1 -1
- package/surfaces/skills/cockpit-fix.skill.md +1 -1
- package/surfaces/skills/cockpit-setup.skill.md +3 -3
- package/surfaces/skills/cockpit-work.skill.md +1 -1
- package/surfaces/skills/procedures/explain.md +9 -4
- package/surfaces/skills/procedures/fix.md +25 -11
- package/surfaces/skills/procedures/setup.md +113 -40
- package/surfaces/skills/procedures/work.md +45 -16
package/dist/cli-main.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { randomUUID } from 'node:crypto';
|
|
2
|
-
import { EVALUATOR_COMMANDS, runningEvaluatorTool } from './schema.js';
|
|
2
|
+
import { ComposeProfileName, EVALUATOR_COMMANDS, runningEvaluatorTool, UNCONFIRMABLE_ANSWERS } from './schema.js';
|
|
3
3
|
import { installedPackageMetadata } from './package-metadata.js';
|
|
4
4
|
import { validateTeamMetadata } from './team-metadata.js';
|
|
5
5
|
import { renderComposeScaffold, SCAFFOLD_BACKING_SERVICES, writeComposeScaffold } from './init/scaffold.js';
|
|
6
6
|
import { discoverReleaseFreshness } from './freshness.js';
|
|
7
|
+
import { observeMcpStartup } from './environment/mcp-observation.js';
|
|
7
8
|
import { lstat, mkdir, realpath, writeFile } from 'node:fs/promises';
|
|
8
9
|
import { parse as parseToml, TomlError } from 'smol-toml';
|
|
9
10
|
import { basename, dirname, isAbsolute, join, relative, resolve } from 'node:path';
|
|
@@ -13,6 +14,7 @@ import { createInvocationRecord, EXIT_CODES } from './invocation.js';
|
|
|
13
14
|
import { commandGroups, commandSpelling, commandStages, commands, describePublicOption, internalCommands, isWorkHandler, isRecordedAnswerField, resolveOrdinaryInvocation } from './command-table.js';
|
|
14
15
|
import { SURFACE_TARGETS } from './surfaces/contract.js';
|
|
15
16
|
import { DEFAULT_SURFACE_TOOLS, GUARD_SURFACE_TARGETS, isGuardSurfaceTarget } from './surfaces/harness-registry.js';
|
|
17
|
+
import { cockpitCommand } from './fix-catalogue.js';
|
|
16
18
|
import { renderHumanDiagnostics, renderHumanValue, renderJsonResponse } from './diagnostics.js';
|
|
17
19
|
import { classifyEstateObservation, draftFromEstate, observeCockpitRoot, observePublicEstate } from './init/draft.js';
|
|
18
20
|
import { runInitOperatorCommand } from './init/command.js';
|
|
@@ -25,25 +27,31 @@ import { beginOperatorReset as beginOperatorResetApplication, runDown as runDown
|
|
|
25
27
|
import { runDoctor as runDoctorApplication } from './environment/application/doctor.js';
|
|
26
28
|
import { runGuardCommand } from './cli-guard.js';
|
|
27
29
|
import { runPreflight as runPreflightApplication } from './environment/application/health.js';
|
|
30
|
+
import { runConfirm } from './environment/application/confirm.js';
|
|
31
|
+
import { isUnconfirmedAnswerName } from './guards/answer-confirmation.js';
|
|
28
32
|
import { runSession as runSessionApplication, runSleep as runSleepApplication, runUp as runUpApplication, } from './environment/application/lifecycle.js';
|
|
29
33
|
import {} from './environment/application/lifecycle-context.js';
|
|
30
34
|
import { runLogs as runLogsApplication } from './environment/application/logs.js';
|
|
31
|
-
import {
|
|
35
|
+
import { runDocsCheck } from './docs/check.js';
|
|
36
|
+
import { runServiceExec, runServicePs, runServiceRestart } from './environment/application/services.js';
|
|
37
|
+
import { executeForegroundProcess, executePlan, executeProbe, foregroundTerminal } from './environment-exec/execute.js';
|
|
32
38
|
import { createDestructiveCommandAdapters, selectDestructiveDaemon } from './environment/destructive-composition.js';
|
|
33
39
|
import { createHealthCommandAdapters } from './environment/health-composition.js';
|
|
34
|
-
import { inspectInitReadiness } from './environment/readiness-composition.js';
|
|
40
|
+
import { inspectInitReadiness, measureInitMachine } from './environment/readiness-composition.js';
|
|
35
41
|
import { createLifecycleCommandAdapters } from './environment/lifecycle-composition.js';
|
|
36
42
|
import { isProcessStart, observeProcessStart, processHost } from './environment/claims.js';
|
|
37
43
|
import { createReadCommandAdapters } from './environment/read-composition.js';
|
|
38
44
|
import { probeInstalledRuntime, runRuntimeMeasure } from './environment/runtime-measurement.js';
|
|
39
45
|
import { initializeLocalGitRepository, runSiblingAdd, runSiblingRemove } from './environment/sibling-registration.js';
|
|
40
46
|
import { runToolChange } from './environment/tool-registration.js';
|
|
47
|
+
import { runPull, runUse } from './environment/application/siblings.js';
|
|
41
48
|
import { issueSetupRunContext, setupEnvironment } from './environment/application/setup.js';
|
|
42
49
|
import { consumeSetupCheckoutProjection, observeEnvironmentIgnore, observeSetupCheckout, observeSetupPath, withSetupCheckoutProjection, } from './environment/setup-observation.js';
|
|
43
50
|
import { RefusalError, ENVIRONMENT_CODES, errorCause, hasErrorCode, INHERITED_TOOL_ENVIRONMENT, isCanonicalTimestamp, } from './environment/contract.js';
|
|
44
51
|
import { refusalOutcome } from './refusal.js';
|
|
45
52
|
import { loadAutonomyPolicy, loadCockpitConfig, observeConfigSource, readBoundedInput } from './config/load.js';
|
|
46
53
|
import { policyWeakening, policyWeakeningDiagnostic } from './autonomy.js';
|
|
54
|
+
import { inspectGuardConfigChange } from './guards/policy-change.js';
|
|
47
55
|
import { gitEnvironment, planSetup } from './environment/setup.js';
|
|
48
56
|
import { synchronizeInstructionPointer } from './environment/instruction-pointer.js';
|
|
49
57
|
import { synchronizeSiblingInstructionFiles } from './environment/sibling-instructions.js';
|
|
@@ -58,7 +66,9 @@ import { prepareSelectedBootstrapSurface, resumeSelectedBootstrapSurface, applyS
|
|
|
58
66
|
import { runUpgrade } from './environment/upgrade-application.js';
|
|
59
67
|
import { inspectCodexHookTrust } from './environment/codex-hook-trust.js';
|
|
60
68
|
import { runConfiguredSurfaceSelftestOutcome, runSelftestOperatorCommand, } from './environment/selftest-application.js';
|
|
61
|
-
import { loadOperatorPolicy, runResetOperatorApplication } from './environment/operator-application.js';
|
|
69
|
+
import { loadOperatorPolicy, runResetOperatorApplication, runStacksPruneOperatorApplication } from './environment/operator-application.js';
|
|
70
|
+
import { beginStacksPrune } from './environment/stack-inventory-prune.js';
|
|
71
|
+
import { runStacksInventory } from './environment/stack-inventory-report.js';
|
|
62
72
|
function setupDiagnostic(code, message, target) {
|
|
63
73
|
return { severity: 'error', code, message, target };
|
|
64
74
|
}
|
|
@@ -260,7 +270,7 @@ function readAdapters(configPath, runtime, sessionId) {
|
|
|
260
270
|
sessionId,
|
|
261
271
|
timeoutMs: RUNTIME_STEP_TIMEOUT_MS,
|
|
262
272
|
...(runtime.signal ? { signal: runtime.signal } : {}),
|
|
263
|
-
}, executeProbe);
|
|
273
|
+
}, executeProbe, executeForegroundProcess);
|
|
264
274
|
}
|
|
265
275
|
// Each return is a literal, so the diagnostic inventory captures the fix beside its code.
|
|
266
276
|
function unsatisfiedWorkDiagnostic(operation, message) {
|
|
@@ -268,6 +278,10 @@ function unsatisfiedWorkDiagnostic(operation, message) {
|
|
|
268
278
|
return { severity: 'error', message,
|
|
269
279
|
code: 'TEST_GATE_UNSATISFIED',
|
|
270
280
|
fix: 'the message gives the verdict and its reasons. FAIL: a check a stage ran failed, so fix the product. DEGRADED: start the stack with npx --no cockpit up --config <your cockpit.toml> for a stage that needs it, make a stage command that could not start runnable from the cockpit root, raise testing.stages[].timeout_seconds for a stage that timed out, or declare testing.stages[].select.<kind> for a --select a stage lacks. VOID: stop changing the checkouts and run the gate again. PARTIAL: only the complete gate can PASS, so run it without --select or --mode staged' };
|
|
281
|
+
if (operation === 'browser-drive')
|
|
282
|
+
return { severity: 'error', message,
|
|
283
|
+
code: 'BROWSER_ROUTE_FINDINGS',
|
|
284
|
+
fix: 'this run asked for a clean route (--fail-on-findings). Read result.receipt.surfaces[].diagnostics, including excerpt.messages where present: fix the product for each console error, uncaught page error and failed request that matters, then rerun npx --no cockpit browser drive --config <your cockpit.toml> --route <name> --fail-on-findings; third-party traffic never counts' };
|
|
271
285
|
if (operation === 'work-check')
|
|
272
286
|
return { severity: 'error', message,
|
|
273
287
|
code: 'WORK_CHECK_FAILED',
|
|
@@ -275,14 +289,19 @@ function unsatisfiedWorkDiagnostic(operation, message) {
|
|
|
275
289
|
if (operation === 'work-verify')
|
|
276
290
|
return { severity: 'error', message,
|
|
277
291
|
code: 'WORK_VERIFY_FAILED',
|
|
278
|
-
fix: 'for an order changed since acceptance, run npx --no cockpit work accept --config <your cockpit.toml> --work-order <id> or revert the change; for a done order without a passing grade, regrade it with work grade' };
|
|
279
|
-
if (operation === 'grade-criteria')
|
|
280
|
-
return { severity: 'error', message,
|
|
281
|
-
code: 'WORK_GRADE_UNSATISFIED',
|
|
282
|
-
fix: 'read result.criteria, result.checks and result.outOfScope: fix the product for an unmet or unclear criterion, make a failed check pass, move or revert a change outside scope, then grade again with npx --no cockpit grade --config <your cockpit.toml> --criteria <file>' };
|
|
292
|
+
fix: 'for an order changed since acceptance, run npx --no cockpit work accept --config <your cockpit.toml> --work-order <id> or revert the change; for a done order without a passing grade, regrade it with work grade; for a decision record that is missing or no longer cites its order, restore it from git or put back its "Work order: <id>" line' };
|
|
283
293
|
return { severity: 'error', message,
|
|
284
294
|
code: 'WORK_GRADE_UNSATISFIED',
|
|
285
|
-
fix:
|
|
295
|
+
fix: operation === 'grade-criteria'
|
|
296
|
+
? 'read result.criteria and result.checks: fix the product for an unmet or unclear criterion, make a failed check pass, revert each change outside scope the scope-changes-necessary verdict calls unneeded, then grade again with npx --no cockpit grade --config <your cockpit.toml> --criteria <file>'
|
|
297
|
+
: 'read result.criteria, result.checks and result.outOfScope: fix the product for an unmet or unclear criterion, make a failed check pass, revert each change outside scope the scope-changes-necessary verdict calls unneeded (under [work] grading = "checks", every change outside scope), have a person amend the order when amendments-hold-bar is unmet, then grade again with npx --no cockpit work grade --config <your cockpit.toml> --work-order <id>' };
|
|
298
|
+
}
|
|
299
|
+
function weakenedTestsDiagnostic(operation, message) {
|
|
300
|
+
return { severity: 'error', message,
|
|
301
|
+
code: 'WORK_TEST_WEAKENED',
|
|
302
|
+
fix: operation === 'work-grade'
|
|
303
|
+
? 'read result.testProtection.findings and the verdict on them: restore each test weakened to make the change pass. Under [work] grading = "independent" the evaluator judged the test changes unjustified in the diff; under "self" your --note names each flagged file and why; under "checks" acknowledge a legitimate change with npx --no cockpit work acknowledge --config <your cockpit.toml> --work-order <id> --note <why it is not a weakening>, which [work.gates] acknowledge = "person" leaves to a person at a terminal with --interactive; never type the digest for them'
|
|
304
|
+
: 'read result.testProtection: restore each deleted, skipped, suppressed or loosened test. A grade without an order has the evaluator judge the test changes; an order records the base they are compared with, and under [work] grading = "checks" a legitimate change is acknowledged with npx --no cockpit work acknowledge --config <your cockpit.toml> --work-order <id> --note <why>, with --interactive by a person where [work.gates] acknowledge = "person"' };
|
|
286
305
|
}
|
|
287
306
|
// A person gate binds what the person reads to the digest they type, so it never completes for an agent.
|
|
288
307
|
function personRequired(handler, prompt) {
|
|
@@ -302,8 +321,12 @@ function sameToolGradeWarnings(value, env) {
|
|
|
302
321
|
function workCommandOutcome(outcome, env) {
|
|
303
322
|
if (outcome.kind === 'complete')
|
|
304
323
|
return { status: 0, result: outcome.value, diagnostics: sameToolGradeWarnings(outcome.value, env) };
|
|
305
|
-
if (outcome.kind === 'unsatisfied')
|
|
306
|
-
|
|
324
|
+
if (outcome.kind === 'unsatisfied') {
|
|
325
|
+
const weakened = outcome.weakening ? [weakenedTestsDiagnostic(outcome.operation, outcome.weakening)] : [];
|
|
326
|
+
// A failure that is only test weakening is already stated by its own diagnostic.
|
|
327
|
+
const unsatisfied = weakened.length === 0 || outcome.reason !== '' ? [unsatisfiedWorkDiagnostic(outcome.operation, outcome.reason)] : [];
|
|
328
|
+
return { status: 2, result: outcome.value, diagnostics: [...weakened, ...unsatisfied, ...sameToolGradeWarnings(outcome.value, env)] };
|
|
329
|
+
}
|
|
307
330
|
return usageOutcome('WORK_APPLICATION_REFUSED', outcome.kind === 'refusal' ? outcome.reason : 'this step needs a person at a terminal');
|
|
308
331
|
}
|
|
309
332
|
async function configuredWorkApplication(input, runtime) {
|
|
@@ -358,18 +381,23 @@ function defaultHandlers(runtime) {
|
|
|
358
381
|
const env = runtime.env ?? process.env;
|
|
359
382
|
const inherited = inheritedSessionId(env);
|
|
360
383
|
const ordinarySessionId = inherited === undefined ? 'cockpit-cli' : inherited;
|
|
361
|
-
const lifecycleAdapters = (configPath, sessionId, holderProcess) => createLifecycleCommandAdapters({
|
|
384
|
+
const lifecycleAdapters = (configPath, sessionId, holderProcess, profiles = []) => createLifecycleCommandAdapters({
|
|
362
385
|
configPath,
|
|
363
386
|
env,
|
|
364
387
|
sessionId,
|
|
365
388
|
timeoutMs: RUNTIME_STEP_TIMEOUT_MS,
|
|
366
389
|
...(holderProcess === undefined ? {} : { holderProcess }),
|
|
390
|
+
activeProfiles: profiles,
|
|
367
391
|
}, executeProbe);
|
|
368
392
|
const healthAdapters = (configPath) => createHealthCommandAdapters({
|
|
369
393
|
configPath,
|
|
370
394
|
env,
|
|
371
395
|
timeoutMs: RUNTIME_STEP_TIMEOUT_MS,
|
|
372
396
|
}, executeProbe);
|
|
397
|
+
// The ordinary (non-session) verbs all need a valid session identity and an absolute config path.
|
|
398
|
+
const withSession = (run) => async (input) => ordinarySessionId === null
|
|
399
|
+
? usageOutcome('CLI_USAGE_INVALID', 'COCKPIT_SESSION_ID must be a canonical UUID', 'COCKPIT_SESSION_ID')
|
|
400
|
+
: run(input, resolve(input.configPath), ordinarySessionId);
|
|
373
401
|
const workHandlers = {};
|
|
374
402
|
for (const command of commands)
|
|
375
403
|
if (isWorkHandler(command.handler)) {
|
|
@@ -388,6 +416,7 @@ function defaultHandlers(runtime) {
|
|
|
388
416
|
infer: deriveInferredFacts,
|
|
389
417
|
composeFindings,
|
|
390
418
|
inspectReadiness: input => inspectInitReadiness(input, { env, timeoutMs: RUNTIME_STEP_TIMEOUT_MS, execute: executeProbe }),
|
|
419
|
+
measureMachine: input => measureInitMachine(input, { env, timeoutMs: RUNTIME_STEP_TIMEOUT_MS, execute: executeProbe }),
|
|
391
420
|
observeDestination: async ({ path, byteLimit }) => {
|
|
392
421
|
const node = await nodeAuthority();
|
|
393
422
|
const read = node ? await readAnchoredBatches(dirname(path), [
|
|
@@ -426,36 +455,28 @@ function defaultHandlers(runtime) {
|
|
|
426
455
|
// A weakening sync would refuse is a failing check, so an agent's edit cannot sit unreported.
|
|
427
456
|
const policy = await loadAutonomyPolicy(input.configPath);
|
|
428
457
|
const weakened = 'recorded' in policy && policy.recorded ? policyWeakening(policy.recorded, policy.current) : [];
|
|
458
|
+
// An edit the guards were not regenerated for holds the agent; one that widens them waits for a person.
|
|
459
|
+
const guardChange = await inspectGuardConfigChange(dirname(loaded.result.source.path), loaded.result.source.path, { sha256: loaded.result.source.sha256, config: loaded.result.config });
|
|
429
460
|
// Codex hook trust concerns a Codex user only; a leftover hooks.json of an unlisted Codex is stale, which check reports.
|
|
430
461
|
const [outcome, hookTrust] = await Promise.all([checkGeneratedEnvironment(input),
|
|
431
462
|
loaded.result.config.surfaces.tools.includes('codexcli')
|
|
432
463
|
? inspectCodexHookTrust({ root: dirname(resolve(input.configPath)), env }) : Promise.resolve([])]);
|
|
433
|
-
if (weakened.length)
|
|
464
|
+
if (weakened.length || guardChange.some(row => row.severity === 'error'))
|
|
434
465
|
return { kind: 'refusal', status: 2, result: null,
|
|
435
|
-
diagnostics: [policyWeakeningDiagnostic(weakened), ...outcome.diagnostics, ...hookTrust] };
|
|
436
|
-
return { ...outcome, diagnostics: [...outcome.diagnostics, ...hookTrust], result: outcome.report
|
|
466
|
+
diagnostics: [...weakened.length ? [policyWeakeningDiagnostic(weakened)] : [], ...guardChange, ...outcome.diagnostics, ...hookTrust] };
|
|
467
|
+
return { ...outcome, diagnostics: [...guardChange, ...outcome.diagnostics, ...hookTrust], result: outcome.report
|
|
437
468
|
? outcome.kind === 'complete' ? { ...outcome.result, currentness: outcome.report } : outcome.report
|
|
438
469
|
: outcome.result };
|
|
439
470
|
},
|
|
440
471
|
selftest: runConfiguredSurfaceSelftestOutcome,
|
|
441
472
|
upgrade: input => runUpgrade({ ...input, env: concreteEnvironment(env) }, { execute: executeProbe, executeForeground: executeForegroundProcess }),
|
|
442
|
-
up: async (input) => {
|
|
443
|
-
const sessionId = ordinarySessionId;
|
|
444
|
-
if (sessionId === null)
|
|
445
|
-
return usageOutcome('CLI_USAGE_INVALID', 'COCKPIT_SESSION_ID must be a canonical UUID', 'COCKPIT_SESSION_ID');
|
|
473
|
+
up: withSession(async (input, configPath, sessionId) => {
|
|
446
474
|
const wrapper = await sessionChildHolderProcess(env);
|
|
447
475
|
if (wrapper.refusal)
|
|
448
476
|
return wrapper.refusal;
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
},
|
|
452
|
-
sleep: (input) => {
|
|
453
|
-
const sessionId = ordinarySessionId;
|
|
454
|
-
if (sessionId === null)
|
|
455
|
-
return Promise.resolve(usageOutcome('CLI_USAGE_INVALID', 'COCKPIT_SESSION_ID must be a canonical UUID', 'COCKPIT_SESSION_ID'));
|
|
456
|
-
const configPath = resolve(input.configPath);
|
|
457
|
-
return runSleepApplication({ configPath }, lifecycleAdapters(configPath, sessionId));
|
|
458
|
-
},
|
|
477
|
+
return runUpApplication({ configPath }, lifecycleAdapters(configPath, sessionId, wrapper.holderProcess, input.profiles));
|
|
478
|
+
}),
|
|
479
|
+
sleep: withSession((_input, configPath, sessionId) => runSleepApplication({ configPath }, lifecycleAdapters(configPath, sessionId))),
|
|
459
480
|
session: async (input) => {
|
|
460
481
|
if (inherited === null)
|
|
461
482
|
return usageOutcome('CLI_USAGE_INVALID', 'COCKPIT_SESSION_ID must be a canonical UUID', 'COCKPIT_SESSION_ID');
|
|
@@ -473,8 +494,9 @@ function defaultHandlers(runtime) {
|
|
|
473
494
|
sessionId,
|
|
474
495
|
timeoutMs: RUNTIME_STEP_TIMEOUT_MS,
|
|
475
496
|
holderProcess: { pid: process.pid, ...(started.state === 'present' ? { processStart: started.start } : {}) },
|
|
497
|
+
activeProfiles: input.profiles,
|
|
476
498
|
}, executeProbe);
|
|
477
|
-
return runSessionApplication({
|
|
499
|
+
return runSessionApplication({ configPath, argv: input.argv, cwd: process.cwd(), env: childEnv }, {
|
|
478
500
|
up: (request) => runUpApplication(request, adapters),
|
|
479
501
|
sleep: (request) => runSleepApplication(request, adapters),
|
|
480
502
|
resolveExecutable: (name) => resolveExecutable(name, sessionEnvironment),
|
|
@@ -482,18 +504,8 @@ function defaultHandlers(runtime) {
|
|
|
482
504
|
...(runtime.signal ? { signal: runtime.signal } : {}),
|
|
483
505
|
});
|
|
484
506
|
},
|
|
485
|
-
down: async (
|
|
486
|
-
|
|
487
|
-
return usageOutcome('CLI_USAGE_INVALID', 'COCKPIT_SESSION_ID must be a canonical UUID', 'COCKPIT_SESSION_ID');
|
|
488
|
-
const configPath = resolve(input.configPath);
|
|
489
|
-
return runDownApplication({ configPath }, await destructiveAdapters(configPath, runtime, ordinarySessionId));
|
|
490
|
-
},
|
|
491
|
-
reset: async (input) => {
|
|
492
|
-
if (ordinarySessionId === null)
|
|
493
|
-
return usageOutcome('CLI_USAGE_INVALID', 'COCKPIT_SESSION_ID must be a canonical UUID', 'COCKPIT_SESSION_ID');
|
|
494
|
-
const configPath = resolve(input.configPath);
|
|
495
|
-
return runResetApplication({ configPath }, await destructiveAdapters(configPath, runtime, ordinarySessionId));
|
|
496
|
-
},
|
|
507
|
+
down: withSession(async (_input, configPath, sessionId) => runDownApplication({ configPath }, await destructiveAdapters(configPath, runtime, sessionId))),
|
|
508
|
+
reset: withSession(async (_input, configPath, sessionId) => runResetApplication({ configPath }, await destructiveAdapters(configPath, runtime, sessionId))),
|
|
497
509
|
preflight: (input) => {
|
|
498
510
|
const configPath = resolve(input.configPath);
|
|
499
511
|
return runPreflightApplication({ configPath }, healthAdapters(configPath));
|
|
@@ -504,19 +516,36 @@ function defaultHandlers(runtime) {
|
|
|
504
516
|
...healthAdapters(configPath),
|
|
505
517
|
discoverReleaseFreshness: () => discoverReleaseFreshness({ verb: 'doctor', env,
|
|
506
518
|
...(runtime.createReleaseMetadataTransport ? { createTransport: runtime.createReleaseMetadataTransport } : {}) }),
|
|
519
|
+
inspectMcpStartup: servers => observeMcpStartup(servers, env),
|
|
507
520
|
});
|
|
508
521
|
},
|
|
509
522
|
runtime: (input) => runRuntimeMeasure({ ...input, env }, { execute: executeProbe, probe: probeInstalledRuntime }),
|
|
523
|
+
stacks: (input) => runStacksInventory({ configPath: resolve(input.configPath), env }, { execute: executeProbe }),
|
|
524
|
+
'docs-check': (input) => runDocsCheck({ configPath: resolve(input.configPath), env }, { execute: executeProbe }),
|
|
525
|
+
confirm: (input) => {
|
|
526
|
+
const configPath = resolve(input.configPath);
|
|
527
|
+
return runConfirm({ configPath, answer: input.answer }, {
|
|
528
|
+
preflight: ({ configPath }) => runPreflightApplication({ configPath }, healthAdapters(configPath)),
|
|
529
|
+
check: async ({ configPath }) => {
|
|
530
|
+
const metadata = teamMetadataOutcome(await validateTeamMetadata(configPath));
|
|
531
|
+
return metadata ?? checkGeneratedEnvironment({ configPath });
|
|
532
|
+
},
|
|
533
|
+
now: () => new Date(),
|
|
534
|
+
});
|
|
535
|
+
},
|
|
510
536
|
sibling: (request) => request.operation === 'add'
|
|
511
537
|
? runSiblingAdd({ ...request.input, env }, { execute: executeProbe })
|
|
512
538
|
: runSiblingRemove({ ...request.input, env }),
|
|
513
539
|
tool: runToolChange,
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
},
|
|
540
|
+
use: (input) => runUse({ ...input, env }, { execute: executeProbe }),
|
|
541
|
+
pull: (input) => runPull({ ...input, env }, { execute: executeProbe }),
|
|
542
|
+
logs: withSession((input, configPath, sessionId) => runLogsApplication({ ...input, configPath }, readAdapters(configPath, runtime, sessionId).logs)),
|
|
543
|
+
ps: withSession((_input, configPath, sessionId) => runServicePs({ configPath }, readAdapters(configPath, runtime, sessionId).services)),
|
|
544
|
+
restart: withSession((input, configPath, sessionId) => runServiceRestart({ configPath, service: input.service, verb: 'restart' }, readAdapters(configPath, runtime, sessionId).services)),
|
|
545
|
+
rebuild: withSession((input, configPath, sessionId) => runServiceRestart({ configPath, service: input.service, verb: 'rebuild' }, readAdapters(configPath, runtime, sessionId).services)),
|
|
546
|
+
// A terminal on both ends gets an interactive shell or client; anything else, an agent's
|
|
547
|
+
// captured output included, gets plain streams.
|
|
548
|
+
exec: withSession((input, configPath, sessionId) => runServiceExec({ configPath, service: input.service, argv: input.argv, tty: foregroundTerminal() }, readAdapters(configPath, runtime, sessionId).services)),
|
|
520
549
|
};
|
|
521
550
|
}
|
|
522
551
|
function commandLines(rows, width = Math.max(...rows.map(row => row.name.length))) {
|
|
@@ -539,8 +568,9 @@ function renderHelp() {
|
|
|
539
568
|
const width = Math.max(...entries.map(entry => entry.name.length));
|
|
540
569
|
const sections = commandStages.map(stage => [`${stage.title}:`,
|
|
541
570
|
...commandLines(entries.filter(entry => entry.stage === stage.id), width)].join('\n'));
|
|
571
|
+
const groups = commandGroups.map(group => `'cockpit ${group.name} --help'`);
|
|
542
572
|
return ['Usage: cockpit <command> [options]', '', sections.join('\n\n'), '',
|
|
543
|
-
`Run 'cockpit <command> --help' for its options, and ${
|
|
573
|
+
`Run 'cockpit <command> --help' for its options, and ${[groups.slice(0, -1).join(', '), ...groups.slice(-1)].filter(Boolean).join(' or ')} for its subcommands.`, ''].join('\n');
|
|
544
574
|
}
|
|
545
575
|
function renderGroupHelp(group) {
|
|
546
576
|
const members = groupMembers(group.name);
|
|
@@ -563,6 +593,15 @@ function renderCommandHelp(definition) {
|
|
|
563
593
|
function renderVersion() {
|
|
564
594
|
return `${installedPackageMetadata.name} ${installedPackageMetadata.version} (Node ${installedPackageMetadata.engines.node}; source ${installedPackageMetadata.cockpitSourceRevision})\n`;
|
|
565
595
|
}
|
|
596
|
+
// Repeated --profile values, each in the spelling environment.profiles accepts, or why not.
|
|
597
|
+
function composeProfiles(values) {
|
|
598
|
+
const profiles = values ?? [];
|
|
599
|
+
const invalid = profiles.find(profile => !ComposeProfileName.safeParse(profile).success);
|
|
600
|
+
if (invalid !== undefined)
|
|
601
|
+
return { error: `--profile ${JSON.stringify(invalid)} must be a lowercase Compose profile name: letters, digits, '.', '_' and '-', starting with a letter or digit` };
|
|
602
|
+
return { profiles: [...new Set(profiles)] };
|
|
603
|
+
}
|
|
604
|
+
const namedOnly = (handler, positionals) => positionals.length > 0 ? usageOutcome('CLI_USAGE_INVALID', `${handler} accepts named inputs only`) : undefined;
|
|
566
605
|
const missingConfig = () => usageOutcome('CLI_USAGE_INVALID', 'provide --config <file>', '--config');
|
|
567
606
|
function usageOutcome(code, message, target) {
|
|
568
607
|
return {
|
|
@@ -743,7 +782,24 @@ async function decodeRequest(handler, parsed, runtimeEnv) {
|
|
|
743
782
|
const note = text('text');
|
|
744
783
|
if (!note)
|
|
745
784
|
return usageOutcome('CLI_USAGE_INVALID', 'provide --text <note>', '--text');
|
|
746
|
-
|
|
785
|
+
if (values.ask === true && values.decision === true)
|
|
786
|
+
return usageOutcome('CLI_USAGE_INVALID', 'pass at most one of --ask and --decision');
|
|
787
|
+
request = { operation: handler, workOrderId: workOrderId, text: note, kind: values.ask === true ? 'question' : values.decision === true ? 'decision' : 'note' };
|
|
788
|
+
}
|
|
789
|
+
else if (handler === 'work-decide') {
|
|
790
|
+
const decision = Number(text('decision')), file = text('record-file'), supersedes = text('supersedes'), reason = text('reason');
|
|
791
|
+
const record = values.record === true || file !== undefined;
|
|
792
|
+
if (!Number.isSafeInteger(decision) || decision < 1 || record === (values.none === true)) {
|
|
793
|
+
return usageOutcome('CLI_USAGE_INVALID', 'provide --decision <number> and one of --record, --record-file <path> or --none --reason <why>');
|
|
794
|
+
}
|
|
795
|
+
if (values.record === true && file !== undefined)
|
|
796
|
+
return usageOutcome('CLI_USAGE_INVALID', 'pass --record to write a new record or --record-file <path> to cite one, not both');
|
|
797
|
+
if (values.none === true && (!reason || supersedes !== undefined))
|
|
798
|
+
return usageOutcome('CLI_USAGE_INVALID', '--none needs --reason <why the decision is not lasting> and takes no --supersedes', '--reason');
|
|
799
|
+
if (record && reason !== undefined)
|
|
800
|
+
return usageOutcome('CLI_USAGE_INVALID', '--reason goes with --none', '--reason');
|
|
801
|
+
request = { operation: handler, workOrderId: workOrderId, decision, resolution: values.none === true ? { kind: 'none', reason: reason }
|
|
802
|
+
: { kind: 'record', ...file !== undefined ? { path: file } : {}, ...supersedes !== undefined ? { supersedes } : {} } };
|
|
747
803
|
}
|
|
748
804
|
else if (handler === 'work-answer') {
|
|
749
805
|
const answer = text('text'), question = Number(text('question'));
|
|
@@ -751,6 +807,12 @@ async function decodeRequest(handler, parsed, runtimeEnv) {
|
|
|
751
807
|
return usageOutcome('CLI_USAGE_INVALID', 'provide --question <number> and --text <answer>');
|
|
752
808
|
request = { operation: handler, workOrderId: workOrderId, question, text: answer };
|
|
753
809
|
}
|
|
810
|
+
else if (handler === 'work-acknowledge') {
|
|
811
|
+
const note = text('note');
|
|
812
|
+
if (!note)
|
|
813
|
+
return usageOutcome('CLI_USAGE_INVALID', 'provide --note <why this change to the tests is not a weakening>', '--note');
|
|
814
|
+
request = { operation: handler, workOrderId: workOrderId, note };
|
|
815
|
+
}
|
|
754
816
|
else if (handler === 'work-grade') {
|
|
755
817
|
const verdict = text('verdict'), note = text('note');
|
|
756
818
|
if (verdict !== undefined && verdict !== 'met' && verdict !== 'unmet')
|
|
@@ -791,7 +853,10 @@ async function decodeRequest(handler, parsed, runtimeEnv) {
|
|
|
791
853
|
const role = text('role');
|
|
792
854
|
if (values.role !== undefined && !role)
|
|
793
855
|
return usageOutcome('CLI_USAGE_INVALID', '--role requires a bounded identifier');
|
|
794
|
-
|
|
856
|
+
const route = text('route');
|
|
857
|
+
if (values.route !== undefined && !route)
|
|
858
|
+
return usageOutcome('CLI_USAGE_INVALID', '--route requires a declared route name or path', '--route');
|
|
859
|
+
request = { operation: handler, ...(role ? { role } : {}), ...(route ? { route } : {}), ...(values['fail-on-findings'] === true ? { failOnFindings: true } : {}) };
|
|
795
860
|
}
|
|
796
861
|
const { captureWorkApplicationRequest } = await import('./work-orders/application.js');
|
|
797
862
|
const captured = captureWorkApplicationRequest(request);
|
|
@@ -955,6 +1020,10 @@ async function decodeRequest(handler, parsed, runtimeEnv) {
|
|
|
955
1020
|
const [command, ...argv] = parsed.positionals;
|
|
956
1021
|
if (!values.config)
|
|
957
1022
|
return missingConfig();
|
|
1023
|
+
const composed = composeProfiles(values.profile);
|
|
1024
|
+
if ('error' in composed)
|
|
1025
|
+
return usageOutcome('CLI_USAGE_INVALID', composed.error, '--profile');
|
|
1026
|
+
const { profiles } = composed;
|
|
958
1027
|
if (!command)
|
|
959
1028
|
return usageOutcome('CLI_USAGE_INVALID', 'session requires a command after --', 'session');
|
|
960
1029
|
const inherited = inheritedSessionId(runtimeEnv);
|
|
@@ -962,17 +1031,40 @@ async function decodeRequest(handler, parsed, runtimeEnv) {
|
|
|
962
1031
|
return usageOutcome('CLI_USAGE_INVALID', 'COCKPIT_SESSION_ID must be a canonical UUID', 'COCKPIT_SESSION_ID');
|
|
963
1032
|
if (inherited !== undefined)
|
|
964
1033
|
return usageOutcome('CLI_USAGE_INVALID', 'session cannot be nested inside another supervised session', 'session');
|
|
965
|
-
return { handler, input: { configPath: values.config, argv: [command, ...argv], sessionId: randomUUID() } };
|
|
1034
|
+
return { handler, input: { configPath: values.config, argv: [command, ...argv], sessionId: randomUUID(), profiles } };
|
|
966
1035
|
}
|
|
967
|
-
if (handler === '
|
|
968
|
-
|| handler === 'preflight' || handler === 'doctor') {
|
|
1036
|
+
if (handler === 'up') {
|
|
969
1037
|
const values = parsed.values;
|
|
970
|
-
if (parsed.positionals.length > 0)
|
|
971
|
-
return usageOutcome('CLI_USAGE_INVALID',
|
|
972
|
-
}
|
|
1038
|
+
if (parsed.positionals.length > 0)
|
|
1039
|
+
return usageOutcome('CLI_USAGE_INVALID', 'up accepts named inputs only');
|
|
973
1040
|
if (!values.config)
|
|
974
1041
|
return missingConfig();
|
|
975
|
-
|
|
1042
|
+
const composed = composeProfiles(values.profile);
|
|
1043
|
+
if ('error' in composed)
|
|
1044
|
+
return usageOutcome('CLI_USAGE_INVALID', composed.error, '--profile');
|
|
1045
|
+
const { profiles } = composed;
|
|
1046
|
+
return { handler, input: { configPath: values.config, profiles } };
|
|
1047
|
+
}
|
|
1048
|
+
if (handler === 'ps' || handler === 'restart' || handler === 'rebuild' || handler === 'exec') {
|
|
1049
|
+
const values = parsed.values;
|
|
1050
|
+
if (!values.config)
|
|
1051
|
+
return missingConfig();
|
|
1052
|
+
if (handler === 'ps')
|
|
1053
|
+
return namedOnly(handler, parsed.positionals) ?? { handler, input: { configPath: values.config } };
|
|
1054
|
+
if (!values.service)
|
|
1055
|
+
return usageOutcome('CLI_USAGE_INVALID', 'provide --service <name>', '--service');
|
|
1056
|
+
if (handler === 'exec') {
|
|
1057
|
+
const [command, ...argv] = parsed.positionals;
|
|
1058
|
+
if (!command)
|
|
1059
|
+
return usageOutcome('CLI_USAGE_INVALID', 'exec requires a command after --', 'exec');
|
|
1060
|
+
return { handler, input: { configPath: values.config, service: values.service, argv: [command, ...argv] } };
|
|
1061
|
+
}
|
|
1062
|
+
return namedOnly(handler, parsed.positionals) ?? { handler, input: { configPath: values.config, service: values.service } };
|
|
1063
|
+
}
|
|
1064
|
+
if (handler === 'check' || handler === 'sleep' || handler === 'down' || handler === 'reset'
|
|
1065
|
+
|| handler === 'preflight' || handler === 'doctor' || handler === 'docs-check') {
|
|
1066
|
+
const values = parsed.values;
|
|
1067
|
+
return namedOnly(handler, parsed.positionals) ?? (values.config ? { handler, input: { configPath: values.config } } : missingConfig());
|
|
976
1068
|
}
|
|
977
1069
|
if (handler === 'logs') {
|
|
978
1070
|
const values = parsed.values;
|
|
@@ -1000,6 +1092,16 @@ async function decodeRequest(handler, parsed, runtimeEnv) {
|
|
|
1000
1092
|
},
|
|
1001
1093
|
};
|
|
1002
1094
|
}
|
|
1095
|
+
if (handler === 'confirm') {
|
|
1096
|
+
const values = parsed.values;
|
|
1097
|
+
const [answer, ...extra] = parsed.positionals;
|
|
1098
|
+
if (answer === undefined || extra.length > 0 || !isUnconfirmedAnswerName(answer)) {
|
|
1099
|
+
return usageOutcome('CLI_USAGE_INVALID', `confirm takes one answer listed under unconfirmed, one of ${UNCONFIRMABLE_ANSWERS.join(', ')}: ${cockpitCommand('confirm', { operand: 'ports.bases' })}`);
|
|
1100
|
+
}
|
|
1101
|
+
if (!values.config)
|
|
1102
|
+
return missingConfig();
|
|
1103
|
+
return { handler, input: { configPath: values.config, answer } };
|
|
1104
|
+
}
|
|
1003
1105
|
if (handler === 'runtime') {
|
|
1004
1106
|
const values = parsed.values;
|
|
1005
1107
|
const [operation, ...extra] = parsed.positionals;
|
|
@@ -1012,6 +1114,21 @@ async function decodeRequest(handler, parsed, runtimeEnv) {
|
|
|
1012
1114
|
return usageOutcome('CLI_USAGE_INVALID', 'provide --image <local-image>; the probe never pulls', '--image');
|
|
1013
1115
|
return { handler, input: { configPath: values.config, image: values.image } };
|
|
1014
1116
|
}
|
|
1117
|
+
if (handler === 'stacks') {
|
|
1118
|
+
const values = parsed.values;
|
|
1119
|
+
const [operation, ...extra] = parsed.positionals;
|
|
1120
|
+
if (operation !== undefined && operation !== 'prune' || extra.length > 0) {
|
|
1121
|
+
return usageOutcome('CLI_USAGE_INVALID', `stacks takes no operation, or prune: ${cockpitCommand('stacks')}, or ${cockpitCommand('stacks prune', { args: '[--apply | --interactive]' })}`);
|
|
1122
|
+
}
|
|
1123
|
+
if (operation === undefined && (values.apply || values.interactive)) {
|
|
1124
|
+
return usageOutcome('CLI_USAGE_INVALID', '--apply and --interactive belong to stacks prune; stacks alone only lists', values.apply ? '--apply' : '--interactive');
|
|
1125
|
+
}
|
|
1126
|
+
if (values.apply && values.interactive)
|
|
1127
|
+
return usageOutcome('CLI_USAGE_INVALID', 'choose --apply or --interactive, not both', '--apply');
|
|
1128
|
+
if (!values.config)
|
|
1129
|
+
return missingConfig();
|
|
1130
|
+
return { handler, input: { configPath: values.config, operation: operation === 'prune' ? 'prune' : 'list' } };
|
|
1131
|
+
}
|
|
1015
1132
|
if (handler === 'sibling') {
|
|
1016
1133
|
const values = parsed.values;
|
|
1017
1134
|
const [operation, slug, ...extra] = parsed.positionals;
|
|
@@ -1036,6 +1153,26 @@ async function decodeRequest(handler, parsed, runtimeEnv) {
|
|
|
1036
1153
|
...(values['instruction-pointer'] === true ? { instructionPointer: true } : {}),
|
|
1037
1154
|
} } };
|
|
1038
1155
|
}
|
|
1156
|
+
if (handler === 'use') {
|
|
1157
|
+
const values = parsed.values;
|
|
1158
|
+
const [slug, checkout, ...extra] = parsed.positionals;
|
|
1159
|
+
const shape = 'npx --no cockpit use <slug> <checkout> --config <your cockpit.toml>, or npx --no cockpit use <slug> --clear --config <your cockpit.toml>';
|
|
1160
|
+
if (!slug || extra.length > 0 || (checkout === undefined) === (values.clear !== true)) {
|
|
1161
|
+
return usageOutcome('CLI_USAGE_INVALID', `use takes a sibling slug and either one checkout directory or --clear: ${shape}`);
|
|
1162
|
+
}
|
|
1163
|
+
if (!values.config)
|
|
1164
|
+
return missingConfig();
|
|
1165
|
+
return { handler, input: { configPath: values.config, slug, clear: values.clear === true,
|
|
1166
|
+
...(checkout === undefined ? {} : { checkout: resolve(checkout) }) } };
|
|
1167
|
+
}
|
|
1168
|
+
if (handler === 'pull') {
|
|
1169
|
+
const values = parsed.values;
|
|
1170
|
+
if (parsed.positionals.length > 0)
|
|
1171
|
+
return usageOutcome('CLI_USAGE_INVALID', 'pull accepts named inputs only');
|
|
1172
|
+
if (!values.config)
|
|
1173
|
+
return missingConfig();
|
|
1174
|
+
return { handler, input: { configPath: values.config } };
|
|
1175
|
+
}
|
|
1039
1176
|
if (handler === 'tool') {
|
|
1040
1177
|
const values = parsed.values;
|
|
1041
1178
|
const [operation, tool, ...extra] = parsed.positionals;
|
|
@@ -1089,7 +1226,7 @@ async function invokeHandler(request, handlers, operators, options, io) {
|
|
|
1089
1226
|
async function dispatchHandler(request, handlers, operators, options, io) {
|
|
1090
1227
|
const unavailable = () => usageOutcome('CLI_INTERNAL_ERROR', `the "${request.handler}" command is unavailable`, request.handler);
|
|
1091
1228
|
if (isWorkHandler(request.handler) && 'request' in request.input) {
|
|
1092
|
-
if (request.handler === 'work-accept' || request.handler === 'work-answer' || request.handler === 'work-signoff') {
|
|
1229
|
+
if (request.handler === 'work-accept' || request.handler === 'work-answer' || request.handler === 'work-signoff' || request.handler === 'work-acknowledge') {
|
|
1093
1230
|
const opened = await operators.beginOperatorWorkApplication?.(request.input);
|
|
1094
1231
|
if (!opened)
|
|
1095
1232
|
return unavailable();
|
|
@@ -1170,6 +1307,10 @@ async function dispatchHandler(request, handlers, operators, options, io) {
|
|
|
1170
1307
|
return runSyncOperatorCommand(request.input, options, io, handlers.sync ?? (async () => unavailable()), operators.loadPolicy ?? loadOperatorPolicy);
|
|
1171
1308
|
if (request.handler === 'check')
|
|
1172
1309
|
return handlers.check?.(request.input) ?? unavailable();
|
|
1310
|
+
if (request.handler === 'docs-check')
|
|
1311
|
+
return handlers['docs-check']?.(request.input) ?? unavailable();
|
|
1312
|
+
if (request.handler === 'confirm')
|
|
1313
|
+
return handlers.confirm?.(request.input) ?? unavailable();
|
|
1173
1314
|
if (request.handler === 'selftest') {
|
|
1174
1315
|
const selftest = handlers.selftest ?? (async () => unavailable());
|
|
1175
1316
|
return 'writeProbeSpecificationsPath' in request.input ? selftest(request.input)
|
|
@@ -1201,12 +1342,49 @@ async function dispatchHandler(request, handlers, operators, options, io) {
|
|
|
1201
1342
|
return handlers.doctor?.(request.input) ?? unavailable();
|
|
1202
1343
|
if (request.handler === 'logs')
|
|
1203
1344
|
return handlers.logs?.(request.input) ?? unavailable();
|
|
1345
|
+
if (request.handler === 'ps')
|
|
1346
|
+
return handlers.ps?.(request.input) ?? unavailable();
|
|
1347
|
+
if (request.handler === 'restart')
|
|
1348
|
+
return handlers.restart?.(request.input) ?? unavailable();
|
|
1349
|
+
if (request.handler === 'rebuild')
|
|
1350
|
+
return handlers.rebuild?.(request.input) ?? unavailable();
|
|
1351
|
+
if (request.handler === 'exec')
|
|
1352
|
+
return handlers.exec?.(request.input) ?? unavailable();
|
|
1204
1353
|
if (request.handler === 'runtime')
|
|
1205
1354
|
return handlers.runtime?.(request.input) ?? unavailable();
|
|
1355
|
+
if (request.handler === 'stacks') {
|
|
1356
|
+
if (request.input.operation === 'list')
|
|
1357
|
+
return handlers.stacks?.(request.input) ?? unavailable();
|
|
1358
|
+
return runStacksPruneOperatorApplication({ configPath: request.input.configPath }, {
|
|
1359
|
+
...options,
|
|
1360
|
+
terminal: io.confirmStacksPrune !== undefined || io.stdin?.isTTY === true,
|
|
1361
|
+
}, {
|
|
1362
|
+
policy: operators.loadPolicy ?? loadOperatorPolicy,
|
|
1363
|
+
begin: operators.beginStacksPrune ?? (async () => unavailable()),
|
|
1364
|
+
confirm: async (preview) => {
|
|
1365
|
+
if (io.confirmStacksPrune)
|
|
1366
|
+
return io.confirmStacksPrune(preview);
|
|
1367
|
+
if (!io.stdin)
|
|
1368
|
+
return undefined;
|
|
1369
|
+
io.stderr.write(renderHumanValue(preview.result));
|
|
1370
|
+
const prompt = createInterface({ input: io.stdin, output: io.stderr, terminal: true });
|
|
1371
|
+
try {
|
|
1372
|
+
return (await prompt.question('Type the exact prune digest to remove these stacks: ')).trim();
|
|
1373
|
+
}
|
|
1374
|
+
finally {
|
|
1375
|
+
prompt.close();
|
|
1376
|
+
}
|
|
1377
|
+
},
|
|
1378
|
+
});
|
|
1379
|
+
}
|
|
1206
1380
|
if (request.handler === 'sibling')
|
|
1207
1381
|
return handlers.sibling?.(request.input) ?? unavailable();
|
|
1208
1382
|
if (request.handler === 'tool')
|
|
1209
1383
|
return handlers.tool?.(request.input) ?? unavailable();
|
|
1384
|
+
if (request.handler === 'use')
|
|
1385
|
+
return handlers.use?.(request.input) ?? unavailable();
|
|
1386
|
+
if (request.handler === 'pull')
|
|
1387
|
+
return handlers.pull?.(request.input) ?? unavailable();
|
|
1210
1388
|
return unavailable();
|
|
1211
1389
|
}
|
|
1212
1390
|
function operatorRefusal(message) {
|
|
@@ -1237,6 +1415,7 @@ function defaultOperatorHandlers(runtime) {
|
|
|
1237
1415
|
return { digest: outcome.continuation.digest, prompt: outcome.continuation.prompt,
|
|
1238
1416
|
decide: async (answer) => workCommandOutcome(await outcome.continuation.decide(answer), runtime.env ?? process.env) };
|
|
1239
1417
|
},
|
|
1418
|
+
beginStacksPrune: (input) => beginStacksPrune({ configPath: resolve(input.configPath), env: runtime.env ?? process.env }, input.scope, { execute: executeProbe }),
|
|
1240
1419
|
beginOperatorReset: async (input) => {
|
|
1241
1420
|
if (sessionId === null)
|
|
1242
1421
|
return operatorRefusal('COCKPIT_SESSION_ID must be a canonical UUID');
|
|
@@ -1264,7 +1443,7 @@ export async function runCli(args, io = process, handlerOverrides = {}, runtime
|
|
|
1264
1443
|
exitCode, resolvedConfigPath });
|
|
1265
1444
|
io.stdout.write(renderJsonResponse({ ...outcome, invocation }, outcome.fixContext));
|
|
1266
1445
|
}
|
|
1267
|
-
return verb === 'session' && Number.isInteger(status) && status > 0 && status < 256 ? status : exitCode;
|
|
1446
|
+
return (verb === 'session' || verb === 'exec') && Number.isInteger(status) && status > 0 && status < 256 ? status : exitCode;
|
|
1268
1447
|
};
|
|
1269
1448
|
// One refusal here instead of per-command checks: process ownership, filesystem publication and
|
|
1270
1449
|
// the shell guards assume POSIX semantics, so a native Windows run could fail part-way through a
|