关于 appscript
appscript 是一项通过终端使用 clasp 开发和管理 Google Apps Script 项目的技能,面向需要本地编辑脚本的开发者。它涵盖从克隆、推送和测试到部署的流程,也提供项目结构与调试指导。
主要功能
- 01本地编辑并推送脚本
- 02克隆和管理绑定或独立脚本
- 03远程运行函数并查看日志
- 04创建和维护部署版本
- 05配置 TypeScript 编译后推送
适用场景
- 01从终端维护 Google Apps Script 项目
- 02测试函数并排查执行日志
- 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.mdLICENSE.gitignoreTODO.mdappscript-workspace/iteration-1/benchmark.jsonappscript-workspace/iteration-1/eval-1-weekly-email/eval_metadata.jsonappscript-workspace/iteration-1/eval-1-weekly-email/with_skill/timing.jsongrading.jsonappscript-workspace/iteration-1/eval-1-weekly-email/with_skill/outputs/response.mdmetrics.jsonappscript-workspace/iteration-1/eval-1-weekly-email/without_skill/grading.jsontiming.jsonappscript-workspace/iteration-1/eval-1-weekly-email/without_skill/outputs/metrics.jsonresponse.mdappscript-workspace/iteration-1/eval-2-webhook-app/eval_metadata.jsonappscript-workspace/iteration-1/eval-2-webhook-app/with_skill/timing.jsongrading.jsonappscript-workspace/iteration-1/eval-2-webhook-app/with_skill/outputs/metrics.jsonresponse.mdappscript-workspace/iteration-1/eval-2-webhook-app/without_skill/grading.jsontiming.jsonappscript-workspace/iteration-1/eval-2-webhook-app/without_skill/outputs/metrics.jsonresponse.mdappscript-workspace/iteration-1/eval-3-timeout-fix/eval_metadata.jsonappscript-workspace/iteration-1/eval-3-timeout-fix/with_skill/timing.jsongrading.jsonappscript-workspace/iteration-1/eval-3-timeout-fix/with_skill/outputs/response.mdappscript-workspace/iteration-1/eval-3-timeout-fix/without_skill/timing.jsonappscript-workspace/iteration-1/eval-3-timeout-fix/without_skill/outputs/metrics.jsonresponse.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) 等触发器仅适用于绑定脚本。
在本地使用绑定脚本:
- 打开 Sheet/Doc → 扩展程序 → Apps Script
- 点击齿轮图标(项目设置)→ 复制 Script ID
- 运行
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 项目之间共享代码,而无需复制文件。
使用库:
- 向库作者获取该库的 Script ID
- 添加到
appsscript.json:
{
"dependencies": {
"libraries": [
{
"userSymbol": "MyLib",
"libraryId": "1T03atLib...",
"version": "3"
}
]
}
}
- 在代码中通过
MyLib.functionName()访问
创建库:
- 像往常一样编写脚本
- 部署为“Library”类型(在脚本编辑器中:部署 → 新建部署 → Library)
- 与使用者分享 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 | 账号中未启用 API | https://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 字段参考 |