@ecmaos/coreutils 0.4.0 → 0.4.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.
Files changed (171) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +6 -0
  3. package/dist/commands/awk.d.ts +4 -0
  4. package/dist/commands/awk.d.ts.map +1 -0
  5. package/dist/commands/awk.js +324 -0
  6. package/dist/commands/awk.js.map +1 -0
  7. package/dist/commands/chgrp.d.ts +4 -0
  8. package/dist/commands/chgrp.d.ts.map +1 -0
  9. package/dist/commands/chgrp.js +187 -0
  10. package/dist/commands/chgrp.js.map +1 -0
  11. package/dist/commands/chmod.d.ts.map +1 -1
  12. package/dist/commands/chmod.js +139 -2
  13. package/dist/commands/chmod.js.map +1 -1
  14. package/dist/commands/chown.d.ts +4 -0
  15. package/dist/commands/chown.d.ts.map +1 -0
  16. package/dist/commands/chown.js +257 -0
  17. package/dist/commands/chown.js.map +1 -0
  18. package/dist/commands/cksum.d.ts +4 -0
  19. package/dist/commands/cksum.d.ts.map +1 -0
  20. package/dist/commands/cksum.js +124 -0
  21. package/dist/commands/cksum.js.map +1 -0
  22. package/dist/commands/cmp.d.ts +4 -0
  23. package/dist/commands/cmp.d.ts.map +1 -0
  24. package/dist/commands/cmp.js +120 -0
  25. package/dist/commands/cmp.js.map +1 -0
  26. package/dist/commands/column.d.ts +4 -0
  27. package/dist/commands/column.d.ts.map +1 -0
  28. package/dist/commands/column.js +274 -0
  29. package/dist/commands/column.js.map +1 -0
  30. package/dist/commands/cp.d.ts.map +1 -1
  31. package/dist/commands/cp.js +81 -4
  32. package/dist/commands/cp.js.map +1 -1
  33. package/dist/commands/curl.d.ts +4 -0
  34. package/dist/commands/curl.d.ts.map +1 -0
  35. package/dist/commands/curl.js +238 -0
  36. package/dist/commands/curl.js.map +1 -0
  37. package/dist/commands/du.d.ts +4 -0
  38. package/dist/commands/du.d.ts.map +1 -0
  39. package/dist/commands/du.js +168 -0
  40. package/dist/commands/du.js.map +1 -0
  41. package/dist/commands/echo.d.ts.map +1 -1
  42. package/dist/commands/echo.js +125 -2
  43. package/dist/commands/echo.js.map +1 -1
  44. package/dist/commands/expand.d.ts +4 -0
  45. package/dist/commands/expand.d.ts.map +1 -0
  46. package/dist/commands/expand.js +197 -0
  47. package/dist/commands/expand.js.map +1 -0
  48. package/dist/commands/factor.d.ts +4 -0
  49. package/dist/commands/factor.d.ts.map +1 -0
  50. package/dist/commands/factor.js +141 -0
  51. package/dist/commands/factor.js.map +1 -0
  52. package/dist/commands/fmt.d.ts +4 -0
  53. package/dist/commands/fmt.d.ts.map +1 -0
  54. package/dist/commands/fmt.js +278 -0
  55. package/dist/commands/fmt.js.map +1 -0
  56. package/dist/commands/fold.d.ts +4 -0
  57. package/dist/commands/fold.d.ts.map +1 -0
  58. package/dist/commands/fold.js +253 -0
  59. package/dist/commands/fold.js.map +1 -0
  60. package/dist/commands/groups.d.ts +4 -0
  61. package/dist/commands/groups.d.ts.map +1 -0
  62. package/dist/commands/groups.js +61 -0
  63. package/dist/commands/groups.js.map +1 -0
  64. package/dist/commands/hostname.d.ts +4 -0
  65. package/dist/commands/hostname.d.ts.map +1 -0
  66. package/dist/commands/hostname.js +80 -0
  67. package/dist/commands/hostname.js.map +1 -0
  68. package/dist/commands/mount.d.ts.map +1 -1
  69. package/dist/commands/mount.js +192 -97
  70. package/dist/commands/mount.js.map +1 -1
  71. package/dist/commands/od.d.ts +4 -0
  72. package/dist/commands/od.d.ts.map +1 -0
  73. package/dist/commands/od.js +342 -0
  74. package/dist/commands/od.js.map +1 -0
  75. package/dist/commands/pr.d.ts +4 -0
  76. package/dist/commands/pr.d.ts.map +1 -0
  77. package/dist/commands/pr.js +298 -0
  78. package/dist/commands/pr.js.map +1 -0
  79. package/dist/commands/printf.d.ts +4 -0
  80. package/dist/commands/printf.d.ts.map +1 -0
  81. package/dist/commands/printf.js +271 -0
  82. package/dist/commands/printf.js.map +1 -0
  83. package/dist/commands/readlink.d.ts +4 -0
  84. package/dist/commands/readlink.d.ts.map +1 -0
  85. package/dist/commands/readlink.js +104 -0
  86. package/dist/commands/readlink.js.map +1 -0
  87. package/dist/commands/realpath.d.ts +4 -0
  88. package/dist/commands/realpath.d.ts.map +1 -0
  89. package/dist/commands/realpath.js +111 -0
  90. package/dist/commands/realpath.js.map +1 -0
  91. package/dist/commands/rev.d.ts +4 -0
  92. package/dist/commands/rev.d.ts.map +1 -0
  93. package/dist/commands/rev.js +134 -0
  94. package/dist/commands/rev.js.map +1 -0
  95. package/dist/commands/shuf.d.ts +4 -0
  96. package/dist/commands/shuf.d.ts.map +1 -0
  97. package/dist/commands/shuf.js +221 -0
  98. package/dist/commands/shuf.js.map +1 -0
  99. package/dist/commands/sleep.d.ts +4 -0
  100. package/dist/commands/sleep.d.ts.map +1 -0
  101. package/dist/commands/sleep.js +102 -0
  102. package/dist/commands/sleep.js.map +1 -0
  103. package/dist/commands/strings.d.ts +4 -0
  104. package/dist/commands/strings.d.ts.map +1 -0
  105. package/dist/commands/strings.js +170 -0
  106. package/dist/commands/strings.js.map +1 -0
  107. package/dist/commands/tac.d.ts +4 -0
  108. package/dist/commands/tac.d.ts.map +1 -0
  109. package/dist/commands/tac.js +130 -0
  110. package/dist/commands/tac.js.map +1 -0
  111. package/dist/commands/time.d.ts +4 -0
  112. package/dist/commands/time.d.ts.map +1 -0
  113. package/dist/commands/time.js +126 -0
  114. package/dist/commands/time.js.map +1 -0
  115. package/dist/commands/umount.d.ts.map +1 -1
  116. package/dist/commands/umount.js +2 -3
  117. package/dist/commands/umount.js.map +1 -1
  118. package/dist/commands/uname.d.ts +4 -0
  119. package/dist/commands/uname.d.ts.map +1 -0
  120. package/dist/commands/uname.js +149 -0
  121. package/dist/commands/uname.js.map +1 -0
  122. package/dist/commands/unexpand.d.ts +4 -0
  123. package/dist/commands/unexpand.d.ts.map +1 -0
  124. package/dist/commands/unexpand.js +286 -0
  125. package/dist/commands/unexpand.js.map +1 -0
  126. package/dist/commands/uptime.d.ts +4 -0
  127. package/dist/commands/uptime.d.ts.map +1 -0
  128. package/dist/commands/uptime.js +62 -0
  129. package/dist/commands/uptime.js.map +1 -0
  130. package/dist/commands/yes.d.ts +4 -0
  131. package/dist/commands/yes.d.ts.map +1 -0
  132. package/dist/commands/yes.js +58 -0
  133. package/dist/commands/yes.js.map +1 -0
  134. package/dist/index.d.ts +21 -0
  135. package/dist/index.d.ts.map +1 -1
  136. package/dist/index.js +73 -0
  137. package/dist/index.js.map +1 -1
  138. package/package.json +3 -2
  139. package/src/commands/awk.ts +340 -0
  140. package/src/commands/chmod.ts +141 -2
  141. package/src/commands/chown.ts +321 -0
  142. package/src/commands/cksum.ts +133 -0
  143. package/src/commands/cmp.ts +126 -0
  144. package/src/commands/column.ts +273 -0
  145. package/src/commands/cp.ts +93 -4
  146. package/src/commands/curl.ts +231 -0
  147. package/src/commands/echo.ts +122 -2
  148. package/src/commands/expand.ts +207 -0
  149. package/src/commands/factor.ts +151 -0
  150. package/src/commands/fmt.ts +293 -0
  151. package/src/commands/fold.ts +257 -0
  152. package/src/commands/groups.ts +72 -0
  153. package/src/commands/hostname.ts +81 -0
  154. package/src/commands/mount.ts +208 -99
  155. package/src/commands/od.ts +327 -0
  156. package/src/commands/pr.ts +291 -0
  157. package/src/commands/printf.ts +271 -0
  158. package/src/commands/readlink.ts +102 -0
  159. package/src/commands/realpath.ts +126 -0
  160. package/src/commands/rev.ts +143 -0
  161. package/src/commands/shuf.ts +218 -0
  162. package/src/commands/sleep.ts +109 -0
  163. package/src/commands/strings.ts +176 -0
  164. package/src/commands/tac.ts +138 -0
  165. package/src/commands/time.ts +144 -0
  166. package/src/commands/umount.ts +2 -3
  167. package/src/commands/uname.ts +130 -0
  168. package/src/commands/unexpand.ts +305 -0
  169. package/src/commands/uptime.ts +73 -0
  170. package/src/index.ts +73 -0
  171. package/tsconfig.json +4 -0
@@ -1,6 +1,8 @@
1
+ // TODO: Dropbox and S3 are WIP
2
+
1
3
  import path from 'path'
2
4
  import chalk from 'chalk'
3
- import { Fetch, InMemory, mounts, resolveMountConfig, SingleBuffer } from '@zenfs/core'
5
+ import { Fetch, InMemory, resolveMountConfig, SingleBuffer } from '@zenfs/core'
4
6
  import { IndexedDB, WebStorage, WebAccess, /* XML */ } from '@zenfs/dom'
5
7
  import { Iso, Zip } from '@zenfs/archives'
6
8
  import { Dropbox, /* S3Bucket, */ GoogleDrive } from '@zenfs/cloud'
@@ -77,7 +79,7 @@ function printUsage(process: Process | undefined, terminal: Terminal): void {
77
79
  Mount a filesystem.
78
80
 
79
81
  Options:
80
- -t, --type TYPE filesystem type (fetch, indexeddb, webstorage, webaccess, memory, singlebuffer, zip, iso, dropbox, /* s3, */ googledrive)
82
+ -t, --type TYPE filesystem type (fetch, indexeddb, webstorage, webaccess, memory, singlebuffer, zip, iso, googledrive)
81
83
  -o, --options OPTS mount options (comma-separated key=value pairs)
82
84
  -a, --all mount all filesystems listed in /etc/fstab
83
85
  -l, --list list all mounted filesystems
@@ -92,20 +94,19 @@ Filesystem types:
92
94
  singlebuffer mount a filesystem backed by a single buffer
93
95
  zip mount a readonly filesystem from a zip archive (requires SOURCE file or URL)
94
96
  iso mount a readonly filesystem from an ISO image (requires SOURCE file or URL)
95
- dropbox mount a Dropbox filesystem (requires client configuration via -o client)
96
97
  googledrive mount a Google Drive filesystem (requires apiKey via -o apiKey, optionally clientId for OAuth)
97
98
 
98
99
  Mount options:
99
100
  baseUrl=URL base URL for fetch operations (fetch type)
100
101
  size=BYTES buffer size in bytes for singlebuffer type (default: 1048576)
101
102
  storage=TYPE storage type for webstorage (localStorage or sessionStorage, default: localStorage)
102
- client=JSON client configuration as JSON string (dropbox type)
103
103
  apiKey=KEY Google API key (googledrive type, required)
104
104
  clientId=ID Google OAuth client ID (googledrive type, optional)
105
105
  scope=SCOPE OAuth scope (googledrive type, default: https://www.googleapis.com/auth/drive)
106
106
  cacheTTL=SECONDS cache TTL in seconds for cloud backends (optional)
107
107
 
108
108
  Examples:
109
+ mount -l list all mounted filesystems
109
110
  mount -t memory /mnt/tmp mount memory filesystem at /mnt/tmp
110
111
  mount -t indexeddb mydb /mnt/db mount IndexedDB store 'mydb' at /mnt/db
111
112
  mount -t webstorage /mnt/storage mount WebStorage filesystem using localStorage
@@ -119,10 +120,8 @@ Examples:
119
120
  mount -t zip /tmp/archive.zip /mnt/zip
120
121
  mount -t iso https://example.com/image.iso /mnt/iso
121
122
  mount -t iso /tmp/image.iso /mnt/iso
122
- mount -t dropbox /mnt/dropbox -o client='{"accessToken":"..."}'
123
- mount -t googledrive /mnt/gdrive -o apiKey=YOUR_API_KEY
124
- mount -t googledrive /mnt/gdrive -o apiKey=YOUR_API_KEY,clientId=YOUR_CLIENT_ID
125
- mount -l list all mounted filesystems`
123
+ mount -t googledrive /mnt/gdrive -o apiKey=YOUR_API_KEY # readonly/public
124
+ mount -t googledrive /mnt/gdrive -o clientId=YOUR_CLIENT_ID # rw/private`
126
125
  writelnStderr(process, terminal, usage)
127
126
  }
128
127
 
@@ -175,7 +174,7 @@ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal):
175
174
  }
176
175
 
177
176
  if (listMode || (argv.length === 0 && !allMode)) {
178
- const mountList = Array.from(mounts.entries())
177
+ const mountList = Array.from(kernel.filesystem.mounts.entries())
179
178
 
180
179
  if (mountList.length === 0) {
181
180
  await writelnStdout(process, terminal, 'No filesystems mounted.')
@@ -191,13 +190,12 @@ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal):
191
190
 
192
191
  return {
193
192
  target: chalk.blue(target),
194
- type: chalk.gray(backendName.toLowerCase()),
195
193
  name: chalk.gray(name)
196
194
  }
197
195
  })
198
196
 
199
197
  for (const row of mountRows) {
200
- await writelnStdout(process, terminal, `${row.target.padEnd(30)} ${row.type.padEnd(15)} ${row.name}`)
198
+ await writelnStdout(process, terminal, `${row.target.padEnd(30)} ${row.name}`)
201
199
  }
202
200
 
203
201
  return 0
@@ -982,21 +980,61 @@ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal):
982
980
  return 1
983
981
  }
984
982
 
985
- if (!mountOptions.apiKey) {
986
- await writelnStderr(process, terminal, chalk.red('mount: googledrive filesystem requires apiKey option'))
987
- await writelnStderr(process, terminal, 'Usage: mount -t googledrive TARGET -o apiKey=YOUR_API_KEY')
988
- return 1
989
- }
983
+ // if (!mountOptions.apiKey) {
984
+ // await writelnStderr(process, terminal, chalk.red('mount: googledrive filesystem requires apiKey option'))
985
+ // await writelnStderr(process, terminal, 'Usage: mount -t googledrive TARGET -o apiKey=YOUR_API_KEY')
986
+ // return 1
987
+ // }
990
988
 
991
989
  const win = window as unknown as {
992
990
  gapi?: {
993
991
  load?: (module: string, callback: () => void) => void
994
992
  client?: {
995
- init?: (config: { apiKey: string; clientId?: string; discoveryDocs?: string[]; scope?: string }) => Promise<void>
993
+ init?: (config: { apiKey: string; discoveryDocs?: string[] }) => Promise<void>
996
994
  request?: (config: { path: string }) => Promise<unknown>
995
+ setToken?: (token: { access_token: string }) => void
997
996
  drive?: unknown
998
997
  }
999
998
  }
999
+ google?: {
1000
+ accounts?: {
1001
+ id?: {
1002
+ initialize: (config: { client_id: string; callback: (response: { credential: string }) => void; scope?: string }) => void
1003
+ prompt: (callback?: (notification: { isNotDisplayed: boolean; isSkippedMoment: boolean; isDismissedMoment: boolean }) => void) => void
1004
+ renderButton: (element: HTMLElement, config: { theme?: string; size?: string; text?: string; width?: number; locale?: string }) => void
1005
+ }
1006
+ oauth2?: {
1007
+ initTokenClient: (config: { client_id: string; scope: string; callback: (response: { access_token: string }) => void }) => { requestAccessToken: () => void }
1008
+ }
1009
+ }
1010
+ }
1011
+ }
1012
+
1013
+ // Load Google Identity Services library if not already loaded
1014
+ if (!win.google?.accounts) {
1015
+ await writelnStdout(process, terminal, chalk.gray('Loading Google Identity Services library...'))
1016
+
1017
+ await new Promise<void>((resolve, reject) => {
1018
+ const script = document.createElement('script')
1019
+ script.src = 'https://accounts.google.com/gsi/client'
1020
+ script.async = true
1021
+ script.defer = true
1022
+ script.onload = () => resolve()
1023
+ script.onerror = () => reject(new Error('Failed to load Google Identity Services script'))
1024
+ document.head.appendChild(script)
1025
+ })
1026
+ }
1027
+
1028
+ // Wait for google.accounts to be available
1029
+ let attempts = 0
1030
+ while (!win.google?.accounts && attempts < 50) {
1031
+ await new Promise(resolve => setTimeout(resolve, 100))
1032
+ attempts++
1033
+ }
1034
+
1035
+ if (!win.google?.accounts) {
1036
+ await writelnStderr(process, terminal, chalk.red('mount: Failed to load Google Identity Services library'))
1037
+ return 1
1000
1038
  }
1001
1039
 
1002
1040
  // Load Google API script if not already loaded
@@ -1013,7 +1051,7 @@ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal):
1013
1051
  }
1014
1052
 
1015
1053
  // Wait for gapi to be available
1016
- let attempts = 0
1054
+ attempts = 0
1017
1055
  while (!win.gapi && attempts < 50) {
1018
1056
  await new Promise(resolve => setTimeout(resolve, 100))
1019
1057
  attempts++
@@ -1024,27 +1062,17 @@ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal):
1024
1062
  return 1
1025
1063
  }
1026
1064
 
1027
- // Initialize gapi.client
1028
1065
  if (!win.gapi.client || !win.gapi.client.drive) {
1029
1066
  await writelnStdout(process, terminal, chalk.gray('Initializing Google API client...'))
1030
-
1067
+
1031
1068
  const initConfig: {
1032
1069
  apiKey: string
1033
- clientId?: string
1034
1070
  discoveryDocs?: string[]
1035
- scope?: string
1036
1071
  } = {
1037
- apiKey: mountOptions.apiKey
1038
- }
1039
-
1040
- if (mountOptions.clientId) {
1041
- initConfig.clientId = mountOptions.clientId
1072
+ apiKey: mountOptions.apiKey!,
1073
+ discoveryDocs: ['https://www.googleapis.com/discovery/v1/apis/drive/v3/rest']
1042
1074
  }
1043
1075
 
1044
- const scope = mountOptions.scope || 'https://www.googleapis.com/auth/drive'
1045
- initConfig.scope = scope
1046
- initConfig.discoveryDocs = ['https://www.googleapis.com/discovery/v1/apis/drive/v3/rest']
1047
-
1048
1076
  // Load the client module
1049
1077
  await new Promise<void>((resolve, reject) => {
1050
1078
  if (!win.gapi?.load) {
@@ -1057,47 +1085,62 @@ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal):
1057
1085
  return
1058
1086
  }
1059
1087
  win.gapi.client.init(initConfig)
1060
- .then(() => resolve())
1061
- .catch(reject)
1088
+ .then(() => {
1089
+ setTimeout(() => {
1090
+ if (win.gapi?.client?.drive) {
1091
+ resolve()
1092
+ } else {
1093
+ reject(new Error('API discovery response missing required fields. The Drive API discovery document may have failed to load. This could be due to: network issues, invalid API key, or the Drive API not being enabled in your Google Cloud project.'))
1094
+ }
1095
+ }, 1000)
1096
+ })
1097
+ .catch((error: Error & { error?: string; details?: string }) => {
1098
+ const err = error as Error & { error?: string; details?: string }
1099
+ let errorMessage = 'Unknown error'
1100
+
1101
+ errorMessage = JSON.stringify({
1102
+ error: err.error,
1103
+ details: err.details
1104
+ }, null, 2)
1105
+
1106
+ const lowerError = err.error?.toLowerCase()
1107
+ if (lowerError?.includes('discovery') || lowerError?.includes('API') || lowerError?.includes('required fields')) {
1108
+ reject(new Error(`API discovery failed: ${errorMessage}. This could be due to: network issues, invalid API key, or the Drive API not being enabled in your Google Cloud project.`))
1109
+ } else {
1110
+ reject(new Error(err.error || err.details || 'Unknown error'))
1111
+ }
1112
+ })
1062
1113
  })
1063
1114
  })
1064
1115
 
1065
- // Load the Drive API
1066
- await new Promise<void>((resolve, reject) => {
1067
- if (!win.gapi?.client?.request) {
1068
- reject(new Error('gapi.client.request is not available'))
1069
- return
1070
- }
1071
- // Drive API is loaded via discoveryDocs, but we need to ensure it's ready
1072
- // The Drive API should be available after init, but we'll wait a bit
1073
- setTimeout(() => {
1074
- const gapi = win.gapi
1075
- if (gapi?.client?.drive) {
1076
- resolve()
1077
- } else if (gapi?.client?.request) {
1078
- // Try to trigger Drive API loading by making a simple request
1079
- gapi.client.request({
1080
- path: 'https://www.googleapis.com/drive/v3/about?fields=user'
1081
- }).then(() => {
1116
+ // Verify Drive API is loaded
1117
+ if (!win.gapi?.client?.drive) {
1118
+ // Try one more time with a longer wait
1119
+ await new Promise<void>((resolve, reject) => {
1120
+ let attempts = 0
1121
+ const checkDrive = () => {
1122
+ if (win.gapi?.client?.drive) {
1082
1123
  resolve()
1083
- }).catch(() => {
1084
- // Even if this fails, drive might still be available
1085
- if (gapi?.client?.drive) {
1086
- resolve()
1087
- } else {
1088
- reject(new Error('Failed to load Drive API'))
1089
- }
1090
- })
1091
- } else {
1092
- reject(new Error('gapi.client.request is not available'))
1124
+ } else if (attempts < 10) {
1125
+ attempts++
1126
+ setTimeout(checkDrive, 200)
1127
+ } else {
1128
+ reject(new Error('Failed to load Drive API. The discovery document may have failed to load. Check the browser console for network errors (502 Bad Gateway suggests a network issue).'))
1129
+ }
1093
1130
  }
1094
- }, 500)
1095
- })
1131
+ checkDrive()
1132
+ })
1133
+ }
1096
1134
  }
1097
1135
 
1098
1136
  if (!win.gapi?.client?.drive) {
1099
1137
  await writelnStderr(process, terminal, chalk.red('mount: Google Drive API is not available'))
1100
- await writelnStderr(process, terminal, 'Please ensure the Drive API is enabled in your Google Cloud project')
1138
+ await writelnStderr(process, terminal, chalk.yellow('Troubleshooting steps:'))
1139
+ await writelnStderr(process, terminal, ' 1. Check the browser console for network errors (502 Bad Gateway suggests a network/server issue)')
1140
+ await writelnStderr(process, terminal, ' 2. Verify your API key is valid and has the Drive API enabled')
1141
+ await writelnStderr(process, terminal, ' 3. Ensure the Drive API is enabled in your Google Cloud project')
1142
+ await writelnStderr(process, terminal, ' 4. Check if there are any API key restrictions (HTTP referrers, IP addresses, etc.)')
1143
+ await writelnStderr(process, terminal, ' 5. Try refreshing the page and mounting again')
1101
1144
  return 1
1102
1145
  }
1103
1146
 
@@ -1107,7 +1150,7 @@ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal):
1107
1150
 
1108
1151
  const driveScope = mountOptions.scope || 'https://www.googleapis.com/auth/drive'
1109
1152
 
1110
- // Check if user is already signed in
1153
+ // Check if user is already authenticated
1111
1154
  try {
1112
1155
  const client = win.gapi?.client
1113
1156
  if (client?.request) {
@@ -1116,58 +1159,124 @@ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal):
1116
1159
  })
1117
1160
  }
1118
1161
  } catch (error) {
1119
- // User needs to authenticate
1120
- await writelnStdout(process, terminal, chalk.yellow('Authentication required. Please sign in to Google...'))
1162
+ // User needs to authenticate using Google Identity Services
1163
+ await writelnStdout(process, terminal, chalk.gray('Authentication required. Please sign in to Google...'))
1121
1164
 
1122
- const authInstance = (win.gapi as unknown as { auth2?: { getAuthInstance?: () => { signIn: () => Promise<unknown> } } }).auth2
1123
- if (authInstance?.getAuthInstance) {
1124
- const auth = authInstance.getAuthInstance()
1125
- await auth.signIn()
1126
- } else {
1127
- // Fallback: try to trigger auth flow
1165
+ try {
1166
+ if (!win.google?.accounts?.oauth2) {
1167
+ throw new Error('Google Identity Services OAuth2 is not available')
1168
+ }
1169
+
1128
1170
  await new Promise<void>((resolve, reject) => {
1129
- if (!win.gapi?.load) {
1130
- reject(new Error('gapi.load is not available'))
1131
- return
1132
- }
1133
- win.gapi.load('auth2', () => {
1134
- const auth2 = (win.gapi as unknown as { auth2?: { init: (config: unknown) => Promise<unknown>; getAuthInstance: () => { signIn: () => Promise<unknown> } } }).auth2
1135
- if (auth2?.init) {
1136
- auth2.init({
1137
- client_id: mountOptions.clientId,
1138
- scope: driveScope
1139
- }).then(() => {
1140
- if (auth2.getAuthInstance) {
1141
- const auth = auth2.getAuthInstance()
1142
- auth.signIn().then(() => resolve()).catch(reject)
1143
- } else {
1144
- resolve()
1145
- }
1146
- }).catch(reject)
1147
- } else {
1148
- resolve()
1149
- }
1171
+ const tokenClient = win.google?.accounts?.oauth2?.initTokenClient({
1172
+ client_id: mountOptions.clientId!,
1173
+ scope: driveScope,
1174
+ callback: (response: { access_token: string }) => {
1175
+ if (response.access_token && win.gapi?.client?.setToken) {
1176
+ win.gapi.client.setToken({ access_token: response.access_token })
1177
+ resolve()
1178
+ } else {
1179
+ reject(new Error('Failed to obtain access token'))
1180
+ }
1181
+ },
1150
1182
  })
1183
+
1184
+ tokenClient?.requestAccessToken()
1151
1185
  })
1186
+ } catch (authError) {
1187
+ const authErrMsg = authError instanceof Error ? authError.message : String(authError)
1188
+ if (authErrMsg.toLowerCase().includes('popup') || authErrMsg.toLowerCase().includes('blocked')) {
1189
+ throw new Error(`OAuth authentication failed: ${authErrMsg}. Please allow popups for this site.`)
1190
+ } else if (authErrMsg.toLowerCase().includes('origin') || authErrMsg.toLowerCase().includes('authorized')) {
1191
+ throw new Error(`OAuth authentication failed: ${authErrMsg}. Your origin may not be authorized in Google Cloud Console. Add your current origin to the OAuth client's authorized JavaScript origins.`)
1192
+ }
1193
+ throw authError
1152
1194
  }
1153
1195
  }
1154
1196
  }
1155
1197
 
1156
1198
  const drive = win.gapi.client.drive
1157
- const cacheTTL = mountOptions.cacheTTL ? parseInt(mountOptions.cacheTTL, 10) : undefined
1199
+ const cacheTTL = mountOptions.cacheTTL ? parseInt(mountOptions.cacheTTL) : undefined
1158
1200
 
1159
1201
  await kernel.filesystem.fsSync.mount(
1160
1202
  target,
1161
1203
  await resolveMountConfig({
1162
1204
  backend: GoogleDrive,
1163
- drive: drive as never, // gapi.client.drive type from global
1205
+ drive: drive as never,
1206
+ disableAsyncCache: true,
1164
1207
  ...(cacheTTL && !isNaN(cacheTTL) ? { cacheTTL } : {})
1165
1208
  })
1166
1209
  )
1167
1210
  } catch (error) {
1168
- await writelnStderr(process, terminal, chalk.red(`mount: failed to mount googledrive filesystem: ${error instanceof Error ? error.message : 'Unknown error'}`))
1169
- if (error instanceof Error && error.stack) {
1170
- await writelnStderr(process, terminal, chalk.gray(error.stack))
1211
+ let errorMessage = 'Unknown error'
1212
+ if (error instanceof Error) {
1213
+ errorMessage = error.message || error.toString()
1214
+ } else if (typeof error === 'string') {
1215
+ errorMessage = error
1216
+ } else if (error && typeof error === 'object') {
1217
+ // Try to extract error message from object
1218
+ const err = error as Record<string, unknown>
1219
+ errorMessage =
1220
+ (typeof err.message === 'string' ? err.message : '') ||
1221
+ (typeof err.error === 'string' ? err.error : '') ||
1222
+ (typeof err.details === 'string' ? err.details : '') ||
1223
+ (typeof err.reason === 'string' ? err.reason : '') ||
1224
+ (err.toString && typeof err.toString === 'function' ? err.toString() : '') ||
1225
+ JSON.stringify(error)
1226
+ } else {
1227
+ errorMessage = String(error)
1228
+ }
1229
+
1230
+ await writelnStderr(process, terminal, chalk.red(`mount: failed to mount googledrive filesystem: ${errorMessage}`))
1231
+
1232
+ // Provide specific guidance for common errors
1233
+ const lowerMessage = errorMessage.toLowerCase()
1234
+ if (lowerMessage.includes('popup') || lowerMessage.includes('blocked')) {
1235
+ await writelnStderr(process, terminal, chalk.yellow('\nOAuth popup was blocked. Common causes:'))
1236
+ await writelnStderr(process, terminal, ' • Browser popup blocker is enabled')
1237
+ await writelnStderr(process, terminal, ' • Browser security restrictions')
1238
+ await writelnStderr(process, terminal, chalk.gray('\nTo fix this:'))
1239
+ await writelnStderr(process, terminal, ' 1. Allow popups for this site in your browser settings')
1240
+ await writelnStderr(process, terminal, ' 2. Try the mount command again')
1241
+ } else if (lowerMessage.includes('origin') || lowerMessage.includes('authorized')) {
1242
+ await writelnStderr(process, terminal, chalk.yellow('\nOAuth authentication failed. Common causes:'))
1243
+ await writelnStderr(process, terminal, ' • Your domain/origin is not authorized in Google Cloud Console')
1244
+ await writelnStderr(process, terminal, ' • Invalid or incorrect OAuth client ID')
1245
+ await writelnStderr(process, terminal, chalk.gray('\nTo fix this:'))
1246
+ await writelnStderr(process, terminal, ' 1. Go to Google Cloud Console > APIs & Services > Credentials')
1247
+ await writelnStderr(process, terminal, ' 2. Find your OAuth 2.0 Client ID')
1248
+ await writelnStderr(process, terminal, ' 3. Add your current origin to "Authorized JavaScript origins"')
1249
+ await writelnStderr(process, terminal, ' (e.g., http://localhost:30443 or your domain)')
1250
+ await writelnStderr(process, terminal, ' 4. If you only need read-only access, try mounting without clientId:')
1251
+ await writelnStderr(process, terminal, ' mount -t googledrive /mnt/gdrive -o apiKey=YOUR_API_KEY')
1252
+ } else if (lowerMessage.includes('discovery') || lowerMessage.includes('api discovery') || lowerMessage.includes('required fields')) {
1253
+ await writelnStderr(process, terminal, chalk.yellow('\nThe Drive API discovery document failed to load. Common causes:'))
1254
+ await writelnStderr(process, terminal, ' • Network connectivity issues (check for 502 Bad Gateway in console)')
1255
+ await writelnStderr(process, terminal, ' • Invalid or restricted API key')
1256
+ await writelnStderr(process, terminal, ' • Drive API not enabled in Google Cloud project')
1257
+ await writelnStderr(process, terminal, ' • CORS or browser security restrictions')
1258
+ await writelnStderr(process, terminal, chalk.gray('\nCheck the browser console for detailed network error messages.'))
1259
+ } else if (lowerMessage.includes('network') || lowerMessage.includes('fetch') || lowerMessage.includes('502') || lowerMessage.includes('bad gateway')) {
1260
+ await writelnStderr(process, terminal, chalk.yellow('\nNetwork error detected. This may be a temporary issue with Google\'s servers.'))
1261
+ await writelnStderr(process, terminal, ' • Wait a few moments and try again')
1262
+ await writelnStderr(process, terminal, ' • Check your internet connection')
1263
+ await writelnStderr(process, terminal, ' • Verify the API key is correct')
1264
+ }
1265
+
1266
+ // Show full error details if it's an object (for debugging)
1267
+ if (error && typeof error === 'object' && !(error instanceof Error)) {
1268
+ try {
1269
+ const errorStr = JSON.stringify(error, null, 2)
1270
+ if (errorStr !== '{}' && errorStr.length < 500) {
1271
+ await writelnStderr(process, terminal, chalk.gray(`\nError details:\n${errorStr}`))
1272
+ }
1273
+ } catch {
1274
+ // Ignore JSON stringify errors
1275
+ }
1276
+ }
1277
+
1278
+ if (error instanceof Error && error.stack && !lowerMessage.includes('discovery') && !lowerMessage.includes('network')) {
1279
+ await writelnStderr(process, terminal, chalk.gray(`\nStack trace:\n${error.stack}`))
1171
1280
  }
1172
1281
  return 1
1173
1282
  }
@@ -1175,7 +1284,7 @@ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal):
1175
1284
  }
1176
1285
  default:
1177
1286
  await writelnStderr(process, terminal, chalk.red(`mount: unknown filesystem type '${type}'`))
1178
- await writelnStderr(process, terminal, 'Supported types: fetch, indexeddb, webstorage, webaccess, memory, singlebuffer, zip, iso, dropbox, /* s3, */ googledrive')
1287
+ await writelnStderr(process, terminal, 'Supported types: fetch, indexeddb, webstorage, webaccess, memory, singlebuffer, zip, iso, dropbox, s3, googledrive')
1179
1288
  return 1
1180
1289
  }
1181
1290