I'm not aware of one that exists...  ...so I wrote one!  !look-at --target @{target|Who to look at|token_id} --looker @{target|Who should look|token_id}  It assumes that the up direction for the graphic is where it looks from.  If that's not correct, you can specify a rotation to be applied to the watching token whenever it's updated.  for example, if your token by default is looking down, you could use this:  !look-at --target @{target|Who to look at|token_id} --looker @{target|Who should look|token_id} --rotation 180        Code:  on('ready',()=>{
  const scriptName = 'LookAt';
  const version = '0.1.0';
  const schemaVersion = 0.1;
  const lastUpdate = 1674229371;
  const checkInstall = () => {
    log(`-=> ${scriptName} v${version} <=-  [${lastUpdate}]`);
    if (
      !state.hasOwnProperty(scriptName) ||
      state[scriptName].version !== schemaVersion
    ) {
      log(`  > Updating Schema to v${schemaVersion} <`);
      switch (state[scriptName] && state[scriptName].version) {
        case 0.1: 
            /* break; // intentional dropthrough */ /* falls through */
        case "UpdateSchemaVersion":
          state[scriptName].version = schemaVersion;
          break;
        default:
          state[scriptName] = {
            version: schemaVersion,
            watched: {},
            options: {}
          };
          break;
      }
    }
  };
  checkInstall();
  const watch = (watchid, watcherid, rotation) => {
    const s = state[scriptName];
    s.watched[watchid]= s.watched[watchid] || {};
    s.watched[watchid][watcherid]={...(s.watched[watchid][watcherid]||{}),rotation};
  };
  const showHelp = (who) => {
    sendChat('',`/w "${who}" <div><code>!look-at --target [token_id] --looker [token_id] --rotation [number]</code></div>`);
  };
  const GetAngleTo = (target,src) => ( ((180/Math.PI)*Math.atan2(target.y-src.y, target.x-src.x))+90);
  const handleChangeGraphic = (obj,prev) => {
    const s = state[scriptName];
    if(s.watched.hasOwnProperty(obj.id)){
      Object.keys(s.watched[obj.id])
        .forEach(id=>{
          let t = getObj('graphic',id);
          if(t){
            let rotation = GetAngleTo(
              {x: obj.get('left'), y: obj.get('top')},
              {x: t.get('left'), y: t.get('top')}
            ) + s.watched[obj.id][t.id].rotation;
            t.set('rotation',rotation);
          } else {
            delete s.watched[obj.id][id];
          }
        });
    }
  };
  on('change:graphic',handleChangeGraphic);
  class DoubleDashArgs {
    #cmd;
    #args;
    constructor(line){
      let p=line.split(/\s+--/);
      this.#cmd = p.shift();
      this.#args = [...p.map(a=>a.split(/\s+/))];
    }
    get cmd() {
      return this.#cmd;
    };
    has(arg) {
      return !!this.#args.find(a=>arg===a[0]);
    }
    params(arg) {
      return [...(this.#args.find(a=>arg===a[0])||[])];
    }
    toObject() {
      return {cmd:this.#cmd,args:this.#args};
    }
  }
  // !look-at --target @{target|Target|token_id} --looker @{target|Looker|token_id} --rotation 90
  on('chat:message',msg=>{
    if('api'===msg.type && /^!look-at(\b\s|$)/i.test(msg.content) && playerIsGM(msg.playerid)){
      let who = (getObj('player',msg.playerid)||{get:()=>'API'}).get('_displayname');
      let DDArgs = new DoubleDashArgs(msg.content);
      if(!DDArgs.has('help') && DDArgs.has('target') && DDArgs.has('looker')){
        DDArgs.params('looker')
          .slice(1)
          .forEach( id => {
            const t = getObj('graphic', DDArgs.params('target')[1]);
            if(t) {
              watch(
                t.id,
                id,
                (parseFloat(DDArgs.params('rotation')[1]) || 0)
              );
              
              handleChangeGraphic(t,{});
            }
          });
      } else {
        showHelp(who);
        return;
      }
    }
  });
  const cleanupWatchTable = () => {
    let keys = Object.keys(state[scriptName].watched);
    const burndown = () => {
      let k = keys.shift();
      if(k){
        let o = getObj('graphic',k);
        if(!o){
          delete state[scriptName].watched[k];
        }
        setTimeout(burndown,0);
      }
    };
    burndown();
  };
  cleanupWatchTable();
});