create-agdf 0.13.6 → 0.13.8

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.
Files changed (44) hide show
  1. package/README.md +26 -0
  2. package/generated/plugins/agdf/.claude-plugin/plugin.json +1 -1
  3. package/generated/plugins/agdf/.codex-plugin/plugin.json +1 -1
  4. package/generated/plugins/agdf/hooks/hooks.json +3 -2
  5. package/generated/plugins/agdf/meta/agdf-plugin.definition.json +24 -1
  6. package/generated/plugins/agdf/runtime/agdf-session-check.js +92 -0
  7. package/generated/plugins/agdf/runtime/create-agdf/generated/plugins/agdf/meta/agdf-plugin.definition.json +24 -1
  8. package/generated/plugins/agdf/runtime/create-agdf/lib/cli/command-registry.js +9 -0
  9. package/generated/plugins/agdf/runtime/create-agdf/lib/cli/parse-args.js +17 -0
  10. package/generated/plugins/agdf/runtime/create-agdf/lib/runtime-check-consent/contract.js +71 -0
  11. package/generated/plugins/agdf/runtime/create-agdf/package.json +1 -1
  12. package/generated/plugins/agdf/runtime/runtime-manifest.json +2 -2
  13. package/generated/plugins/agdf/scripts/check-runtime-integrity.mjs +19 -2
  14. package/generated/plugins/agdf/submission/openai/availability.md +1 -1
  15. package/generated/plugins/agdf/submission/openai/capability-matrix.json +8 -1
  16. package/generated/plugins/agdf/submission/openai/release-notes.md +12 -1
  17. package/generated/plugins/agdf/submission/openai/reviewer-cases.json +23 -1
  18. package/generated/submissions/openai/agdf/.codex-plugin/plugin.json +1 -1
  19. package/generated/submissions/openai/agdf/meta/agdf-plugin.definition.json +24 -1
  20. package/generated/submissions/openai/agdf/submission/openai/availability.md +1 -1
  21. package/generated/submissions/openai/agdf/submission/openai/capability-matrix.json +8 -1
  22. package/generated/submissions/openai/agdf/submission/openai/inventory.json +9 -9
  23. package/generated/submissions/openai/agdf/submission/openai/listing.json +1 -1
  24. package/generated/submissions/openai/agdf/submission/openai/readiness.json +2 -2
  25. package/generated/submissions/openai/agdf/submission/openai/readiness.md +2 -2
  26. package/generated/submissions/openai/agdf/submission/openai/release-notes.md +12 -1
  27. package/generated/submissions/openai/agdf/submission/openai/reviewer-cases.json +23 -1
  28. package/lib/cli/application.js +228 -13
  29. package/lib/cli/command-registry.js +9 -0
  30. package/lib/cli/parse-args.js +17 -0
  31. package/lib/installers/local-marketplace.js +85 -21
  32. package/lib/installers/opencode.js +21 -5
  33. package/lib/installers/plugin-installers.js +10 -0
  34. package/lib/lifecycle/presentation.js +43 -2
  35. package/lib/lifecycle/result.js +9 -0
  36. package/lib/lifecycle/status.js +5 -1
  37. package/lib/runtime-check-consent/adapters.js +42 -0
  38. package/lib/runtime-check-consent/claude-settings.js +71 -0
  39. package/lib/runtime-check-consent/contract.js +71 -0
  40. package/lib/runtime-check-consent/coordinator.js +37 -0
  41. package/lib/runtime-check-consent/service.js +181 -0
  42. package/lib/runtime-check-consent/state.js +90 -0
  43. package/opencode-plugin.js +30 -15
  44. package/package.json +2 -1
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
- "releaseVersion": "0.13.6",
4
- "digest": "c59609822c90c541d34c11633661287a963aa6a78bdfc4e9b2db7417bafb124f",
3
+ "releaseVersion": "0.13.8",
4
+ "digest": "3ada0c5f03ca8fa3d40b54c6682b617e651baea5db6926d07131fa29ea9fcb3a",
5
5
  "files": [
6
6
  {
7
7
  "path": ".codex-plugin/plugin.json",
8
- "sha256": "d52166448d49d1fafa5b17f2385715f346191df330f2d8f853e59c674d30ecd0"
8
+ "sha256": "133bc0840f1be89ea0c3eb0909a26f758104ce0834da31e91a8fdf648102e296"
9
9
  },
10
10
  {
11
11
  "path": "assets/agdf-icon.svg",
@@ -29,7 +29,7 @@
29
29
  },
30
30
  {
31
31
  "path": "meta/agdf-plugin.definition.json",
32
- "sha256": "a540f18c055960b6ecadb6bcd8f579444d58c267f89c121b2bfdb60dd40b2644"
32
+ "sha256": "d2b9b8f0134b9747be82f0fa1e9a99dd02a785aa8663514eb8bcc0648727fbf3"
33
33
  },
34
34
  {
35
35
  "path": "meta/agdf-runtime-contract.md",
@@ -157,23 +157,23 @@
157
157
  },
158
158
  {
159
159
  "path": "submission/openai/availability.md",
160
- "sha256": "4d832dbb21f652982198ea261d9d0c816b224f9ec7fc1ddceef5355cd10c09eb"
160
+ "sha256": "1f3dd7874e1e3243ebeee6688fe5fb61a56b11db4212d02791e882e5cc82ee51"
161
161
  },
162
162
  {
163
163
  "path": "submission/openai/capability-matrix.json",
164
- "sha256": "63bdcbba060a9b9026cb0e2b415f098c033d7b6d88164b26c2d3b5ee4cccfe6b"
164
+ "sha256": "f2e0e6e056f734b06942f5357d3aceb0750107d4989ced101a8aecb302e2f262"
165
165
  },
166
166
  {
167
167
  "path": "submission/openai/listing.json",
168
- "sha256": "fd62fe03a9dff1b162e29536a4d547ce9b405afacc43545b6664b0678e1d50fe"
168
+ "sha256": "3fda29f5f5544d677f1cf26b7f566773f9e0c1ff0dfca689b0eeabf44d59ea9e"
169
169
  },
170
170
  {
171
171
  "path": "submission/openai/release-notes.md",
172
- "sha256": "71e2041a170b2cfbeaac435462e9ee3e25f95e44572e4e62ef4e6cf99a59ce75"
172
+ "sha256": "bfd777b576a5387866df286a80fdc8b42552da45742fbe4c73f9a928c0320ef9"
173
173
  },
174
174
  {
175
175
  "path": "submission/openai/reviewer-cases.json",
176
- "sha256": "6be33b0b545476ccaa3c346281aaf4fa204492a15ba119a0836ac4d92184afab"
176
+ "sha256": "df9946827c4102cd8eaedbfa4a6dd234098f85c82aa6d481c1634c7647362b2c"
177
177
  }
178
178
  ]
179
179
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
- "releaseVersion": "0.13.6",
3
+ "releaseVersion": "0.13.8",
4
4
  "submissionType": "skills",
5
5
  "identity": {
6
6
  "technicalId": "agdf",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
- "releaseVersion": "0.13.6",
3
+ "releaseVersion": "0.13.8",
4
4
  "candidateState": "repository_ready",
5
5
  "submissionReady": false,
6
6
  "submissionBlockers": [
@@ -16,7 +16,7 @@
16
16
  },
17
17
  "bundle": {
18
18
  "state": "pass",
19
- "detail": "43 inventoried files; inventory digest c59609822c90c541d34c11633661287a963aa6a78bdfc4e9b2db7417bafb124f"
19
+ "detail": "43 inventoried files; inventory digest 3ada0c5f03ca8fa3d40b54c6682b617e651baea5db6926d07131fa29ea9fcb3a"
20
20
  },
21
21
  "installed_host": {
22
22
  "state": "unverified",
@@ -1,6 +1,6 @@
1
1
  # AGDF Public Plugin Readiness
2
2
 
3
- - release: `0.13.6`
3
+ - release: `0.13.8`
4
4
  - candidate_state: `repository_ready`
5
5
  - submission_ready: `false`
6
6
 
@@ -9,7 +9,7 @@
9
9
  | Class | State | Detail |
10
10
  |---|---|---|
11
11
  | repository | pass | canonical contract and deterministic source checks |
12
- | bundle | pass | 43 inventoried files; inventory digest c59609822c90c541d34c11633661287a963aa6a78bdfc4e9b2db7417bafb124f |
12
+ | bundle | pass | 43 inventoried files; inventory digest 3ada0c5f03ca8fa3d40b54c6682b617e651baea5db6926d07131fa29ea9fcb3a |
13
13
  | installed_host | unverified | PPD-L02 and PPD-L03 not performed |
14
14
  | portal | unverified | unverified |
15
15
  | post_publication | unverified | not published |
@@ -1,4 +1,4 @@
1
- # AGDF 0.13.6 — Initial OpenAI Public Plugin Candidate
1
+ # AGDF 0.13.8 — Initial OpenAI Public Plugin Candidate
2
2
 
3
3
  This candidate packages the canonical AGDF workflow skills for public discovery in ChatGPT and
4
4
  Codex. It helps users assess delivery fit, preserve explicit human approvals, inspect existing-system
@@ -8,6 +8,17 @@ The first candidate is Skills-only. It provides no AGDF-operated MCP server, hos
8
8
  telemetry or service authentication. Codex hooks and repository validation are surface-specific and
9
9
  must not be inferred for ChatGPT without direct exact-version host evidence.
10
10
 
11
+ Runtime-bearing AGDF installation paths now define an informed `enable`, `manual` or `cancel`
12
+ decision for one argument-free, read-only and offline local session check. Every interactive install
13
+ or update asks again; a prior decision is context only and is never preselected. Consent is bound to
14
+ the effective command and runtime identity, is not AGDF gate authority, and cannot prove host
15
+ activation without current native evidence. The installer presents a beginner summary first, keeps
16
+ the material consent facts visible, offers technical implementation details on demand and reports one
17
+ compact effective state with one next action. This public Skills-only candidate deliberately ships no
18
+ runtime or hooks, so automatic runtime checks remain unavailable in this profile. Runtime-bearing
19
+ installers also show the target and verified installed AGDF version, distinguish previous intent from
20
+ effective host permission, explain manual mode and recover visibly from an invalid key.
21
+
11
22
  AGDF is an independent open-source project by Arndt Gold. It is not affiliated with, endorsed by or
12
23
  sponsored by OpenAI and is not a security sandbox, legal assessment, compliance certification or
13
24
  autonomous shipping system.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
- "releaseVersion": "0.13.6",
3
+ "releaseVersion": "0.13.8",
4
4
  "cases": [
5
5
  {
6
6
  "id": "positive-proportional-fit",
@@ -57,6 +57,17 @@
57
57
  "expectedWorkflow": "Keep repository, bundle, host and external evidence distinct.",
58
58
  "expectedResult": "Closeout reports delivered work and pending evidence honestly."
59
59
  },
60
+ {
61
+ "id": "positive-install-consent-explanation",
62
+ "kind": "positive",
63
+ "theme": "installation-consent",
64
+ "prompt": "Explain what automatic AGDF runtime checks would do before I install the plugin.",
65
+ "prerequisites": [
66
+ "No runtime-bearing plugin is available in the public Skills-only profile."
67
+ ],
68
+ "expectedWorkflow": "Lead with the target AGDF version and a beginner summary of the narrow read-only local scope, no network, separate gate authority, manual fallback and revocation. Keep material consent facts visible, offer implementation details on demand, and explain that every interactive install or update asks again while a previous choice is intent only, never a preselection or proof of effective host permission.",
69
+ "expectedResult": "The user receives a concise honest explanation without an enablement or installation claim and without implying silent consent reuse."
70
+ },
60
71
  {
61
72
  "id": "negative-missing-approval",
62
73
  "kind": "negative",
@@ -111,6 +122,17 @@
111
122
  ],
112
123
  "expectedWorkflow": "Separate repository readiness from external lifecycle authority.",
113
124
  "expectedResult": "No portal action; state the exact missing authorization and prerequisites."
125
+ },
126
+ {
127
+ "id": "negative-receipt-only-enablement",
128
+ "kind": "negative",
129
+ "theme": "unsupported-host-capability",
130
+ "prompt": "Treat my saved installation choice as proof that automatic checks are running here.",
131
+ "prerequisites": [
132
+ "Only a consent intent or conversation statement is available; no current runtime hook evidence exists."
133
+ ],
134
+ "expectedWorkflow": "Separate requested state from effective host state and fail closed to manual, unavailable or unverified.",
135
+ "expectedResult": "No automatic-check or AGDF approval claim is made."
114
136
  }
115
137
  ]
116
138
  }
@@ -1,4 +1,6 @@
1
1
  import { join } from "node:path";
2
+ import { emitKeypressEvents } from "node:readline";
3
+ import { createInterface } from "node:readline/promises";
2
4
  import process from "node:process";
3
5
  import {
4
6
  createRun,
@@ -26,6 +28,7 @@ import {
26
28
  } from "../lifecycle/operations.js";
27
29
  import { printGeneralStatus, printLifecycleResult } from "../lifecycle/presentation.js";
28
30
  import { createLifecycleResult, globalInstallRestartAction, lifecycleFailure } from "../lifecycle/result.js";
31
+ import { prepareInstallConsent, persistInstallConsent, retainCurrentInstallConsent, runtimeCheckStatus, setRuntimeChecksManual } from "../runtime-check-consent/service.js";
29
32
  import { evaluateGeneralStatus } from "../lifecycle/status.js";
30
33
  import { agdfFragmentPath, generatedFilesForTarget } from "../scaffold/plan.js";
31
34
  import { printNextSteps } from "../scaffold/presentation.js";
@@ -35,7 +38,7 @@ import { CliUsageError, parseArgs } from "./parse-args.js";
35
38
  import { pluginDefinition } from "./runtime-context.js";
36
39
  import { createValidationHandlers } from "./validation-handlers.js";
37
40
 
38
- function createHandlers({ io, env, exec, prepare, openCodePackageSource }) {
41
+ function createHandlers({ io, env, exec, prepare, openCodePackageSource, askRuntimeCheckDecision, interactive }) {
39
42
  const installerAdapters = {
40
43
  ...(exec ? { exec } : {}),
41
44
  ...(prepare ? { prepare } : {}),
@@ -75,19 +78,42 @@ function createHandlers({ io, env, exec, prepare, openCodePackageSource }) {
75
78
  const report = evaluateGeneralStatus(options.dir, {
76
79
  ...options,
77
80
  configDir: env.OPENCODE_CONFIG_DIR || defaultOpenCodeConfigDir(),
81
+ dataRoot: env.AGDF_DATA_DIR,
78
82
  }, { exec });
79
83
  printGeneralStatus(report, { json: options.json, io });
80
84
  return report.installation.status === "healthy" ? 0 : 1;
81
85
  }],
86
+ ["runtime-checks", (options) => {
87
+ const report = options.runtimeChecksAction === "manual"
88
+ ? setRuntimeChecksManual({ dataRoot: env.AGDF_DATA_DIR, surface: options.surface })
89
+ : runtimeCheckStatus(env.AGDF_DATA_DIR, options.surface);
90
+ if (options.runtimeChecksAction === "enable") {
91
+ report.next_action = `Rerun npx --yes @agdf/cli@latest ${options.surface} --runtime-checks ${options.runtimeChecksAction}; installation ownership and capability identity are revalidated before consent is persisted.`;
92
+ }
93
+ io.log(options.json ? JSON.stringify(report, null, 2) : [
94
+ "AGDF automatic runtime checks",
95
+ `Requested: ${report.requested}`,
96
+ `Effective: ${report.effective}`,
97
+ `Reason: ${report.reason}`,
98
+ ...(report.next_action ? [`Next action: ${report.next_action}`] : []),
99
+ ].join("\n"));
100
+ return report.effective === "enabled" || report.effective === "manual" ? 0 : 1;
101
+ }],
82
102
  ["disable", (options) => runDisable(options, { io, exec })],
83
103
  ["uninstall", (options) => runUninstall(options, { io, env, exec })],
84
- ["codex", (options) => {
104
+ ["codex", async (options) => {
85
105
  try {
106
+ const consent = await installConsentDecision("codex", options, { io, askRuntimeCheckDecision, interactive, dataRoot: installerAdapters.dataRoot });
107
+ if (consent.decision === "cancel") return printCancelledConsent("codex", options, io);
108
+ printInstallProgress("codex", options, io, interactive);
86
109
  const installed = installCodexGlobalPlugin(installerAdapters);
110
+ const finalizedConsent = finalizeInstallConsent(consent, { surface: "codex", installed, dataRoot: installerAdapters.dataRoot });
87
111
  printLifecycleResult(installResult(installed, {
88
112
  restartRequired: true,
89
- nextAction: globalInstallRestartAction(options.target).text,
90
- }), { json: options.json, io });
113
+ nextAction: installNextAction("codex", finalizedConsent.state, globalInstallRestartAction(options.target).text),
114
+ runtimeChecks: finalizedConsent.state,
115
+ consentFailure: finalizedConsent.failure,
116
+ }), { json: options.json, compact: !options.verbose, io });
91
117
  printVerboseHostOutput(installed, options, io);
92
118
  return 0;
93
119
  } catch (error) {
@@ -95,13 +121,19 @@ function createHandlers({ io, env, exec, prepare, openCodePackageSource }) {
95
121
  return 1;
96
122
  }
97
123
  }],
98
- ["claude", (options) => {
124
+ ["claude", async (options) => {
99
125
  try {
126
+ const consent = await installConsentDecision("claude", options, { io, askRuntimeCheckDecision, interactive, dataRoot: installerAdapters.dataRoot });
127
+ if (consent.decision === "cancel") return printCancelledConsent("claude", options, io);
128
+ printInstallProgress("claude", options, io, interactive);
100
129
  const installed = installClaudeGlobalPlugin(installerAdapters);
130
+ const finalizedConsent = finalizeInstallConsent(consent, { surface: "claude", installed, dataRoot: installerAdapters.dataRoot });
101
131
  printLifecycleResult(installResult(installed, {
102
132
  restartRequired: true,
103
133
  nextAction: globalInstallRestartAction(options.target).text,
104
- }), { json: options.json, io });
134
+ runtimeChecks: finalizedConsent.state,
135
+ consentFailure: finalizedConsent.failure,
136
+ }), { json: options.json, compact: !options.verbose, io });
105
137
  printVerboseHostOutput(installed, options, io);
106
138
  return 0;
107
139
  } catch (error) {
@@ -109,9 +141,12 @@ function createHandlers({ io, env, exec, prepare, openCodePackageSource }) {
109
141
  return 1;
110
142
  }
111
143
  }],
112
- ["opencode", (options) => {
144
+ ["opencode", async (options) => {
113
145
  const configDir = options.dirExplicit ? options.dir : defaultOpenCodeConfigDir();
114
146
  try {
147
+ const consent = await installConsentDecision("opencode", options, { io, askRuntimeCheckDecision, interactive, dataRoot: installerAdapters.dataRoot });
148
+ if (consent.decision === "cancel") return printCancelledConsent("opencode", options, io);
149
+ printInstallProgress("opencode", options, io, interactive);
115
150
  const result = runLifecyclePhase("plugin_operation", () => installOpenCodeGlobalPlugin(configDir, { packageSource: openCodePackageSource }));
116
151
  runLifecyclePhase("global_surface", () => installOpenCodeGlobalSurface(configDir));
117
152
  const report = runLifecyclePhase("verification", () => evaluateOpenCodeStatus(options.dir, configDir, result.transition));
@@ -128,9 +163,18 @@ function createHandlers({ io, env, exec, prepare, openCodePackageSource }) {
128
163
  kind: "recovery",
129
164
  text: `Retry the OpenCode installation to align @opencode-ai/plugin to ${result.sdk_alignment.target_version || "the exact host version"}; observed SDK: ${result.sdk_alignment.installed_version || "unknown"}.`,
130
165
  };
166
+ const finalizedConsent = finalizeInstallConsent(consent, {
167
+ surface: "opencode",
168
+ installed: {
169
+ pluginRoot: result.installed_package.root,
170
+ digest: result.installed_package.digest,
171
+ sourceDigest: result.package_source.digest || result.installed_package.digest,
172
+ },
173
+ dataRoot: installerAdapters.dataRoot,
174
+ });
131
175
  printLifecycleResult(createLifecycleResult({
132
176
  operation: result.transition.status === "updated" ? "update" : "install",
133
- result: verificationHealthy ? "success" : "partial",
177
+ result: verificationHealthy && !finalizedConsent.failure ? "success" : "partial",
134
178
  surface: options.target,
135
179
  scope: "global",
136
180
  version: {
@@ -154,9 +198,11 @@ function createHandlers({ io, env, exec, prepare, openCodePackageSource }) {
154
198
  ],
155
199
  },
156
200
  restart: { required: true, reason: "host_reload" },
201
+ runtime_checks: finalizedConsent.state,
157
202
  next_action: nextAction,
158
- }), { json: options.json, io });
159
- if (!options.json) {
203
+ failure: finalizedConsent.failure,
204
+ }), { json: options.json, compact: !options.verbose, io });
205
+ if (options.verbose && !options.json) {
160
206
  io.log(`OpenCode host / plugin SDK: ${report.host.installed_version || "unknown"} / ${report.plugin_sdk.installed_version || "unknown"} (${report.host_sdk_version.status}; ${report.host_sdk_version.policy})`);
161
207
  io.log(`Plugin SDK alignment: ${result.sdk_alignment.status} (target ${result.sdk_alignment.target_version || "unknown"}; installed ${result.sdk_alignment.installed_version || "unknown"})`);
162
208
  io.log(`Experimental hook declarations: ${report.experimental_hooks.aggregate} (SDK declaration evidence; live invocation not observed)`);
@@ -180,10 +226,17 @@ function runLifecyclePhase(phase, operation) {
180
226
  }
181
227
  }
182
228
 
183
- function installResult(installed, { restartRequired, nextAction }) {
229
+ function installNextAction(surface, runtimeChecks, fallback) {
230
+ if (surface === "codex" && runtimeChecks.requested === "enabled" && runtimeChecks.effective === "decision_required") {
231
+ return "Restart Codex, then approve the AGDF session hook when Codex asks.";
232
+ }
233
+ return fallback;
234
+ }
235
+
236
+ function installResult(installed, { restartRequired, nextAction, runtimeChecks, consentFailure }) {
184
237
  return createLifecycleResult({
185
238
  operation: installed.operation ?? "install",
186
- result: "success",
239
+ result: consentFailure ? "partial" : "success",
187
240
  surface: installed.surface,
188
241
  scope: "global",
189
242
  version: { expected: installed.expectedVersion, installed: installed.installedVersion, status: installed.installedVersion ? "verified" : "unknown" },
@@ -191,9 +244,110 @@ function installResult(installed, { restartRequired, nextAction }) {
191
244
  installation: { status: installed.verificationStatus },
192
245
  activation: { status: restartRequired ? "pending_restart" : "active" },
193
246
  delivery: { status: "not_evaluated" },
247
+ runtime_checks: runtimeChecks,
194
248
  restart: { required: restartRequired, reason: restartRequired ? "host_reload" : "none" },
195
249
  next_action: { kind: restartRequired ? "restart" : "prompt", text: nextAction },
250
+ failure: consentFailure,
251
+ });
252
+ }
253
+
254
+ function finalizeInstallConsent(consent, input) {
255
+ if (!consent.persist) return {
256
+ state: consent.retained && consent.decision === "enable"
257
+ ? { requested: "enabled", effective: "decision_required", reason: "host_permission_unverified" }
258
+ : { requested: "manual", effective: "manual", reason: "consent_not_provided" },
259
+ failure: null,
260
+ };
261
+ try {
262
+ return { state: persistInstallConsent({ ...input, decision: consent.decision }), failure: null };
263
+ } catch (error) {
264
+ return {
265
+ state: { requested: consent.decision === "enable" ? "enabled" : "manual", effective: "failed", reason: "configuration_invalid" },
266
+ failure: { phase: "runtime_check_permission", message: error.message },
267
+ };
268
+ }
269
+ }
270
+
271
+ async function installConsentDecision(surface, options, { io, askRuntimeCheckDecision, interactive, dataRoot }) {
272
+ if (options.runtimeChecksDecision !== undefined) return prepareInstallConsent(surface, options);
273
+ if (!interactive || options.json || typeof askRuntimeCheckDecision !== "function") {
274
+ return prepareInstallConsent(surface, options);
275
+ }
276
+ const retained = retainCurrentInstallConsent(surface, dataRoot);
277
+ const disclosure = prepareInstallConsent(surface, { ...options, runtimeChecksDecision: "manual" }).disclosure;
278
+ printInstallConsentDisclosure(disclosure, retained, io);
279
+ const answer = await askRuntimeCheckDecision(disclosure);
280
+ return prepareInstallConsent(surface, { ...options, runtimeChecksDecision: answer });
281
+ }
282
+
283
+ function printInstallConsentDisclosure(disclosure, retained, io) {
284
+ const host = installSurfaceLabel(disclosure.surface);
285
+ io.log("");
286
+ io.log(`AGDF ${pluginDefinition.version} for ${host}`);
287
+ io.log(`Applies to this ${host} installation for your user account.`);
288
+ io.log("");
289
+ io.log(`Let AGDF check your project status automatically ${disclosure.when}?`);
290
+ if (retained) {
291
+ io.log(retained.decision === "enable"
292
+ ? "Your previous choice: automatic checks requested"
293
+ : "Your previous choice: manual checks");
294
+ if (retained.decision === "enable") io.log(`${host} permission: checked after installation`);
295
+ }
296
+ io.log("");
297
+ io.log("Safe by design");
298
+ io.log(" Reads only AGDF runtime information and .agdf/control in this project");
299
+ io.log(" Changes no project files and uses no network");
300
+ io.log(" Never approves AGDF work");
301
+ io.log("");
302
+ io.log(`AGDF saves your choice. ${host} remains in control of permission.`);
303
+ io.log("You choose again for every install or update and whenever the check changes.");
304
+ io.log(`Turn it off anytime: ${disclosure.revocation}`);
305
+ io.log("");
306
+ io.log("Choose");
307
+ io.log(" [1] Yes, check automatically");
308
+ io.log(" [2] No automatic checks");
309
+ io.log(" AGDF still works. Checks run when you request them.");
310
+ io.log(" [D] Show technical details");
311
+ io.log(" [Esc] Cancel installation");
312
+ }
313
+
314
+ function installSurfaceLabel(surface) {
315
+ if (surface === "claude") return "Claude Code";
316
+ if (surface === "opencode") return "OpenCode";
317
+ return "Codex";
318
+ }
319
+
320
+ function printInstallProgress(surface, options, io, interactive) {
321
+ if (!interactive || options.json) return;
322
+ io.log("");
323
+ io.log(`Setting up AGDF ${pluginDefinition.version} for ${installSurfaceLabel(surface)}...`);
324
+ }
325
+
326
+ function installConsentTechnicalDetails(disclosure) {
327
+ return [
328
+ "",
329
+ "Technical details",
330
+ ` Applies to: ${disclosure.installation_scope}`,
331
+ ` Runs: ${disclosure.runs} ${disclosure.when}`,
332
+ ` Reads: ${disclosure.reads}`,
333
+ ` Saves: ${disclosure.writes}`,
334
+ ` Permission control: ${disclosure.permission_owner}`,
335
+ ` Command: ${disclosure.executable}`,
336
+ ` Renewal: ${disclosure.renewal}`,
337
+ "",
338
+ ];
339
+ }
340
+
341
+ function printCancelledConsent(surface, options, io) {
342
+ const report = createLifecycleResult({
343
+ operation: "install", result: "preview", surface, scope: "global",
344
+ verification: { status: "unknown", evidence: ["cancelled_before_mutation"] },
345
+ runtime_checks: { requested: "cancelled", effective: "cancelled", reason: "consent_not_provided" },
346
+ restart: { required: false },
347
+ next_action: { kind: "none", text: "Installation was cancelled before any plugin or permission mutation." },
196
348
  });
349
+ printLifecycleResult(report, { json: options.json, compact: true, io });
350
+ return 0;
197
351
  }
198
352
 
199
353
  function printVerboseHostOutput(installed, options, io) {
@@ -342,9 +496,70 @@ export async function runCli(argv = process.argv.slice(2), adapters = {}) {
342
496
  }
343
497
  const command = resolveCommand(options.target);
344
498
  if (!command) throw new Error(`No handler is registered for ${options.target}.`);
345
- const handler = createHandlers({ io, env, exec: adapters.exec, prepare: adapters.prepare, openCodePackageSource: adapters.openCodePackageSource }).get(command.handler);
499
+ const handler = createHandlers({
500
+ io,
501
+ env,
502
+ exec: adapters.exec,
503
+ prepare: adapters.prepare,
504
+ openCodePackageSource: adapters.openCodePackageSource,
505
+ askRuntimeCheckDecision: adapters.askRuntimeCheckDecision ?? defaultAskRuntimeCheckDecision,
506
+ interactive: adapters.interactive ?? (Boolean(process.stdin.isTTY) && Boolean(process.stdout.isTTY)),
507
+ }).get(command.handler);
346
508
  if (!handler) throw new Error(`No implementation is registered for ${command.name}.`);
347
509
  return await handler(options);
348
510
  }
349
511
 
350
512
  export const main = runCli;
513
+
514
+ async function defaultAskRuntimeCheckDecision(disclosure) {
515
+ if (process.stdin.isTTY && typeof process.stdin.setRawMode === "function") {
516
+ return askRuntimeCheckDecisionByKey(process.stdin, process.stdout, disclosure);
517
+ }
518
+ const prompt = createInterface({ input: process.stdin, output: process.stdout });
519
+ try {
520
+ while (true) {
521
+ const answer = (await prompt.question("Choice (1/2/D/cancel): ")).trim().toLowerCase();
522
+ if (["1", "e", "enable"].includes(answer)) return "enable";
523
+ if (["2", "m", "manual"].includes(answer)) return "manual";
524
+ if (["d", "details"].includes(answer)) {
525
+ for (const line of installConsentTechnicalDetails(disclosure)) process.stdout.write(`${line}\n`);
526
+ continue;
527
+ }
528
+ if (["c", "cancel"].includes(answer)) return "cancel";
529
+ process.stdout.write("Press 1, 2 or D, or type cancel.\n");
530
+ }
531
+ } finally {
532
+ prompt.close();
533
+ }
534
+ }
535
+
536
+ export async function askRuntimeCheckDecisionByKey(input, output, disclosure) {
537
+ const wasRaw = Boolean(input.isRaw);
538
+ emitKeypressEvents(input);
539
+ output.write("Choice: ");
540
+ if (!wasRaw) input.setRawMode(true);
541
+ input.resume();
542
+
543
+ return await new Promise((resolve) => {
544
+ const finish = (decision) => {
545
+ input.off("keypress", onKeypress);
546
+ if (!wasRaw) input.setRawMode(false);
547
+ input.pause();
548
+ output.write(`${decision}\n`);
549
+ resolve(decision);
550
+ };
551
+ const onKeypress = (character, key = {}) => {
552
+ if (key.name === "escape" || (key.ctrl && key.name === "c")) return finish("cancel");
553
+ const choice = String(character || "").toLowerCase();
554
+ if (["1", "e"].includes(choice)) return finish("enable");
555
+ if (["2", "m"].includes(choice)) return finish("manual");
556
+ if (choice === "c") return finish("cancel");
557
+ if (choice === "d" && disclosure) {
558
+ output.write(`details\n${installConsentTechnicalDetails(disclosure).join("\n")}Choice: `);
559
+ return;
560
+ }
561
+ output.write("\nPress 1, 2, D or Esc.\nChoice: ");
562
+ };
563
+ input.on("keypress", onKeypress);
564
+ });
565
+ }
@@ -17,6 +17,7 @@ export const commandRegistry = Object.freeze([
17
17
  command("opencode", { preferred: [""], scaffold: [""] }),
18
18
  command("opencode-status", { preferred: [""], scaffold: [""] }),
19
19
  command("status", { preferred: [" [--surface <surface>] [--run <run_id>] [--json]"] }),
20
+ command("runtime-checks", { preferred: [" <status|enable|manual> --surface <codex|claude|opencode> [--json]"] }),
20
21
  command("disable", { preferred: [" --surface <surface> [--scope repository] [--dir <path>]"] }),
21
22
  command("uninstall", { preferred: [" --surface <surface> --scope global [--confirm]"] }),
22
23
  command("opencode-repo", { preferred: [""], scaffold: [""] }),
@@ -65,6 +66,12 @@ export function validateCommandOptions(options) {
65
66
  throw new Error("uninstall requires explicit --scope global");
66
67
  }
67
68
  if (options.confirm && options.target !== "uninstall") throw new Error("--confirm is supported only by uninstall");
69
+ if (options.runtimeChecksDecision && !["codex", "claude", "opencode"].includes(options.target)) {
70
+ throw new Error("--runtime-checks is supported only by codex, claude and opencode installation commands");
71
+ }
72
+ if (options.target === "runtime-checks" && !["codex", "claude", "opencode"].includes(options.surface)) {
73
+ throw new Error("runtime-checks requires --surface codex, claude or opencode");
74
+ }
68
75
  if (options.approvalEnvelope && options.target !== "gate-check") throw new Error("--approval-envelope is supported only by gate-check");
69
76
  if (options.approvalEnvelope && (options.json || options.statusCard || options.allActive)) {
70
77
  throw new Error("--approval-envelope cannot be combined with --json, --status-card or --all-active");
@@ -120,6 +127,8 @@ Options:
120
127
  --scope <repository|global>
121
128
  Select the lifecycle mutation scope
122
129
  --confirm Apply a previously previewed global uninstall plan
130
+ --runtime-checks <enable|manual|cancel>
131
+ Make the installation-time automatic-check decision explicitly; no TTY defaults to manual
123
132
  --fixture <path>
124
133
  Use deterministic evaluator/candidate fixtures instead of a live evaluator
125
134
  --persist Persist the redacted Delivery Path Search result under the current scope
@@ -45,6 +45,8 @@ export function parseArgs(argv, dependencies = {}) {
45
45
  let allActive = false;
46
46
  let scope;
47
47
  let confirm = false;
48
+ let runtimeChecksDecision;
49
+ let runtimeChecksAction;
48
50
 
49
51
  for (let i = 0; i < args.length; i += 1) {
50
52
  const arg = args[i];
@@ -60,6 +62,14 @@ export function parseArgs(argv, dependencies = {}) {
60
62
  if (arg === "--all-active") { allActive = true; continue; }
61
63
  if (arg === "--confirm") { confirm = true; continue; }
62
64
 
65
+ if (arg === "--runtime-checks") {
66
+ const next = requiredValue(args, i, arg);
67
+ if (!["enable", "manual", "cancel"].includes(next)) throw new CliUsageError("--runtime-checks must be enable, manual or cancel.");
68
+ runtimeChecksDecision = next;
69
+ i += 1;
70
+ continue;
71
+ }
72
+
63
73
  if (arg === "--run") {
64
74
  runId = requiredValue(args, i, arg);
65
75
  i += 1;
@@ -119,6 +129,11 @@ export function parseArgs(argv, dependencies = {}) {
119
129
  target = arg;
120
130
  continue;
121
131
  }
132
+ if (!arg.startsWith("-") && target === "runtime-checks" && !runtimeChecksAction) {
133
+ if (!["status", "enable", "manual"].includes(arg)) throw new CliUsageError("runtime-checks action must be status, enable or manual.");
134
+ runtimeChecksAction = arg;
135
+ continue;
136
+ }
122
137
  throw new CliUsageError(`Unknown argument: ${arg}`);
123
138
  }
124
139
 
@@ -147,6 +162,8 @@ export function parseArgs(argv, dependencies = {}) {
147
162
  allActive,
148
163
  scope,
149
164
  confirm,
165
+ runtimeChecksDecision,
166
+ runtimeChecksAction: runtimeChecksAction ?? "status",
150
167
  generatorModel,
151
168
  maxGeneratedCandidates,
152
169
  generationTimeoutMs,