chore: import upstream snapshot with attribution
Publish OpenClaw Skills / publish (push) Has been cancelled
Audit / Security Audit (push) Has been cancelled
Automation / Gemini Review (push) Has been cancelled
CI / Detect Changes (push) Has been cancelled
CI / Test (macos-latest) (push) Has been cancelled
CI / Test (ubuntu-latest) (push) Has been cancelled
CI / Nix (push) Has been cancelled
CI / Lint (push) Has been cancelled
CI / Cargo Deny (push) Has been cancelled
CI / Verify Skills (push) Has been cancelled
CI / Lint Skills (push) Has been cancelled
CI / Build (Linux x86_64) (push) Has been cancelled
CI / Build (macos-latest, aarch64-apple-darwin) (push) Has been cancelled
CI / Build (macos-latest, x86_64-apple-darwin) (push) Has been cancelled
CI / Build (ubuntu-latest, aarch64-unknown-linux-gnu) (push) Has been cancelled
CI / Build (windows-latest, x86_64-pc-windows-msvc) (push) Has been cancelled
CI / API Smoketest (push) Has been cancelled
Policy / Policy Check (push) Has been cancelled
Release (Changeset) / Release (push) Has been cancelled
Automation / Gemini Reviewed (push) Has been cancelled
Coverage / Coverage (push) Has been cancelled
Automation / Format (push) Has been cancelled
Automation / File Labeler (push) Has been cancelled
Publish OpenClaw Skills / publish (push) Has been cancelled
Audit / Security Audit (push) Has been cancelled
Automation / Gemini Review (push) Has been cancelled
CI / Detect Changes (push) Has been cancelled
CI / Test (macos-latest) (push) Has been cancelled
CI / Test (ubuntu-latest) (push) Has been cancelled
CI / Nix (push) Has been cancelled
CI / Lint (push) Has been cancelled
CI / Cargo Deny (push) Has been cancelled
CI / Verify Skills (push) Has been cancelled
CI / Lint Skills (push) Has been cancelled
CI / Build (Linux x86_64) (push) Has been cancelled
CI / Build (macos-latest, aarch64-apple-darwin) (push) Has been cancelled
CI / Build (macos-latest, x86_64-apple-darwin) (push) Has been cancelled
CI / Build (ubuntu-latest, aarch64-unknown-linux-gnu) (push) Has been cancelled
CI / Build (windows-latest, x86_64-pc-windows-msvc) (push) Has been cancelled
CI / API Smoketest (push) Has been cancelled
Policy / Policy Check (push) Has been cancelled
Release (Changeset) / Release (push) Has been cancelled
Automation / Gemini Reviewed (push) Has been cancelled
Coverage / Coverage (push) Has been cancelled
Automation / Format (push) Has been cancelled
Automation / File Labeler (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
# Downloaded binary (created during npm postinstall)
|
||||
bin/
|
||||
+170
@@ -0,0 +1,170 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
"use strict";
|
||||
|
||||
const crypto = require("crypto");
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const os = require("os");
|
||||
const { pipeline } = require("stream/promises");
|
||||
const { createWriteStream, mkdirSync, rmSync } = require("fs");
|
||||
const { spawnSync } = require("child_process");
|
||||
const { getPlatform } = require("./platform");
|
||||
|
||||
const INSTALL_DIR = path.join(__dirname, "bin");
|
||||
|
||||
/**
|
||||
* Get the GitHub release download URL base for the current package version.
|
||||
*/
|
||||
function getDownloadUrl(artifactName) {
|
||||
const { version } = require("./package.json");
|
||||
return `https://github.com/googleworkspace/cli/releases/download/v${version}/${artifactName}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Strip ANSI escape sequences from a string.
|
||||
*/
|
||||
function sanitize(str) {
|
||||
// eslint-disable-next-line no-control-regex
|
||||
return String(str).replace(/\x1b\[[0-9;]*[a-zA-Z]/g, "");
|
||||
}
|
||||
|
||||
/**
|
||||
* Download a file using native fetch (Node 18+).
|
||||
*
|
||||
* NOTE: Native fetch does not respect HTTP_PROXY / HTTPS_PROXY environment
|
||||
* variables. If proxy support is needed, consider using the `undici` ProxyAgent
|
||||
* or a Node.js build with proxy support.
|
||||
*/
|
||||
async function download(url, dest) {
|
||||
const res = await fetch(url, { redirect: "follow" });
|
||||
|
||||
if (!res.ok) {
|
||||
throw new Error(`Failed to download ${url}: ${res.status} ${res.statusText}`);
|
||||
}
|
||||
|
||||
if (!res.body) {
|
||||
throw new Error(`Failed to download ${url}: Response body is empty`);
|
||||
}
|
||||
|
||||
const fileStream = createWriteStream(dest);
|
||||
// Convert web ReadableStream to Node stream and pipe
|
||||
const { Readable } = require("stream");
|
||||
const nodeStream = Readable.fromWeb(res.body);
|
||||
await pipeline(nodeStream, fileStream);
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a command and throw on failure.
|
||||
*/
|
||||
function run(cmd, args) {
|
||||
const result = spawnSync(cmd, args, { stdio: "pipe" });
|
||||
if (result.error) {
|
||||
throw new Error(`Failed to run ${cmd}: ${result.error.message}`);
|
||||
}
|
||||
if ((result.status ?? 1) !== 0) {
|
||||
const stderr = result.stderr ? result.stderr.toString() : "";
|
||||
throw new Error(
|
||||
`Command failed: ${cmd} ${args.join(" ")}\n${stderr}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the archive to the install directory.
|
||||
*/
|
||||
function extract(archivePath, destDir) {
|
||||
const isZip = archivePath.endsWith(".zip");
|
||||
const isTar = archivePath.includes(".tar.");
|
||||
|
||||
if (isTar) {
|
||||
run("tar", ["xf", archivePath, "-C", destDir]);
|
||||
} else if (isZip) {
|
||||
if (process.platform === "win32") {
|
||||
// Use single-quoted PowerShell strings with doubled single-quote escaping
|
||||
// to safely handle paths containing spaces and special characters.
|
||||
const psArchive = archivePath.replace(/'/g, "''");
|
||||
const psDest = destDir.replace(/'/g, "''");
|
||||
run("powershell.exe", [
|
||||
"-NoProfile",
|
||||
"-NonInteractive",
|
||||
"-Command",
|
||||
`Expand-Archive -LiteralPath '${psArchive}' -DestinationPath '${psDest}' -Force`,
|
||||
]);
|
||||
} else {
|
||||
run("unzip", ["-q", "-o", archivePath, "-d", destDir]);
|
||||
}
|
||||
} else {
|
||||
throw new Error(`Unsupported archive format: ${archivePath}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function install() {
|
||||
const platform = getPlatform();
|
||||
const { version } = require("./package.json");
|
||||
const url = getDownloadUrl(platform.artifact);
|
||||
|
||||
// Check if the correct version is already installed
|
||||
const binPath = path.join(INSTALL_DIR, platform.binary);
|
||||
const versionFile = path.join(INSTALL_DIR, ".version");
|
||||
if (fs.existsSync(binPath) && fs.existsSync(versionFile)) {
|
||||
const installed = fs.readFileSync(versionFile, "utf8").trim();
|
||||
if (installed === version) {
|
||||
console.error(`gws v${version} is already installed, skipping.`);
|
||||
return;
|
||||
}
|
||||
console.error(`Upgrading gws from v${installed} to v${version}`);
|
||||
}
|
||||
|
||||
// Clean and create install directory
|
||||
if (fs.existsSync(INSTALL_DIR)) {
|
||||
rmSync(INSTALL_DIR, { recursive: true, force: true });
|
||||
}
|
||||
mkdirSync(INSTALL_DIR, { recursive: true });
|
||||
|
||||
// Download to a temp file
|
||||
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "gws-"));
|
||||
const archiveName = path.basename(platform.artifact);
|
||||
const tmpFile = path.join(tmpDir, archiveName);
|
||||
|
||||
try {
|
||||
console.error(`Downloading gws from ${url}`);
|
||||
await download(url, tmpFile);
|
||||
|
||||
// Verify SHA256 checksum
|
||||
const sha256Url = `${url}.sha256`;
|
||||
const sha256File = `${tmpFile}.sha256`;
|
||||
console.error(`Verifying checksum from ${sha256Url}`);
|
||||
await download(sha256Url, sha256File);
|
||||
|
||||
const expectedHash = fs.readFileSync(sha256File, "utf8").trim().split(/\s+/)[0].toLowerCase();
|
||||
const fileBuffer = fs.readFileSync(tmpFile);
|
||||
const actualHash = crypto.createHash("sha256").update(fileBuffer).digest("hex").toLowerCase();
|
||||
|
||||
if (actualHash !== expectedHash) {
|
||||
throw new Error(
|
||||
`SHA256 checksum mismatch!\n Expected: ${expectedHash}\n Actual: ${actualHash}\nThe downloaded binary may have been tampered with.`,
|
||||
);
|
||||
}
|
||||
console.error("Checksum verified ✓");
|
||||
|
||||
console.error(`Extracting to ${INSTALL_DIR}`);
|
||||
extract(tmpFile, INSTALL_DIR);
|
||||
|
||||
// Make binary executable on Unix
|
||||
if (process.platform !== "win32") {
|
||||
fs.chmodSync(binPath, 0o755);
|
||||
}
|
||||
|
||||
console.error(`gws v${version} has been installed!`);
|
||||
fs.writeFileSync(versionFile, version);
|
||||
} finally {
|
||||
// Clean up temp files
|
||||
rmSync(tmpDir, { recursive: true, force: true });
|
||||
}
|
||||
}
|
||||
|
||||
install().catch((err) => {
|
||||
console.error(`Error installing gws: ${sanitize(err.message)}`);
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -0,0 +1,79 @@
|
||||
{
|
||||
"name": "@googleworkspace/cli",
|
||||
"description": "Google Workspace CLI — dynamic command surface from Discovery Service",
|
||||
"version": "0.22.5",
|
||||
"license": "Apache-2.0",
|
||||
"author": "Justin Poehnelt",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/googleworkspace/cli.git"
|
||||
},
|
||||
"homepage": "https://github.com/googleworkspace/cli",
|
||||
"bugs": {
|
||||
"url": "https://github.com/googleworkspace/cli/issues"
|
||||
},
|
||||
"bin": {
|
||||
"gws": "run.js"
|
||||
},
|
||||
"scripts": {
|
||||
"postinstall": "node install.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"preferUnplugged": true,
|
||||
"keywords": [
|
||||
"cli",
|
||||
"google-workspace",
|
||||
"google",
|
||||
"google-api",
|
||||
"google-drive",
|
||||
"google-gmail",
|
||||
"google-sheets",
|
||||
"google-calendar",
|
||||
"google-docs",
|
||||
"google-chat",
|
||||
"google-admin",
|
||||
"gsuite",
|
||||
"discovery-api",
|
||||
"ai-agent",
|
||||
"agent-skills",
|
||||
"automation",
|
||||
"oauth2",
|
||||
"rust"
|
||||
],
|
||||
"publishConfig": {
|
||||
"provenance": true,
|
||||
"registry": "https://wombat-dressing-room.appspot.com"
|
||||
},
|
||||
"supportedPlatforms": {
|
||||
"aarch64-apple-darwin": {
|
||||
"artifact": "google-workspace-cli-aarch64-apple-darwin.tar.gz",
|
||||
"binary": "gws"
|
||||
},
|
||||
"x86_64-apple-darwin": {
|
||||
"artifact": "google-workspace-cli-x86_64-apple-darwin.tar.gz",
|
||||
"binary": "gws"
|
||||
},
|
||||
"aarch64-unknown-linux-gnu": {
|
||||
"artifact": "google-workspace-cli-aarch64-unknown-linux-gnu.tar.gz",
|
||||
"binary": "gws"
|
||||
},
|
||||
"aarch64-unknown-linux-musl": {
|
||||
"artifact": "google-workspace-cli-aarch64-unknown-linux-musl.tar.gz",
|
||||
"binary": "gws"
|
||||
},
|
||||
"x86_64-unknown-linux-gnu": {
|
||||
"artifact": "google-workspace-cli-x86_64-unknown-linux-gnu.tar.gz",
|
||||
"binary": "gws"
|
||||
},
|
||||
"x86_64-unknown-linux-musl": {
|
||||
"artifact": "google-workspace-cli-x86_64-unknown-linux-musl.tar.gz",
|
||||
"binary": "gws"
|
||||
},
|
||||
"x86_64-pc-windows-msvc": {
|
||||
"artifact": "google-workspace-cli-x86_64-pc-windows-msvc.zip",
|
||||
"binary": "gws.exe"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
"use strict";
|
||||
|
||||
const os = require("os");
|
||||
const path = require("path");
|
||||
const fs = require("fs");
|
||||
const { spawnSync } = require("child_process");
|
||||
|
||||
const { supportedPlatforms } = require("./package.json");
|
||||
|
||||
/**
|
||||
* Map Node.js os.type() and os.arch() to Rust-style target triples.
|
||||
*/
|
||||
function getPlatformKey() {
|
||||
const rawOs = os.type();
|
||||
const rawArch = os.arch();
|
||||
|
||||
let osType;
|
||||
switch (rawOs) {
|
||||
case "Windows_NT":
|
||||
osType = "pc-windows-msvc";
|
||||
break;
|
||||
case "Darwin":
|
||||
osType = "apple-darwin";
|
||||
break;
|
||||
case "Linux":
|
||||
osType = "unknown-linux-gnu";
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Unsupported operating system: ${rawOs}`);
|
||||
}
|
||||
|
||||
let arch;
|
||||
switch (rawArch) {
|
||||
case "x64":
|
||||
arch = "x86_64";
|
||||
break;
|
||||
case "arm64":
|
||||
arch = "aarch64";
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Unsupported architecture: ${rawArch}`);
|
||||
}
|
||||
|
||||
// On Linux, try to detect musl libc
|
||||
if (rawOs === "Linux") {
|
||||
try {
|
||||
const result = spawnSync("ldd", ["--version"], {
|
||||
encoding: "utf8",
|
||||
stdio: ["pipe", "pipe", "pipe"],
|
||||
});
|
||||
// musl ldd prints version info to stderr
|
||||
const output = (result.stdout || "") + (result.stderr || "");
|
||||
if (output.toLowerCase().includes("musl")) {
|
||||
osType = "unknown-linux-musl";
|
||||
}
|
||||
} catch {
|
||||
// If ldd fails, assume glibc
|
||||
}
|
||||
}
|
||||
|
||||
const key = `${arch}-${osType}`;
|
||||
|
||||
if (!supportedPlatforms[key]) {
|
||||
// Try musl fallback on Linux if glibc binary is not available
|
||||
if (rawOs === "Linux") {
|
||||
const muslKey = `${arch}-unknown-linux-musl`;
|
||||
if (supportedPlatforms[muslKey]) {
|
||||
return muslKey;
|
||||
}
|
||||
}
|
||||
throw new Error(
|
||||
`Unsupported platform: ${key}\nSupported platforms: ${Object.keys(supportedPlatforms).join(", ")}`,
|
||||
);
|
||||
}
|
||||
|
||||
return key;
|
||||
}
|
||||
|
||||
function getPlatform() {
|
||||
const key = getPlatformKey();
|
||||
return supportedPlatforms[key];
|
||||
}
|
||||
|
||||
module.exports = { getPlatform, getPlatformKey };
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
"use strict";
|
||||
|
||||
const path = require("path");
|
||||
const fs = require("fs");
|
||||
const { spawnSync } = require("child_process");
|
||||
const { getPlatform } = require("./platform");
|
||||
|
||||
const platform = getPlatform();
|
||||
const binPath = path.join(__dirname, "bin", platform.binary);
|
||||
|
||||
if (!fs.existsSync(binPath)) {
|
||||
console.error(
|
||||
`gws binary not found at ${binPath}\nAuto-installing...`
|
||||
);
|
||||
const install = spawnSync(process.execPath, [path.join(__dirname, "install.js")], {
|
||||
cwd: __dirname,
|
||||
stdio: "inherit",
|
||||
});
|
||||
if (install.status !== 0) {
|
||||
process.exit(install.status ?? 1);
|
||||
}
|
||||
}
|
||||
|
||||
const result = spawnSync(binPath, process.argv.slice(2), {
|
||||
cwd: process.cwd(),
|
||||
stdio: "inherit",
|
||||
});
|
||||
|
||||
if (result.error) {
|
||||
console.error(`Error running gws: ${result.error.message}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
process.exit(result.status ?? 1);
|
||||
Reference in New Issue
Block a user