@yuiseki/gyazocli 0.0.2 → 0.2.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.
@@ -0,0 +1,101 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.registerListCommand = registerListCommand;
4
+ const credentials_1 = require("../credentials");
5
+ const dates_1 = require("../dates");
6
+ const options_1 = require("../options");
7
+ const memory_1 = require("../services/memory");
8
+ const images_1 = require("../services/images");
9
+ function registerListCommand(program) {
10
+ program
11
+ .command('list')
12
+ .alias('ls')
13
+ .description('List recent images')
14
+ .option('-p, --page <number>', 'page number', '1')
15
+ .option('-l, --limit <number>', 'items per page', '20')
16
+ .option('-j, --json', 'output as JSON')
17
+ .option('-H, --hour <yyyy-mm-dd-hh>', 'target hour')
18
+ .option('--date <yyyy|yyyy-mm|yyyy-mm-dd>', 'target date/range')
19
+ .option('--today', 'target today only')
20
+ .option('--max-pages <number>', 'max pages to scan for --date/--today mode', '100')
21
+ .option('--photos', 'alias of search "has:location"')
22
+ .option('--uploaded', 'alias of search "gyazocli_uploads"')
23
+ .option('--no-cache', 'force fetch from API')
24
+ .action(async (options) => {
25
+ await (0, credentials_1.ensureAccessToken)();
26
+ try {
27
+ const useCache = options.cache !== false;
28
+ const page = (0, options_1.parsePositiveIntegerOption)(options.page, '--page');
29
+ const limit = (0, options_1.parsePositiveIntegerOption)(options.limit, '--limit');
30
+ const maxPages = (0, options_1.parsePositiveIntegerOption)(options.maxPages, '--max-pages');
31
+ const hasDateRange = Boolean(options.date || options.today);
32
+ if (options.photos && options.uploaded) {
33
+ console.error('Error: --photos and --uploaded cannot be used together.');
34
+ process.exit(1);
35
+ }
36
+ if (options.today && options.date) {
37
+ console.error('Error: --today and --date cannot be used together.');
38
+ process.exit(1);
39
+ }
40
+ if ((options.photos || options.uploaded) && options.hour) {
41
+ console.error('Error: --photos/--uploaded and --hour cannot be used together.');
42
+ process.exit(1);
43
+ }
44
+ if (options.hour && hasDateRange) {
45
+ console.error('Error: --hour and --date/--today cannot be used together.');
46
+ process.exit(1);
47
+ }
48
+ const targetDate = hasDateRange
49
+ ? options.today
50
+ ? (0, dates_1.parseDateOption)()
51
+ : (0, dates_1.parseDateOption)(options.date)
52
+ : undefined;
53
+ let hour = null;
54
+ if (options.hour) {
55
+ hour = (0, dates_1.parseHourOption)(options.hour);
56
+ if (!hour) {
57
+ console.error('Error: hour format must be yyyy-mm-dd-hh');
58
+ process.exit(1);
59
+ }
60
+ }
61
+ const alias = options.photos
62
+ ? 'photos'
63
+ : options.uploaded
64
+ ? 'uploaded'
65
+ : undefined;
66
+ const { images, empty } = await (0, memory_1.listCaptures)({
67
+ page,
68
+ limit,
69
+ maxPages,
70
+ useCache,
71
+ date: targetDate,
72
+ hour: hour || undefined,
73
+ alias,
74
+ });
75
+ if (empty === 'date' && targetDate) {
76
+ console.log(`No images found for ${targetDate.dateKey}.`);
77
+ return;
78
+ }
79
+ if (empty === 'hour') {
80
+ console.log(`No images found for ${options.hour} in cache.`);
81
+ return;
82
+ }
83
+ if (options.json) {
84
+ console.log(JSON.stringify(images, null, 2));
85
+ return;
86
+ }
87
+ // A search result carries less than a listing does, so the alias paths
88
+ // ask for their results to be cached as they are shown.
89
+ const imagesForDisplay = await (0, images_1.prepareImagesForDisplay)(images, {
90
+ cacheSearchResults: Boolean(alias),
91
+ enrichLocation: true,
92
+ useCache,
93
+ });
94
+ (0, images_1.printListImages)(imagesForDisplay);
95
+ }
96
+ catch (error) {
97
+ console.error('Error listing images:', error.message);
98
+ process.exit(1);
99
+ }
100
+ });
101
+ }
@@ -0,0 +1,72 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.registerLocationsCommand = registerLocationsCommand;
4
+ const credentials_1 = require("../credentials");
5
+ const dates_1 = require("../dates");
6
+ const options_1 = require("../options");
7
+ const memory_1 = require("../services/memory");
8
+ const analytics_1 = require("../services/analytics");
9
+ function registerLocationsCommand(program) {
10
+ program
11
+ .command('locations')
12
+ .description('Rank metadata locations for a specific date')
13
+ .option('--date <yyyy|yyyy-mm|yyyy-mm-dd>', 'target date/range')
14
+ .option('--today', 'target today only (overrides default weekly range)')
15
+ .option('-l, --limit <number>', 'maximum ranking rows (max: 10)', '10')
16
+ .option('--max-pages <number>', 'max pages to scan before stopping', '10')
17
+ .option('-j, --json', 'output as JSON')
18
+ .option('--no-cache', 'force fetch from API')
19
+ .action(async (options) => {
20
+ await (0, credentials_1.ensureAccessToken)();
21
+ try {
22
+ const targetDate = (0, dates_1.resolveRankingRangeOption)(options);
23
+ const requestedLimit = (0, options_1.parsePositiveIntegerOption)(options.limit, '--limit');
24
+ const limit = Math.min(requestedLimit, 10);
25
+ const maxPages = (0, options_1.parsePositiveIntegerOption)(options.maxPages, '--max-pages');
26
+ const useCache = options.cache !== false;
27
+ let ranking = [];
28
+ let totalWithLocation = 0;
29
+ let totalImages = 0;
30
+ if (useCache) {
31
+ let cacheSummary = (0, analytics_1.buildLocationsRankingFromHourlyCache)(targetDate);
32
+ if (cacheSummary.totalImages === 0) {
33
+ await (0, memory_1.warmDateCacheForLocations)(targetDate, maxPages, true);
34
+ cacheSummary = (0, analytics_1.buildLocationsRankingFromHourlyCache)(targetDate);
35
+ }
36
+ ranking = cacheSummary.ranking;
37
+ totalWithLocation = cacheSummary.imageCountWithLocations;
38
+ totalImages = cacheSummary.totalImages;
39
+ }
40
+ else {
41
+ const imageIds = await (0, memory_1.warmDateCacheForLocations)(targetDate, maxPages, false);
42
+ ranking = (0, analytics_1.buildLocationsRankingFromCache)(imageIds);
43
+ totalWithLocation = ranking.reduce((sum, item) => sum + item.count, 0);
44
+ totalImages = imageIds.length;
45
+ }
46
+ const displayedRanking = ranking.slice(0, limit);
47
+ if (options.json) {
48
+ console.log(JSON.stringify({
49
+ date: targetDate.dateKey,
50
+ image_count: totalImages,
51
+ location_image_count: totalWithLocation,
52
+ total_locations: ranking.length,
53
+ ranking: displayedRanking,
54
+ }, null, 2));
55
+ return;
56
+ }
57
+ if (ranking.length === 0) {
58
+ console.log(`No location metadata found for ${targetDate.dateKey}.`);
59
+ return;
60
+ }
61
+ console.log(`Locations on ${targetDate.dateKey}`);
62
+ displayedRanking.forEach((item, index) => {
63
+ console.log(`${index + 1}. ${item.location}: ${item.count}`);
64
+ });
65
+ console.log(`Total images with location metadata: ${totalWithLocation}`);
66
+ }
67
+ catch (error) {
68
+ console.error('Error ranking locations:', error.message);
69
+ process.exit(1);
70
+ }
71
+ });
72
+ }
@@ -0,0 +1,43 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.registerSearchCommand = registerSearchCommand;
4
+ const api_1 = require("../api");
5
+ const credentials_1 = require("../credentials");
6
+ const format_1 = require("../format");
7
+ const memory_1 = require("../services/memory");
8
+ const images_1 = require("../services/images");
9
+ function registerSearchCommand(program) {
10
+ program
11
+ .command('search [query]')
12
+ .description('Search images')
13
+ .option('-j, --json', 'output as JSON')
14
+ .option('--no-cache', 'force fetch from API')
15
+ .action(async (query, options) => {
16
+ await (0, credentials_1.ensureAccessToken)();
17
+ try {
18
+ if (!(0, format_1.normalizeText)(query)) {
19
+ console.error('Error: Query is required.');
20
+ console.error('Hint: Run `gyazo search -h` for usage.');
21
+ process.exit(1);
22
+ }
23
+ const images = await (0, api_1.searchImages)(query);
24
+ const useCache = options.cache !== false;
25
+ if (options.json) {
26
+ (0, memory_1.cacheSearchResultImages)(images);
27
+ console.log(JSON.stringify(images, null, 2));
28
+ }
29
+ else {
30
+ const imagesForDisplay = await (0, images_1.prepareImagesForDisplay)(images, {
31
+ cacheSearchResults: true,
32
+ enrichLocation: true,
33
+ useCache,
34
+ });
35
+ (0, images_1.printListImages)(imagesForDisplay);
36
+ }
37
+ }
38
+ catch (error) {
39
+ console.error('Error searching images:', error.message);
40
+ process.exit(1);
41
+ }
42
+ });
43
+ }
@@ -0,0 +1,81 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.registerStatsCommand = registerStatsCommand;
4
+ const credentials_1 = require("../credentials");
5
+ const dates_1 = require("../dates");
6
+ const options_1 = require("../options");
7
+ const memory_1 = require("../services/memory");
8
+ const analytics_1 = require("../services/analytics");
9
+ function registerStatsCommand(program) {
10
+ program
11
+ .command('stats')
12
+ .description('Show weekly stats summary in Markdown')
13
+ .option('--date <yyyy|yyyy-mm|yyyy-mm-dd>', 'window end date anchor (default: yesterday)')
14
+ .option('--days <number>', 'window length in days', '7')
15
+ .option('--top <number>', 'rows per section', '10')
16
+ .option('--max-pages <number>', 'max pages to fetch when warming cache', '10')
17
+ .option('--no-cache', 'force fetch from API')
18
+ .action(async (options) => {
19
+ await (0, credentials_1.ensureAccessToken)();
20
+ try {
21
+ const { range, days, startLabel, endLabel } = (0, dates_1.buildStatsDateRange)(options.date, options.days || '7');
22
+ const top = Math.min((0, options_1.parsePositiveIntegerOption)(options.top, '--top'), 20);
23
+ const maxPages = (0, options_1.parsePositiveIntegerOption)(options.maxPages, '--max-pages');
24
+ const useCache = options.cache !== false;
25
+ let uploadTime;
26
+ let apps = [];
27
+ let domains = [];
28
+ let tags = [];
29
+ let totalUploads = 0;
30
+ if (useCache) {
31
+ uploadTime = (0, analytics_1.buildUploadTimeSummaryFromHourlyCache)(range);
32
+ if (uploadTime.totalImages === 0) {
33
+ await (0, memory_1.warmDateCacheForApps)(range, maxPages, true);
34
+ uploadTime = (0, analytics_1.buildUploadTimeSummaryFromHourlyCache)(range);
35
+ }
36
+ let appsSummary = (0, analytics_1.buildAppsRankingFromHourlyCache)(range);
37
+ if (uploadTime.totalImages > 0 && appsSummary.totalImages === 0) {
38
+ await (0, memory_1.warmDateCacheForApps)(range, maxPages, true);
39
+ appsSummary = (0, analytics_1.buildAppsRankingFromHourlyCache)(range);
40
+ }
41
+ let domainsSummary = (0, analytics_1.buildDomainsRankingFromHourlyCache)(range);
42
+ if (uploadTime.totalImages > 0 && domainsSummary.totalImages === 0) {
43
+ await (0, memory_1.warmDateCacheForDomains)(range, maxPages, true);
44
+ domainsSummary = (0, analytics_1.buildDomainsRankingFromHourlyCache)(range);
45
+ }
46
+ let tagsSummary = (0, analytics_1.buildTagsRankingFromHourlyCache)(range);
47
+ if (uploadTime.totalImages > 0 && tagsSummary.totalImages === 0) {
48
+ await (0, memory_1.warmDateCacheForTags)(range, maxPages, true);
49
+ tagsSummary = (0, analytics_1.buildTagsRankingFromHourlyCache)(range);
50
+ }
51
+ apps = appsSummary.ranking;
52
+ domains = domainsSummary.ranking;
53
+ tags = tagsSummary.ranking;
54
+ totalUploads = uploadTime.totalImages;
55
+ }
56
+ else {
57
+ const imageIds = await (0, memory_1.warmDateCacheForTags)(range, maxPages, false);
58
+ uploadTime = (0, analytics_1.buildUploadTimeSummaryFromImageCache)(imageIds, range);
59
+ apps = (0, analytics_1.buildAppsRankingFromCache)(imageIds);
60
+ domains = (0, analytics_1.buildDomainsRankingFromCache)(imageIds);
61
+ tags = (0, analytics_1.buildTagsRankingFromCache)(imageIds).ranking;
62
+ totalUploads = uploadTime.totalImages;
63
+ }
64
+ console.log((0, analytics_1.renderStatsMarkdown)({
65
+ startLabel,
66
+ endLabel,
67
+ days,
68
+ totalUploads,
69
+ uploadTime,
70
+ apps,
71
+ domains,
72
+ tags,
73
+ top,
74
+ }));
75
+ }
76
+ catch (error) {
77
+ console.error('Error building stats:', error.message);
78
+ process.exit(1);
79
+ }
80
+ });
81
+ }
@@ -0,0 +1,42 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.registerSummaryCommand = registerSummaryCommand;
4
+ const credentials_1 = require("../credentials");
5
+ const dates_1 = require("../dates");
6
+ const options_1 = require("../options");
7
+ const analytics_1 = require("../services/analytics");
8
+ function registerSummaryCommand(program) {
9
+ program
10
+ .command('summary')
11
+ .description('Show weekly summary with daily uploads and metadata rankings')
12
+ .option('--date <yyyy|yyyy-mm|yyyy-mm-dd>', 'target date/range')
13
+ .option('--today', 'target today only (overrides default weekly range)')
14
+ .option('-l, --limit <number>', 'maximum ranking rows (max: 10)', '10')
15
+ .option('--max-pages <number>', 'max pages to scan before stopping', '10')
16
+ .option('-j, --json', 'output as JSON')
17
+ .option('--no-cache', 'force fetch from API')
18
+ .action(async (options) => {
19
+ await (0, credentials_1.ensureAccessToken)();
20
+ try {
21
+ const targetDate = (0, dates_1.resolveRankingRangeOption)(options);
22
+ const requestedLimit = (0, options_1.parsePositiveIntegerOption)(options.limit, '--limit');
23
+ const limit = Math.min(requestedLimit, 10);
24
+ const maxPages = (0, options_1.parsePositiveIntegerOption)(options.maxPages, '--max-pages');
25
+ const useCache = options.cache !== false;
26
+ const dailySummaries = await (0, analytics_1.buildSummary)({ targetDate, maxPages, useCache });
27
+ if (options.json) {
28
+ console.log(JSON.stringify((0, analytics_1.toSummaryJson)(targetDate.dateKey, dailySummaries, limit), null, 2));
29
+ return;
30
+ }
31
+ console.log((0, analytics_1.renderSummaryText)({
32
+ dateKey: targetDate.dateKey,
33
+ dailySummaries,
34
+ limit,
35
+ }));
36
+ }
37
+ catch (error) {
38
+ console.error('Error building summary:', error.message);
39
+ process.exit(1);
40
+ }
41
+ });
42
+ }
@@ -0,0 +1,95 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.registerSyncCommand = registerSyncCommand;
4
+ const api_1 = require("../api");
5
+ const storage_1 = require("../storage");
6
+ const credentials_1 = require("../credentials");
7
+ const dates_1 = require("../dates");
8
+ const options_1 = require("../options");
9
+ function registerSyncCommand(program) {
10
+ program
11
+ .command('sync')
12
+ .description('Sync images from yesterday back to N days')
13
+ .option('--days <number>', 'number of days to sync (used when --date is omitted)')
14
+ .option('--date <yyyy|yyyy-mm|yyyy-mm-dd>', 'sync only this date/month/year range')
15
+ .option('--max-pages <number>', 'max pages to fetch', '10')
16
+ .action(async (options) => {
17
+ await (0, credentials_1.ensureAccessToken)();
18
+ if (options.date && options.days) {
19
+ console.error('Error: --date and --days cannot be used together.');
20
+ process.exit(1);
21
+ }
22
+ const maxPages = (0, options_1.parsePositiveIntegerOption)(options.maxPages, '--max-pages');
23
+ let startDate;
24
+ let endDate;
25
+ if (options.date) {
26
+ const parsed = (0, dates_1.parseDateOption)(options.date);
27
+ startDate = parsed.start;
28
+ endDate = parsed.end;
29
+ }
30
+ else {
31
+ const days = options.days ? (0, options_1.parsePositiveIntegerOption)(options.days, '--days') : 1;
32
+ const now = new Date();
33
+ endDate = new Date(now);
34
+ endDate.setDate(endDate.getDate() - 1);
35
+ endDate.setHours(23, 59, 59, 999);
36
+ startDate = new Date(now);
37
+ startDate.setDate(startDate.getDate() - days - 1);
38
+ startDate.setHours(0, 0, 0, 0);
39
+ }
40
+ console.log(`Syncing images between ${startDate.toISOString()} and ${endDate.toISOString()}...`);
41
+ const hourlyIndices = new Map();
42
+ for (let page = 1; page <= maxPages; page++) {
43
+ const images = await (0, api_1.listImages)(page, 100);
44
+ if (images.length === 0)
45
+ break;
46
+ let reachedLimit = false;
47
+ for (const img of images) {
48
+ const createdAt = new Date(img.created_at);
49
+ if (createdAt > endDate) {
50
+ // Skip images newer than target range.
51
+ continue;
52
+ }
53
+ if (createdAt < startDate) {
54
+ reachedLimit = true;
55
+ break;
56
+ }
57
+ // Add to hourly index
58
+ const y = createdAt.getFullYear().toString();
59
+ const m = (createdAt.getMonth() + 1).toString().padStart(2, '0');
60
+ const d = createdAt.getDate().toString().padStart(2, '0');
61
+ const h = createdAt.getHours().toString().padStart(2, '0');
62
+ const key = `${y}-${m}-${d}-${h}`;
63
+ if (!hourlyIndices.has(key))
64
+ hourlyIndices.set(key, new Set());
65
+ hourlyIndices.get(key)?.add(img.image_id);
66
+ const cached = (0, storage_1.loadImageCache)(img.image_id);
67
+ if (cached && cached.ocr) {
68
+ process.stdout.write(`s`);
69
+ continue;
70
+ }
71
+ process.stdout.write(`.`);
72
+ try {
73
+ const detail = await (0, api_1.getImageDetail)(img.image_id);
74
+ (0, storage_1.saveImageCache)(img.image_id, detail);
75
+ await new Promise(resolve => setTimeout(resolve, 200));
76
+ }
77
+ catch (e) {
78
+ process.stdout.write(`x`);
79
+ }
80
+ }
81
+ console.log(`\nPage ${page} processed.`);
82
+ if (reachedLimit)
83
+ break;
84
+ }
85
+ // Save hourly indices
86
+ console.log(`Updating hourly indices...`);
87
+ for (const [key, ids] of hourlyIndices.entries()) {
88
+ const [y, m, d, h] = key.split('-');
89
+ const existing = (0, storage_1.loadHourlyCache)(y, m, d, h) || [];
90
+ const merged = Array.from(new Set([...existing, ...ids]));
91
+ (0, storage_1.saveHourlyCache)(y, m, d, h, merged);
92
+ }
93
+ console.log(`Sync complete.`);
94
+ });
95
+ }
@@ -0,0 +1,70 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.registerTagsCommand = registerTagsCommand;
4
+ const credentials_1 = require("../credentials");
5
+ const dates_1 = require("../dates");
6
+ const options_1 = require("../options");
7
+ const memory_1 = require("../services/memory");
8
+ const analytics_1 = require("../services/analytics");
9
+ function registerTagsCommand(program) {
10
+ program
11
+ .command('tags')
12
+ .description('Rank metadata tags for a specific date')
13
+ .option('--date <yyyy|yyyy-mm|yyyy-mm-dd>', 'target date/range')
14
+ .option('--today', 'target today only (overrides default weekly range)')
15
+ .option('-l, --limit <number>', 'maximum ranking rows (max: 10)', '10')
16
+ .option('--max-pages <number>', 'max pages to scan before stopping', '10')
17
+ .option('-j, --json', 'output as JSON')
18
+ .option('--no-cache', 'force fetch from API')
19
+ .action(async (options) => {
20
+ await (0, credentials_1.ensureAccessToken)();
21
+ try {
22
+ const targetDate = (0, dates_1.resolveRankingRangeOption)(options);
23
+ const requestedLimit = (0, options_1.parsePositiveIntegerOption)(options.limit, '--limit');
24
+ const limit = Math.min(requestedLimit, 10);
25
+ const maxPages = (0, options_1.parsePositiveIntegerOption)(options.maxPages, '--max-pages');
26
+ const useCache = options.cache !== false;
27
+ let summary;
28
+ let totalImages = 0;
29
+ if (useCache) {
30
+ let cacheSummary = (0, analytics_1.buildTagsRankingFromHourlyCache)(targetDate);
31
+ if (cacheSummary.totalImages === 0) {
32
+ await (0, memory_1.warmDateCacheForTags)(targetDate, maxPages, true);
33
+ cacheSummary = (0, analytics_1.buildTagsRankingFromHourlyCache)(targetDate);
34
+ }
35
+ summary = cacheSummary;
36
+ totalImages = cacheSummary.totalImages;
37
+ }
38
+ else {
39
+ const imageIds = await (0, memory_1.warmDateCacheForTags)(targetDate, maxPages, false);
40
+ summary = (0, analytics_1.buildTagsRankingFromCache)(imageIds);
41
+ totalImages = imageIds.length;
42
+ }
43
+ const displayedRanking = summary.ranking.slice(0, limit);
44
+ if (options.json) {
45
+ console.log(JSON.stringify({
46
+ date: targetDate.dateKey,
47
+ image_count: totalImages,
48
+ image_count_with_tags: summary.imageCountWithTags,
49
+ total_tag_assignments: summary.totalTagAssignments,
50
+ total_tags: summary.ranking.length,
51
+ ranking: displayedRanking,
52
+ }, null, 2));
53
+ return;
54
+ }
55
+ if (summary.ranking.length === 0) {
56
+ console.log(`No tag metadata found for ${targetDate.dateKey}.`);
57
+ return;
58
+ }
59
+ console.log(`Tags on ${targetDate.dateKey}`);
60
+ displayedRanking.forEach((item, index) => {
61
+ console.log(`${index + 1}. #${item.tag}: ${item.count}`);
62
+ });
63
+ console.log(`Total images with tag metadata: ${summary.imageCountWithTags}`);
64
+ }
65
+ catch (error) {
66
+ console.error('Error ranking tags:', error.message);
67
+ process.exit(1);
68
+ }
69
+ });
70
+ }
@@ -0,0 +1,72 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.registerUploadCommand = registerUploadCommand;
7
+ const fs_1 = __importDefault(require("fs"));
8
+ const path_1 = __importDefault(require("path"));
9
+ const api_1 = require("../api");
10
+ const credentials_1 = require("../credentials");
11
+ const dates_1 = require("../dates");
12
+ const images_1 = require("../services/images");
13
+ function registerUploadCommand(program) {
14
+ program
15
+ .command('upload [path]')
16
+ .description('Upload an image file (or read image bytes from stdin)')
17
+ .option('-j, --json', 'output the upload response as JSON')
18
+ .option('--title <title>', 'image title')
19
+ .option('--app <app>', 'application name', 'gyazocli')
20
+ .option('--url <url>', 'source URL (sent as referer_url)')
21
+ .option('--timestamp <unix_timestamp>', 'created_at unix timestamp (current or past)')
22
+ .option('--desc <desc>', 'image description')
23
+ .action(async (inputPath, options) => {
24
+ await (0, credentials_1.ensureAccessToken)();
25
+ let imageData;
26
+ let filename = 'stdin-upload.bin';
27
+ if (inputPath && inputPath !== '-') {
28
+ const resolvedPath = path_1.default.resolve(inputPath);
29
+ if (!fs_1.default.existsSync(resolvedPath)) {
30
+ console.error(`Error: File not found: ${resolvedPath}`);
31
+ process.exit(1);
32
+ }
33
+ imageData = fs_1.default.readFileSync(resolvedPath);
34
+ filename = path_1.default.basename(resolvedPath);
35
+ }
36
+ else {
37
+ if (process.stdin.isTTY) {
38
+ console.error('Error: Provide an image path or pipe image data via stdin.');
39
+ console.error('Hint: Run `gyazo upload -h` for usage.');
40
+ process.exit(1);
41
+ }
42
+ imageData = await (0, images_1.readStdinBuffer)();
43
+ if (imageData.length === 0) {
44
+ console.error('Error: No image data received from stdin.');
45
+ process.exit(1);
46
+ }
47
+ }
48
+ const desc = (0, images_1.ensureUploadDescTag)(options.desc);
49
+ const timestamp = (0, dates_1.parseUploadTimestamp)(options.timestamp);
50
+ try {
51
+ const uploaded = await (0, api_1.uploadImage)({
52
+ imageData,
53
+ filename,
54
+ title: options.title,
55
+ app: options.app || 'gyazocli',
56
+ refererUrl: options.url,
57
+ desc,
58
+ timestamp,
59
+ });
60
+ if (options.json) {
61
+ console.log(JSON.stringify(uploaded, null, 2));
62
+ }
63
+ else {
64
+ console.log(uploaded.permalink_url);
65
+ }
66
+ }
67
+ catch (error) {
68
+ console.error('Error uploading image:', error.message);
69
+ process.exit(1);
70
+ }
71
+ });
72
+ }
package/dist/config.js CHANGED
@@ -13,12 +13,18 @@ const configSchema = zod_1.z.object({
13
13
  GYAZO_CLIENT_ID: zod_1.z.string().optional(),
14
14
  GYAZO_CLIENT_SECRET: zod_1.z.string().optional(),
15
15
  GYAZO_CACHE_DIR: zod_1.z.string().optional(),
16
+ GYAZO_API_ORIGIN: zod_1.z.string().optional(),
17
+ GYAZO_UPLOAD_ORIGIN: zod_1.z.string().optional(),
18
+ GYAZO_WEB_ORIGIN: zod_1.z.string().optional(),
16
19
  });
17
20
  exports.config = configSchema.parse({
18
21
  GYAZO_ACCESS_TOKEN: process.env.GYAZO_ACCESS_TOKEN,
19
22
  GYAZO_CLIENT_ID: process.env.GYAZO_CLIENT_ID,
20
23
  GYAZO_CLIENT_SECRET: process.env.GYAZO_CLIENT_SECRET,
21
24
  GYAZO_CACHE_DIR: process.env.GYAZO_CACHE_DIR,
25
+ GYAZO_API_ORIGIN: process.env.GYAZO_API_ORIGIN,
26
+ GYAZO_UPLOAD_ORIGIN: process.env.GYAZO_UPLOAD_ORIGIN,
27
+ GYAZO_WEB_ORIGIN: process.env.GYAZO_WEB_ORIGIN,
22
28
  });
23
29
  function setAccessToken(token) {
24
30
  exports.config.GYAZO_ACCESS_TOKEN = token;
@@ -5,6 +5,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.getStoredConfig = getStoredConfig;
7
7
  exports.setStoredConfig = setStoredConfig;
8
+ exports.resolveAccessToken = resolveAccessToken;
8
9
  exports.ensureAccessToken = ensureAccessToken;
9
10
  const fs_1 = __importDefault(require("fs"));
10
11
  const path_1 = __importDefault(require("path"));
@@ -44,7 +45,11 @@ function setStoredConfig(key, value) {
44
45
  fs_1.default.writeFileSync(CREDENTIALS_FILE, JSON.stringify(stored, null, 2));
45
46
  console.error(`Config set: ${key}=${value}`);
46
47
  }
47
- async function ensureAccessToken() {
48
+ /**
49
+ * Load the access token if there is one, without failing when there is not.
50
+ * Read-only commands fall back to anonymous access instead of exiting.
51
+ */
52
+ function resolveAccessToken() {
48
53
  if (config_1.config.GYAZO_ACCESS_TOKEN) {
49
54
  return config_1.config.GYAZO_ACCESS_TOKEN;
50
55
  }
@@ -53,6 +58,13 @@ async function ensureAccessToken() {
53
58
  (0, config_1.setAccessToken)(storedToken);
54
59
  return storedToken;
55
60
  }
61
+ return undefined;
62
+ }
63
+ async function ensureAccessToken() {
64
+ const token = resolveAccessToken();
65
+ if (token) {
66
+ return token;
67
+ }
56
68
  console.error('Error: Gyazo Access Token is not set.');
57
69
  console.error('Please run the following command to set your access token:');
58
70
  console.error(' gyazo config set token <your_access_token>');