@soederpop/luca 0.0.2
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/CLAUDE.md +71 -0
- package/README.md +78 -0
- package/bun.lock +2928 -0
- package/bunfig.toml +3 -0
- package/commands/audit-docs.ts +740 -0
- package/commands/build-scaffolds.ts +154 -0
- package/commands/generate-api-docs.ts +114 -0
- package/commands/update-introspection.ts +67 -0
- package/docs/CLI.md +335 -0
- package/docs/README.md +88 -0
- package/docs/TABLE-OF-CONTENTS.md +157 -0
- package/docs/apis/clients/elevenlabs.md +84 -0
- package/docs/apis/clients/graph.md +56 -0
- package/docs/apis/clients/openai.md +69 -0
- package/docs/apis/clients/rest.md +41 -0
- package/docs/apis/clients/websocket.md +107 -0
- package/docs/apis/features/agi/assistant.md +471 -0
- package/docs/apis/features/agi/assistants-manager.md +154 -0
- package/docs/apis/features/agi/claude-code.md +602 -0
- package/docs/apis/features/agi/conversation-history.md +352 -0
- package/docs/apis/features/agi/conversation.md +333 -0
- package/docs/apis/features/agi/docs-reader.md +121 -0
- package/docs/apis/features/agi/openai-codex.md +318 -0
- package/docs/apis/features/agi/openapi.md +138 -0
- package/docs/apis/features/agi/semantic-search.md +387 -0
- package/docs/apis/features/agi/skills-library.md +216 -0
- package/docs/apis/features/node/container-link.md +133 -0
- package/docs/apis/features/node/content-db.md +313 -0
- package/docs/apis/features/node/disk-cache.md +379 -0
- package/docs/apis/features/node/dns.md +651 -0
- package/docs/apis/features/node/docker.md +705 -0
- package/docs/apis/features/node/downloader.md +81 -0
- package/docs/apis/features/node/esbuild.md +59 -0
- package/docs/apis/features/node/file-manager.md +182 -0
- package/docs/apis/features/node/fs.md +581 -0
- package/docs/apis/features/node/git.md +330 -0
- package/docs/apis/features/node/google-auth.md +174 -0
- package/docs/apis/features/node/google-calendar.md +187 -0
- package/docs/apis/features/node/google-docs.md +151 -0
- package/docs/apis/features/node/google-drive.md +225 -0
- package/docs/apis/features/node/google-sheets.md +179 -0
- package/docs/apis/features/node/grep.md +290 -0
- package/docs/apis/features/node/helpers.md +135 -0
- package/docs/apis/features/node/ink.md +334 -0
- package/docs/apis/features/node/ipc-socket.md +260 -0
- package/docs/apis/features/node/json-tree.md +86 -0
- package/docs/apis/features/node/launcher-app-command-listener.md +145 -0
- package/docs/apis/features/node/networking.md +281 -0
- package/docs/apis/features/node/nlp.md +133 -0
- package/docs/apis/features/node/opener.md +97 -0
- package/docs/apis/features/node/os.md +118 -0
- package/docs/apis/features/node/package-finder.md +402 -0
- package/docs/apis/features/node/postgres.md +212 -0
- package/docs/apis/features/node/proc.md +430 -0
- package/docs/apis/features/node/process-manager.md +210 -0
- package/docs/apis/features/node/python.md +278 -0
- package/docs/apis/features/node/repl.md +88 -0
- package/docs/apis/features/node/runpod.md +673 -0
- package/docs/apis/features/node/secure-shell.md +169 -0
- package/docs/apis/features/node/semantic-search.md +401 -0
- package/docs/apis/features/node/sqlite.md +211 -0
- package/docs/apis/features/node/telegram.md +254 -0
- package/docs/apis/features/node/tts.md +118 -0
- package/docs/apis/features/node/ui.md +703 -0
- package/docs/apis/features/node/vault.md +64 -0
- package/docs/apis/features/node/vm.md +84 -0
- package/docs/apis/features/node/window-manager.md +337 -0
- package/docs/apis/features/node/yaml-tree.md +85 -0
- package/docs/apis/features/node/yaml.md +176 -0
- package/docs/apis/features/web/asset-loader.md +47 -0
- package/docs/apis/features/web/container-link.md +133 -0
- package/docs/apis/features/web/esbuild.md +59 -0
- package/docs/apis/features/web/helpers.md +135 -0
- package/docs/apis/features/web/network.md +30 -0
- package/docs/apis/features/web/speech.md +55 -0
- package/docs/apis/features/web/vault.md +64 -0
- package/docs/apis/features/web/vm.md +84 -0
- package/docs/apis/features/web/voice.md +67 -0
- package/docs/apis/servers/express.md +127 -0
- package/docs/apis/servers/mcp.md +213 -0
- package/docs/apis/servers/websocket.md +99 -0
- package/docs/documentation-audit.md +134 -0
- package/docs/examples/content-db.md +77 -0
- package/docs/examples/disk-cache.md +83 -0
- package/docs/examples/docker.md +101 -0
- package/docs/examples/downloader.md +70 -0
- package/docs/examples/esbuild.md +80 -0
- package/docs/examples/file-manager.md +82 -0
- package/docs/examples/fs.md +83 -0
- package/docs/examples/git.md +85 -0
- package/docs/examples/google-auth.md +88 -0
- package/docs/examples/google-calendar.md +94 -0
- package/docs/examples/google-docs.md +82 -0
- package/docs/examples/google-drive.md +96 -0
- package/docs/examples/google-sheets.md +95 -0
- package/docs/examples/grep.md +85 -0
- package/docs/examples/ink-blocks.md +75 -0
- package/docs/examples/ink-renderer.md +41 -0
- package/docs/examples/ink.md +103 -0
- package/docs/examples/ipc-socket.md +103 -0
- package/docs/examples/json-tree.md +91 -0
- package/docs/examples/launcher-app-command-listener.md +120 -0
- package/docs/examples/networking.md +58 -0
- package/docs/examples/nlp.md +91 -0
- package/docs/examples/opener.md +78 -0
- package/docs/examples/os.md +72 -0
- package/docs/examples/package-finder.md +89 -0
- package/docs/examples/port-exposer.md +89 -0
- package/docs/examples/postgres.md +91 -0
- package/docs/examples/proc.md +81 -0
- package/docs/examples/process-manager.md +79 -0
- package/docs/examples/python.md +91 -0
- package/docs/examples/repl.md +93 -0
- package/docs/examples/runpod.md +119 -0
- package/docs/examples/secure-shell.md +92 -0
- package/docs/examples/sqlite.md +86 -0
- package/docs/examples/telegram.md +77 -0
- package/docs/examples/tts.md +86 -0
- package/docs/examples/ui.md +80 -0
- package/docs/examples/vault.md +70 -0
- package/docs/examples/vm.md +86 -0
- package/docs/examples/window-manager.md +125 -0
- package/docs/examples/yaml-tree.md +93 -0
- package/docs/examples/yaml.md +104 -0
- package/docs/ideas/class-registration-refactor-possibilities.md +197 -0
- package/docs/ideas/container-use-api.md +9 -0
- package/docs/ideas/easy-auth-for-express-servers-and-luca-serve.md +0 -0
- package/docs/ideas/feature-stacks.md +22 -0
- package/docs/ideas/luca-cli-self-sufficiency-demo.md +23 -0
- package/docs/ideas/mcp-design.md +9 -0
- package/docs/ideas/web-container-debugging-feature.md +13 -0
- package/docs/introspection-audit.md +49 -0
- package/docs/introspection.md +154 -0
- package/docs/mcp/readme.md +162 -0
- package/docs/models.ts +38 -0
- package/docs/philosophy.md +85 -0
- package/docs/principles.md +7 -0
- package/docs/prompts/audit-codebase-for-failures-to-use-the-container.md +34 -0
- package/docs/prompts/mcp-test-easy-command.md +27 -0
- package/docs/reports/assistant-bugs.md +38 -0
- package/docs/reports/attach-pattern-usage.md +18 -0
- package/docs/reports/code-audit-results.md +391 -0
- package/docs/reports/introspection-audit-tasks.md +378 -0
- package/docs/reports/luca-mcp-improvements.md +128 -0
- package/docs/scaffolds/client.md +140 -0
- package/docs/scaffolds/command.md +106 -0
- package/docs/scaffolds/endpoint.md +176 -0
- package/docs/scaffolds/feature.md +148 -0
- package/docs/scaffolds/server.md +187 -0
- package/docs/tasks/web-container-helper-discovery.md +71 -0
- package/docs/todos.md +1 -0
- package/docs/tutorials/01-getting-started.md +106 -0
- package/docs/tutorials/02-container.md +210 -0
- package/docs/tutorials/03-scripts.md +194 -0
- package/docs/tutorials/04-features-overview.md +196 -0
- package/docs/tutorials/05-state-and-events.md +171 -0
- package/docs/tutorials/06-servers.md +157 -0
- package/docs/tutorials/07-endpoints.md +198 -0
- package/docs/tutorials/08-commands.md +171 -0
- package/docs/tutorials/09-clients.md +162 -0
- package/docs/tutorials/10-creating-features.md +198 -0
- package/docs/tutorials/11-contentbase.md +191 -0
- package/docs/tutorials/12-assistants.md +215 -0
- package/docs/tutorials/13-introspection.md +147 -0
- package/docs/tutorials/14-type-system.md +174 -0
- package/docs/tutorials/15-project-patterns.md +222 -0
- package/docs/tutorials/16-google-features.md +534 -0
- package/docs/tutorials/17-tui-blocks.md +530 -0
- package/docs/tutorials/18-semantic-search.md +334 -0
- package/index.ts +1 -0
- package/luca.console.ts +9 -0
- package/main.py +6 -0
- package/package.json +154 -0
- package/pyproject.toml +7 -0
- package/scripts/animations/chrome-glitch.ts +55 -0
- package/scripts/animations/index.ts +16 -0
- package/scripts/animations/neon-pulse.ts +64 -0
- package/scripts/animations/types.ts +6 -0
- package/scripts/build-web.ts +28 -0
- package/scripts/examples/ask-luca-expert.ts +42 -0
- package/scripts/examples/assistant-questions.ts +12 -0
- package/scripts/examples/excalidraw-expert.ts +75 -0
- package/scripts/examples/expert-chat.ts +0 -0
- package/scripts/examples/file-manager.ts +14 -0
- package/scripts/examples/ideas.ts +12 -0
- package/scripts/examples/interactive-chat.ts +20 -0
- package/scripts/examples/openai-tool-calls.ts +113 -0
- package/scripts/examples/opening-a-web-browser.ts +5 -0
- package/scripts/examples/telegram-bot.ts +79 -0
- package/scripts/examples/telegram-ink-ui.ts +302 -0
- package/scripts/examples/using-assistant-with-mcp.ts +560 -0
- package/scripts/examples/using-claude-code.ts +10 -0
- package/scripts/examples/using-contentdb.ts +35 -0
- package/scripts/examples/using-conversations.ts +35 -0
- package/scripts/examples/using-disk-cache.ts +10 -0
- package/scripts/examples/using-docker-shell.ts +75 -0
- package/scripts/examples/using-elevenlabs.ts +25 -0
- package/scripts/examples/using-google-calendar.ts +57 -0
- package/scripts/examples/using-google-docs.ts +74 -0
- package/scripts/examples/using-google-drive.ts +74 -0
- package/scripts/examples/using-google-sheets.ts +89 -0
- package/scripts/examples/using-nlp.ts +55 -0
- package/scripts/examples/using-ollama.ts +10 -0
- package/scripts/examples/using-openai-codex.ts +23 -0
- package/scripts/examples/using-postgres.ts +55 -0
- package/scripts/examples/using-runpod.ts +32 -0
- package/scripts/examples/using-tts.ts +40 -0
- package/scripts/examples/vm-loading-esm-modules.ts +16 -0
- package/scripts/scaffold.ts +391 -0
- package/scripts/scratch.ts +15 -0
- package/scripts/test-command-listener.ts +123 -0
- package/scripts/test-window-manager-lifecycle.ts +86 -0
- package/scripts/test-window-manager.ts +43 -0
- package/scripts/update-introspection-data.ts +58 -0
- package/src/agi/README.md +14 -0
- package/src/agi/container.server.ts +114 -0
- package/src/agi/endpoints/ask.ts +60 -0
- package/src/agi/endpoints/conversations/[id].ts +45 -0
- package/src/agi/endpoints/conversations.ts +31 -0
- package/src/agi/endpoints/experts.ts +37 -0
- package/src/agi/features/assistant.ts +767 -0
- package/src/agi/features/assistants-manager.ts +260 -0
- package/src/agi/features/claude-code.ts +1111 -0
- package/src/agi/features/conversation-history.ts +497 -0
- package/src/agi/features/conversation.ts +799 -0
- package/src/agi/features/openai-codex.ts +631 -0
- package/src/agi/features/openapi.ts +438 -0
- package/src/agi/features/skills-library.ts +425 -0
- package/src/agi/index.ts +6 -0
- package/src/agi/lib/token-counter.ts +122 -0
- package/src/browser.ts +25 -0
- package/src/bus.ts +100 -0
- package/src/cli/cli.ts +70 -0
- package/src/client.ts +461 -0
- package/src/clients/civitai/index.ts +541 -0
- package/src/clients/client-template.ts +41 -0
- package/src/clients/comfyui/index.ts +597 -0
- package/src/clients/elevenlabs/index.ts +291 -0
- package/src/clients/openai/index.ts +451 -0
- package/src/clients/supabase/index.ts +366 -0
- package/src/command.ts +164 -0
- package/src/commands/chat.ts +182 -0
- package/src/commands/console.ts +192 -0
- package/src/commands/describe.ts +433 -0
- package/src/commands/eval.ts +116 -0
- package/src/commands/help.ts +214 -0
- package/src/commands/index.ts +14 -0
- package/src/commands/mcp.ts +64 -0
- package/src/commands/prompt.ts +807 -0
- package/src/commands/run.ts +257 -0
- package/src/commands/sandbox-mcp.ts +439 -0
- package/src/commands/scaffold.ts +79 -0
- package/src/commands/serve.ts +172 -0
- package/src/container.ts +781 -0
- package/src/endpoint.ts +340 -0
- package/src/feature.ts +75 -0
- package/src/hash-object.ts +97 -0
- package/src/helper.ts +543 -0
- package/src/introspection/generated.agi.ts +23388 -0
- package/src/introspection/generated.node.ts +18899 -0
- package/src/introspection/generated.web.ts +2021 -0
- package/src/introspection/index.ts +256 -0
- package/src/introspection/scan.ts +912 -0
- package/src/node/container.ts +354 -0
- package/src/node/feature.ts +13 -0
- package/src/node/features/container-link.ts +558 -0
- package/src/node/features/content-db.ts +475 -0
- package/src/node/features/disk-cache.ts +382 -0
- package/src/node/features/dns.ts +655 -0
- package/src/node/features/docker.ts +912 -0
- package/src/node/features/downloader.ts +92 -0
- package/src/node/features/esbuild.ts +68 -0
- package/src/node/features/file-manager.ts +357 -0
- package/src/node/features/fs.ts +534 -0
- package/src/node/features/git.ts +492 -0
- package/src/node/features/google-auth.ts +502 -0
- package/src/node/features/google-calendar.ts +300 -0
- package/src/node/features/google-docs.ts +404 -0
- package/src/node/features/google-drive.ts +339 -0
- package/src/node/features/google-sheets.ts +279 -0
- package/src/node/features/grep.ts +406 -0
- package/src/node/features/helpers.ts +374 -0
- package/src/node/features/ink.ts +490 -0
- package/src/node/features/ipc-socket.ts +459 -0
- package/src/node/features/json-tree.ts +188 -0
- package/src/node/features/launcher-app-command-listener.ts +388 -0
- package/src/node/features/networking.ts +925 -0
- package/src/node/features/nlp.ts +211 -0
- package/src/node/features/opener.ts +166 -0
- package/src/node/features/os.ts +157 -0
- package/src/node/features/package-finder.ts +539 -0
- package/src/node/features/port-exposer.ts +342 -0
- package/src/node/features/postgres.ts +273 -0
- package/src/node/features/proc.ts +502 -0
- package/src/node/features/process-manager.ts +542 -0
- package/src/node/features/python.ts +444 -0
- package/src/node/features/repl.ts +194 -0
- package/src/node/features/runpod.ts +802 -0
- package/src/node/features/secure-shell.ts +248 -0
- package/src/node/features/semantic-search.ts +924 -0
- package/src/node/features/sqlite.ts +289 -0
- package/src/node/features/telegram.ts +342 -0
- package/src/node/features/tts.ts +184 -0
- package/src/node/features/ui.ts +857 -0
- package/src/node/features/vault.ts +164 -0
- package/src/node/features/vm.ts +312 -0
- package/src/node/features/window-manager.ts +804 -0
- package/src/node/features/yaml-tree.ts +149 -0
- package/src/node/features/yaml.ts +132 -0
- package/src/node.ts +70 -0
- package/src/react/index.ts +175 -0
- package/src/registry.ts +199 -0
- package/src/scaffolds/generated.ts +1613 -0
- package/src/scaffolds/template.ts +37 -0
- package/src/schemas/base.ts +255 -0
- package/src/server.ts +135 -0
- package/src/servers/express.ts +209 -0
- package/src/servers/mcp.ts +805 -0
- package/src/servers/socket.ts +120 -0
- package/src/state.ts +101 -0
- package/src/web/clients/socket.ts +82 -0
- package/src/web/container.ts +74 -0
- package/src/web/extension.ts +30 -0
- package/src/web/feature.ts +12 -0
- package/src/web/features/asset-loader.ts +64 -0
- package/src/web/features/container-link.ts +385 -0
- package/src/web/features/esbuild.ts +79 -0
- package/src/web/features/helpers.ts +267 -0
- package/src/web/features/network.ts +61 -0
- package/src/web/features/speech.ts +87 -0
- package/src/web/features/vault.ts +189 -0
- package/src/web/features/vm.ts +78 -0
- package/src/web/features/voice-recognition.ts +129 -0
- package/src/web/shims/isomorphic-vm.ts +149 -0
- package/test/bus.test.ts +134 -0
- package/test/clients-servers.test.ts +216 -0
- package/test/container-link.test.ts +274 -0
- package/test/features.test.ts +160 -0
- package/test/integration.test.ts +787 -0
- package/test/node-container.test.ts +121 -0
- package/test/rate-limit.test.ts +272 -0
- package/test/semantic-search.test.ts +550 -0
- package/test/state.test.ts +121 -0
- package/test-integration/assistant.test.ts +138 -0
- package/test-integration/assistants-manager.test.ts +123 -0
- package/test-integration/claude-code.test.ts +98 -0
- package/test-integration/conversation-history.test.ts +205 -0
- package/test-integration/conversation.test.ts +137 -0
- package/test-integration/elevenlabs.test.ts +55 -0
- package/test-integration/google-services.test.ts +80 -0
- package/test-integration/helpers.ts +89 -0
- package/test-integration/openai-codex.test.ts +93 -0
- package/test-integration/runpod.test.ts +58 -0
- package/test-integration/server-endpoints.test.ts +97 -0
- package/test-integration/skills-library.test.ts +157 -0
- package/test-integration/telegram.test.ts +46 -0
- package/tsconfig.json +58 -0
- package/uv.lock +8 -0
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
import { z } from 'zod'
|
|
2
|
+
import { FeatureStateSchema, FeatureOptionsSchema, FeatureEventsSchema } from '../../schemas/base.js'
|
|
3
|
+
import { Feature, features } from '../feature.js'
|
|
4
|
+
import { google, type drive_v3 } from 'googleapis'
|
|
5
|
+
import type { GoogleAuth } from './google-auth.js'
|
|
6
|
+
|
|
7
|
+
export type DriveFile = {
|
|
8
|
+
id: string
|
|
9
|
+
name: string
|
|
10
|
+
mimeType: string
|
|
11
|
+
size?: string
|
|
12
|
+
createdTime?: string
|
|
13
|
+
modifiedTime?: string
|
|
14
|
+
parents?: string[]
|
|
15
|
+
webViewLink?: string
|
|
16
|
+
iconLink?: string
|
|
17
|
+
owners?: Array<{ displayName?: string | null; emailAddress?: string | null }>
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export type DriveFileList = {
|
|
21
|
+
files: DriveFile[]
|
|
22
|
+
nextPageToken?: string
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export type DriveBrowseResult = {
|
|
26
|
+
folder: DriveFile
|
|
27
|
+
files: DriveFile[]
|
|
28
|
+
folders: DriveFile[]
|
|
29
|
+
nextPageToken?: string
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export type ListFilesOptions = {
|
|
33
|
+
pageSize?: number
|
|
34
|
+
pageToken?: string
|
|
35
|
+
orderBy?: string
|
|
36
|
+
fields?: string
|
|
37
|
+
corpora?: 'user' | 'drive' | 'allDrives'
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export type SearchOptions = ListFilesOptions & {
|
|
41
|
+
mimeType?: string
|
|
42
|
+
inFolder?: string
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export type SharedDrive = {
|
|
46
|
+
id: string
|
|
47
|
+
name: string
|
|
48
|
+
colorRgb?: string
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export const GoogleDriveStateSchema = FeatureStateSchema.extend({
|
|
52
|
+
lastQuery: z.string().optional()
|
|
53
|
+
.describe('Last search query or folder ID browsed'),
|
|
54
|
+
lastResultCount: z.number().optional()
|
|
55
|
+
.describe('Number of results from last list/search operation'),
|
|
56
|
+
lastError: z.string().optional()
|
|
57
|
+
.describe('Last Drive API error message'),
|
|
58
|
+
})
|
|
59
|
+
export type GoogleDriveState = z.infer<typeof GoogleDriveStateSchema>
|
|
60
|
+
|
|
61
|
+
export const GoogleDriveOptionsSchema = FeatureOptionsSchema.extend({
|
|
62
|
+
defaultCorpora: z.enum(['user', 'drive', 'allDrives']).optional()
|
|
63
|
+
.describe('Default corpus for file queries (default: user)'),
|
|
64
|
+
pageSize: z.number().optional()
|
|
65
|
+
.describe('Default number of results per page (default: 100)'),
|
|
66
|
+
})
|
|
67
|
+
export type GoogleDriveOptions = z.infer<typeof GoogleDriveOptionsSchema>
|
|
68
|
+
|
|
69
|
+
export const GoogleDriveEventsSchema = FeatureEventsSchema.extend({
|
|
70
|
+
filesFetched: z.tuple([z.number().describe('Number of files returned')])
|
|
71
|
+
.describe('Files were fetched from Drive'),
|
|
72
|
+
fileDownloaded: z.tuple([z.string().describe('File ID')])
|
|
73
|
+
.describe('A file was downloaded'),
|
|
74
|
+
error: z.tuple([z.any().describe('The error')]).describe('Drive API error occurred'),
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
const DEFAULT_FIELDS = 'files(id,name,mimeType,size,createdTime,modifiedTime,parents,webViewLink,iconLink,owners),nextPageToken'
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Google Drive feature for listing, searching, browsing, and downloading files.
|
|
81
|
+
*
|
|
82
|
+
* Depends on the googleAuth feature for authentication. Creates a Drive v3 API
|
|
83
|
+
* client lazily and passes the auth client from googleAuth.
|
|
84
|
+
*
|
|
85
|
+
* @example
|
|
86
|
+
* ```typescript
|
|
87
|
+
* const drive = container.feature('googleDrive')
|
|
88
|
+
*
|
|
89
|
+
* // List recent files
|
|
90
|
+
* const { files } = await drive.listFiles()
|
|
91
|
+
*
|
|
92
|
+
* // Search for documents
|
|
93
|
+
* const { files: docs } = await drive.search('quarterly report', { mimeType: 'application/pdf' })
|
|
94
|
+
*
|
|
95
|
+
* // Browse a folder
|
|
96
|
+
* const contents = await drive.browse('folder-id-here')
|
|
97
|
+
*
|
|
98
|
+
* // Download a file to disk
|
|
99
|
+
* await drive.downloadTo('file-id', './downloads/report.pdf')
|
|
100
|
+
* ```
|
|
101
|
+
*/
|
|
102
|
+
export class GoogleDrive extends Feature<GoogleDriveState, GoogleDriveOptions> {
|
|
103
|
+
static override shortcut = 'features.googleDrive' as const
|
|
104
|
+
static override stateSchema = GoogleDriveStateSchema
|
|
105
|
+
static override optionsSchema = GoogleDriveOptionsSchema
|
|
106
|
+
static override eventsSchema = GoogleDriveEventsSchema
|
|
107
|
+
|
|
108
|
+
private _drive?: drive_v3.Drive
|
|
109
|
+
|
|
110
|
+
override get initialState(): GoogleDriveState {
|
|
111
|
+
return { ...super.initialState }
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** Access the google-auth feature lazily. */
|
|
115
|
+
get auth(): GoogleAuth {
|
|
116
|
+
return this.container.feature('googleAuth') as unknown as GoogleAuth
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** Get or create the Drive v3 API client. */
|
|
120
|
+
private async getDrive(): Promise<drive_v3.Drive> {
|
|
121
|
+
if (this._drive) return this._drive
|
|
122
|
+
const auth = await this.auth.getAuthClient()
|
|
123
|
+
this._drive = google.drive({ version: 'v3', auth: auth as any })
|
|
124
|
+
return this._drive
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* List files in the user's Drive with an optional query filter.
|
|
129
|
+
*
|
|
130
|
+
* @param query - Drive search query (e.g. "name contains 'report'", "mimeType='application/pdf'")
|
|
131
|
+
* @param options - Pagination and filtering options
|
|
132
|
+
* @returns Files array and optional nextPageToken
|
|
133
|
+
*/
|
|
134
|
+
async listFiles(query?: string, options: ListFilesOptions = {}): Promise<DriveFileList> {
|
|
135
|
+
try {
|
|
136
|
+
const drive = await this.getDrive()
|
|
137
|
+
const res = await drive.files.list({
|
|
138
|
+
q: query || undefined,
|
|
139
|
+
pageSize: options.pageSize || this.options.pageSize || 100,
|
|
140
|
+
pageToken: options.pageToken || undefined,
|
|
141
|
+
orderBy: options.orderBy || 'modifiedTime desc',
|
|
142
|
+
fields: options.fields || DEFAULT_FIELDS,
|
|
143
|
+
corpora: options.corpora || this.options.defaultCorpora || 'user',
|
|
144
|
+
includeItemsFromAllDrives: (options.corpora || this.options.defaultCorpora) === 'allDrives',
|
|
145
|
+
supportsAllDrives: true,
|
|
146
|
+
})
|
|
147
|
+
|
|
148
|
+
const files = (res.data.files || []).map(normalizeDriveFile)
|
|
149
|
+
this.setState({ lastQuery: query, lastResultCount: files.length })
|
|
150
|
+
this.emit('filesFetched', files.length)
|
|
151
|
+
return { files, nextPageToken: res.data.nextPageToken || undefined }
|
|
152
|
+
} catch (err: any) {
|
|
153
|
+
this.setState({ lastError: err.message })
|
|
154
|
+
this.emit('error', err)
|
|
155
|
+
throw err
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* List files within a specific folder.
|
|
161
|
+
*
|
|
162
|
+
* @param folderId - The Drive folder ID
|
|
163
|
+
* @param options - Pagination and filtering options
|
|
164
|
+
*/
|
|
165
|
+
async listFolder(folderId: string, options: ListFilesOptions = {}): Promise<DriveFileList> {
|
|
166
|
+
const query = `'${folderId}' in parents and trashed = false`
|
|
167
|
+
return this.listFiles(query, options)
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Browse a folder's contents, separating files from subfolders.
|
|
172
|
+
*
|
|
173
|
+
* @param folderId - Folder ID to browse (defaults to 'root')
|
|
174
|
+
* @returns Folder metadata, child files, and child folders
|
|
175
|
+
*/
|
|
176
|
+
async browse(folderId: string = 'root'): Promise<DriveBrowseResult> {
|
|
177
|
+
const drive = await this.getDrive()
|
|
178
|
+
|
|
179
|
+
// Get folder metadata
|
|
180
|
+
const folderRes = await drive.files.get({
|
|
181
|
+
fileId: folderId,
|
|
182
|
+
fields: 'id,name,mimeType,createdTime,modifiedTime,parents,webViewLink',
|
|
183
|
+
supportsAllDrives: true,
|
|
184
|
+
})
|
|
185
|
+
const folder = normalizeDriveFile(folderRes.data)
|
|
186
|
+
|
|
187
|
+
// List folder contents
|
|
188
|
+
const { files } = await this.listFolder(folderId, { pageSize: 1000 })
|
|
189
|
+
const folders = files.filter(f => f.mimeType === 'application/vnd.google-apps.folder')
|
|
190
|
+
const nonFolders = files.filter(f => f.mimeType !== 'application/vnd.google-apps.folder')
|
|
191
|
+
|
|
192
|
+
return { folder, files: nonFolders, folders }
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Search files by name, content, or MIME type.
|
|
197
|
+
*
|
|
198
|
+
* @param term - Search term to look for in file names and content
|
|
199
|
+
* @param options - Additional search options like mimeType filter or folder restriction
|
|
200
|
+
*/
|
|
201
|
+
async search(term: string, options: SearchOptions = {}): Promise<DriveFileList> {
|
|
202
|
+
const parts: string[] = [`fullText contains '${term.replace(/'/g, "\\'")}'`]
|
|
203
|
+
|
|
204
|
+
if (options.mimeType) {
|
|
205
|
+
parts.push(`mimeType = '${options.mimeType}'`)
|
|
206
|
+
}
|
|
207
|
+
if (options.inFolder) {
|
|
208
|
+
parts.push(`'${options.inFolder}' in parents`)
|
|
209
|
+
}
|
|
210
|
+
parts.push('trashed = false')
|
|
211
|
+
|
|
212
|
+
return this.listFiles(parts.join(' and '), options)
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Get file metadata by file ID.
|
|
217
|
+
*
|
|
218
|
+
* @param fileId - The Drive file ID
|
|
219
|
+
* @param fields - Specific fields to request (defaults to common fields)
|
|
220
|
+
*/
|
|
221
|
+
async getFile(fileId: string, fields?: string): Promise<DriveFile> {
|
|
222
|
+
try {
|
|
223
|
+
const drive = await this.getDrive()
|
|
224
|
+
const res = await drive.files.get({
|
|
225
|
+
fileId,
|
|
226
|
+
fields: fields || 'id,name,mimeType,size,createdTime,modifiedTime,parents,webViewLink,iconLink,owners',
|
|
227
|
+
supportsAllDrives: true,
|
|
228
|
+
})
|
|
229
|
+
return normalizeDriveFile(res.data)
|
|
230
|
+
} catch (err: any) {
|
|
231
|
+
this.setState({ lastError: err.message })
|
|
232
|
+
this.emit('error', err)
|
|
233
|
+
throw err
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Download a file's content as a Buffer.
|
|
239
|
+
* Uses alt=media for binary download of non-Google files.
|
|
240
|
+
*
|
|
241
|
+
* @param fileId - The Drive file ID
|
|
242
|
+
*/
|
|
243
|
+
async download(fileId: string): Promise<Buffer> {
|
|
244
|
+
try {
|
|
245
|
+
const drive = await this.getDrive()
|
|
246
|
+
const res = await drive.files.get(
|
|
247
|
+
{ fileId, alt: 'media', supportsAllDrives: true },
|
|
248
|
+
{ responseType: 'arraybuffer' }
|
|
249
|
+
)
|
|
250
|
+
this.emit('fileDownloaded', fileId)
|
|
251
|
+
return Buffer.from(res.data as ArrayBuffer)
|
|
252
|
+
} catch (err: any) {
|
|
253
|
+
this.setState({ lastError: err.message })
|
|
254
|
+
this.emit('error', err)
|
|
255
|
+
throw err
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Download a file and save it to a local path.
|
|
261
|
+
*
|
|
262
|
+
* @param fileId - The Drive file ID
|
|
263
|
+
* @param localPath - Local file path (resolved relative to container cwd)
|
|
264
|
+
* @returns Absolute path of the saved file
|
|
265
|
+
*/
|
|
266
|
+
async downloadTo(fileId: string, localPath: string): Promise<string> {
|
|
267
|
+
const buffer = await this.download(fileId)
|
|
268
|
+
const outPath = this.container.paths.resolve(localPath)
|
|
269
|
+
await this.container.fs.writeFileAsync(outPath, buffer)
|
|
270
|
+
return outPath
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Export a Google Workspace file (Docs, Sheets, Slides) to a given MIME type.
|
|
275
|
+
* Uses the Files.export endpoint.
|
|
276
|
+
*
|
|
277
|
+
* @param fileId - The Drive file ID of a Google Workspace document
|
|
278
|
+
* @param mimeType - Target MIME type (e.g. 'text/plain', 'application/pdf', 'text/csv')
|
|
279
|
+
*/
|
|
280
|
+
async exportFile(fileId: string, mimeType: string): Promise<Buffer> {
|
|
281
|
+
try {
|
|
282
|
+
const drive = await this.getDrive()
|
|
283
|
+
const res = await drive.files.export(
|
|
284
|
+
{ fileId, mimeType },
|
|
285
|
+
{ responseType: 'arraybuffer' }
|
|
286
|
+
)
|
|
287
|
+
this.emit('fileDownloaded', fileId)
|
|
288
|
+
return Buffer.from(res.data as ArrayBuffer)
|
|
289
|
+
} catch (err: any) {
|
|
290
|
+
this.setState({ lastError: err.message })
|
|
291
|
+
this.emit('error', err)
|
|
292
|
+
throw err
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* List all shared drives the user has access to.
|
|
298
|
+
*
|
|
299
|
+
* @returns Array of shared drive objects
|
|
300
|
+
*/
|
|
301
|
+
async listDrives(): Promise<SharedDrive[]> {
|
|
302
|
+
try {
|
|
303
|
+
const drive = await this.getDrive()
|
|
304
|
+
const res = await drive.drives.list({ pageSize: 100 })
|
|
305
|
+
return (res.data.drives || []).map(d => ({
|
|
306
|
+
id: d.id || '',
|
|
307
|
+
name: d.name || '',
|
|
308
|
+
colorRgb: d.colorRgb || undefined,
|
|
309
|
+
}))
|
|
310
|
+
} catch (err: any) {
|
|
311
|
+
this.setState({ lastError: err.message })
|
|
312
|
+
this.emit('error', err)
|
|
313
|
+
throw err
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
function normalizeDriveFile(f: drive_v3.Schema$File): DriveFile {
|
|
319
|
+
return {
|
|
320
|
+
id: f.id || '',
|
|
321
|
+
name: f.name || '',
|
|
322
|
+
mimeType: f.mimeType || '',
|
|
323
|
+
size: f.size || undefined,
|
|
324
|
+
createdTime: f.createdTime || undefined,
|
|
325
|
+
modifiedTime: f.modifiedTime || undefined,
|
|
326
|
+
parents: f.parents || undefined,
|
|
327
|
+
webViewLink: f.webViewLink || undefined,
|
|
328
|
+
iconLink: f.iconLink || undefined,
|
|
329
|
+
owners: f.owners?.map(o => ({ displayName: o.displayName, emailAddress: o.emailAddress })) || undefined,
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
declare module '../../feature' {
|
|
334
|
+
interface AvailableFeatures {
|
|
335
|
+
googleDrive: typeof GoogleDrive
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
export default features.register('googleDrive', GoogleDrive)
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
import { z } from 'zod'
|
|
2
|
+
import { FeatureStateSchema, FeatureOptionsSchema, FeatureEventsSchema } from '../../schemas/base.js'
|
|
3
|
+
import { Feature, features } from '../feature.js'
|
|
4
|
+
import { google, type sheets_v4 } from 'googleapis'
|
|
5
|
+
import type { GoogleAuth } from './google-auth.js'
|
|
6
|
+
|
|
7
|
+
export type SpreadsheetMeta = {
|
|
8
|
+
spreadsheetId: string
|
|
9
|
+
title: string
|
|
10
|
+
locale: string
|
|
11
|
+
sheets: SheetInfo[]
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export type SheetInfo = {
|
|
15
|
+
sheetId: number
|
|
16
|
+
title: string
|
|
17
|
+
index: number
|
|
18
|
+
rowCount: number
|
|
19
|
+
columnCount: number
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export const GoogleSheetsStateSchema = FeatureStateSchema.extend({
|
|
23
|
+
lastSpreadsheetId: z.string().optional()
|
|
24
|
+
.describe('Last spreadsheet ID accessed'),
|
|
25
|
+
lastSheetName: z.string().optional()
|
|
26
|
+
.describe('Last sheet/tab name accessed'),
|
|
27
|
+
lastRowCount: z.number().optional()
|
|
28
|
+
.describe('Number of rows returned in last read'),
|
|
29
|
+
lastError: z.string().optional()
|
|
30
|
+
.describe('Last Sheets API error message'),
|
|
31
|
+
})
|
|
32
|
+
export type GoogleSheetsState = z.infer<typeof GoogleSheetsStateSchema>
|
|
33
|
+
|
|
34
|
+
export const GoogleSheetsOptionsSchema = FeatureOptionsSchema.extend({
|
|
35
|
+
defaultSpreadsheetId: z.string().optional()
|
|
36
|
+
.describe('Default spreadsheet ID for operations'),
|
|
37
|
+
})
|
|
38
|
+
export type GoogleSheetsOptions = z.infer<typeof GoogleSheetsOptionsSchema>
|
|
39
|
+
|
|
40
|
+
export const GoogleSheetsEventsSchema = FeatureEventsSchema.extend({
|
|
41
|
+
dataFetched: z.tuple([z.number().describe('Number of rows')])
|
|
42
|
+
.describe('Sheet data was fetched'),
|
|
43
|
+
error: z.tuple([z.any().describe('The error')]).describe('Sheets API error occurred'),
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Google Sheets feature for reading spreadsheet data as JSON, CSV, or raw arrays.
|
|
48
|
+
*
|
|
49
|
+
* Depends on the googleAuth feature for authentication. Creates a Sheets v4 API
|
|
50
|
+
* client lazily and provides convenient methods for reading tabular data.
|
|
51
|
+
*
|
|
52
|
+
* @example
|
|
53
|
+
* ```typescript
|
|
54
|
+
* const sheets = container.feature('googleSheets', {
|
|
55
|
+
* defaultSpreadsheetId: '1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgVE2upms'
|
|
56
|
+
* })
|
|
57
|
+
*
|
|
58
|
+
* // Read as JSON objects (first row = headers)
|
|
59
|
+
* const data = await sheets.getAsJson('Sheet1')
|
|
60
|
+
* // => [{ name: 'Alice', age: '30' }, { name: 'Bob', age: '25' }]
|
|
61
|
+
*
|
|
62
|
+
* // Read as CSV string
|
|
63
|
+
* const csv = await sheets.getAsCsv('Revenue')
|
|
64
|
+
*
|
|
65
|
+
* // Read a specific range
|
|
66
|
+
* const values = await sheets.getRange('Sheet1!A1:D10')
|
|
67
|
+
*
|
|
68
|
+
* // Save to file
|
|
69
|
+
* await sheets.saveAsJson('./data/export.json')
|
|
70
|
+
* ```
|
|
71
|
+
*/
|
|
72
|
+
export class GoogleSheets extends Feature<GoogleSheetsState, GoogleSheetsOptions> {
|
|
73
|
+
static override shortcut = 'features.googleSheets' as const
|
|
74
|
+
static override stateSchema = GoogleSheetsStateSchema
|
|
75
|
+
static override optionsSchema = GoogleSheetsOptionsSchema
|
|
76
|
+
static override eventsSchema = GoogleSheetsEventsSchema
|
|
77
|
+
|
|
78
|
+
private _sheets?: sheets_v4.Sheets
|
|
79
|
+
|
|
80
|
+
override get initialState(): GoogleSheetsState {
|
|
81
|
+
return { ...super.initialState }
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** Access the google-auth feature lazily. */
|
|
85
|
+
get auth(): GoogleAuth {
|
|
86
|
+
return this.container.feature('googleAuth') as unknown as GoogleAuth
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Get or create the Sheets v4 API client. */
|
|
90
|
+
private async getSheets(): Promise<sheets_v4.Sheets> {
|
|
91
|
+
if (this._sheets) return this._sheets
|
|
92
|
+
const auth = await this.auth.getAuthClient()
|
|
93
|
+
this._sheets = google.sheets({ version: 'v4', auth: auth as any })
|
|
94
|
+
return this._sheets
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** Resolve spreadsheet ID from argument or default option. */
|
|
98
|
+
private resolveId(spreadsheetId?: string): string {
|
|
99
|
+
const id = spreadsheetId || this.options.defaultSpreadsheetId
|
|
100
|
+
if (!id) throw new Error('Spreadsheet ID required. Pass it as argument or set options.defaultSpreadsheetId.')
|
|
101
|
+
return id
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Get spreadsheet metadata including title, locale, and sheet list.
|
|
106
|
+
*
|
|
107
|
+
* @param spreadsheetId - The spreadsheet ID (defaults to options.defaultSpreadsheetId)
|
|
108
|
+
*/
|
|
109
|
+
async getSpreadsheet(spreadsheetId?: string): Promise<SpreadsheetMeta> {
|
|
110
|
+
const id = this.resolveId(spreadsheetId)
|
|
111
|
+
try {
|
|
112
|
+
const sheets = await this.getSheets()
|
|
113
|
+
const res = await sheets.spreadsheets.get({ spreadsheetId: id })
|
|
114
|
+
const data = res.data
|
|
115
|
+
|
|
116
|
+
this.setState({ lastSpreadsheetId: id })
|
|
117
|
+
return {
|
|
118
|
+
spreadsheetId: data.spreadsheetId || id,
|
|
119
|
+
title: data.properties?.title || '',
|
|
120
|
+
locale: data.properties?.locale || 'en_US',
|
|
121
|
+
sheets: (data.sheets || []).map(s => ({
|
|
122
|
+
sheetId: s.properties?.sheetId || 0,
|
|
123
|
+
title: s.properties?.title || '',
|
|
124
|
+
index: s.properties?.index || 0,
|
|
125
|
+
rowCount: s.properties?.gridProperties?.rowCount || 0,
|
|
126
|
+
columnCount: s.properties?.gridProperties?.columnCount || 0,
|
|
127
|
+
})),
|
|
128
|
+
}
|
|
129
|
+
} catch (err: any) {
|
|
130
|
+
this.setState({ lastError: err.message })
|
|
131
|
+
this.emit('error', err)
|
|
132
|
+
throw err
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* List all sheets (tabs) in a spreadsheet.
|
|
138
|
+
*
|
|
139
|
+
* @param spreadsheetId - The spreadsheet ID
|
|
140
|
+
*/
|
|
141
|
+
async listSheets(spreadsheetId?: string): Promise<SheetInfo[]> {
|
|
142
|
+
const meta = await this.getSpreadsheet(spreadsheetId)
|
|
143
|
+
return meta.sheets
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Read a range of values from a sheet.
|
|
148
|
+
*
|
|
149
|
+
* @param range - A1 notation range (e.g. "Sheet1!A1:D10" or "Sheet1" for entire sheet)
|
|
150
|
+
* @param spreadsheetId - The spreadsheet ID
|
|
151
|
+
* @returns Raw values as a 2D string array
|
|
152
|
+
*/
|
|
153
|
+
async getRange(range: string, spreadsheetId?: string): Promise<string[][]> {
|
|
154
|
+
const id = this.resolveId(spreadsheetId)
|
|
155
|
+
try {
|
|
156
|
+
const sheets = await this.getSheets()
|
|
157
|
+
const res = await sheets.spreadsheets.values.get({
|
|
158
|
+
spreadsheetId: id,
|
|
159
|
+
range,
|
|
160
|
+
valueRenderOption: 'FORMATTED_VALUE',
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
const values = (res.data.values || []) as string[][]
|
|
164
|
+
this.setState({
|
|
165
|
+
lastSpreadsheetId: id,
|
|
166
|
+
lastSheetName: range.split('!')[0],
|
|
167
|
+
lastRowCount: values.length,
|
|
168
|
+
})
|
|
169
|
+
this.emit('dataFetched', values.length)
|
|
170
|
+
return values
|
|
171
|
+
} catch (err: any) {
|
|
172
|
+
this.setState({ lastError: err.message })
|
|
173
|
+
this.emit('error', err)
|
|
174
|
+
throw err
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Read a sheet as an array of JSON objects.
|
|
180
|
+
* The first row is treated as headers; subsequent rows become objects keyed by those headers.
|
|
181
|
+
*
|
|
182
|
+
* @param sheetName - Name of the sheet tab (if omitted, reads the first sheet)
|
|
183
|
+
* @param spreadsheetId - The spreadsheet ID
|
|
184
|
+
*/
|
|
185
|
+
async getAsJson<T extends Record<string, any> = Record<string, string>>(
|
|
186
|
+
sheetName?: string,
|
|
187
|
+
spreadsheetId?: string
|
|
188
|
+
): Promise<T[]> {
|
|
189
|
+
const id = this.resolveId(spreadsheetId)
|
|
190
|
+
|
|
191
|
+
// If no sheet name, get the first sheet's name
|
|
192
|
+
let range: string
|
|
193
|
+
if (sheetName) {
|
|
194
|
+
range = sheetName
|
|
195
|
+
} else {
|
|
196
|
+
const meta = await this.getSpreadsheet(id)
|
|
197
|
+
range = meta.sheets[0]?.title || 'Sheet1'
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const values = await this.getRange(range, id)
|
|
201
|
+
if (values.length < 2) return []
|
|
202
|
+
|
|
203
|
+
const headers = values[0]!
|
|
204
|
+
return values.slice(1).map(row => {
|
|
205
|
+
const obj: Record<string, string> = {}
|
|
206
|
+
headers.forEach((header, i) => {
|
|
207
|
+
obj[header] = row[i] || ''
|
|
208
|
+
})
|
|
209
|
+
return obj as T
|
|
210
|
+
})
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Read a sheet and return it as a CSV string.
|
|
215
|
+
*
|
|
216
|
+
* @param sheetName - Name of the sheet tab (if omitted, reads the first sheet)
|
|
217
|
+
* @param spreadsheetId - The spreadsheet ID
|
|
218
|
+
*/
|
|
219
|
+
async getAsCsv(sheetName?: string, spreadsheetId?: string): Promise<string> {
|
|
220
|
+
const id = this.resolveId(spreadsheetId)
|
|
221
|
+
|
|
222
|
+
let range: string
|
|
223
|
+
if (sheetName) {
|
|
224
|
+
range = sheetName
|
|
225
|
+
} else {
|
|
226
|
+
const meta = await this.getSpreadsheet(id)
|
|
227
|
+
range = meta.sheets[0]?.title || 'Sheet1'
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const values = await this.getRange(range, id)
|
|
231
|
+
return values.map(row =>
|
|
232
|
+
row.map(cell => {
|
|
233
|
+
const str = String(cell)
|
|
234
|
+
if (str.includes(',') || str.includes('"') || str.includes('\n')) {
|
|
235
|
+
return `"${str.replace(/"/g, '""')}"`
|
|
236
|
+
}
|
|
237
|
+
return str
|
|
238
|
+
}).join(',')
|
|
239
|
+
).join('\n')
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Download sheet data as JSON and save to a local file.
|
|
244
|
+
*
|
|
245
|
+
* @param localPath - Local file path (resolved relative to container cwd)
|
|
246
|
+
* @param sheetName - Sheet tab name (defaults to first sheet)
|
|
247
|
+
* @param spreadsheetId - The spreadsheet ID
|
|
248
|
+
* @returns Absolute path of the saved file
|
|
249
|
+
*/
|
|
250
|
+
async saveAsJson(localPath: string, sheetName?: string, spreadsheetId?: string): Promise<string> {
|
|
251
|
+
const data = await this.getAsJson(sheetName, spreadsheetId)
|
|
252
|
+
const outPath = this.container.paths.resolve(localPath)
|
|
253
|
+
await this.container.fs.writeFileAsync(outPath, JSON.stringify(data, null, 2))
|
|
254
|
+
return outPath
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Download sheet data as CSV and save to a local file.
|
|
259
|
+
*
|
|
260
|
+
* @param localPath - Local file path (resolved relative to container cwd)
|
|
261
|
+
* @param sheetName - Sheet tab name (defaults to first sheet)
|
|
262
|
+
* @param spreadsheetId - The spreadsheet ID
|
|
263
|
+
* @returns Absolute path of the saved file
|
|
264
|
+
*/
|
|
265
|
+
async saveAsCsv(localPath: string, sheetName?: string, spreadsheetId?: string): Promise<string> {
|
|
266
|
+
const csv = await this.getAsCsv(sheetName, spreadsheetId)
|
|
267
|
+
const outPath = this.container.paths.resolve(localPath)
|
|
268
|
+
await this.container.fs.writeFileAsync(outPath, csv)
|
|
269
|
+
return outPath
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
declare module '../../feature' {
|
|
274
|
+
interface AvailableFeatures {
|
|
275
|
+
googleSheets: typeof GoogleSheets
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
export default features.register('googleSheets', GoogleSheets)
|