@gpdoc/cli 1.5.0 → 1.7.0
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/bin/gpdoc.js +88 -14
- package/lib/remote.js +9 -1
- package/package.json +1 -1
package/bin/gpdoc.js
CHANGED
|
@@ -159,7 +159,7 @@ function parseArguments(argv) {
|
|
|
159
159
|
options[key] = true;
|
|
160
160
|
continue;
|
|
161
161
|
}
|
|
162
|
-
if (!['title', 'to', 'output', 'drive-id', 'item-id', 'parent-id', 'revision', 'filetype', 'filename', 'role', 'scope', 'description', 'branch', 'path', 'message', 'editor', 'cursor', 'query'].includes(key) || !rest[index + 1] || rest[index + 1].startsWith('--')) {
|
|
162
|
+
if (!['title', 'to', 'output', 'drive-id', 'item-id', 'parent-id', 'revision', 'filetype', 'filename', 'role', 'scope', 'description', 'branch', 'path', 'message', 'editor', 'cursor', 'query', 'open'].includes(key) || !rest[index + 1] || rest[index + 1].startsWith('--')) {
|
|
163
163
|
throw new CliError('USAGE', `Unknown or incomplete option: ${value}.`);
|
|
164
164
|
}
|
|
165
165
|
options[key] = rest[index + 1];
|
|
@@ -318,18 +318,91 @@ function renderRemote(result, json) {
|
|
|
318
318
|
return printResult(result, false);
|
|
319
319
|
}
|
|
320
320
|
|
|
321
|
-
function
|
|
322
|
-
|
|
321
|
+
function truncate(value, width) {
|
|
322
|
+
const text = String(value || '');
|
|
323
|
+
if (text.length <= width) return text;
|
|
324
|
+
if (width < 2) return text.slice(0, width);
|
|
325
|
+
const prefixLength = Math.ceil((width - 1) * 0.7);
|
|
326
|
+
const suffixLength = Math.floor((width - 1) * 0.3);
|
|
327
|
+
return `${text.slice(0, prefixLength)}…${suffixLength ? text.slice(-suffixLength) : ''}`;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
function formatBytes(value) {
|
|
331
|
+
const bytes = Number(value || 0);
|
|
332
|
+
if (!Number.isFinite(bytes) || bytes <= 0) return '—';
|
|
333
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
334
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(bytes >= 10 * 1024 ? 0 : 1)} KiB`;
|
|
335
|
+
return `${(bytes / (1024 * 1024)).toFixed(bytes >= 10 * 1024 * 1024 ? 0 : 1)} MiB`;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
function formatDate(value) {
|
|
339
|
+
const date = new Date(value || '');
|
|
340
|
+
return Number.isNaN(date.getTime()) ? '—' : date.toISOString().slice(0, 10);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
function displayProvider(provider) {
|
|
344
|
+
return provider === 'google' ? 'Google Drive' : provider === 'microsoft' ? 'Microsoft 365' : 'GPDoc';
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
function listedItemId(item) {
|
|
348
|
+
return item?.providerData?.itemId || item?.itemId || item?.id || '';
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// @spec CLI-038
|
|
352
|
+
export function selectListedItem(items, row) {
|
|
353
|
+
const index = Number(row);
|
|
354
|
+
if (!Number.isSafeInteger(index) || index < 1 || index > items.length) {
|
|
355
|
+
throw new CliError('USAGE', 'Choose a row number from the displayed list.');
|
|
356
|
+
}
|
|
357
|
+
return items[index - 1];
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// @spec CLI-038
|
|
361
|
+
export function formatList(result) {
|
|
323
362
|
if (Array.isArray(result.items)) {
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
363
|
+
if (!result.items.length) return 'No files found.';
|
|
364
|
+
const columns = [
|
|
365
|
+
['#', 3], ['Type', 8], ['Name', 34], ['Modified', 10], ['Size', 9], ['ID', 18],
|
|
366
|
+
];
|
|
367
|
+
const header = columns.map(([label, width]) => label.padEnd(width)).join(' ').trimEnd();
|
|
368
|
+
const divider = columns.map(([, width]) => '─'.repeat(width)).join(' ');
|
|
369
|
+
const rows = result.items.map((item, index) => {
|
|
370
|
+
const values = [
|
|
371
|
+
String(index + 1),
|
|
372
|
+
item?.kind === 'folder' ? 'Folder' : 'File',
|
|
373
|
+
item?.name || 'Untitled',
|
|
374
|
+
formatDate(item?.modifiedAt),
|
|
375
|
+
item?.kind === 'folder' ? '—' : formatBytes(item?.size),
|
|
376
|
+
listedItemId(item),
|
|
377
|
+
];
|
|
378
|
+
return values.map((value, columnIndex) => truncate(value, columns[columnIndex][1]).padEnd(columns[columnIndex][1])).join(' ').trimEnd();
|
|
327
379
|
});
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
const
|
|
332
|
-
|
|
380
|
+
const provider = result.provider || 'provider';
|
|
381
|
+
return `${displayProvider(provider)}\n${header}\n${divider}\n${rows.join('\n')}\n\nOpen an item: gpdoc ${provider} list --open ROW\nUse --json for full IDs and URLs.`;
|
|
382
|
+
}
|
|
383
|
+
const entries = [
|
|
384
|
+
...(result.owned || []).map((file) => ({ access: 'Owned', name: file?.filename || 'Untitled', id: file?.shareId || '' })),
|
|
385
|
+
...(result.sharedWithMe || []).map((file) => ({ access: 'Shared', name: file?.filename || 'Untitled', id: file?.shareId || '' })),
|
|
386
|
+
];
|
|
387
|
+
if (!entries.length) return 'No shared files found.';
|
|
388
|
+
const header = 'Access Name Share ID';
|
|
389
|
+
const rows = entries.map((entry) => `${entry.access.padEnd(6)} ${truncate(entry.name, 34).padEnd(34)} ${truncate(entry.id, 18)}`);
|
|
390
|
+
return `GPDoc shared files\n${header}\n${'─'.repeat(header.length)}\n${rows.join('\n')}\n\nUse --json for full IDs.`;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
function renderList(result, json) {
|
|
394
|
+
if (json) return printResult(result, true);
|
|
395
|
+
return printResult(formatList(result), false);
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
async function renderProviderList(result, { open, json, runtime }) {
|
|
399
|
+
if (!open) return renderList(result, json);
|
|
400
|
+
if (json) throw new CliError('USAGE', 'Use either --open ROW or --json, not both.');
|
|
401
|
+
const item = selectListedItem(result.items || [], open);
|
|
402
|
+
if (!item?.webUrl) throw new CliError('PROVIDER_URL_UNAVAILABLE', 'The selected provider item has no browser URL. Use --json to inspect its identifiers.');
|
|
403
|
+
renderList(result, false);
|
|
404
|
+
const browserOpened = await (runtime.openBrowser || openBrowser)(item.webUrl);
|
|
405
|
+
process.stdout.write(browserOpened ? `Opened ${item.name || 'provider item'} in your browser.\n` : `Could not open a browser. Open this URL manually: ${item.webUrl}\n`);
|
|
333
406
|
}
|
|
334
407
|
|
|
335
408
|
function createProgressReporter(stderr = process.stderr) {
|
|
@@ -372,7 +445,7 @@ async function waitForProviderConnection(remote, providerName, sleep = delay) {
|
|
|
372
445
|
throw new CliError('PROVIDER_CONNECTION_TIMEOUT', `Timed out waiting for ${providerName} connection. Run gpdoc ${providerName} status to check it later.`);
|
|
373
446
|
}
|
|
374
447
|
|
|
375
|
-
// @spec CLI-001, CLI-002, CLI-003, CLI-004, CLI-005, CLI-006, CLI-007, CLI-008, CLI-009, CLI-014, CLI-015, CLI-016, CLI-017, CLI-018, CLI-019, CLI-020, CLI-021, CLI-022, CLI-023, CLI-024, CLI-025, CLI-028, CLI-030, CLI-032, CLI-033, CLI-034, CLI-035, CLI-036
|
|
448
|
+
// @spec CLI-001, CLI-002, CLI-003, CLI-004, CLI-005, CLI-006, CLI-007, CLI-008, CLI-009, CLI-014, CLI-015, CLI-016, CLI-017, CLI-018, CLI-019, CLI-020, CLI-021, CLI-022, CLI-023, CLI-024, CLI-025, CLI-028, CLI-030, CLI-032, CLI-033, CLI-034, CLI-035, CLI-036, CLI-038
|
|
376
449
|
export async function run(argv, runtime = {}) {
|
|
377
450
|
if (argv[0] === 'knowledge') {
|
|
378
451
|
await runKnowledge(argv.slice(1), runtime);
|
|
@@ -381,6 +454,7 @@ export async function run(argv, runtime = {}) {
|
|
|
381
454
|
const parsed = parseArguments(argv);
|
|
382
455
|
if (parsed.command === 'help') {
|
|
383
456
|
process.stdout.write(usage());
|
|
457
|
+
process.stdout.write('\nCloud file lists: use gpdoc google list --open ROW or gpdoc microsoft list --open ROW to open a numbered item in your browser.\n');
|
|
384
458
|
return;
|
|
385
459
|
}
|
|
386
460
|
if (parsed.command === 'version') {
|
|
@@ -434,7 +508,7 @@ export async function run(argv, runtime = {}) {
|
|
|
434
508
|
const connected = await waitForProviderConnection(remote, 'google', runtime.sleep || delay);
|
|
435
509
|
return renderRemote({ ...started, browserOpened, ...connected }, parsed.options.json);
|
|
436
510
|
}
|
|
437
|
-
if (action === 'list') return
|
|
511
|
+
if (action === 'list') return renderProviderList(await remote.googleList({ driveId: parsed.options['drive-id'], parentId: parsed.options['parent-id'], cursor: parsed.options.cursor, shared: parsed.options.shared === true }), { open: parsed.options.open, json: parsed.options.json, runtime });
|
|
438
512
|
const filePath = await requireLocalInput(input);
|
|
439
513
|
const progress = parsed.options.json ? undefined : (runtime.progress || createProgressReporter(runtime.stderr));
|
|
440
514
|
if (action === 'upload') return renderRemote(await remote.googleSave({ filePath, title: parsed.options.title, filetype: parsed.options.filetype || 'document', onProgress: progress }), parsed.options.json);
|
|
@@ -452,7 +526,7 @@ export async function run(argv, runtime = {}) {
|
|
|
452
526
|
const connected = await waitForProviderConnection(remote, 'microsoft', runtime.sleep || delay);
|
|
453
527
|
return renderRemote({ ...started, browserOpened, ...connected }, parsed.options.json);
|
|
454
528
|
}
|
|
455
|
-
if (action === 'list') return
|
|
529
|
+
if (action === 'list') return renderProviderList(await remote.microsoftList({ driveId: parsed.options['drive-id'], itemId: parsed.options['item-id'], cursor: parsed.options.cursor, query: parsed.options.query, shared: parsed.options.shared === true }), { open: parsed.options.open, json: parsed.options.json, runtime });
|
|
456
530
|
if (action === 'share') return renderRemote(await remote.microsoftShare({ driveId: requireOption(parsed.options, 'drive-id'), itemId: requireOption(parsed.options, 'item-id'), role: requireOption(parsed.options, 'role'), scope: requireOption(parsed.options, 'scope') }), parsed.options.json);
|
|
457
531
|
const filePath = await requireLocalInput(input);
|
|
458
532
|
const progress = parsed.options.json ? undefined : (runtime.progress || createProgressReporter(runtime.stderr));
|
package/lib/remote.js
CHANGED
|
@@ -17,6 +17,14 @@ function apiBase(env) {
|
|
|
17
17
|
return String(env.GPDOC_API_BASE_URL || 'https://gpdoc.io').replace(/\/$/, '');
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
+
// @spec RUNTIME-CF-ACCOUNT-019
|
|
21
|
+
function accountApiBase(env) {
|
|
22
|
+
const configured = String(env.GPDOC_ACCOUNT_API_BASE_URL || '').trim().replace(/\/$/, '');
|
|
23
|
+
if (configured) return configured;
|
|
24
|
+
const configuredApiBase = apiBase(env);
|
|
25
|
+
return configuredApiBase === 'https://gpdoc.io' ? 'https://api.gpdoc.io' : configuredApiBase;
|
|
26
|
+
}
|
|
27
|
+
|
|
20
28
|
async function jsonRequest(fetchImpl, url, init, fallbackCode = 'REMOTE_REQUEST_FAILED') {
|
|
21
29
|
let response;
|
|
22
30
|
try {
|
|
@@ -69,7 +77,7 @@ function microsoftFilename(filename, title) {
|
|
|
69
77
|
|
|
70
78
|
async function githubToken({ auth, env, fetchImpl }) {
|
|
71
79
|
const token = await auth.getAccessToken();
|
|
72
|
-
const payload = await jsonRequest(fetchImpl, `${
|
|
80
|
+
const payload = await jsonRequest(fetchImpl, `${accountApiBase(env)}/account/profile`, { headers: bearer(token) }, 'GITHUB_IDENTITY_REQUIRED');
|
|
73
81
|
if (typeof payload.github_access_token !== 'string' || !payload.github_access_token) {
|
|
74
82
|
throw new RemoteError('GITHUB_IDENTITY_REQUIRED', 'Connect GitHub to the signed-in GPDoc account before using GitHub sources.');
|
|
75
83
|
}
|