@google/clasp 3.0.3-alpha → 3.0.5-alpha
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +27 -9
- package/build/src/commands/program.js +8 -2
- package/build/src/commands/pull.js +38 -2
- package/build/src/commands/start-mcp.js +12 -0
- package/build/src/core/clasp.js +4 -2
- package/build/src/core/files.js +2 -2
- package/build/src/mcp/server.js +329 -0
- package/package.json +25 -45
package/README.md
CHANGED
|
@@ -4,7 +4,6 @@
|
|
|
4
4
|
<a href="https://coveralls.io/github/google/clasp?branch=master"><img src="https://coveralls.io/repos/github/google/clasp/badge.svg?branch=master" alt="Coverage Status"></a>
|
|
5
5
|
<a href="https://www.npmjs.com/package/@google/clasp"><img src="https://img.shields.io/npm/v/@google/clasp.svg" alt="npm Version"></a>
|
|
6
6
|
<a href="https://npmcharts.com/compare/@google/clasp?minimal=true"><img src="https://img.shields.io/npm/dw/@google/clasp.svg" alt="npm Downloads"></a>
|
|
7
|
-
<a href="https://david-dm.org/google/clasp" title="dependencies status"><img src="https://david-dm.org/google/clasp/status.svg"/></a>
|
|
8
7
|
<a href="https://github.com/google/gts" title="Code Style: Google"><img src="https://img.shields.io/badge/code%20style-google-blueviolet.svg"/></a>
|
|
9
8
|
|
|
10
9
|
> Develop [Apps Script](https://developers.google.com/apps-script/) projects locally using clasp (**C**ommand **L**ine **A**pps **S**cript **P**rojects).
|
|
@@ -82,7 +81,7 @@ clasp
|
|
|
82
81
|
- [`clasp pull [--versionNumber]`](#pull)
|
|
83
82
|
- [`clasp push [--watch] [--force]`](#push)
|
|
84
83
|
- [`clasp show-file-status [--json]`](#status)
|
|
85
|
-
- [`clasp open-script](#open)
|
|
84
|
+
- [`clasp open-script`](#open)
|
|
86
85
|
- [`clasp list-deployments`](#deployments)
|
|
87
86
|
- [`clasp create-deployment [--versionNumber <version>] [--description <description>] [--deploymentId <id>]`](#deploy)
|
|
88
87
|
- [`clasp delete-deployment [deploymentId] [--all]`](#undeploy)
|
|
@@ -98,7 +97,7 @@ clasp
|
|
|
98
97
|
- [`clasp list-apis`](#apis)
|
|
99
98
|
- [`clasp enable-api<api>`](#apis)
|
|
100
99
|
- [`clasp disable-api <api>`](#apis)
|
|
101
|
-
- [`clasp run-function [function]`](#
|
|
100
|
+
- [`clasp run-function [function]`](#clasp-run)
|
|
102
101
|
|
|
103
102
|
## Guides
|
|
104
103
|
|
|
@@ -144,7 +143,7 @@ Most command require user authorization. Run `clasp login` to authorize access t
|
|
|
144
143
|
|
|
145
144
|
#### Multiple user support
|
|
146
145
|
|
|
147
|
-
Use the global `--user` option to switch between accounts.
|
|
146
|
+
Use the global `--user` option to switch between accounts. This supports both running clasp as different users as well as when invoking the `clasp run-function` command.
|
|
148
147
|
|
|
149
148
|
Examples:
|
|
150
149
|
|
|
@@ -152,7 +151,7 @@ Examples:
|
|
|
152
151
|
clasp login # Saves as default credentials
|
|
153
152
|
clasp clone # User not specified, runs using default credentials
|
|
154
153
|
clasp login --user testaccount # Authorized new named credentials
|
|
155
|
-
|
|
154
|
+
clasp run-function --user testaccount myFunction # Runs function as test account
|
|
156
155
|
```
|
|
157
156
|
|
|
158
157
|
### Bring your own project/credentials
|
|
@@ -161,9 +160,9 @@ While clasp includes a default OAuth client, using your own project is recommend
|
|
|
161
160
|
|
|
162
161
|
1. [Create a new project](https://cloud.google.com/resource-manager/docs/creating-managing-projects) in the Google Cloud Developer Console.
|
|
163
162
|
1. [Create an OAuth client](https://support.google.com/cloud/answer/15549257?hl=en#:~:text=To%20create%20an%20OAuth%202.0,are%20yet%20to%20do%20so.). The client type must be `Desktop Application`. Download and save the generated client secrets file. This is required when authorizing using the`clasp login --creds <filename>` command.
|
|
164
|
-
1. [Enable services](https://cloud.google.com/endpoints/docs/openapi/enable-api). For full
|
|
165
|
-
* Apps
|
|
166
|
-
* Service Usage API - `serviceusage.googleapis.com` (
|
|
163
|
+
1. [Enable services](https://cloud.google.com/endpoints/docs/openapi/enable-api). For full functionality, clasp requires the following:
|
|
164
|
+
* Apps Script API - `script.googleapis.com` (required)
|
|
165
|
+
* Service Usage API - `serviceusage.googleapis.com` (required to list/enable/disable APIs)
|
|
167
166
|
* Google Drive API - `drive.googleapis.com` (required to list scripts, create container-bound scripts)
|
|
168
167
|
- Cloud Logging API - `logging.googleapis.com` (required to read logs)
|
|
169
168
|
|
|
@@ -192,7 +191,7 @@ If your organization restricts authorization for third-party apps, you may eithe
|
|
|
192
191
|
|
|
193
192
|
### Service accounts
|
|
194
193
|
|
|
195
|
-
Use the `--adc` option on any command to read credentials from the
|
|
194
|
+
Use the `--adc` option on any command to read credentials from the environment using Google Cloud's [application default credentials](https://cloud.google.com/docs/authentication/application-default-credentials) mechanism.
|
|
196
195
|
|
|
197
196
|
Note that if using a service account, service accounts can not own scripts. To use a service account to push or pull files from Apps Script, the scripts must be shared with the service account with the appropriate role (e.g. `Editor` in able to push.)
|
|
198
197
|
|
|
@@ -395,6 +394,8 @@ Updates local files with Apps Script project.
|
|
|
395
394
|
#### Options
|
|
396
395
|
|
|
397
396
|
- `--versionNumber <number>`: The version number of the project to retrieve.
|
|
397
|
+
- `--deleteUnusedFiles`: Deletes local files that would have been pushed that were not returned by the server. Prompts for confirmation
|
|
398
|
+
- `--force`: Used with `--deleteUnusedFiles` to automatically confirm. Use with caution.
|
|
398
399
|
|
|
399
400
|
#### Examples
|
|
400
401
|
|
|
@@ -542,6 +543,23 @@ Lists your most recent Apps Script projects.
|
|
|
542
543
|
|
|
543
544
|
- `clasp list-scripts`: Prints `helloworld1 – xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx ...`
|
|
544
545
|
|
|
546
|
+
### MCP (EXPERIMENTAL)
|
|
547
|
+
|
|
548
|
+
Runs clasp in MCP (model context protocol) mode for use with coding agents. Configure clasp as a local tool using STDIO transport. While running in MCP mode clasp uses the same credentials as
|
|
549
|
+
normal when used as a CLI. Run `clasp login` ahead of time to authorize.
|
|
550
|
+
|
|
551
|
+
When used in MCP mode clasp does not need to be started from the project directory. The project directoy is specified in the tool calls. Switching projects does not require a restart of the MCP server, while switching credentials does.
|
|
552
|
+
|
|
553
|
+
This feature is experimental and currently offers a limited subset of tools for agents. Feedback is welcome.
|
|
554
|
+
|
|
555
|
+
#### Options
|
|
556
|
+
|
|
557
|
+
N/A
|
|
558
|
+
|
|
559
|
+
#### Examples
|
|
560
|
+
|
|
561
|
+
- `clasp mcp`
|
|
562
|
+
|
|
545
563
|
## Advanced Commands
|
|
546
564
|
|
|
547
565
|
> **NOTE**: These commands require Project ID/credentials setup ([see below](#projectid-optional)).
|
|
@@ -24,17 +24,22 @@ import { command as runCommand } from './run-function.js';
|
|
|
24
24
|
import { command as setupLogsCommand } from './setup-logs.js';
|
|
25
25
|
import { command as authStatusCommand } from './show-authorized-user.js';
|
|
26
26
|
import { command as filesStatusCommand } from './show-file-status.js';
|
|
27
|
+
import { command as mcpCommand } from './start-mcp.js';
|
|
27
28
|
import { command as tailLogsCommand } from './tail-logs.js';
|
|
28
29
|
import { dirname } from 'path';
|
|
29
30
|
import { fileURLToPath } from 'url';
|
|
30
|
-
import { readPackageUpSync } from 'read-
|
|
31
|
+
import { readPackageUpSync } from 'read-package-up';
|
|
31
32
|
import { initAuth } from '../auth/auth.js';
|
|
32
33
|
import { initClaspInstance } from '../core/clasp.js';
|
|
33
34
|
import { intl } from '../intl.js';
|
|
34
|
-
export function
|
|
35
|
+
export function getVersion() {
|
|
35
36
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
36
37
|
const manifest = readPackageUpSync({ cwd: __dirname });
|
|
37
38
|
const version = manifest ? manifest.packageJson.version : 'unknown';
|
|
39
|
+
return version;
|
|
40
|
+
}
|
|
41
|
+
export function makeProgram(exitOveride) {
|
|
42
|
+
const version = getVersion();
|
|
38
43
|
const program = new Command();
|
|
39
44
|
program.exitOverride(exitOveride);
|
|
40
45
|
program.storeOptionsAsProperties(false);
|
|
@@ -92,6 +97,7 @@ export function makeProgram(exitOveride) {
|
|
|
92
97
|
listCommand,
|
|
93
98
|
createVersionCommand,
|
|
94
99
|
listVersionsCommand,
|
|
100
|
+
mcpCommand,
|
|
95
101
|
];
|
|
96
102
|
for (const cmd of commandsToAdd) {
|
|
97
103
|
program.addCommand(cmd);
|
|
@@ -1,19 +1,55 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
|
+
import fs from 'fs/promises';
|
|
3
|
+
import inquirer from 'inquirer';
|
|
2
4
|
import { intl } from '../intl.js';
|
|
3
|
-
import { withSpinner } from './utils.js';
|
|
5
|
+
import { isInteractive, withSpinner } from './utils.js';
|
|
4
6
|
export const command = new Command('pull')
|
|
5
7
|
.description('Fetch a remote project')
|
|
6
8
|
.option('--versionNumber <version>', 'The version number of the project to retrieve.')
|
|
9
|
+
.option('-d, --deleteUnusedFiles ', 'Delete local files that are not in the remote project. Use with caution.')
|
|
10
|
+
.option('-f, --force', 'Forcibly delete local files that are not in the remote project without prompting.')
|
|
7
11
|
.action(async function (options) {
|
|
8
12
|
const clasp = this.opts().clasp;
|
|
9
13
|
const versionNumber = options.versionNumber;
|
|
10
|
-
const
|
|
14
|
+
const forceDelete = options.force;
|
|
15
|
+
let spinnerMsg = intl.formatMessage({ id: "dh7Bw6", defaultMessage: [{ type: 0, value: "Checking local files..." }] });
|
|
16
|
+
const localFiles = await clasp.files.collectLocalFiles();
|
|
17
|
+
spinnerMsg = intl.formatMessage({ id: "jilcJH", defaultMessage: [{ type: 0, value: "Pulling files..." }] });
|
|
11
18
|
const files = await withSpinner(spinnerMsg, async () => {
|
|
12
19
|
return await clasp.files.pull(versionNumber);
|
|
13
20
|
});
|
|
21
|
+
if (options.deleteUnusedFiles) {
|
|
22
|
+
const filesToDelete = localFiles.filter(f => !files.find(p => p.localPath === f.localPath));
|
|
23
|
+
await deleteLocalFiles(filesToDelete, forceDelete);
|
|
24
|
+
}
|
|
14
25
|
files.forEach(f => console.log(`└─ ${f.localPath}`));
|
|
15
26
|
const successMessage = intl.formatMessage({ id: "4mRAfN", defaultMessage: [{ type: 0, value: "Pulled " }, { type: 6, value: "count", options: { "=0": { value: [{ type: 0, value: "no files." }] }, one: { value: [{ type: 0, value: "one file." }] }, other: { value: [{ type: 7 }, { type: 0, value: " files" }] } }, offset: 0, pluralType: "cardinal" }, { type: 0, value: "." }] }, {
|
|
16
27
|
count: files.length,
|
|
17
28
|
});
|
|
18
29
|
console.log(successMessage);
|
|
19
30
|
});
|
|
31
|
+
async function deleteLocalFiles(filesToDelete, forceDelete = false) {
|
|
32
|
+
if (!filesToDelete || filesToDelete.length === 0) {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
const skipConfirmation = forceDelete;
|
|
36
|
+
if (!isInteractive() && !forceDelete) {
|
|
37
|
+
const msg = intl.formatMessage({ id: "zLuvSg", defaultMessage: [{ type: 0, value: "You are not in an interactive terminal and --force not used. Skipping file deletion." }] });
|
|
38
|
+
console.warn(msg);
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
for (const file of filesToDelete) {
|
|
42
|
+
if (!skipConfirmation) {
|
|
43
|
+
const confirm = await inquirer.prompt({
|
|
44
|
+
type: 'confirm',
|
|
45
|
+
name: 'deleteFile',
|
|
46
|
+
message: intl.formatMessage({ id: "lVx/lI", defaultMessage: [{ type: 0, value: "Delete " }, { type: 1, value: "file" }, { type: 0, value: "?" }] }, { file: file.localPath }),
|
|
47
|
+
});
|
|
48
|
+
if (!confirm.deleteFile) {
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
await fs.unlink(file.localPath);
|
|
53
|
+
console.log(intl.formatMessage({ id: "Nx315v", defaultMessage: [{ type: 0, value: "Deleted " }, { type: 1, value: "file" }] }, { file: file.localPath }));
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
3
|
+
import { buildMcpServer } from '../mcp/server.js';
|
|
4
|
+
export const command = new Command('start-mcp-server')
|
|
5
|
+
.alias('mcp')
|
|
6
|
+
.description('Starts an MCP server for interacting with apps script.')
|
|
7
|
+
.action(async function () {
|
|
8
|
+
const auth = this.opts().auth;
|
|
9
|
+
const server = buildMcpServer(auth);
|
|
10
|
+
const transport = new StdioServerTransport();
|
|
11
|
+
await server.connect(transport);
|
|
12
|
+
});
|
package/build/src/core/clasp.js
CHANGED
|
@@ -63,11 +63,13 @@ export class Clasp {
|
|
|
63
63
|
}
|
|
64
64
|
}
|
|
65
65
|
export async function initClaspInstance(options) {
|
|
66
|
+
var _a;
|
|
66
67
|
debug('Initializing clasp instance');
|
|
67
68
|
const projectRoot = await findProjectRootdDir(options.configFile);
|
|
68
69
|
if (!projectRoot) {
|
|
69
|
-
|
|
70
|
-
|
|
70
|
+
const dir = (_a = options.rootDir) !== null && _a !== void 0 ? _a : process.cwd();
|
|
71
|
+
debug(`No project found, defaulting to ${dir}`);
|
|
72
|
+
const rootDir = path.resolve(dir);
|
|
71
73
|
const configFilePath = path.resolve(rootDir, '.clasp.json');
|
|
72
74
|
const ignoreFile = await findIgnoreFile(rootDir, options.ignoreFile);
|
|
73
75
|
const ignoreRules = await loadIgnoreFileOrDefaults(ignoreFile);
|
package/build/src/core/files.js
CHANGED
|
@@ -177,7 +177,7 @@ export class Files {
|
|
|
177
177
|
const localPath = path.relative(process.cwd(), path.join(contentDir, filename));
|
|
178
178
|
const resolvedPath = path.relative(contentDir, localPath);
|
|
179
179
|
const parsedPath = path.parse(resolvedPath);
|
|
180
|
-
let remotePath = path.format({ dir:
|
|
180
|
+
let remotePath = normalizePath(path.format({ dir: parsedPath.dir, name: parsedPath.name }));
|
|
181
181
|
const type = getFileType(localPath, fileExtensionMap);
|
|
182
182
|
if (!type) {
|
|
183
183
|
debug('Ignoring unsupported file %s', localPath);
|
|
@@ -270,7 +270,7 @@ export class Files {
|
|
|
270
270
|
if (dirsWithIncludedFiles.has(dir)) {
|
|
271
271
|
break;
|
|
272
272
|
}
|
|
273
|
-
excludedPath = `${dir}
|
|
273
|
+
excludedPath = path.normalize(`${dir}/`);
|
|
274
274
|
}
|
|
275
275
|
debug('Found untracked file %s', excludedPath);
|
|
276
276
|
untrackedFiles.add(excludedPath);
|
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
3
|
+
import { mkdir } from 'fs/promises';
|
|
4
|
+
import { z } from 'zod';
|
|
5
|
+
import { getDefaultProjectName } from '../commands/create-script.js';
|
|
6
|
+
import { getVersion } from '../commands/program.js';
|
|
7
|
+
import { initClaspInstance } from '../core/clasp.js';
|
|
8
|
+
export function buildMcpServer(auth) {
|
|
9
|
+
const server = new McpServer({
|
|
10
|
+
name: 'Clasp',
|
|
11
|
+
version: getVersion(),
|
|
12
|
+
});
|
|
13
|
+
server.tool('push_files', 'Pushes the local Apps Script project to the remote server.', {
|
|
14
|
+
projectDir: z
|
|
15
|
+
.string()
|
|
16
|
+
.describe('The local directory of the Apps Script project to push. Must contain a .clasp.json file containing the project info.'),
|
|
17
|
+
}, {
|
|
18
|
+
title: 'Push project files to Apps Script',
|
|
19
|
+
openWorldHint: false,
|
|
20
|
+
destructiveHint: true,
|
|
21
|
+
idempotentHint: false,
|
|
22
|
+
readOnlyHint: false,
|
|
23
|
+
}, async ({ projectDir }) => {
|
|
24
|
+
if (!projectDir) {
|
|
25
|
+
return {
|
|
26
|
+
isError: true,
|
|
27
|
+
content: [
|
|
28
|
+
{
|
|
29
|
+
type: 'text',
|
|
30
|
+
text: 'Project directory is required.',
|
|
31
|
+
},
|
|
32
|
+
],
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
const clasp = await initClaspInstance({
|
|
36
|
+
credentials: auth.credentials,
|
|
37
|
+
configFile: projectDir,
|
|
38
|
+
rootDir: projectDir,
|
|
39
|
+
});
|
|
40
|
+
try {
|
|
41
|
+
const files = await clasp.files.push();
|
|
42
|
+
const fileList = files.map(file => ({
|
|
43
|
+
type: 'text',
|
|
44
|
+
text: `Updated file: ${path.resolve(file.localPath)}`,
|
|
45
|
+
}));
|
|
46
|
+
return {
|
|
47
|
+
status: 'success',
|
|
48
|
+
content: [
|
|
49
|
+
{
|
|
50
|
+
type: 'text',
|
|
51
|
+
text: `Pushed project in ${projectDir} to remote server successfully.`,
|
|
52
|
+
},
|
|
53
|
+
...fileList,
|
|
54
|
+
],
|
|
55
|
+
structuredContent: {
|
|
56
|
+
scriptId: clasp.project.scriptId,
|
|
57
|
+
projectDir: projectDir,
|
|
58
|
+
files: files.map(file => path.resolve(file.localPath)),
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
catch (err) {
|
|
63
|
+
return {
|
|
64
|
+
isError: true,
|
|
65
|
+
content: [
|
|
66
|
+
{
|
|
67
|
+
type: 'text',
|
|
68
|
+
text: `Error pushing project: ${err.message}`,
|
|
69
|
+
},
|
|
70
|
+
],
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
server.tool('pull_files', 'Pulls files from Apps Script project to local file system.', {
|
|
75
|
+
projectDir: z
|
|
76
|
+
.string()
|
|
77
|
+
.describe('The local directory of the Apps Script project to update. Must contain a .clasp.json file containing the project info.'),
|
|
78
|
+
}, {
|
|
79
|
+
title: 'Pull project files from Apps Script',
|
|
80
|
+
openWorldHint: false,
|
|
81
|
+
destructiveHint: true,
|
|
82
|
+
idempotentHint: false,
|
|
83
|
+
readOnlyHint: false,
|
|
84
|
+
}, async ({ projectDir }) => {
|
|
85
|
+
if (!projectDir) {
|
|
86
|
+
return {
|
|
87
|
+
isError: true,
|
|
88
|
+
content: [
|
|
89
|
+
{
|
|
90
|
+
type: 'text',
|
|
91
|
+
text: 'Project directory is required.',
|
|
92
|
+
},
|
|
93
|
+
],
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
const clasp = await initClaspInstance({
|
|
97
|
+
credentials: auth.credentials,
|
|
98
|
+
configFile: projectDir,
|
|
99
|
+
rootDir: projectDir,
|
|
100
|
+
});
|
|
101
|
+
try {
|
|
102
|
+
const files = await clasp.files.pull();
|
|
103
|
+
const fileList = files.map(file => ({
|
|
104
|
+
type: 'text',
|
|
105
|
+
text: `Updated file: ${path.resolve(file.localPath)}`,
|
|
106
|
+
}));
|
|
107
|
+
return {
|
|
108
|
+
content: [
|
|
109
|
+
{
|
|
110
|
+
type: 'text',
|
|
111
|
+
text: `Pushed project in ${projectDir} to remote server successfully.`,
|
|
112
|
+
},
|
|
113
|
+
...fileList,
|
|
114
|
+
],
|
|
115
|
+
structuredContent: {
|
|
116
|
+
scriptId: clasp.project.scriptId,
|
|
117
|
+
projectDir: projectDir,
|
|
118
|
+
files: files.map(file => path.resolve(file.localPath)),
|
|
119
|
+
},
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
catch (err) {
|
|
123
|
+
return {
|
|
124
|
+
isError: true,
|
|
125
|
+
content: [
|
|
126
|
+
{
|
|
127
|
+
type: 'text',
|
|
128
|
+
text: `Error pushing project: ${err.message}`,
|
|
129
|
+
},
|
|
130
|
+
],
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
server.tool('create_project', 'Create a new apps script project.', {
|
|
135
|
+
projectDir: z.string().describe('The local directory where the Apps Script project will be created.'),
|
|
136
|
+
sourceDir: z
|
|
137
|
+
.string()
|
|
138
|
+
.optional()
|
|
139
|
+
.describe('Local directory relative to projectDir where the Apps Script source files are located. If not specified, files are placed in the project directory.'),
|
|
140
|
+
projectName: z
|
|
141
|
+
.string()
|
|
142
|
+
.optional()
|
|
143
|
+
.describe('Name of the project. If not provided, the project name will be infered from the directory.'),
|
|
144
|
+
}, {
|
|
145
|
+
title: 'Create Apps Script project',
|
|
146
|
+
openWorldHint: false,
|
|
147
|
+
destructiveHint: true,
|
|
148
|
+
idempotentHint: false,
|
|
149
|
+
readOnlyHint: false,
|
|
150
|
+
}, async ({ projectDir, sourceDir, projectName }) => {
|
|
151
|
+
if (!projectDir) {
|
|
152
|
+
return {
|
|
153
|
+
isError: true,
|
|
154
|
+
content: [
|
|
155
|
+
{
|
|
156
|
+
type: 'text',
|
|
157
|
+
text: 'Project directory is required.',
|
|
158
|
+
},
|
|
159
|
+
],
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
await mkdir(projectDir, { recursive: true });
|
|
163
|
+
if (!projectName) {
|
|
164
|
+
projectName = getDefaultProjectName(projectDir);
|
|
165
|
+
}
|
|
166
|
+
const clasp = await initClaspInstance({
|
|
167
|
+
credentials: auth.credentials,
|
|
168
|
+
configFile: projectDir,
|
|
169
|
+
rootDir: projectDir,
|
|
170
|
+
});
|
|
171
|
+
clasp.withContentDir(sourceDir !== null && sourceDir !== void 0 ? sourceDir : '.');
|
|
172
|
+
try {
|
|
173
|
+
const id = await clasp.project.createScript(projectName);
|
|
174
|
+
const files = await clasp.files.pull();
|
|
175
|
+
await clasp.project.updateSettings();
|
|
176
|
+
const fileList = files.map(file => ({
|
|
177
|
+
type: 'text',
|
|
178
|
+
text: `Updated file: ${path.resolve(file.localPath)}`,
|
|
179
|
+
}));
|
|
180
|
+
return {
|
|
181
|
+
content: [
|
|
182
|
+
{
|
|
183
|
+
type: 'text',
|
|
184
|
+
text: `Created project ${id} in ${projectDir} successfully.`,
|
|
185
|
+
},
|
|
186
|
+
...fileList,
|
|
187
|
+
],
|
|
188
|
+
structuredContent: {
|
|
189
|
+
scriptId: id,
|
|
190
|
+
projectDir: projectDir,
|
|
191
|
+
files: files.map(file => path.resolve(file.localPath)),
|
|
192
|
+
},
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
catch (err) {
|
|
196
|
+
return {
|
|
197
|
+
isError: true,
|
|
198
|
+
content: [
|
|
199
|
+
{
|
|
200
|
+
type: 'text',
|
|
201
|
+
text: `Error pushing project: ${err.message}`,
|
|
202
|
+
},
|
|
203
|
+
],
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
});
|
|
207
|
+
server.tool('clone_project', 'Clones and pulls an existing Apps Script project to a local directory.', {
|
|
208
|
+
projectDir: z.string().describe('The local directory where the Apps Script project will be created.'),
|
|
209
|
+
sourceDir: z
|
|
210
|
+
.string()
|
|
211
|
+
.optional()
|
|
212
|
+
.describe('Local directory relative to projectDir where the Apps Script source files are located. If not specified, files are placed in the project directory.'),
|
|
213
|
+
scriptId: z.string().optional().describe('ID of the Apps Script project to clone.'),
|
|
214
|
+
}, {
|
|
215
|
+
title: 'Create Apps Script project',
|
|
216
|
+
openWorldHint: false,
|
|
217
|
+
destructiveHint: true,
|
|
218
|
+
idempotentHint: false,
|
|
219
|
+
readOnlyHint: false,
|
|
220
|
+
}, async ({ projectDir, sourceDir, scriptId }) => {
|
|
221
|
+
if (!projectDir) {
|
|
222
|
+
return {
|
|
223
|
+
isError: true,
|
|
224
|
+
content: [
|
|
225
|
+
{
|
|
226
|
+
type: 'text',
|
|
227
|
+
text: 'Project directory is required.',
|
|
228
|
+
},
|
|
229
|
+
],
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
await mkdir(projectDir, { recursive: true });
|
|
233
|
+
if (!scriptId) {
|
|
234
|
+
return {
|
|
235
|
+
isError: true,
|
|
236
|
+
content: [
|
|
237
|
+
{
|
|
238
|
+
type: 'text',
|
|
239
|
+
text: 'Script ID is required.',
|
|
240
|
+
},
|
|
241
|
+
],
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
const clasp = await initClaspInstance({
|
|
245
|
+
credentials: auth.credentials,
|
|
246
|
+
configFile: projectDir,
|
|
247
|
+
rootDir: projectDir,
|
|
248
|
+
});
|
|
249
|
+
clasp.withContentDir(sourceDir !== null && sourceDir !== void 0 ? sourceDir : '.').withScriptId(scriptId);
|
|
250
|
+
try {
|
|
251
|
+
const files = await clasp.files.pull();
|
|
252
|
+
clasp.project.updateSettings();
|
|
253
|
+
const fileList = files.map(file => ({
|
|
254
|
+
type: 'text',
|
|
255
|
+
text: `Updated file: ${path.resolve(file.localPath)}`,
|
|
256
|
+
}));
|
|
257
|
+
return {
|
|
258
|
+
content: [
|
|
259
|
+
{
|
|
260
|
+
type: 'text',
|
|
261
|
+
text: `Cloned project ${scriptId} in ${projectDir} successfully.`,
|
|
262
|
+
},
|
|
263
|
+
...fileList,
|
|
264
|
+
],
|
|
265
|
+
structuredContent: {
|
|
266
|
+
scriptId: scriptId,
|
|
267
|
+
projectDir: projectDir,
|
|
268
|
+
files: files.map(file => path.resolve(file.localPath)),
|
|
269
|
+
},
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
catch (err) {
|
|
273
|
+
return {
|
|
274
|
+
isError: true,
|
|
275
|
+
content: [
|
|
276
|
+
{
|
|
277
|
+
type: 'text',
|
|
278
|
+
text: `Error pushing project: ${err.message}`,
|
|
279
|
+
},
|
|
280
|
+
],
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
});
|
|
284
|
+
server.tool('list_projects', 'List Apps Script projects', {}, {
|
|
285
|
+
title: 'List Apps Script projects',
|
|
286
|
+
openWorldHint: false,
|
|
287
|
+
destructiveHint: true,
|
|
288
|
+
idempotentHint: false,
|
|
289
|
+
readOnlyHint: false,
|
|
290
|
+
}, async () => {
|
|
291
|
+
const clasp = await initClaspInstance({
|
|
292
|
+
credentials: auth.credentials,
|
|
293
|
+
});
|
|
294
|
+
try {
|
|
295
|
+
const scripts = await clasp.project.listScripts();
|
|
296
|
+
const scriptList = scripts.results.map(script => ({
|
|
297
|
+
type: 'text',
|
|
298
|
+
text: `${script.name} (${script.id})`,
|
|
299
|
+
}));
|
|
300
|
+
return {
|
|
301
|
+
content: [
|
|
302
|
+
{
|
|
303
|
+
type: 'text',
|
|
304
|
+
text: `Found ${scripts.results.length} Apps Script projects (script ID in parentheses):`,
|
|
305
|
+
},
|
|
306
|
+
...scriptList,
|
|
307
|
+
],
|
|
308
|
+
structuredContent: {
|
|
309
|
+
scripts: scripts.results.map(script => ({
|
|
310
|
+
scriptId: script.id,
|
|
311
|
+
name: script.name,
|
|
312
|
+
})),
|
|
313
|
+
},
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
catch (err) {
|
|
317
|
+
return {
|
|
318
|
+
isError: true,
|
|
319
|
+
content: [
|
|
320
|
+
{
|
|
321
|
+
type: 'text',
|
|
322
|
+
text: `Error listing projects: ${err.message}`,
|
|
323
|
+
},
|
|
324
|
+
],
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
});
|
|
328
|
+
return server;
|
|
329
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@google/clasp",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.5-alpha",
|
|
4
4
|
"description": "Develop Apps Script Projects locally",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": "./build/src/index.js",
|
|
@@ -55,81 +55,61 @@
|
|
|
55
55
|
"author": "Grant Timmerman",
|
|
56
56
|
"license": "Apache-2.0",
|
|
57
57
|
"dependencies": {
|
|
58
|
-
"@formatjs/intl": "^3.1.
|
|
59
|
-
"@messageformat/core": "^3.4.0",
|
|
60
|
-
"@sindresorhus/is": "^7.0.1",
|
|
61
|
-
"@types/debug": "^4.1.12",
|
|
58
|
+
"@formatjs/intl": "^3.1.6",
|
|
62
59
|
"chalk": "^5.4.1",
|
|
63
60
|
"chokidar": "^4.0.3",
|
|
64
61
|
"cli-truncate": "^4.0.0",
|
|
65
|
-
"commander": "^13.
|
|
66
|
-
"debounce": "^2.2.0",
|
|
62
|
+
"commander": "^13.1.0",
|
|
67
63
|
"debug": "^4.4.0",
|
|
68
|
-
"
|
|
69
|
-
"fdir": "^6.4.3",
|
|
64
|
+
"fdir": "^6.4.4",
|
|
70
65
|
"find-up": "^7.0.0",
|
|
71
66
|
"fuzzy": "^0.1.3",
|
|
72
|
-
"
|
|
73
|
-
"
|
|
74
|
-
"googleapis": "^144.0.0",
|
|
67
|
+
"google-auth-library": "^9.15.1",
|
|
68
|
+
"googleapis": "^148.0.0",
|
|
75
69
|
"googleapis-common": "7.2.0",
|
|
76
70
|
"inflection": "^3.0.2",
|
|
77
|
-
"inquirer": "^12.
|
|
71
|
+
"inquirer": "^12.6.0",
|
|
78
72
|
"inquirer-autocomplete-standalone": "^0.8.1",
|
|
79
|
-
"log-symbols": "^7.0.0",
|
|
80
73
|
"loud-rejection": "^2.2.0",
|
|
81
|
-
"make-dir": "^5.0.0",
|
|
82
74
|
"micromatch": "^4.0.8",
|
|
83
|
-
"
|
|
84
|
-
"normalize-newline": "^4.1.0",
|
|
75
|
+
"@modelcontextprotocol/sdk": "^1.12.1",
|
|
85
76
|
"normalize-path": "^3.0.0",
|
|
86
|
-
"open": "^10.1.
|
|
77
|
+
"open": "^10.1.2",
|
|
87
78
|
"ora": "^8.1.1",
|
|
88
79
|
"p-map": "^7.0.3",
|
|
89
80
|
"picomatch": "^4.0.2",
|
|
90
|
-
"read-
|
|
81
|
+
"read-package-up": "^11.0.0",
|
|
91
82
|
"server-destroy": "^1.0.1",
|
|
92
83
|
"split-lines": "^3.0.0",
|
|
93
84
|
"strip-bom": "^5.0.0",
|
|
94
|
-
"
|
|
85
|
+
"zod": "^3.25.36"
|
|
95
86
|
},
|
|
96
87
|
"devDependencies": {
|
|
97
88
|
"@biomejs/biome": "^1.9.4",
|
|
98
|
-
"@commander-js/extra-typings": "^13.
|
|
99
|
-
"@formatjs/ts-transformer": "^3.13.
|
|
100
|
-
"@
|
|
101
|
-
"@
|
|
102
|
-
"@types/
|
|
103
|
-
"@types/chai-as-promised": "^8.0.1",
|
|
104
|
-
"@types/chai-fs": "^2.0.5",
|
|
105
|
-
"@types/chai-subset": "^1.3.5",
|
|
106
|
-
"@types/debounce": "^1.2.4",
|
|
107
|
-
"@types/fs-extra": "^11.0.4",
|
|
89
|
+
"@commander-js/extra-typings": "^13.1.0",
|
|
90
|
+
"@formatjs/ts-transformer": "^3.13.34",
|
|
91
|
+
"@types/chai": "^5.2.2",
|
|
92
|
+
"@types/chai-as-promised": "^8.0.2",
|
|
93
|
+
"@types/debug": "^4.1.12",
|
|
108
94
|
"@types/micromatch": "^4.0.9",
|
|
109
95
|
"@types/mocha": "^10.0.10",
|
|
110
96
|
"@types/mock-fs": "^4.13.4",
|
|
111
|
-
"@types/node": "^22.
|
|
97
|
+
"@types/node": "^22.15.17",
|
|
112
98
|
"@types/normalize-path": "^3.0.2",
|
|
113
|
-
"@types/picomatch": "^
|
|
99
|
+
"@types/picomatch": "^4.0.0",
|
|
114
100
|
"@types/server-destroy": "^1.0.4",
|
|
115
101
|
"@types/sinon": "^17.0.4",
|
|
116
|
-
"@types/tmp": "^0.2.6",
|
|
117
|
-
"@types/wtfnode": "^0.7.3",
|
|
118
102
|
"c8": "^10.1.3",
|
|
119
|
-
"chai": "^5.
|
|
103
|
+
"chai": "^5.2.0",
|
|
120
104
|
"chai-as-promised": "^8.0.1",
|
|
121
|
-
"
|
|
122
|
-
"
|
|
123
|
-
"
|
|
124
|
-
"
|
|
125
|
-
"nyc": "^17.1.0",
|
|
126
|
-
"sinon": "^19.0.2",
|
|
105
|
+
"mocha": "^11.2.2",
|
|
106
|
+
"mock-fs": "^5.5.0",
|
|
107
|
+
"nock": "^14.0.4",
|
|
108
|
+
"sinon": "^20.0.0",
|
|
127
109
|
"source-map-support": "^0.5.21",
|
|
128
|
-
"tmp": "^0.2.3",
|
|
129
110
|
"ts-node": "^10.9.2",
|
|
130
111
|
"ts-patch": "^3.3.0",
|
|
131
|
-
"type-fest": "^4.
|
|
132
|
-
"
|
|
133
|
-
"wtfnode": "^0.10.0"
|
|
112
|
+
"type-fest": "^4.41.0",
|
|
113
|
+
"typescript": "^5.8.3"
|
|
134
114
|
}
|
|
135
115
|
}
|