Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,9 @@ gscli gmail folders-list
# List files in root directory
gscli drive list

# List files including shared with you
gscli drive list --include-shared

# List files in specific folder
gscli drive list --folder "Project Docs"

Expand Down Expand Up @@ -299,6 +302,9 @@ gscli calendar search "standup"
# List recent documents
gscli drive list --limit 10

# List documents including those shared with you
gscli drive list --include-shared --limit 20

# Search for specific docs
gscli drive search "PRD"

Expand All @@ -324,6 +330,48 @@ src/
└── formatter.ts # Output formatting
```

## Custom Build with Embedded Credentials

For advanced users who want to distribute a custom build with embedded OAuth2 credentials, you can inject your Google client credentials directly during the build process. This eliminates the need for users to provide credentials via the `--client` flag or environment variables.

### Benefits

- No need to distribute or manage `client.json` files
- Users can authenticate immediately without additional setup
- Ideal for internal tools or controlled distribution

### Build Command

```bash
# 1. Export your credentials as environment variables
export GOOGLE_CLIENT_ID="your-client-id-here.apps.googleusercontent.com"
export GOOGLE_CLIENT_SECRET="your-client-secret-here"

# 2. Build with embedded credentials
bun run build:custom # Build for current platform
bun run build:custom-all # Build for all platforms
bun run build:custom-linux # Build for Linux only
bun run build:custom-macos # Build for macOS only
bun run build:custom-windows # Build for Windows only
```

The built binaries will be in the `dist/` folder with the `-custom` suffix (e.g., `dist/gscli-custom-linux`).

### Important Notes

- The credentials are embedded in the compiled binary at build time
- Users of your custom build will still need to authenticate via OAuth2 (`gscli auth login`)
- This is suitable for internal distribution or when you trust the users
- For public distribution, it's recommended to let users provide their own credentials

### Security Considerations

When building with embedded credentials:
- Only distribute to trusted users or within your organization
- Consider the OAuth consent screen settings in Google Cloud Console
- Users will authenticate with their own Google accounts, but using your OAuth2 app
- Monitor usage through Google Cloud Console

## Development

```bash
Expand Down
5 changes: 5 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@
"build:linux": "bun build --compile --minify --target=bun-linux-x64 src/index.ts --outfile dist/gscli-linux",
"build:macos": "bun build --compile --minify --target=bun-darwin-x64 src/index.ts --outfile dist/gscli-macos",
"build:windows": "bun build --compile --minify --target=bun-windows-x64 src/index.ts --outfile dist/gscli-windows.exe",
"build:custom": "bun build --compile --minify --sourcemap src/index.ts --outfile dist/gscli-custom --define process.env.GOOGLE_CLIENT_ID=\"'$GOOGLE_CLIENT_ID'\" --define process.env.GOOGLE_CLIENT_SECRET=\"'$GOOGLE_CLIENT_SECRET'\"",
"build:custom-all": "bun run build:custom-linux && bun run build:custom-macos && bun run build:custom-windows",
"build:custom-linux": "bun build --compile --minify --target=bun-linux-x64 src/index.ts --outfile dist/gscli-custom-linux --define process.env.GOOGLE_CLIENT_ID=\"'$GOOGLE_CLIENT_ID'\" --define process.env.GOOGLE_CLIENT_SECRET=\"'$GOOGLE_CLIENT_SECRET'\"",
"build:custom-macos": "bun build --compile --minify --target=bun-darwin-x64 src/index.ts --outfile dist/gscli-custom-macos --define process.env.GOOGLE_CLIENT_ID=\"'$GOOGLE_CLIENT_ID'\" --define process.env.GOOGLE_CLIENT_SECRET=\"'$GOOGLE_CLIENT_SECRET'\"",
"build:custom-windows": "bun build --compile --minify --target=bun-windows-x64 src/index.ts --outfile dist/gscli-custom-windows.exe --define process.env.GOOGLE_CLIENT_ID=\"'$GOOGLE_CLIENT_ID'\" --define process.env.GOOGLE_CLIENT_SECRET=\"'$GOOGLE_CLIENT_SECRET'\"",
"start": "./dist/gscli"
},
"keywords": [
Expand Down
6 changes: 4 additions & 2 deletions src/commands/drive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,21 @@ export function createDriveCommand(): Command {
.description('List files in your Google Drive')
.option('-f, --folder <name-or-id>', 'Folder name or ID to list from')
.option('-l, --limit <number>', 'Maximum number of files to list', '100')
.option('--include-shared', 'Include files shared with you (not just files in your Drive folders)')
.option('-a, --account <email>', 'Google account email to use (uses default if not specified)')
.action(async (options) => {
const spinner = ora('Fetching files...').start();

try {
const auth = await getAuthenticatedClient(options.account);
const limit = parseInt(options.limit);

const files = await listFiles(auth, {
folder: options.folder,
limit,
includeShared: options.includeShared,
});

spinner.stop();
formatDriveFiles(files);
} catch (error: any) {
Expand Down
39 changes: 28 additions & 11 deletions src/lib/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,9 @@ interface Credentials {
* Load client credentials
* Priority:
* 1. Saved in credentials.json (from previous auth login --client)
* 2. Path specified in GOOGLE_CLIENT_CREDENTIAL_FILE environment variable
* 3. ./client.json in current directory
* 2. Build-time injected environment variables (GOOGLE_CLIENT_ID & GOOGLE_CLIENT_SECRET)
* 3. Path specified in GOOGLE_CLIENT_CREDENTIAL_FILE environment variable
* 4. ./client.json in current directory
*/
export function loadClientCredentials(clientPath?: string) {
// 1. First, check if we have saved client credentials in credentials.json
Expand All @@ -49,18 +50,29 @@ export function loadClientCredentials(clientPath?: string) {
};
}

// 2. Check provided path (from --client flag)
// 2. Check for build-time injected environment variables
const envClientId = process.env.GOOGLE_CLIENT_ID;
const envClientSecret = process.env.GOOGLE_CLIENT_SECRET;

if (envClientId && envClientSecret) {
return {
client_id: envClientId,
client_secret: envClientSecret,
};
}

// 3. Check provided path (from --client flag)
if (clientPath) {
try {
if (existsSync(clientPath)) {
const content = readFileSync(clientPath, 'utf-8');
const credentials = JSON.parse(content);
const creds = credentials.installed || credentials.web || credentials;

if (!creds.client_id || !creds.client_secret) {
throw new Error('Invalid credentials format: missing client_id or client_secret');
}

return creds;
} else {
throw new Error(`Client file not found: ${clientPath}`);
Expand All @@ -70,22 +82,22 @@ export function loadClientCredentials(clientPath?: string) {
}
}

// 3. Check GOOGLE_CLIENT_CREDENTIAL_FILE environment variable
// 4. Check GOOGLE_CLIENT_CREDENTIAL_FILE environment variable
const envPath = process.env.GOOGLE_CLIENT_CREDENTIAL_FILE;
const credentialPath = envPath || DEFAULT_CLIENT_CONFIG_PATH;

try {
if (existsSync(credentialPath)) {
const content = readFileSync(credentialPath, 'utf-8');
const credentials = JSON.parse(content);

// Support both Google Cloud Console formats
const creds = credentials.installed || credentials.web || credentials;

if (!creds.client_id || !creds.client_secret) {
throw new Error('Invalid credentials format: missing client_id or client_secret');
}

return creds;
}
} catch (error: any) {
Expand All @@ -104,10 +116,15 @@ Please provide credentials using one of these methods:
1. During auth login (saves credentials for future use):
gscli auth login --client /path/to/client.json

2. Environment Variable:
2. Build-time injection (for custom builds):
bun build ./src/index.ts --compile --outfile my-cli \\
--define process.env.GOOGLE_CLIENT_ID="'YOUR_CLIENT_ID'" \\
--define process.env.GOOGLE_CLIENT_SECRET="'YOUR_CLIENT_SECRET'"

3. Environment Variable:
export GOOGLE_CLIENT_CREDENTIAL_FILE="/path/to/your/client.json"

3. Local File:
4. Local File:
Create a file named "client.json" in the current directory

To get credentials:
Expand Down
9 changes: 7 additions & 2 deletions src/lib/drive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,14 +59,15 @@ export async function listFiles(
options: {
folder?: string;
limit?: number;
includeShared?: boolean;
} = {}
): Promise<DriveFile[]> {
const drive = google.drive({ version: 'v3', auth });
const limit = options.limit || 100;

try {
let query = "trashed = false";

// If folder is specified, find it first
if (options.folder) {
// Try to find folder by name or use as ID
Expand All @@ -80,6 +81,9 @@ export async function listFiles(
const folder = folderResponse.data.files?.[0];
const folderId = folder?.id || options.folder;
query = `'${folderId}' in parents and trashed = false`;
} else if (options.includeShared) {
// List all files including shared with me
query = "trashed = false";
} else {
// List root files only
query = "'root' in parents and trashed = false";
Expand All @@ -90,10 +94,11 @@ export async function listFiles(
pageSize: limit,
fields: 'files(id, name, mimeType, size, modifiedTime, webViewLink)',
orderBy: 'modifiedTime desc',
...(options.includeShared && { corpora: 'allDrives', includeItemsFromAllDrives: true, supportsAllDrives: true }),
});

const files = response.data.files || [];

return files.map((file) => ({
id: file.id!,
name: file.name!,
Expand Down