Luigit
repositories / dotfiles

dotfiles

bugabingas dorkfiles

owned by admin

pi/agent/skills/google-workspace/scripts/workspace.js

Raw
#!/usr/bin/env node

const vm = require('node:vm');
const util = require('node:util');

const {
  DEFAULT_VERSIONS,
  authorize,
  formatScopes,
  getGoogleApis,
  loadToken,
  normalizeEmail,
  resolveAuthMode,
} = require('./common');

function printHelp() {
  console.log(`Google Workspace API helper (exec-only)

Usage:
  node scripts/workspace.js exec --email user@example.com [--script 'return 1'] [--timeout 30000] [--scopes s1,s2]

Example:
  node scripts/workspace.js exec --email user@example.com <<'JS'
  const me = await workspace.whoAmI();
  const files = await workspace.call('drive', 'files.list', {
    pageSize: 3,
    fields: 'files(id,name)'
  });
  return { me, files: files.files };
  JS
`);
}

function parseTimeout(raw) {
  if (!raw) {
    throw new Error('--timeout requires a value');
  }

  const numeric = Number(raw);
  if (!Number.isFinite(numeric) || numeric <= 0) {
    throw new Error('--timeout must be a positive number of milliseconds');
  }

  return Math.min(Math.floor(numeric), 5 * 60_000);
}

function parseOptions(argv) {
  const positional = [];
  const options = {
    email: undefined,
    scopes: undefined,
    timeout: undefined,
    script: undefined,
  };

  for (let i = 0; i < argv.length; i += 1) {
    const arg = argv[i];

    if (arg === '--email') {
      const value = argv[i + 1];
      if (!value) {
        throw new Error('--email requires a value');
      }
      options.email = normalizeEmail(value);
      i += 1;
      continue;
    }

    if (arg === '--scopes') {
      const value = argv[i + 1];
      if (!value) {
        throw new Error('--scopes requires a value');
      }
      options.scopes = formatScopes(value);
      i += 1;
      continue;
    }

    if (arg === '--timeout') {
      options.timeout = parseTimeout(argv[i + 1]);
      i += 1;
      continue;
    }

    if (arg === '--script') {
      const value = argv[i + 1];
      if (value === undefined) {
        throw new Error('--script requires a value');
      }
      options.script = value;
      i += 1;
      continue;
    }

    positional.push(arg);
  }

  return { positional, options };
}

function resolveMethod(root, methodPath) {
  const parts = methodPath.split('.').filter(Boolean);
  if (parts.length === 0) {
    throw new Error('method.path is empty');
  }

  let parent = root;
  for (let i = 0; i < parts.length - 1; i += 1) {
    parent = parent?.[parts[i]];
    if (!parent) {
      throw new Error(`Invalid method path (missing segment: ${parts[i]})`);
    }
  }

  const methodName = parts[parts.length - 1];
  const method = parent?.[methodName];

  if (typeof method !== 'function') {
    throw new Error(
      `Invalid method path: ${methodPath}. Final segment is not callable.`,
    );
  }

  return { parent, method };
}

async function callApi({
  service,
  methodPath,
  params,
  version,
  scopes,
  authClient,
  email,
}) {
  const google = getGoogleApis();
  const factory = google[service];
  if (typeof factory !== 'function') {
    throw new Error(`Unknown Google API service: ${service}`);
  }

  const auth =
    authClient ||
    (await authorize({
      email,
      interactive: true,
      scopes: scopes && scopes.length > 0 ? scopes : undefined,
    }));

  const api = factory({
    version: version || DEFAULT_VERSIONS[service] || 'v1',
    auth,
  });

  const { parent, method } = resolveMethod(api, methodPath);
  const response = await method.call(parent, params || {});
  return response?.data ?? response;
}

function getServiceClient({ google, auth, service, version }) {
  const factory = google[service];
  if (typeof factory !== 'function') {
    throw new Error(`Unknown Google API service: ${service}`);
  }

  return factory({
    version: version || DEFAULT_VERSIONS[service] || 'v1',
    auth,
  });
}

function createWorkspaceHelper({ auth, google, email }) {
  return {
    accountEmail: email,
    versions: { ...DEFAULT_VERSIONS },

    async call(service, methodPath, params = {}, options = {}) {
      return callApi({
        service,
        methodPath,
        params,
        version: options.version,
        authClient: auth,
        email,
      });
    },

    service(service, options = {}) {
      return getServiceClient({
        google,
        auth,
        service,
        version: options.version,
      });
    },

    async whoAmI() {
      const oauth2 = google.oauth2({ version: 'v2', auth });
      const response = await oauth2.userinfo.get();
      return response?.data ?? response;
    },
  };
}

const SECRET_KEY_RE = /(?:^|_|-)(?:access|refresh|id)?_?token$|authorization|credentials/i;
const REDACTED = '[REDACTED]';

function isSecretKey(key) {
  return SECRET_KEY_RE.test(String(key || ''));
}

function formatLogArg(value) {
  if (typeof value === 'string') {
    return value;
  }

  return util.inspect(normalizeForJson(value), {
    depth: 6,
    maxArrayLength: 200,
    breakLength: 120,
    compact: 2,
  });
}

function createExecutionConsole(logs) {
  const write = (level, args) => {
    logs.push({
      level,
      message: args.map(formatLogArg).join(' '),
      timestamp: new Date().toISOString(),
    });
  };

  return {
    log: (...args) => write('log', args),
    info: (...args) => write('info', args),
    warn: (...args) => write('warn', args),
    error: (...args) => write('error', args),
    debug: (...args) => write('debug', args),
  };
}

function normalizeForJson(value, seen = new WeakSet(), key = '') {
  if (isSecretKey(key) && value !== null && value !== undefined) {
    return REDACTED;
  }

  if (value === null || value === undefined) {
    return value;
  }

  if (typeof value === 'bigint') {
    return `${value}n`;
  }

  if (typeof value === 'function') {
    return `[Function ${value.name || 'anonymous'}]`;
  }

  if (value instanceof Date) {
    return value.toISOString();
  }

  if (value instanceof Error) {
    return {
      name: value.name,
      message: value.message,
      stack: value.stack,
    };
  }

  if (Array.isArray(value)) {
    return value.map((item) => normalizeForJson(item, seen, key));
  }

  if (typeof value === 'object') {
    if (seen.has(value)) {
      return '[Circular]';
    }
    seen.add(value);

    const output = {};
    for (const [nestedKey, nested] of Object.entries(value)) {
      output[nestedKey] = normalizeForJson(nested, seen, nestedKey);
    }
    return output;
  }

  return value;
}

function withTimeout(promise, timeoutMs) {
  if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {
    return promise;
  }

  return new Promise((resolve, reject) => {
    const timer = setTimeout(() => {
      reject(new Error(`Execution timed out after ${Math.round(timeoutMs)}ms.`));
    }, timeoutMs);

    Promise.resolve(promise).then(
      (value) => {
        clearTimeout(timer);
        resolve(value);
      },
      (error) => {
        clearTimeout(timer);
        reject(error);
      },
    );
  });
}

function readStdinText() {
  return new Promise((resolve, reject) => {
    if (process.stdin.isTTY) {
      resolve('');
      return;
    }

    let data = '';
    process.stdin.setEncoding('utf8');
    process.stdin.on('data', (chunk) => {
      data += chunk;
    });
    process.stdin.on('end', () => resolve(data));
    process.stdin.on('error', reject);
  });
}

function createUnauthorizedHint(message, email) {
  const msg = String(message || '').toLowerCase();
  const looksUnauthorized =
    msg.includes('401') ||
    msg.includes('unauthorized') ||
    msg.includes('invalid credentials') ||
    msg.includes('insufficient permissions') ||
    msg.includes('forbidden');

  if (!looksUnauthorized) {
    return null;
  }

  return [
    `Auth/account hint: this request may not be authorized for ${email}.`,
    'List known accounts with: node scripts/auth.js accounts',
    `Sign in or refresh this account with: node scripts/auth.js login --email ${email}`,
    'Then retry workspace.js exec with the intended --email.',
  ].join(' ');
}

function errorPayload(error, logs, timeoutMs, email) {
  const baseMessage = error?.message || String(error);
  const hint = createUnauthorizedHint(baseMessage, email);

  return {
    ok: false,
    timeoutMs,
    logs,
    error: {
      name: error?.name || 'Error',
      message: hint ? `${baseMessage}\n${hint}` : baseMessage,
      stack: error?.stack || null,
    },
  };
}

async function cmdExec(args, options) {
  if (!options.email) {
    throw new Error(
      'Missing required --email <account@example.com>. ' +
        'Use node scripts/auth.js accounts to list known accounts.',
    );
  }

  let script = options.script;

  if (!script && args.length > 0) {
    script = args.join(' ');
  }

  if (!script) {
    script = await readStdinText();
  }

  script = String(script || '').trim();

  if (!script) {
    throw new Error(
      'Usage: exec --email user@example.com [--script "..."] (or pipe script via stdin / heredoc)',
    );
  }

  const timeoutMs = options.timeout || 30_000;
  const logs = [];

  try {
    const auth = await authorize({
      email: options.email,
      interactive: true,
      scopes: options.scopes && options.scopes.length > 0
        ? options.scopes
        : undefined,
    });

    const google = getGoogleApis();
    const workspace = createWorkspaceHelper({ auth, google, email: options.email });

    const context = vm.createContext({
      Buffer,
      URL,
      URLSearchParams,
      TextEncoder,
      TextDecoder,
      setTimeout,
      clearTimeout,
      console: createExecutionConsole(logs),
      auth,
      google,
      workspace,
    });

    const wrappedScript = `
(async () => {
${script}
})()
`;

    const compiled = new vm.Script(wrappedScript, {
      filename: 'workspace-exec.js',
      displayErrors: true,
    });

    const resultPromise = Promise.resolve(
      compiled.runInContext(context, {
        timeout: Math.min(timeoutMs, 60_000),
        displayErrors: true,
      }),
    );

    const result = await withTimeout(resultPromise, timeoutMs);
    const token = loadToken(options.email);

    console.log(
      JSON.stringify(
        {
          ok: true,
          email: options.email,
          authMode: resolveAuthMode(token),
          timeoutMs,
          logs,
          result: normalizeForJson(result),
        },
        null,
        2,
      ),
    );
  } catch (error) {
    console.log(
      JSON.stringify(
        errorPayload(error, logs, timeoutMs, options.email),
        null,
        2,
      ),
    );
    process.exitCode = 1;
  }
}

async function main() {
  const { positional, options } = parseOptions(process.argv.slice(2));
  const [command, ...args] = positional;

  if (
    !command ||
    command === 'help' ||
    command === '--help' ||
    command === '-h'
  ) {
    printHelp();
    return;
  }

  if (command !== 'exec') {
    throw new Error(`Unknown command: ${command}. Only 'exec' is supported.`);
  }

  await cmdExec(args, options);
}

if (require.main === module) {
  main().catch((error) => {
    console.error(`❌ ${error.message}`);
    process.exit(1);
  });
}

module.exports = {
  formatLogArg,
  normalizeForJson,
};