{"id":33235917,"url":"https://github.com/oharu121/fs-box-sync","last_synced_at":"2026-02-07T03:02:32.086Z","repository":{"id":324549362,"uuid":"1097634753","full_name":"oharu121/fs-box-sync","owner":"oharu121","description":"TypeScript SDK for Box API with seamless Box Drive integration - manage files, folders, webhooks, and sync between cloud and local filesystem","archived":false,"fork":false,"pushed_at":"2026-02-03T01:30:56.000Z","size":692,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-02-03T15:40:19.781Z","etag":null,"topics":["box","box-api","box-drive","file-sync","oauth","webhooks"],"latest_commit_sha":null,"homepage":"https://www.npmjs.com/package/fs-box-sync","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/oharu121.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-11-16T14:58:01.000Z","updated_at":"2026-02-03T01:30:54.000Z","dependencies_parsed_at":"2025-12-13T06:04:22.216Z","dependency_job_id":null,"html_url":"https://github.com/oharu121/fs-box-sync","commit_stats":null,"previous_names":["oharu121/fs-box-sync"],"tags_count":13,"template":false,"template_full_name":null,"purl":"pkg:github/oharu121/fs-box-sync","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/oharu121%2Ffs-box-sync","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/oharu121%2Ffs-box-sync/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/oharu121%2Ffs-box-sync/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/oharu121%2Ffs-box-sync/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/oharu121","download_url":"https://codeload.github.com/oharu121/fs-box-sync/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/oharu121%2Ffs-box-sync/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29185113,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-07T00:44:15.062Z","status":"online","status_checked_at":"2026-02-07T02:00:07.217Z","response_time":63,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["box","box-api","box-drive","file-sync","oauth","webhooks"],"created_at":"2025-11-16T18:01:48.814Z","updated_at":"2026-02-07T03:02:32.080Z","avatar_url":"https://github.com/oharu121.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# fs-box-sync\n\n[![npm version](https://badge.fury.io/js/fs-box-sync.svg)](https://badge.fury.io/js/fs-box-sync)\n![License](https://img.shields.io/npm/l/fs-box-sync)\n![Types](https://img.shields.io/npm/types/fs-box-sync)\n![NPM Downloads](https://img.shields.io/npm/dw/fs-box-sync)\n![Last Commit](https://img.shields.io/github/last-commit/oharu121/fs-box-sync)\n![Coverage](https://codecov.io/gh/oharu121/fs-box-sync/branch/main/graph/badge.svg)\n![CI Status](https://github.com/oharu121/fs-box-sync/actions/workflows/ci.yml/badge.svg)\n![GitHub Stars](https://img.shields.io/github/stars/oharu121/fs-box-sync?style=social)\n\nToolkit for Box REST API with automatic token management, OAuth automation support, and Box Drive integration.\n\n## Features\n\n- **3-Layer Architecture** - BoxAPI (raw API) → BoxDrive (sync bridge) → BoxFS (fs-like interface)\n- **Token Provider Pattern** - Injectable OAuth automation (e.g., Playwright)\n- **Persistent Storage** - Tokens saved across sessions\n  - **Windows:** `C:\\Users\\{user}\\AppData\\Local\\fs-box-sync\\tokens.json`\n  - **Mac/Linux:** `~/.config/fs-box-sync/tokens.json`\n- **Smart Sync** - Intelligent Box Drive sync verification with multiple strategies\n- **ID-based Operations** - All operations use Box IDs (no local paths in public API)\n- **Chunked Uploads** - Automatic chunking for files \u003e20MB\n\n## Architecture\n\n```\n┌─────────────────────────────────────┐\n│   BoxFS (High-level API)            │  ← Recommended\n│  - readDir(id, ensureSync)          │\n│  - readFile(id, ensureSync)         │\n│  - uploadWithYearMonthFolders()     │\n└──────────────┬──────────────────────┘\n               │\n┌──────────────▼──────────────────────┐\n│     BoxDrive (Sync Bridge)          │\n│  - getLocalPath(id)                 │\n│  - waitForSync(id, strategy)        │\n│  - Smart sync verification          │\n└──────────────┬──────────────────────┘\n               │\n┌──────────────▼──────────────────────┐\n│      BoxAPI (Pure API)              │\n│  - getFileInfo(id)                  │\n│  - uploadFile(id, file)             │\n└─────────────────────────────────────┘\n```\n\n## Installation\n\n```bash\nnpm install fs-box-sync\n```\n\n## Usage Guide: Choose Your Integration Level\n\n`fs-box-sync` supports three usage patterns based on your needs:\n\n### 🟢 Tier 1: Quick Testing (Access Token Only)\n\n**Best for:** Learning Box API, POC, quick experiments, one-off scripts\n\n**Get access token:** [Box Developer Console](https://app.box.com/developers/console) → My Apps → Your App → Configuration → Developer Token (Generate)\n\n```typescript\nimport { BoxAPI } from 'fs-box-sync';\n\n// Just paste the developer token - works immediately!\nconst api = new BoxAPI({\n  accessToken: 'your-developer-token-from-console',\n});\n\n// Use right away - perfect for testing\nconst files = await api.listFolderItems('folder-id');\nawait api.uploadFile('folder-id', './test.pdf');\n```\n\n**Characteristics:**\n\n- ✅ **Zero setup** - paste token and go\n- ✅ **No OAuth flow** required\n- ✅ **Perfect for learning** Box API\n- ⚠️ **Expires in ~1 hour** - must regenerate manually\n- ⚠️ **No auto-refresh** - you manage token lifecycle\n\n---\n\n### 🟡 Tier 2: Production (Refresh Token)\n\n**Best for:** Scheduled tasks, automation scripts, long-running services (up to 60 days)\n\n**Setup:** Perform OAuth flow once to get refresh token, then use it directly\n\n```typescript\nimport box from 'fs-box-sync';\n\nbox.configure({\n  clientId: process.env.BOX_CLIENT_ID,\n  clientSecret: process.env.BOX_CLIENT_SECRET,\n  refreshToken: 'your-refresh-token', // From initial OAuth flow\n});\n\n// Auto-refreshes access tokens for ~60 days\nawait box.uploadFile('folder-id', './file.pdf');\nawait box.uploadWithYearMonthFolders('folder-id', './file.pdf', 'ja-JP');\n```\n\n**Characteristics:**\n\n- ✅ **Auto-refreshes** access tokens\n- ✅ **Works for ~60 days**\n- ✅ **Tokens persist** to disk automatically\n- ✅ **Cross-platform storage** (Windows/Mac/Linux)\n- ⚠️ Requires **initial OAuth** to get refresh token\n- ⚠️ Needs **re-auth every ~60 days**\n\n**Storage locations:**\n\n- Windows: `C:\\Users\\{user}\\AppData\\Local\\fs-box-sync\\tokens.json`\n- Mac/Linux: `~/.config/fs-box-sync\\tokens.json`\n\n---\n\n### 🔵 Tier 3: Enterprise (Token Provider - Recommended for Automation)\n\n**Best for:** Fully automated systems, CI/CD, unattended services, production deployments\n\n**Setup:** Implement OAuth automation (e.g., Playwright) - works indefinitely\n\n```typescript\n// boxClient.ts - Create a wrapper module\nimport credentials from '@constants/credentials';\nimport box from 'fs-box-sync';\nimport Playwright from './Playwright';\n\nbox.configure({\n  clientId: credentials.BOX_CLIENT_ID,\n  clientSecret: credentials.BOX_CLIENT_SECRET,\n  tokenProvider: async (authUrl) =\u003e {\n    // Fully automate OAuth - no manual intervention needed\n    return await Playwright.getBoxCode(authUrl);\n  },\n});\n\nexport default box;\n```\n\nThen use anywhere in your app:\n\n```typescript\nimport box from './boxClient';\n\n// Works indefinitely - auto re-authenticates when needed\nawait box.uploadFile('folder-id', './file.pdf');\nawait box.readDir('folder-id'); // Always reads from synced local filesystem\n```\n\n**Characteristics:**\n\n- ✅ **Works indefinitely** - never expires\n- ✅ **Zero manual intervention** after setup\n- ✅ **Perfect for unattended** automation\n- ✅ **Handles token expiration** automatically\n- ⚠️ Requires **OAuth automation** setup (Playwright, Puppeteer, etc.)\n\n---\n\n### Comparison Table\n\n| Feature              | Tier 1: Testing      | Tier 2: Production    | Tier 3: Enterprise  |\n| -------------------- | -------------------- | --------------------- | ------------------- |\n| **Setup Complexity** | Minimal (copy/paste) | Low                   | Medium              |\n| **Duration**         | ~1 hour              | ~60 days              | Indefinite          |\n| **Auto-Refresh**     | ❌ No                | ✅ Yes                | ✅ Yes              |\n| **Manual Work**      | Regenerate hourly    | Re-auth every 60 days | None                |\n| **Best For**         | Testing, Learning    | Automation Scripts    | Production Services |\n| **OAuth Required**   | ❌ No                | ✅ Initial only       | ✅ Fully automated  |\n\n---\n\n### Quick Decision Guide\n\n**Choose Tier 1 if you want to:**\n\n- 🧪 Test Box API quickly\n- 📚 Learn how the API works\n- ⚡ Get started in 30 seconds\n- 🔬 Experiment with features\n\n**Choose Tier 2 if you have:**\n\n- 🤖 Automated workflows\n- ⏰ Scheduled tasks (cron jobs)\n- 📊 Scripts that run periodically\n- ✅ OK with re-auth every ~60 days\n\n**Choose Tier 3 if you need:**\n\n- 🏢 Production-grade automation\n- 🔄 Services that run 24/7\n- 🚫 Zero manual intervention\n- ⚙️ CI/CD integration\n\n---\n\n## Best Practice: Wrapper Module Pattern (Recommended)\n\nFor **any production application**, we recommend creating a wrapper module that exports a pre-configured singleton instance. This is the **cleanest and most maintainable** approach.\n\n### Why Use a Wrapper Module?\n\n✅ **Single source of truth** - Configuration in one place\n✅ **No duplication** - Import once, use everywhere\n✅ **Type safety** - Full TypeScript support\n✅ **Easy testing** - Simple to mock in tests\n✅ **DI friendly** - Easy to swap implementations\n\n### How to Set Up\n\n**Step 1:** Create a wrapper module (e.g., `boxClient.ts` or `lib/box.ts`)\n\n```typescript\n// src/lib/boxClient.ts\nimport credentials from '@constants/credentials';\nimport box from 'fs-box-sync';\nimport Playwright from './Playwright';\n\n// Configure once\nbox.configure({\n  clientId: credentials.BOX_CLIENT_ID,\n  clientSecret: credentials.BOX_CLIENT_SECRET,\n  tokenProvider: async (authUrl) =\u003e {\n    return await Playwright.getBoxCode(authUrl);\n  },\n});\n\n// Export the pre-configured singleton\nexport default box;\n```\n\n**Step 2:** Use anywhere in your application\n\n```typescript\n// In any file - just import and use!\nimport box from '@/lib/boxClient';\n\n// Ready to use - no configuration needed\nasync function uploadReport() {\n  await box.uploadFile('folder-id', './report.pdf');\n}\n\nasync function listFiles() {\n  const files = await box.readDir('folder-id');\n  return files;\n}\n```\n\n### Pattern Benefits\n\nThis pattern works for **all tiers**:\n\n**Tier 1 (Testing):**\n\n```typescript\n// boxClient.ts\nimport { BoxAPI } from 'fs-box-sync';\n\nconst api = new BoxAPI({\n  accessToken: process.env.BOX_ACCESS_TOKEN,\n});\n\nexport default api;\n```\n\n**Tier 2 (Production):**\n\n```typescript\n// boxClient.ts\nimport box from 'fs-box-sync';\n\nbox.configure({\n  clientId: process.env.BOX_CLIENT_ID,\n  clientSecret: process.env.BOX_CLIENT_SECRET,\n  refreshToken: process.env.BOX_REFRESH_TOKEN,\n});\n\nexport default box;\n```\n\n**Tier 3 (Enterprise):**\n\n```typescript\n// boxClient.ts\nimport box from 'fs-box-sync';\nimport Playwright from './auth/Playwright';\n\nbox.configure({\n  clientId: process.env.BOX_CLIENT_ID,\n  clientSecret: process.env.BOX_CLIENT_SECRET,\n  tokenProvider: async (authUrl) =\u003e {\n    return await Playwright.getBoxCode(authUrl);\n  },\n});\n\nexport default box;\n```\n\n### Why This Is the Intended Design\n\nThe package **exports a singleton by default** (`export default Box.getInstance()`), which means:\n\n1. ✅ **Designed for global use** - One instance across your app\n2. ✅ **Token storage coordination** - All calls share the same tokens\n3. ✅ **Built-in singleton pattern** - You don't manage instances\n\nThe wrapper module pattern simply **organizes** the singleton configuration - it's the recommended way to use this package in production.\n\n---\n\n## Configuration\n\n```typescript\ninterface BoxConfig {\n  // === Authentication ===\n  accessToken?: string; // For quick testing (Tier 1)\n  tokenProvider?: (callback: string) =\u003e Promise\u003cstring\u003e | string; // For automation (Tier 3)\n  refreshToken?: string; // For production (Tier 2)\n  clientId?: string;\n  clientSecret?: string;\n  redirectUri?: string; // Default: 'https://oauth.pstmn.io/v1/callback'\n\n  // === Box Drive ===\n  boxDriveRoot?: string; // Auto-detected if not provided\n  // Windows: C:/Users/{username}/Box\n  // Mac: ~/Library/CloudStorage/Box-Box\n  // Linux: ~/Box\n\n  // === Box Domain ===\n  domain?: string; // Default: 'app.box.com'\n\n  // === Sync Settings ===\n  syncTimeout?: number; // Default: 30000 (30 seconds)\n  syncInterval?: number; // Default: 1000 (1 second)\n}\n```\n\n## Sync Strategies\n\nBoxDrive supports 3 sync strategies:\n\n### `poll` - Simple existence check\n\n```typescript\n// Just checks if file exists locally (fastest, least reliable)\nawait box.waitForSync('file-id', 'file', 'poll');\n```\n\n### `smart` - Size \u0026 modification verification (default)\n\n```typescript\n// Verifies file size matches cloud (recommended)\nawait box.waitForSync('file-id', 'file', 'smart');\n```\n\n### `force` - Try to trigger sync\n\n```typescript\n// Attempts to force Box Drive sync (limited capabilities)\nawait box.waitForSync('file-id', 'file', 'force');\n```\n\n## API Reference\n\n### BoxFS API (High-level)\n\n#### Filesystem Operations\n\n- `readDir(folderId)` - Read directory contents (always synced locally)\n- `listFolderItems(folderId)` - Read with IDs and types (from cloud API)\n- `readFile(fileId)` - Read file content (always synced locally)\n- `writeFile(folderId, filename, content)` - Write file\n- `deleteFile(fileId)` - Delete file\n- `getLocalPath(id, type)` - Get Box Drive path (fast, may not exist)\n- `getLocalPathSynced(id, type, strategy?)` - Get Box Drive path (guaranteed to exist)\n- `openLocally(id, type)` - Open in Box Drive\n- `existsAndSynced(id, type)` - Check if ID exists and is synced\n- `existsByNameAndSynced(parentId, name, type)` - Check if named item exists and is synced\n\n#### Search \u0026 Find\n\n- `findByName(folderId, name)` - Find by partial name\n- `search(folderId, query, type?)` - Search in folder\n\n#### Upload \u0026 Download\n\n- `uploadFile(folderId, filePath)` - Upload file\n- `downloadFile(fileId, destPath)` - Download file\n- `uploadWithYearMonthFolders(folderId, filePath, locale?)` - Upload with date structure (default locale: 'en-US')\n- `moveFile(fileId, toFolderId)` - Move file\n\n#### Folder Operations\n\n- `createFolderIfNotExists(parentId, name)` - Create if needed\n- `getFileInfo(fileId)` - Get metadata\n- `getFolderInfo(folderId)` - Get metadata\n\n#### Box Drive\n\n- `isBoxDriveRunning()` - Check if Box Drive is running\n- `waitForSync(id, type, strategy?)` - Wait for sync\n- `getBoxDriveRoot()` - Get Box Drive root path\n\n#### Webhooks\n\n- `getAllWebhooks()` - List webhooks\n- `createWebhook(folderId, address)` - Create webhook\n- `deleteWebhook(webhookId)` - Delete webhook\n\n#### Utilities\n\n- `getOfficeOnlineUrl(fileId)` - Get Office Online URL\n- `getOfficeOnlineUrlByName(folderId, fileName)` - Get by search\n\n### BoxAPI API (Low-level)\n\nAll pure Box REST API operations without Box Drive integration:\n\n- `getFileInfo(fileId)`, `getFolderInfo(folderId)`\n- `listFolderItems(folderId)`\n- `uploadFile(folderId, filePath)` - With auto-chunking \u003e20MB\n- `downloadFile(fileId, destPath)`\n- `createFolder(parentId, name)`\n- `deleteFile(fileId)`, `moveFile(fileId, toId)`\n- `searchInFolder(folderId, query, type?)`\n- Webhooks, shared links, etc.\n\n### BoxDrive API (Sync Bridge)\n\n- `getLocalPath(id, type)` - Convert ID to local path\n- `waitForSync(id, type, strategy)` - Wait for sync\n- `isSynced(id, type)` - Check sync status\n- `isBoxDriveRunning()` - Health check\n- `openLocally(localPath)` - Open file/folder\n\n## Development\n\n### Build\n\n```bash\nnpm run build\n```\n\n### Test\n\n```bash\nnpm test\n```\n\n### Lint\n\n```bash\nnpm run lint\nnpm run format\n```\n\n### Validate Package Exports\n\n```bash\nnpm run check:exports\n```\n\n## Contributing\n\nContributions are welcome! Please feel free to submit a Pull Request.\n\n## Issues\n\nIf you encounter any issues, please report them [here](https://github.com/oharu121/fs-box-sync/issues).\n\n## License\n\nMIT © oharu121\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Foharu121%2Ffs-box-sync","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Foharu121%2Ffs-box-sync","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Foharu121%2Ffs-box-sync/lists"}