create-agdf 0.13.7 → 0.14.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/README.md +31 -7
- package/generated/plugins/agdf/.claude-plugin/plugin.json +1 -1
- package/generated/plugins/agdf/.codex-plugin/plugin.json +2 -4
- package/generated/plugins/agdf/hooks/copilot-hooks.json +15 -0
- package/generated/plugins/agdf/hooks/hooks.json +3 -2
- package/generated/plugins/agdf/meta/agdf-plugin.definition.json +44 -2
- package/generated/plugins/agdf/plugin.json +22 -0
- package/generated/plugins/agdf/runtime/agdf-local.js +1 -1
- package/generated/plugins/agdf/runtime/agdf-session-check.js +97 -0
- package/generated/plugins/agdf/runtime/create-agdf/generated/plugins/agdf/meta/agdf-plugin.definition.json +44 -2
- package/generated/plugins/agdf/runtime/create-agdf/lib/cli/command-registry.js +10 -2
- package/generated/plugins/agdf/runtime/create-agdf/lib/cli/parse-args.js +17 -0
- package/generated/plugins/agdf/runtime/create-agdf/lib/runtime-check-consent/contract.js +73 -0
- package/generated/plugins/agdf/runtime/create-agdf/package.json +1 -1
- package/generated/plugins/agdf/runtime/runtime-manifest.json +2 -2
- package/generated/plugins/agdf/scripts/check-runtime-integrity.mjs +50 -5
- package/generated/plugins/agdf/submission/openai/availability.md +1 -1
- package/generated/plugins/agdf/submission/openai/capability-matrix.json +8 -1
- package/generated/plugins/agdf/submission/openai/release-notes.md +12 -1
- package/generated/plugins/agdf/submission/openai/reviewer-cases.json +23 -1
- package/generated/submissions/openai/agdf/.codex-plugin/plugin.json +1 -2
- package/generated/submissions/openai/agdf/meta/agdf-plugin.definition.json +44 -2
- package/generated/submissions/openai/agdf/submission/openai/availability.md +1 -1
- package/generated/submissions/openai/agdf/submission/openai/capability-matrix.json +8 -1
- package/generated/submissions/openai/agdf/submission/openai/inventory.json +9 -9
- package/generated/submissions/openai/agdf/submission/openai/listing.json +1 -1
- package/generated/submissions/openai/agdf/submission/openai/readiness.json +2 -2
- package/generated/submissions/openai/agdf/submission/openai/readiness.md +2 -2
- package/generated/submissions/openai/agdf/submission/openai/release-notes.md +12 -1
- package/generated/submissions/openai/agdf/submission/openai/reviewer-cases.json +23 -1
- package/lib/cli/application.js +291 -20
- package/lib/cli/command-registry.js +10 -2
- package/lib/cli/parse-args.js +17 -0
- package/lib/installers/copilot-precedence.js +34 -0
- package/lib/installers/copilot-settings.js +84 -0
- package/lib/installers/local-marketplace.js +2 -0
- package/lib/installers/opencode.js +21 -5
- package/lib/installers/plugin-installers.js +155 -4
- package/lib/lifecycle/operations.js +3 -0
- package/lib/lifecycle/presentation.js +43 -2
- package/lib/lifecycle/result.js +10 -0
- package/lib/lifecycle/status.js +7 -3
- package/lib/public-plugin/manifest.js +34 -2
- package/lib/public-plugin/validator.js +1 -1
- package/lib/release/version-coherence.js +1 -0
- package/lib/runtime-check-consent/adapters.js +42 -0
- package/lib/runtime-check-consent/claude-settings.js +71 -0
- package/lib/runtime-check-consent/contract.js +73 -0
- package/lib/runtime-check-consent/coordinator.js +38 -0
- package/lib/runtime-check-consent/service.js +181 -0
- package/lib/runtime-check-consent/state.js +90 -0
- package/lib/scaffold/plan.js +3 -89
- package/lib/scaffold/presentation.js +3 -22
- package/opencode-plugin.js +30 -15
- package/package.json +6 -3
- package/generated/.github/copilot-instructions.md +0 -16
- package/generated/.github/instructions/agdf-governance.instructions.md +0 -14
- package/generated/.github/skills/README.md +0 -20
- package/generated/AGENTS.md +0 -113
- /package/generated/{.github/skills → plugins/agdf/copilot-skills}/agdf-brownfield-analysis/SKILL.md +0 -0
- /package/generated/{.github/skills → plugins/agdf/copilot-skills}/agdf-clean-implementation-review/SKILL.md +0 -0
- /package/generated/{.github/skills → plugins/agdf/copilot-skills}/agdf-code-review/SKILL.md +0 -0
- /package/generated/{.github/skills → plugins/agdf/copilot-skills}/agdf-delivery-closeout/SKILL.md +0 -0
- /package/generated/{.github/skills → plugins/agdf/copilot-skills}/agdf-delivery-path-search/SKILL.md +0 -0
- /package/generated/{.github/skills → plugins/agdf/copilot-skills}/agdf-gate-check/SKILL.md +0 -0
- /package/generated/{.github/skills → plugins/agdf/copilot-skills}/agdf-interaction-locales.json +0 -0
- /package/generated/{.github/skills → plugins/agdf/copilot-skills}/agdf-qa-gate/SKILL.md +0 -0
- /package/generated/{.github/skills → plugins/agdf/copilot-skills}/agdf-release-or/SKILL.md +0 -0
- /package/generated/{.github/skills → plugins/agdf/copilot-skills}/agdf-runtime-contract.md +0 -0
- /package/generated/{.github/skills → plugins/agdf/copilot-skills}/agdf-task-plan-review/SKILL.md +0 -0
- /package/generated/{.github/skills → plugins/agdf/copilot-skills}/agdf-ux-intent-definition/SKILL.md +0 -0
- /package/generated/{.github/skills → plugins/agdf/copilot-skills}/contracts/closeout.md +0 -0
- /package/generated/{.github/skills → plugins/agdf/copilot-skills}/contracts/context-graph.md +0 -0
- /package/generated/{.github/skills → plugins/agdf/copilot-skills}/contracts/control-scaffold.md +0 -0
- /package/generated/{.github/skills → plugins/agdf/copilot-skills}/contracts/gate-transition.md +0 -0
- /package/generated/{.github/skills → plugins/agdf/copilot-skills}/contracts/interaction.md +0 -0
- /package/generated/{.github/skills → plugins/agdf/copilot-skills}/contracts/modes.md +0 -0
- /package/generated/{.github/skills → plugins/agdf/copilot-skills}/contracts/quality.md +0 -0
- /package/generated/{.github/skills → plugins/agdf/copilot-skills}/contracts/task-target-resolution.md +0 -0
package/lib/cli/application.js
CHANGED
|
@@ -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,
|
|
@@ -15,6 +17,7 @@ import {
|
|
|
15
17
|
} from "../installers/opencode.js";
|
|
16
18
|
import {
|
|
17
19
|
installClaudeGlobalPlugin,
|
|
20
|
+
installCopilotGlobalPlugin,
|
|
18
21
|
installCodexGlobalPlugin,
|
|
19
22
|
} from "../installers/plugin-installers.js";
|
|
20
23
|
import {
|
|
@@ -26,8 +29,9 @@ import {
|
|
|
26
29
|
} from "../lifecycle/operations.js";
|
|
27
30
|
import { printGeneralStatus, printLifecycleResult } from "../lifecycle/presentation.js";
|
|
28
31
|
import { createLifecycleResult, globalInstallRestartAction, lifecycleFailure } from "../lifecycle/result.js";
|
|
32
|
+
import { prepareInstallConsent, persistInstallConsent, retainCurrentInstallConsent, runtimeCheckStatus, setRuntimeChecksManual } from "../runtime-check-consent/service.js";
|
|
29
33
|
import { evaluateGeneralStatus } from "../lifecycle/status.js";
|
|
30
|
-
import {
|
|
34
|
+
import { generatedFilesForTarget } from "../scaffold/plan.js";
|
|
31
35
|
import { printNextSteps } from "../scaffold/presentation.js";
|
|
32
36
|
import { assertGeneratedWritePlan, writeGeneratedFile } from "../scaffold/write.js";
|
|
33
37
|
import { renderUsage, resolveCommand, validateCommandOptions } from "./command-registry.js";
|
|
@@ -35,19 +39,19 @@ import { CliUsageError, parseArgs } from "./parse-args.js";
|
|
|
35
39
|
import { pluginDefinition } from "./runtime-context.js";
|
|
36
40
|
import { createValidationHandlers } from "./validation-handlers.js";
|
|
37
41
|
|
|
38
|
-
function createHandlers({ io, env, exec, prepare, openCodePackageSource }) {
|
|
42
|
+
function createHandlers({ io, env, exec, packagedCopilotExec, prepare, openCodePackageSource, copilotSettingsPath, askRuntimeCheckDecision, interactive }) {
|
|
39
43
|
const installerAdapters = {
|
|
40
44
|
...(exec ? { exec } : {}),
|
|
45
|
+
...(packagedCopilotExec ? { packagedCopilotExec } : {}),
|
|
41
46
|
...(prepare ? { prepare } : {}),
|
|
47
|
+
...(copilotSettingsPath ? { copilotSettingsPath } : {}),
|
|
42
48
|
...(env.AGDF_DATA_DIR ? { dataRoot: env.AGDF_DATA_DIR } : {}),
|
|
43
49
|
};
|
|
44
50
|
const scaffoldHandler = (options) => runScaffold(options, io);
|
|
45
51
|
return new Map([
|
|
46
52
|
...createValidationHandlers(io),
|
|
47
53
|
["codex-repo", scaffoldHandler],
|
|
48
|
-
["copilot", scaffoldHandler],
|
|
49
54
|
["opencode-repo", scaffoldHandler],
|
|
50
|
-
["both", scaffoldHandler],
|
|
51
55
|
["init", scaffoldHandler],
|
|
52
56
|
["config", scaffoldHandler],
|
|
53
57
|
["run-create", (options) => {
|
|
@@ -75,19 +79,42 @@ function createHandlers({ io, env, exec, prepare, openCodePackageSource }) {
|
|
|
75
79
|
const report = evaluateGeneralStatus(options.dir, {
|
|
76
80
|
...options,
|
|
77
81
|
configDir: env.OPENCODE_CONFIG_DIR || defaultOpenCodeConfigDir(),
|
|
82
|
+
dataRoot: env.AGDF_DATA_DIR,
|
|
78
83
|
}, { exec });
|
|
79
84
|
printGeneralStatus(report, { json: options.json, io });
|
|
80
85
|
return report.installation.status === "healthy" ? 0 : 1;
|
|
81
86
|
}],
|
|
87
|
+
["runtime-checks", (options) => {
|
|
88
|
+
const report = options.runtimeChecksAction === "manual"
|
|
89
|
+
? setRuntimeChecksManual({ dataRoot: env.AGDF_DATA_DIR, surface: options.surface })
|
|
90
|
+
: runtimeCheckStatus(env.AGDF_DATA_DIR, options.surface);
|
|
91
|
+
if (options.runtimeChecksAction === "enable") {
|
|
92
|
+
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.`;
|
|
93
|
+
}
|
|
94
|
+
io.log(options.json ? JSON.stringify(report, null, 2) : [
|
|
95
|
+
"AGDF automatic runtime checks",
|
|
96
|
+
`Requested: ${report.requested}`,
|
|
97
|
+
`Effective: ${report.effective}`,
|
|
98
|
+
`Reason: ${report.reason}`,
|
|
99
|
+
...(report.next_action ? [`Next action: ${report.next_action}`] : []),
|
|
100
|
+
].join("\n"));
|
|
101
|
+
return report.effective === "enabled" || report.effective === "manual" ? 0 : 1;
|
|
102
|
+
}],
|
|
82
103
|
["disable", (options) => runDisable(options, { io, exec })],
|
|
83
104
|
["uninstall", (options) => runUninstall(options, { io, env, exec })],
|
|
84
|
-
["codex", (options) => {
|
|
105
|
+
["codex", async (options) => {
|
|
85
106
|
try {
|
|
107
|
+
const consent = await installConsentDecision("codex", options, { io, askRuntimeCheckDecision, interactive, dataRoot: installerAdapters.dataRoot });
|
|
108
|
+
if (consent.decision === "cancel") return printCancelledConsent("codex", options, io);
|
|
109
|
+
printInstallProgress("codex", options, io, interactive);
|
|
86
110
|
const installed = installCodexGlobalPlugin(installerAdapters);
|
|
111
|
+
const finalizedConsent = finalizeInstallConsent(consent, { surface: "codex", installed, dataRoot: installerAdapters.dataRoot });
|
|
87
112
|
printLifecycleResult(installResult(installed, {
|
|
88
113
|
restartRequired: true,
|
|
89
|
-
nextAction: globalInstallRestartAction(options.target).text,
|
|
90
|
-
|
|
114
|
+
nextAction: installNextAction("codex", finalizedConsent.state, globalInstallRestartAction(options.target).text),
|
|
115
|
+
runtimeChecks: finalizedConsent.state,
|
|
116
|
+
consentFailure: finalizedConsent.failure,
|
|
117
|
+
}), { json: options.json, compact: !options.verbose, io });
|
|
91
118
|
printVerboseHostOutput(installed, options, io);
|
|
92
119
|
return 0;
|
|
93
120
|
} catch (error) {
|
|
@@ -95,13 +122,19 @@ function createHandlers({ io, env, exec, prepare, openCodePackageSource }) {
|
|
|
95
122
|
return 1;
|
|
96
123
|
}
|
|
97
124
|
}],
|
|
98
|
-
["claude", (options) => {
|
|
125
|
+
["claude", async (options) => {
|
|
99
126
|
try {
|
|
127
|
+
const consent = await installConsentDecision("claude", options, { io, askRuntimeCheckDecision, interactive, dataRoot: installerAdapters.dataRoot });
|
|
128
|
+
if (consent.decision === "cancel") return printCancelledConsent("claude", options, io);
|
|
129
|
+
printInstallProgress("claude", options, io, interactive);
|
|
100
130
|
const installed = installClaudeGlobalPlugin(installerAdapters);
|
|
131
|
+
const finalizedConsent = finalizeInstallConsent(consent, { surface: "claude", installed, dataRoot: installerAdapters.dataRoot });
|
|
101
132
|
printLifecycleResult(installResult(installed, {
|
|
102
133
|
restartRequired: true,
|
|
103
134
|
nextAction: globalInstallRestartAction(options.target).text,
|
|
104
|
-
|
|
135
|
+
runtimeChecks: finalizedConsent.state,
|
|
136
|
+
consentFailure: finalizedConsent.failure,
|
|
137
|
+
}), { json: options.json, compact: !options.verbose, io });
|
|
105
138
|
printVerboseHostOutput(installed, options, io);
|
|
106
139
|
return 0;
|
|
107
140
|
} catch (error) {
|
|
@@ -109,9 +142,62 @@ function createHandlers({ io, env, exec, prepare, openCodePackageSource }) {
|
|
|
109
142
|
return 1;
|
|
110
143
|
}
|
|
111
144
|
}],
|
|
112
|
-
["
|
|
145
|
+
["copilot", async (options) => {
|
|
146
|
+
const surface = "copilot";
|
|
147
|
+
try {
|
|
148
|
+
const consent = await installConsentDecision(surface, options, { io, askRuntimeCheckDecision, interactive, dataRoot: installerAdapters.dataRoot });
|
|
149
|
+
if (consent.decision === "cancel") return printCancelledConsent(surface, options, io);
|
|
150
|
+
printInstallProgress(surface, options, io, interactive);
|
|
151
|
+
const installed = installCopilotGlobalPlugin({
|
|
152
|
+
...installerAdapters,
|
|
153
|
+
});
|
|
154
|
+
const finalizedConsent = finalizeInstallConsent(consent, { surface, installed, dataRoot: installerAdapters.dataRoot });
|
|
155
|
+
if (installed.declarativeConfigured) {
|
|
156
|
+
printLifecycleResult(createLifecycleResult({
|
|
157
|
+
operation: "install", result: "partial", surface, scope: "global",
|
|
158
|
+
version: { expected: installed.expectedVersion, installed: null, status: "unknown" },
|
|
159
|
+
verification: { status: "configured_pending_restart", evidence: installed.evidence },
|
|
160
|
+
installation: { status: "configured_pending_restart" },
|
|
161
|
+
activation: { status: "pending_restart" },
|
|
162
|
+
runtime_checks: finalizedConsent.state,
|
|
163
|
+
restart: { required: true, reason: "host_reload" },
|
|
164
|
+
next_action: { kind: "restart", text: "Restart GitHub Copilot. Then verify AGDF in Plugins and the agdf- skills in a fresh session." },
|
|
165
|
+
}), { json: options.json, compact: !options.verbose, io });
|
|
166
|
+
return 0;
|
|
167
|
+
}
|
|
168
|
+
if (installed.manualHandoff) {
|
|
169
|
+
printLifecycleResult(createLifecycleResult({
|
|
170
|
+
operation: "install", result: "partial", surface, scope: "global",
|
|
171
|
+
version: { expected: installed.expectedVersion, installed: null, status: "unknown" },
|
|
172
|
+
verification: { status: "unavailable", evidence: installed.evidence },
|
|
173
|
+
installation: { status: "not_verified" },
|
|
174
|
+
activation: { status: "not_verified" },
|
|
175
|
+
runtime_checks: finalizedConsent.state,
|
|
176
|
+
restart: { required: false, reason: "none" },
|
|
177
|
+
next_action: { kind: "manual_install", text: `In GitHub Copilot, run /plugins install ${installed.pluginRoot}. Then run /restart and verify /plugins list and /skills list.` },
|
|
178
|
+
failure: { phase: "executable", message: "Copilot CLI was not available; no plugin installation was performed." },
|
|
179
|
+
}), { json: options.json, compact: !options.verbose, io });
|
|
180
|
+
return 1;
|
|
181
|
+
}
|
|
182
|
+
printLifecycleResult(installResult(installed, {
|
|
183
|
+
restartRequired: true,
|
|
184
|
+
nextAction: installNextAction(surface, finalizedConsent.state, globalInstallRestartAction(surface).text),
|
|
185
|
+
runtimeChecks: finalizedConsent.state,
|
|
186
|
+
consentFailure: finalizedConsent.failure,
|
|
187
|
+
}), { json: options.json, compact: !options.verbose, io });
|
|
188
|
+
printVerboseHostOutput(installed, options, io);
|
|
189
|
+
return installed.verificationStatus === "healthy" ? 0 : 1;
|
|
190
|
+
} catch (error) {
|
|
191
|
+
printInstallFailure(surface, error, options, io, "copilot");
|
|
192
|
+
return 1;
|
|
193
|
+
}
|
|
194
|
+
}],
|
|
195
|
+
["opencode", async (options) => {
|
|
113
196
|
const configDir = options.dirExplicit ? options.dir : defaultOpenCodeConfigDir();
|
|
114
197
|
try {
|
|
198
|
+
const consent = await installConsentDecision("opencode", options, { io, askRuntimeCheckDecision, interactive, dataRoot: installerAdapters.dataRoot });
|
|
199
|
+
if (consent.decision === "cancel") return printCancelledConsent("opencode", options, io);
|
|
200
|
+
printInstallProgress("opencode", options, io, interactive);
|
|
115
201
|
const result = runLifecyclePhase("plugin_operation", () => installOpenCodeGlobalPlugin(configDir, { packageSource: openCodePackageSource }));
|
|
116
202
|
runLifecyclePhase("global_surface", () => installOpenCodeGlobalSurface(configDir));
|
|
117
203
|
const report = runLifecyclePhase("verification", () => evaluateOpenCodeStatus(options.dir, configDir, result.transition));
|
|
@@ -128,9 +214,18 @@ function createHandlers({ io, env, exec, prepare, openCodePackageSource }) {
|
|
|
128
214
|
kind: "recovery",
|
|
129
215
|
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
216
|
};
|
|
217
|
+
const finalizedConsent = finalizeInstallConsent(consent, {
|
|
218
|
+
surface: "opencode",
|
|
219
|
+
installed: {
|
|
220
|
+
pluginRoot: result.installed_package.root,
|
|
221
|
+
digest: result.installed_package.digest,
|
|
222
|
+
sourceDigest: result.package_source.digest || result.installed_package.digest,
|
|
223
|
+
},
|
|
224
|
+
dataRoot: installerAdapters.dataRoot,
|
|
225
|
+
});
|
|
131
226
|
printLifecycleResult(createLifecycleResult({
|
|
132
227
|
operation: result.transition.status === "updated" ? "update" : "install",
|
|
133
|
-
result: verificationHealthy ? "success" : "partial",
|
|
228
|
+
result: verificationHealthy && !finalizedConsent.failure ? "success" : "partial",
|
|
134
229
|
surface: options.target,
|
|
135
230
|
scope: "global",
|
|
136
231
|
version: {
|
|
@@ -154,9 +249,11 @@ function createHandlers({ io, env, exec, prepare, openCodePackageSource }) {
|
|
|
154
249
|
],
|
|
155
250
|
},
|
|
156
251
|
restart: { required: true, reason: "host_reload" },
|
|
252
|
+
runtime_checks: finalizedConsent.state,
|
|
157
253
|
next_action: nextAction,
|
|
158
|
-
|
|
159
|
-
|
|
254
|
+
failure: finalizedConsent.failure,
|
|
255
|
+
}), { json: options.json, compact: !options.verbose, io });
|
|
256
|
+
if (options.verbose && !options.json) {
|
|
160
257
|
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
258
|
io.log(`Plugin SDK alignment: ${result.sdk_alignment.status} (target ${result.sdk_alignment.target_version || "unknown"}; installed ${result.sdk_alignment.installed_version || "unknown"})`);
|
|
162
259
|
io.log(`Experimental hook declarations: ${report.experimental_hooks.aggregate} (SDK declaration evidence; live invocation not observed)`);
|
|
@@ -180,10 +277,20 @@ function runLifecyclePhase(phase, operation) {
|
|
|
180
277
|
}
|
|
181
278
|
}
|
|
182
279
|
|
|
183
|
-
function
|
|
280
|
+
function installNextAction(surface, runtimeChecks, fallback) {
|
|
281
|
+
if (surface === "codex" && runtimeChecks.requested === "enabled" && runtimeChecks.effective === "decision_required") {
|
|
282
|
+
return "Restart Codex, then approve the AGDF session hook when Codex asks.";
|
|
283
|
+
}
|
|
284
|
+
if (surface === "copilot" && runtimeChecks.requested === "enabled" && runtimeChecks.effective === "decision_required") {
|
|
285
|
+
return "Restart GitHub Copilot, then review the AGDF session hook when Copilot asks.";
|
|
286
|
+
}
|
|
287
|
+
return fallback;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
function installResult(installed, { restartRequired, nextAction, runtimeChecks, consentFailure }) {
|
|
184
291
|
return createLifecycleResult({
|
|
185
292
|
operation: installed.operation ?? "install",
|
|
186
|
-
result: "success",
|
|
293
|
+
result: consentFailure ? "partial" : "success",
|
|
187
294
|
surface: installed.surface,
|
|
188
295
|
scope: "global",
|
|
189
296
|
version: { expected: installed.expectedVersion, installed: installed.installedVersion, status: installed.installedVersion ? "verified" : "unknown" },
|
|
@@ -191,18 +298,120 @@ function installResult(installed, { restartRequired, nextAction }) {
|
|
|
191
298
|
installation: { status: installed.verificationStatus },
|
|
192
299
|
activation: { status: restartRequired ? "pending_restart" : "active" },
|
|
193
300
|
delivery: { status: "not_evaluated" },
|
|
301
|
+
runtime_checks: runtimeChecks,
|
|
194
302
|
restart: { required: restartRequired, reason: restartRequired ? "host_reload" : "none" },
|
|
195
303
|
next_action: { kind: restartRequired ? "restart" : "prompt", text: nextAction },
|
|
304
|
+
failure: consentFailure,
|
|
196
305
|
});
|
|
197
306
|
}
|
|
198
307
|
|
|
308
|
+
function finalizeInstallConsent(consent, input) {
|
|
309
|
+
if (!consent.persist) return {
|
|
310
|
+
state: consent.retained && consent.decision === "enable"
|
|
311
|
+
? { requested: "enabled", effective: "decision_required", reason: "host_permission_unverified" }
|
|
312
|
+
: { requested: "manual", effective: "manual", reason: "consent_not_provided" },
|
|
313
|
+
failure: null,
|
|
314
|
+
};
|
|
315
|
+
try {
|
|
316
|
+
return { state: persistInstallConsent({ ...input, decision: consent.decision }), failure: null };
|
|
317
|
+
} catch (error) {
|
|
318
|
+
return {
|
|
319
|
+
state: { requested: consent.decision === "enable" ? "enabled" : "manual", effective: "failed", reason: "configuration_invalid" },
|
|
320
|
+
failure: { phase: "runtime_check_permission", message: error.message },
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
async function installConsentDecision(surface, options, { io, askRuntimeCheckDecision, interactive, dataRoot }) {
|
|
326
|
+
if (options.runtimeChecksDecision !== undefined) return prepareInstallConsent(surface, options);
|
|
327
|
+
if (!interactive || options.json || typeof askRuntimeCheckDecision !== "function") {
|
|
328
|
+
return prepareInstallConsent(surface, options);
|
|
329
|
+
}
|
|
330
|
+
const retained = retainCurrentInstallConsent(surface, dataRoot);
|
|
331
|
+
const disclosure = prepareInstallConsent(surface, { ...options, runtimeChecksDecision: "manual" }).disclosure;
|
|
332
|
+
printInstallConsentDisclosure(disclosure, retained, io);
|
|
333
|
+
const answer = await askRuntimeCheckDecision(disclosure);
|
|
334
|
+
return prepareInstallConsent(surface, { ...options, runtimeChecksDecision: answer });
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
function printInstallConsentDisclosure(disclosure, retained, io) {
|
|
338
|
+
const host = installSurfaceLabel(disclosure.surface);
|
|
339
|
+
io.log("");
|
|
340
|
+
io.log(`AGDF ${pluginDefinition.version} for ${host}`);
|
|
341
|
+
io.log(`Applies to this ${host} installation for your user account.`);
|
|
342
|
+
io.log("");
|
|
343
|
+
io.log(`Let AGDF check your project status automatically ${disclosure.when}?`);
|
|
344
|
+
if (retained) {
|
|
345
|
+
io.log(retained.decision === "enable"
|
|
346
|
+
? "Your previous choice: automatic checks requested"
|
|
347
|
+
: "Your previous choice: manual checks");
|
|
348
|
+
if (retained.decision === "enable") io.log(`${host} permission: checked after installation`);
|
|
349
|
+
}
|
|
350
|
+
io.log("");
|
|
351
|
+
io.log("Safe by design");
|
|
352
|
+
io.log(" Reads only AGDF runtime information and .agdf/control in this project");
|
|
353
|
+
io.log(" Changes no project files and uses no network");
|
|
354
|
+
io.log(" Never approves AGDF work");
|
|
355
|
+
io.log("");
|
|
356
|
+
io.log(`AGDF saves your choice. ${host} remains in control of permission.`);
|
|
357
|
+
io.log("You choose again for every install or update and whenever the check changes.");
|
|
358
|
+
io.log(`Turn it off anytime: ${disclosure.revocation}`);
|
|
359
|
+
io.log("");
|
|
360
|
+
io.log("Choose");
|
|
361
|
+
io.log(" [1] Yes, check automatically");
|
|
362
|
+
io.log(" [2] No automatic checks");
|
|
363
|
+
io.log(" AGDF still works. Checks run when you request them.");
|
|
364
|
+
io.log(" [D] Show technical details");
|
|
365
|
+
io.log(" [Esc] Cancel installation");
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
function installSurfaceLabel(surface) {
|
|
369
|
+
if (surface === "claude") return "Claude Code";
|
|
370
|
+
if (surface === "copilot") return "GitHub Copilot";
|
|
371
|
+
if (surface === "opencode") return "OpenCode";
|
|
372
|
+
return "Codex";
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
function printInstallProgress(surface, options, io, interactive) {
|
|
376
|
+
if (!interactive || options.json) return;
|
|
377
|
+
io.log("");
|
|
378
|
+
io.log(`Setting up AGDF ${pluginDefinition.version} for ${installSurfaceLabel(surface)}...`);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
function installConsentTechnicalDetails(disclosure) {
|
|
382
|
+
return [
|
|
383
|
+
"",
|
|
384
|
+
"Technical details",
|
|
385
|
+
` Applies to: ${disclosure.installation_scope}`,
|
|
386
|
+
` Runs: ${disclosure.runs} ${disclosure.when}`,
|
|
387
|
+
` Reads: ${disclosure.reads}`,
|
|
388
|
+
` Saves: ${disclosure.writes}`,
|
|
389
|
+
` Permission control: ${disclosure.permission_owner}`,
|
|
390
|
+
` Command: ${disclosure.executable}`,
|
|
391
|
+
` Renewal: ${disclosure.renewal}`,
|
|
392
|
+
"",
|
|
393
|
+
];
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
function printCancelledConsent(surface, options, io) {
|
|
397
|
+
const report = createLifecycleResult({
|
|
398
|
+
operation: "install", result: "preview", surface, scope: "global",
|
|
399
|
+
verification: { status: "unknown", evidence: ["cancelled_before_mutation"] },
|
|
400
|
+
runtime_checks: { requested: "cancelled", effective: "cancelled", reason: "consent_not_provided" },
|
|
401
|
+
restart: { required: false },
|
|
402
|
+
next_action: { kind: "none", text: "Installation was cancelled before any plugin or permission mutation." },
|
|
403
|
+
});
|
|
404
|
+
printLifecycleResult(report, { json: options.json, compact: true, io });
|
|
405
|
+
return 0;
|
|
406
|
+
}
|
|
407
|
+
|
|
199
408
|
function printVerboseHostOutput(installed, options, io) {
|
|
200
409
|
if (!options.verbose || options.json || !installed.nativeOutput?.length) return;
|
|
201
410
|
io.log("Host command output:");
|
|
202
411
|
for (const output of installed.nativeOutput) io.log(output);
|
|
203
412
|
}
|
|
204
413
|
|
|
205
|
-
function printInstallFailure(surface, error, options, io) {
|
|
414
|
+
function printInstallFailure(surface, error, options, io, command = surface) {
|
|
206
415
|
const report = lifecycleFailure({
|
|
207
416
|
operation: "install",
|
|
208
417
|
surface,
|
|
@@ -210,7 +419,7 @@ function printInstallFailure(surface, error, options, io) {
|
|
|
210
419
|
phase: error.phase || "plugin_operation",
|
|
211
420
|
message: error.message,
|
|
212
421
|
evidence: [error.evidence ?? {}],
|
|
213
|
-
nextAction: `Resolve the ${error.phase || "plugin operation"} failure and rerun npx --yes @agdf/cli@latest ${
|
|
422
|
+
nextAction: `Resolve the ${error.phase || "plugin operation"} failure and rerun npx --yes @agdf/cli@latest ${command}.`,
|
|
214
423
|
});
|
|
215
424
|
if (options.json) printLifecycleResult(report, { json: true, io });
|
|
216
425
|
else {
|
|
@@ -291,7 +500,6 @@ function runUninstall(options, { io, env, exec }) {
|
|
|
291
500
|
|
|
292
501
|
function runScaffold(options, io) {
|
|
293
502
|
const files = generatedFilesForTarget(options.target, options.dir, options.force, options.language);
|
|
294
|
-
const wroteAgentsFragment = files.some((file) => file.path === agdfFragmentPath);
|
|
295
503
|
let removedOpenCodeAgents = [];
|
|
296
504
|
|
|
297
505
|
try {
|
|
@@ -306,7 +514,7 @@ function runScaffold(options, io) {
|
|
|
306
514
|
return 1;
|
|
307
515
|
}
|
|
308
516
|
|
|
309
|
-
printNextSteps(options.target, options.dir, files,
|
|
517
|
+
printNextSteps(options.target, options.dir, files, removedOpenCodeAgents, { verbose: options.verbose, json: options.json, io });
|
|
310
518
|
return 0;
|
|
311
519
|
}
|
|
312
520
|
|
|
@@ -342,9 +550,72 @@ export async function runCli(argv = process.argv.slice(2), adapters = {}) {
|
|
|
342
550
|
}
|
|
343
551
|
const command = resolveCommand(options.target);
|
|
344
552
|
if (!command) throw new Error(`No handler is registered for ${options.target}.`);
|
|
345
|
-
const handler = createHandlers({
|
|
553
|
+
const handler = createHandlers({
|
|
554
|
+
io,
|
|
555
|
+
env,
|
|
556
|
+
exec: adapters.exec,
|
|
557
|
+
packagedCopilotExec: adapters.packagedCopilotExec,
|
|
558
|
+
prepare: adapters.prepare,
|
|
559
|
+
openCodePackageSource: adapters.openCodePackageSource,
|
|
560
|
+
copilotSettingsPath: adapters.copilotSettingsPath,
|
|
561
|
+
askRuntimeCheckDecision: adapters.askRuntimeCheckDecision ?? defaultAskRuntimeCheckDecision,
|
|
562
|
+
interactive: adapters.interactive ?? (Boolean(process.stdin.isTTY) && Boolean(process.stdout.isTTY)),
|
|
563
|
+
}).get(command.handler);
|
|
346
564
|
if (!handler) throw new Error(`No implementation is registered for ${command.name}.`);
|
|
347
565
|
return await handler(options);
|
|
348
566
|
}
|
|
349
567
|
|
|
350
568
|
export const main = runCli;
|
|
569
|
+
|
|
570
|
+
async function defaultAskRuntimeCheckDecision(disclosure) {
|
|
571
|
+
if (process.stdin.isTTY && typeof process.stdin.setRawMode === "function") {
|
|
572
|
+
return askRuntimeCheckDecisionByKey(process.stdin, process.stdout, disclosure);
|
|
573
|
+
}
|
|
574
|
+
const prompt = createInterface({ input: process.stdin, output: process.stdout });
|
|
575
|
+
try {
|
|
576
|
+
while (true) {
|
|
577
|
+
const answer = (await prompt.question("Choice (1/2/D/cancel): ")).trim().toLowerCase();
|
|
578
|
+
if (["1", "e", "enable"].includes(answer)) return "enable";
|
|
579
|
+
if (["2", "m", "manual"].includes(answer)) return "manual";
|
|
580
|
+
if (["d", "details"].includes(answer)) {
|
|
581
|
+
for (const line of installConsentTechnicalDetails(disclosure)) process.stdout.write(`${line}\n`);
|
|
582
|
+
continue;
|
|
583
|
+
}
|
|
584
|
+
if (["c", "cancel"].includes(answer)) return "cancel";
|
|
585
|
+
process.stdout.write("Press 1, 2 or D, or type cancel.\n");
|
|
586
|
+
}
|
|
587
|
+
} finally {
|
|
588
|
+
prompt.close();
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
export async function askRuntimeCheckDecisionByKey(input, output, disclosure) {
|
|
593
|
+
const wasRaw = Boolean(input.isRaw);
|
|
594
|
+
emitKeypressEvents(input);
|
|
595
|
+
output.write("Choice: ");
|
|
596
|
+
if (!wasRaw) input.setRawMode(true);
|
|
597
|
+
input.resume();
|
|
598
|
+
|
|
599
|
+
return await new Promise((resolve) => {
|
|
600
|
+
const finish = (decision) => {
|
|
601
|
+
input.off("keypress", onKeypress);
|
|
602
|
+
if (!wasRaw) input.setRawMode(false);
|
|
603
|
+
input.pause();
|
|
604
|
+
output.write(`${decision}\n`);
|
|
605
|
+
resolve(decision);
|
|
606
|
+
};
|
|
607
|
+
const onKeypress = (character, key = {}) => {
|
|
608
|
+
if (key.name === "escape" || (key.ctrl && key.name === "c")) return finish("cancel");
|
|
609
|
+
const choice = String(character || "").toLowerCase();
|
|
610
|
+
if (["1", "e"].includes(choice)) return finish("enable");
|
|
611
|
+
if (["2", "m"].includes(choice)) return finish("manual");
|
|
612
|
+
if (choice === "c") return finish("cancel");
|
|
613
|
+
if (choice === "d" && disclosure) {
|
|
614
|
+
output.write(`details\n${installConsentTechnicalDetails(disclosure).join("\n")}Choice: `);
|
|
615
|
+
return;
|
|
616
|
+
}
|
|
617
|
+
output.write("\nPress 1, 2, D or Esc.\nChoice: ");
|
|
618
|
+
};
|
|
619
|
+
input.on("keypress", onKeypress);
|
|
620
|
+
});
|
|
621
|
+
}
|
|
@@ -13,14 +13,14 @@ export const commandRegistry = Object.freeze([
|
|
|
13
13
|
command("codex", { preferred: [""], scaffold: [""] }),
|
|
14
14
|
command("codex-repo", { preferred: [""], scaffold: [""] }),
|
|
15
15
|
command("claude", { preferred: [""], scaffold: [""] }),
|
|
16
|
-
command("copilot", { preferred: [""]
|
|
16
|
+
command("copilot", { preferred: [""] }),
|
|
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|copilot|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: [""] }),
|
|
23
|
-
command("both", { scaffold: [""] }),
|
|
24
24
|
command("init", { preferred: [""], scaffold: [""] }),
|
|
25
25
|
command("config", { scaffold: [" --language de"] }),
|
|
26
26
|
command("doctor", { local: [""], scaffold: [""], legacy: [" --json"] }),
|
|
@@ -65,6 +65,12 @@ export function validateCommandOptions(options) {
|
|
|
65
65
|
throw new Error("uninstall requires explicit --scope global");
|
|
66
66
|
}
|
|
67
67
|
if (options.confirm && options.target !== "uninstall") throw new Error("--confirm is supported only by uninstall");
|
|
68
|
+
if (options.runtimeChecksDecision && !["codex", "claude", "copilot", "opencode"].includes(options.target)) {
|
|
69
|
+
throw new Error("--runtime-checks is supported only by codex, claude, copilot and opencode installation commands");
|
|
70
|
+
}
|
|
71
|
+
if (options.target === "runtime-checks" && !["codex", "claude", "copilot", "opencode"].includes(options.surface)) {
|
|
72
|
+
throw new Error("runtime-checks requires --surface codex, claude, copilot or opencode");
|
|
73
|
+
}
|
|
68
74
|
if (options.approvalEnvelope && options.target !== "gate-check") throw new Error("--approval-envelope is supported only by gate-check");
|
|
69
75
|
if (options.approvalEnvelope && (options.json || options.statusCard || options.allActive)) {
|
|
70
76
|
throw new Error("--approval-envelope cannot be combined with --json, --status-card or --all-active");
|
|
@@ -120,6 +126,8 @@ Options:
|
|
|
120
126
|
--scope <repository|global>
|
|
121
127
|
Select the lifecycle mutation scope
|
|
122
128
|
--confirm Apply a previously previewed global uninstall plan
|
|
129
|
+
--runtime-checks <enable|manual|cancel>
|
|
130
|
+
Make the installation-time automatic-check decision explicitly; no TTY defaults to manual
|
|
123
131
|
--fixture <path>
|
|
124
132
|
Use deterministic evaluator/candidate fixtures instead of a live evaluator
|
|
125
133
|
--persist Persist the redacted Delivery Path Search result under the current scope
|
package/lib/cli/parse-args.js
CHANGED
|
@@ -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,
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
function skillNames(values, field) {
|
|
2
|
+
if (!Array.isArray(values) || values.some((value) => typeof value !== "string" || !value.trim())) {
|
|
3
|
+
throw new Error(`AGDF_COPILOT_PRECEDENCE_INVALID: ${field}`);
|
|
4
|
+
}
|
|
5
|
+
const normalized = values.map((value) => value.trim());
|
|
6
|
+
if (new Set(normalized).size !== normalized.length) throw new Error(`AGDF_COPILOT_PRECEDENCE_DUPLICATE: ${field}`);
|
|
7
|
+
return new Set(normalized);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function diagnoseCopilotSkillPrecedence({ declaredPluginSkills, projectSkills = [], personalSkills = [] } = {}) {
|
|
11
|
+
const plugin = skillNames(declaredPluginSkills, "declaredPluginSkills");
|
|
12
|
+
const project = skillNames(projectSkills, "projectSkills");
|
|
13
|
+
const personal = skillNames(personalSkills, "personalSkills");
|
|
14
|
+
const skills = [...plugin].sort().map((name) => {
|
|
15
|
+
const projectMatch = project.has(name);
|
|
16
|
+
const personalMatch = personal.has(name);
|
|
17
|
+
return Object.freeze({
|
|
18
|
+
name,
|
|
19
|
+
effective_source: projectMatch ? "project" : personalMatch ? "personal" : "plugin",
|
|
20
|
+
plugin_loaded: !projectMatch && !personalMatch,
|
|
21
|
+
collisions: Object.freeze([
|
|
22
|
+
...(projectMatch ? ["project"] : []),
|
|
23
|
+
...(personalMatch ? ["personal"] : []),
|
|
24
|
+
]),
|
|
25
|
+
mutation: "none",
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
return Object.freeze({
|
|
29
|
+
schema_version: 1,
|
|
30
|
+
precedence: Object.freeze(["project", "personal", "plugin"]),
|
|
31
|
+
skills: Object.freeze(skills),
|
|
32
|
+
destructive_override: false,
|
|
33
|
+
});
|
|
34
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { chmodSync, existsSync, lstatSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { dirname, isAbsolute, join, resolve } from "node:path";
|
|
4
|
+
import process from "node:process";
|
|
5
|
+
import { renameSyncWithRetry } from "../fs-swap.js";
|
|
6
|
+
|
|
7
|
+
export function defaultCopilotSettingsPath({ env = process.env, home = homedir() } = {}) {
|
|
8
|
+
return join(resolve(env.COPILOT_HOME || join(home, ".copilot")), "settings.json");
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function readCopilotSettings(path = defaultCopilotSettingsPath()) {
|
|
12
|
+
if (!existsSync(path)) return { exists: false, settings: {} };
|
|
13
|
+
const stats = lstatSync(path);
|
|
14
|
+
if (!stats.isFile() || stats.isSymbolicLink()) throw new Error("AGDF_COPILOT_SETTINGS_UNOWNED_PATH");
|
|
15
|
+
try {
|
|
16
|
+
const settings = JSON.parse(readFileSync(path, "utf8"));
|
|
17
|
+
if (!settings || typeof settings !== "object" || Array.isArray(settings)) throw new Error("not an object");
|
|
18
|
+
if (settings.enabledPlugins !== undefined
|
|
19
|
+
&& (!settings.enabledPlugins || typeof settings.enabledPlugins !== "object" || Array.isArray(settings.enabledPlugins))) {
|
|
20
|
+
throw new Error("enabledPlugins must be an object");
|
|
21
|
+
}
|
|
22
|
+
return { exists: true, settings };
|
|
23
|
+
} catch (error) {
|
|
24
|
+
throw new Error(`AGDF_COPILOT_SETTINGS_INVALID: ${error.message}`);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function atomicSettingsWrite(path, settings) {
|
|
29
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
30
|
+
const temp = `${path}.agdf-tmp-${process.pid}`;
|
|
31
|
+
const backup = `${path}.agdf-backup-${process.pid}`;
|
|
32
|
+
const hadPrevious = existsSync(path);
|
|
33
|
+
const mode = hadPrevious ? lstatSync(path).mode & 0o777 : 0o600;
|
|
34
|
+
writeFileSync(temp, `${JSON.stringify(settings, null, 2)}\n`, { encoding: "utf8", flag: "wx", mode });
|
|
35
|
+
if (process.platform !== "win32") chmodSync(temp, mode);
|
|
36
|
+
try {
|
|
37
|
+
if (hadPrevious && process.platform === "win32") renameSyncWithRetry(path, backup);
|
|
38
|
+
renameSyncWithRetry(temp, path);
|
|
39
|
+
if (hadPrevious && process.platform === "win32") rmSync(backup, { force: true });
|
|
40
|
+
} catch (error) {
|
|
41
|
+
try { rmSync(temp, { force: true }); } catch {}
|
|
42
|
+
if (hadPrevious && process.platform === "win32" && existsSync(backup) && !existsSync(path)) renameSyncWithRetry(backup, path);
|
|
43
|
+
throw error;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function configureCopilotDeclarativePlugin({ path = defaultCopilotSettingsPath(), pluginRoot }) {
|
|
48
|
+
if (typeof pluginRoot !== "string" || !pluginRoot.trim() || !isAbsolute(pluginRoot)) {
|
|
49
|
+
throw new Error("AGDF_COPILOT_PLUGIN_SPEC_INVALID");
|
|
50
|
+
}
|
|
51
|
+
const spec = resolve(pluginRoot);
|
|
52
|
+
if (!existsSync(spec) || !lstatSync(spec).isDirectory() || lstatSync(spec).isSymbolicLink()) {
|
|
53
|
+
throw new Error("AGDF_COPILOT_PLUGIN_SPEC_UNAVAILABLE");
|
|
54
|
+
}
|
|
55
|
+
const before = readCopilotSettings(path);
|
|
56
|
+
const next = structuredClone(before.settings);
|
|
57
|
+
next.enabledPlugins = { ...(next.enabledPlugins ?? {}), [spec]: true };
|
|
58
|
+
atomicSettingsWrite(path, next);
|
|
59
|
+
const verified = readCopilotSettings(path);
|
|
60
|
+
if (verified.settings.enabledPlugins?.[spec] !== true) throw new Error("AGDF_COPILOT_SETTINGS_VERIFICATION_FAILED");
|
|
61
|
+
return {
|
|
62
|
+
status: "configured_pending_restart",
|
|
63
|
+
reason: "host_activation_unverified",
|
|
64
|
+
path,
|
|
65
|
+
spec,
|
|
66
|
+
rollback() {
|
|
67
|
+
if (before.exists) atomicSettingsWrite(path, before.settings);
|
|
68
|
+
else rmSync(path, { force: true });
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function revokeCopilotDeclarativePlugin({ path = defaultCopilotSettingsPath(), pluginRoot }) {
|
|
74
|
+
const before = readCopilotSettings(path);
|
|
75
|
+
if (!before.exists) return { status: "not_configured", path };
|
|
76
|
+
const spec = resolve(pluginRoot);
|
|
77
|
+
const next = structuredClone(before.settings);
|
|
78
|
+
if (next.enabledPlugins && Object.hasOwn(next.enabledPlugins, spec)) {
|
|
79
|
+
delete next.enabledPlugins[spec];
|
|
80
|
+
if (Object.keys(next.enabledPlugins).length === 0) delete next.enabledPlugins;
|
|
81
|
+
atomicSettingsWrite(path, next);
|
|
82
|
+
}
|
|
83
|
+
return { status: "removed", path, spec };
|
|
84
|
+
}
|