@@ -38,9 +38,11 @@ import type { VariantAnalysisResultsManager } from "./variant-analysis-results-m
3838import {
3939 getAutofixPath ,
4040 getAutofixModel ,
41+ getAutofixCapiDevKey ,
4142 downloadTimeout ,
4243 AUTOFIX_PATH ,
4344 AUTOFIX_MODEL ,
45+ AUTOFIX_CAPI_DEV_KEY ,
4446} from "../config" ;
4547import { asError , getErrorMessage } from "../common/helpers-pure" ;
4648import { createTimeoutSignal } from "../common/fetch-stream" ;
@@ -155,6 +157,29 @@ async function findLocalAutofix(): Promise<string> {
155157 return localAutofixPath ;
156158}
157159
160+ async function findCapiDevKey ( ) : Promise < string > {
161+ let capiDevKey = getAutofixCapiDevKey ( ) || "env:CAPI_DEV_KEY" ;
162+
163+ if ( capiDevKey . startsWith ( "env:" ) ) {
164+ const envVarName = capiDevKey . substring ( "env:" . length ) ;
165+ capiDevKey = process . env [ envVarName ] || "" ;
166+ } else if ( capiDevKey . startsWith ( "op://" ) ) {
167+ capiDevKey = await opRead ( capiDevKey ) ;
168+ } else {
169+ // Don't allow literal keys for security reasons
170+ throw new Error (
171+ `Invalid CAPI dev key format. Use 'env:<ENV_VAR_NAME>' or 'op://<1PASSWORD_SECRET_REFERENCE>'.` ,
172+ ) ;
173+ }
174+
175+ if ( ! capiDevKey ) {
176+ throw new Error (
177+ `Copilot API dev key not found. Make sure ${ AUTOFIX_CAPI_DEV_KEY . qualifiedName } is set correctly.` ,
178+ ) ;
179+ }
180+ return capiDevKey ;
181+ }
182+
158183/**
159184 * Overrides the query help from a given variant analysis
160185 * at a location within the `localAutofixPath` directory .
@@ -758,7 +783,7 @@ async function runAutofixOnResults(
758783 {
759784 cwd : workDir ,
760785 env : {
761- CAPI_DEV_KEY : process . env . CAPI_DEV_KEY ,
786+ CAPI_DEV_KEY : await findCapiDevKey ( ) ,
762787 PATH : process . env . PATH ,
763788 } ,
764789 } ,
@@ -828,6 +853,42 @@ function execAutofix(
828853 } ) ;
829854}
830855
856+ /** Execute the 1Password CLI command `op read <secretReference>`, if the `op` command exists on the PATH. */
857+ async function opRead ( secretReference : string ) : Promise < string > {
858+ return new Promise ( ( resolve , reject ) => {
859+ const opProcess = spawn ( "op" , [ "read" , secretReference ] , {
860+ stdio : [ "ignore" , "pipe" , "pipe" ] ,
861+ } ) ;
862+
863+ let stdoutBuffer = "" ;
864+ let stderrBuffer = "" ;
865+
866+ opProcess . stdout ?. on ( "data" , ( data ) => {
867+ stdoutBuffer += data . toString ( ) ;
868+ } ) ;
869+
870+ opProcess . stderr ?. on ( "data" , ( data ) => {
871+ stderrBuffer += data . toString ( ) ;
872+ } ) ;
873+
874+ opProcess . on ( "error" , ( error ) => {
875+ reject ( error ) ;
876+ } ) ;
877+
878+ opProcess . on ( "exit" , ( code ) => {
879+ if ( code === 0 ) {
880+ resolve ( stdoutBuffer . trim ( ) ) ;
881+ } else {
882+ reject (
883+ new Error (
884+ `1Password CLI exited with code ${ code } . Stderr: ${ stderrBuffer . trim ( ) } ` ,
885+ ) ,
886+ ) ;
887+ }
888+ } ) ;
889+ } ) ;
890+ }
891+
831892/**
832893 * Creates a new file path by appending the given suffix.
833894 * @param filePath The original file path.
0 commit comments