mcpField
A
appscript

appscript

github.com/smkeramati开发185 次安装12 周前更新
代码仓库

关于 appscript

appscript 是一项通过终端使用 clasp 开发和管理 Google Apps Script 项目的技能,面向需要本地编辑脚本的开发者。它涵盖从克隆、推送和测试到部署的流程,也提供项目结构与调试指导。

主要功能

  1. 01本地编辑并推送脚本
  2. 02克隆和管理绑定或独立脚本
  3. 03远程运行函数并查看日志
  4. 04创建和维护部署版本
  5. 05配置 TypeScript 编译后推送

适用场景

  1. 01从终端维护 Google Apps Script 项目
  2. 02测试函数并排查执行日志
  3. 03发布 Web App、API Executable 或 Add-on

MCP 相关

文档说明可将 clasp 作为 MCP 服务使用,让 Claude Code 直接管理 Google Apps Script 项目。

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/smkeramati/appscript --skill appscript

安装后在 Claude Code 中输入/appscript确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

README.md
LICENSE
.gitignore
TODO.md
appscript-workspace/iteration-1/
benchmark.json
appscript-workspace/iteration-1/eval-1-weekly-email/
eval_metadata.json
appscript-workspace/iteration-1/eval-1-weekly-email/with_skill/
timing.json
grading.json
appscript-workspace/iteration-1/eval-1-weekly-email/with_skill/outputs/
response.md
metrics.json
appscript-workspace/iteration-1/eval-1-weekly-email/without_skill/
grading.json
timing.json
appscript-workspace/iteration-1/eval-1-weekly-email/without_skill/outputs/
metrics.json
response.md
appscript-workspace/iteration-1/eval-2-webhook-app/
eval_metadata.json
appscript-workspace/iteration-1/eval-2-webhook-app/with_skill/
timing.json
grading.json
appscript-workspace/iteration-1/eval-2-webhook-app/with_skill/outputs/
metrics.json
response.md
appscript-workspace/iteration-1/eval-2-webhook-app/without_skill/
grading.json
timing.json
appscript-workspace/iteration-1/eval-2-webhook-app/without_skill/outputs/
metrics.json
response.md
appscript-workspace/iteration-1/eval-3-timeout-fix/
eval_metadata.json
appscript-workspace/iteration-1/eval-3-timeout-fix/with_skill/
timing.json
grading.json
appscript-workspace/iteration-1/eval-3-timeout-fix/with_skill/outputs/
response.md
appscript-workspace/iteration-1/eval-3-timeout-fix/without_skill/
timing.json
appscript-workspace/iteration-1/eval-3-timeout-fix/without_skill/outputs/
metrics.json
response.md

详细介绍

appscript — 通过 clasp 使用 Google Apps Script

在终端中开发、编辑和部署 Google Apps Script 项目。覆盖完整生命周期:创建/克隆 → 本地编辑 → 推送 → 测试 → 部署。无需在浏览器中复制粘贴。

快速开始

# Verify clasp is installed (needs Node 22+)
clasp --version
npm install -g @google/clasp   # install if missing

# One-time: enable Apps Script API in your Google account
# https://script.google.com/home/usersettings → toggle ON
# Without this, every clasp command fails with an authorization error.

clasp login                                    # authenticate (opens browser)
clasp create-script --title "My Script"        # start a new standalone project
# — or —
clasp clone-script <scriptId>                  # clone existing project
# scriptId: script.google.com → gear icon → Script ID

在本地编辑 .gs 文件,然后运行 clasp push。这就是核心工作循环。


脚本类型:绑定脚本与独立脚本

两者的区别会影响创建、克隆和访问数据的方式。

独立脚本 — 通过 script.google.com 或 clasp create-script 创建。独立存储在 Drive 中。通过显式 ID 访问 Google 服务(例如,SpreadsheetApp.openById('...'))。

容器绑定脚本 — 在 Sheet、Doc、Form 或 Slide 中通过“扩展程序 → Apps Script”创建。与父文件紧密关联。由于 GAS 知道脚本属于哪个文件,因此可以不带参数调用 SpreadsheetApp.getActiveSpreadsheet()。onEdit(e) 等触发器仅适用于绑定脚本。

在本地使用绑定脚本:

  1. 打开 Sheet/Doc → 扩展程序 → Apps Script
  2. 点击齿轮图标(项目设置)→ 复制 Script ID
  3. 运行 clasp clone-script <scriptId> — 现在你就有了本地文件

关键行为差异:

// Bound script — works without an ID
const ss = SpreadsheetApp.getActiveSpreadsheet();

// Standalone script — must supply the ID
const ss = SpreadsheetApp.openById('1BxSheet...');

项目结构

my-project/
├── .clasp.json           ← project config (scriptId, rootDir)
├── .claspignore          ← gitignore-style exclusions from push
├── appsscript.json       ← manifest (must exist and be pushed)
├── Code.gs               ← main logic
├── Utils.gs              ← helpers
└── ui.html               ← HTML for web apps / sidebars

所有 .gs 文件共享全局作用域 — 不存在模块,也没有 import/export。在 Utils.gs 中定义的函数会自动在 Code.gs 中可用。文件加载顺序会影响顶层声明;如有需要,可通过 .clasp.json 中的 filePushOrder 控制。

始终在 appsscript.json 中使用 V8 运行时。V8 支持 ES6+(箭头函数、async/await 语法、解构)。否则你使用的就是 ES5 Rhino。

{
  "timeZone": "America/New_York",
  "exceptionLogging": "STACKDRIVER",
  "runtimeVersion": "V8"
}

日常工作流

clasp show-file-status          # preview what will push/pull (dry run)
clasp push                      # upload local → Google
clasp push --watch              # auto-push on every file save
clasp push --force              # skip confirmation prompts

clasp pull                      # download Google → local
clasp pull --versionNumber 3    # pull a specific version

运行与调试

# Execute a function remotely
clasp run-function myFunction
clasp run-function processData --params '["arg1", 42]'

# Stream Cloud Logging
clasp tail-logs
clasp tail-logs --watch         # continuous
clasp tail-logs --json          # machine-readable

# Debug clasp itself
DEBUG=clasp:* clasp push

clasp run-function 要求项目部署为 API Executable,或关联了启用 Apps Script API 的 GCP 项目。如果未完成此设置,请直接通过 script.google.com 上的 Apps Script 编辑器推送并测试,或从关联的 Sheet 触发。


版本与部署

版本是不可变的快照。部署指向某个版本,并将脚本对外开放。

clasp create-deployment --description "v1.0 initial release"
clasp create-deployment --versionNumber 3 --description "hotfix"

clasp list-deployments
clasp update-deployment <deploymentId> --versionNumber 4 --description "v1.1"
clasp delete-deployment <deploymentId>
clasp delete-deployment --all

部署类型(在 appsscript.json 中配置):

  • Web App — 必须提供 doGet(e) / doPost(e);可通过公共 URL 访问
  • API Executable — 支持外部调用方使用 clasp run-function
  • Add-on — 可通过 Google Workspace Marketplace 分发

身份验证

clasp login                              # standard OAuth (opens browser)
clasp show-authorized-user               # confirm active account
clasp logout

# Multiple accounts
clasp login --user work
clasp login --user personal
clasp push --user work                   # specify per command

# CI/CD (no browser)
clasp login --adc                        # uses GOOGLE_APPLICATION_CREDENTIALS
clasp login --creds client_secret.json   # org GCP project OAuth

API 管理

clasp enable-api sheets                  # enable Sheets API for this project
clasp list-apis                          # see available and enabled APIs
clasp disable-api drive

TypeScript

Clasp v3 移除了内置 TypeScript 转译功能。建议的做法是在外部编译,然后推送编译结果。

设置:

npm install --save-dev typescript rollup @rollup/plugin-typescript @types/google-apps-script

rollup.config.js:

import typescript from '@rollup/plugin-typescript';
export default {
  input: 'src/index.ts',
  output: { file: 'dist/Code.js', format: 'esm' },
  plugins: [typescript()]
};

.clasp.json — 将 rootDir 指向编译输出目录:

{ "scriptId": "...", "rootDir": "dist/" }

构建并推送:

npx rollup -c && clasp push

不使用 TypeScript 的类型提示 — 安装类型定义,以便在 VS Code 中获得自动补全:

npm install --save-dev @types/google-apps-script

GAS 库

库可以让你在 GAS 项目之间共享代码,而无需复制文件。

使用库:

  1. 向库作者获取该库的 Script ID
  2. 添加到 appsscript.json:
{
  "dependencies": {
    "libraries": [
      {
        "userSymbol": "MyLib",
        "libraryId": "1T03atLib...",
        "version": "3"
      }
    ]
  }
}
  1. 在代码中通过 MyLib.functionName() 访问

创建库:

  1. 像往常一样编写脚本
  2. 部署为“Library”类型(在脚本编辑器中:部署 → 新建部署 → Library)
  3. 与使用者分享 Script ID;每次新部署后,他们都需要添加版本号

注意:由于 GAS 会在运行时解析库,因此每次函数调用会增加约 50ms 的开销。对于性能关键的热点路径,请改为内联代码。


GAS 代码质量

批量执行所有 Sheets 操作

每次调用 Google 服务(Sheets、Drive、Gmail 等)都是一次 HTTP 往返,耗时约 100–500 毫秒。在循环中,100 次单独的单元格读取会变成 100 次串行请求,往往足以触发 6 分钟的执行超时。请改为批量读写。

❌ 100 次 API 调用——速度慢,有超时风险:

for (let i = 1; i <= 100; i++) {
  const val = sheet.getRange(i, 1).getValue();
  sheet.getRange(i, 2).setValue(val * 2);
}

✅ 2 次 API 调用——速度快:

const vals = sheet.getRange('A1:A100').getValues();   // one read
const result = vals.map(([v]) => [v * 2]);
sheet.getRange('B1:B100').setValues(result);           // one write
SpreadsheetApp.flush();   // commit buffered writes before any subsequent read

由于 GAS 会缓冲写入,因此需要调用 SpreadsheetApp.flush()。如果跳过这一步,写入后立即读取可能会返回过期数据。

语言限制
// ✅ Works in V8
const fn = (x) => x * 2;
const [a, ...rest] = array;

// ⚠️ async/await syntax works in V8, BUT GAS has no event loop
// Only use it if you have a real native Promise to await.
// setTimeout, setInterval, and browser I/O never fire — there's no scheduler.
async function example() {
  const result = await someNativePromise;   // ok
  // await fetch(...) — won't work, use UrlFetchApp.fetch() instead
}

// ❌ Never works in GAS (any runtime)
import { foo } from './utils.js'   // no ES modules — global scope only
export default function() {}
fetch('https://api.example.com')   // use UrlFetchApp.fetch() instead
setTimeout(() => {}, 1000)         // no timer callbacks
window / document / localStorage   // no DOM or browser APIs

// ❌ V8 parse error — avoid
class Foo { #privateField = 1; }   // private class fields not supported
使用 PropertiesService 存储配置

将 API 密钥或设置硬编码在 .gs 文件中,意味着它们会被推送到 Google,并对脚本编辑者可见。请改用 PropertiesService 存储——它会在不同部署之间保留数据,并且不会出现在源文件中。

// Write once (e.g., a setup function)
PropertiesService.getScriptProperties().setProperty('API_KEY', 'your-secret');

// Read anywhere
const key = PropertiesService.getScriptProperties().getProperty('API_KEY');

常见模式

Web 应用(HTTP 处理程序):

function doGet(e) {
  return HtmlService.createHtmlOutputFromFile('ui').setTitle('My App');
}
function doPost(e) {
  const data = JSON.parse(e.postData.contents);
  // process...
  return ContentService.createTextOutput(JSON.stringify({ ok: true }))
    .setMimeType(ContentService.MimeType.JSON);
}

基于时间的触发器:

function setupTrigger() {
  ScriptApp.newTrigger('myFunction').timeBased().everyHours(1).create();
}
// To remove all triggers:
ScriptApp.getProjectTriggers().forEach(t => ScriptApp.deleteTrigger(t));

外部 HTTP 请求:

const res = UrlFetchApp.fetch('https://api.example.com/data', {
  method: 'post',
  contentType: 'application/json',
  payload: JSON.stringify({ key: 'value' }),
  headers: { Authorization: 'Bearer ' + token }
});
const json = JSON.parse(res.getContentText());

缓存开销较大的获取结果(最长 6 小时):

const cache = CacheService.getScriptCache();
let data = cache.get('mykey');
if (!data) {
  data = JSON.stringify(UrlFetchApp.fetch('https://api.example.com').getContentText());
  cache.put('mykey', data, 1500);   // 25 minutes
}
return JSON.parse(data);

使用休眠进行速率限制保护:

// When hitting APIs that throttle (e.g. UrlFetch returning 429)
items.forEach((item, i) => {
  processItem(item);
  if (i % 10 === 9) Utilities.sleep(1000);   // pause 1s every 10 calls
});

Gemini AI(通过 Ai 服务——GAS 原生功能):

// Available in GAS without any API key setup — uses project's linked GCP
function summarizeText(text) {
  const model = Ai.getGenerativeModel({ model: 'gemini-2.0-flash' });
  const result = model.generateContent(text);
  return result.response.text();
}

注意:Ai 服务要求项目关联一个已启用 Vertex AI 或 Generative Language API 的 GCP 项目。或者,也可以使用自己的 API 密钥,通过 UrlFetchApp.fetch() 调用 Gemini REST API。


故障排除

错误原因解决方法
Apps Script API has not been used账号中未启用 APIhttps://script.google.com/home/usersettings → 启用
Could not find .clasp.json当前目录不是项目根目录使用 cd 切换到项目目录
Authorization required清单中的 OAuth 范围已更改重新推送,然后在脚本编辑器中重新授权
Script timeout超出 6 分钟执行时限拆分为更小的批次;通过触发器串联执行
Service using too much computer time已达到每日配额使用批量操作;等待 24 小时;Workspace 账号的限制更高
写入后数据过期写入会被缓冲读取前调用 SpreadsheetApp.flush()
推送确认循环终端提示需要交互操作添加 --force 标志
升级后找不到命令Clasp v2 → v3 重命名查看 references/clasp-commands.md 中的重命名表
HttpResponseException: 429已达到 UrlFetch 速率限制在调用之间添加 Utilities.sleep(1000);批量发送请求
ReferenceError: X is not defined文件加载顺序问题在 .clasp.json 中添加 filePushOrder

MCP / AI 集成

将 clasp 用作 MCP 服务,让 Claude Code 可以直接管理 GAS 项目:

claude mcp add clasp -- npx -y @google/clasp mcp
# Or in Claude Code:
# /plugin install @google/clasp

参考文件

按需加载——除非需要了解详情,否则不要预先读取。

文件何时加载
references/clasp-commands.md需要任何命令的完整标志参考,或从 clasp v2 迁移(完整重命名表见其中)
references/gas-reference.md需要每日配额、所有可用服务、部署清单字段、更多代码模式或触发器类型
references/config-files.md需要完整的 .clasp.json / appsscript.json / .claspignore 字段参考

评论

支持 Markdown