@yuiseki/gyazocli 0.0.1 → 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/README.md CHANGED
@@ -16,6 +16,26 @@ gyazo sync --days 10
16
16
  gyazo --help
17
17
  ```
18
18
 
19
+ ### Detail
20
+
21
+ - `gyazo config set token <token>`: Save your access token
22
+ - `gyazo config get token|me`: Show saved token (masked) or `me` profile info
23
+ - `gyazo ls` (`gyazo list`): List images (`--date`/`--today`, `--photos`, `--uploaded`, `-H` available; `--photos/--uploaded` can be combined with `--date`/`--today`)
24
+ - `gyazo search <query>`: Search images
25
+ - `gyazo get <image_id>`: Show image details (`--ocr`, `--objects`, `-j` available)
26
+ - `gyazo apps|domains|tags|locations`: Show rankings
27
+ - `gyazo summary`: Show day-by-day weekly summary in Markdown (`##`/`###` headings, image count, apps, domains, tags, locations per day)
28
+ - `gyazo stats`: Show weekly summary
29
+ - `gyazo upload [path]`: Upload an image (uses stdin when path is omitted)
30
+ - `gyazo sync`: Sync cache
31
+
32
+ Date range notes:
33
+ - Default range for `apps|domains|tags|locations|stats` is from 8 days ago to yesterday
34
+ - Use `--today` for today only, or `--date <yyyy|yyyy-mm|yyyy-mm-dd>` for a custom range
35
+
36
+ JSON output:
37
+ - `-j, --json` is available for `config get`, `ls`, `get`, `search`, `apps`, `domains`, `tags`, `locations`, and `summary`
38
+
19
39
  ## Development
20
40
 
21
41
  ### Build
package/dist/index.js CHANGED
@@ -16,7 +16,7 @@ const WEEKDAY_LABELS = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
16
16
  program
17
17
  .name('gyazo')
18
18
  .description('Gyazo Memory CLI for AI Secretary')
19
- .version('0.0.1');
19
+ .version('0.0.2');
20
20
  // Config Command
21
21
  const configCmd = program.command('config').description('Manage configuration');
22
22
  configCmd
@@ -540,6 +540,19 @@ function getDatePartsInRange(start, end) {
540
540
  }
541
541
  return dates;
542
542
  }
543
+ function loadImageIdsFromDateRangeCache(targetDate) {
544
+ const imageIds = new Set();
545
+ const dates = getDatePartsInRange(targetDate.start, targetDate.end);
546
+ const hours = getDateHourStrings();
547
+ for (const date of dates) {
548
+ for (const hour of hours) {
549
+ const ids = (0, storage_1.loadHourlyCache)(date.year, date.month, date.day, hour) || [];
550
+ for (const id of ids)
551
+ imageIds.add(id);
552
+ }
553
+ }
554
+ return Array.from(imageIds);
555
+ }
543
556
  function normalizeRankingValues(values) {
544
557
  const uniqueByLower = new Map();
545
558
  for (const raw of values) {
@@ -630,6 +643,59 @@ async function warmDateCacheForTags(targetDate, maxPages, useCache) {
630
643
  async function warmDateCacheForLocations(targetDate, maxPages, useCache) {
631
644
  return warmDateCacheForRanking(targetDate, maxPages, useCache, 'locations', extractImageLocations);
632
645
  }
646
+ async function warmDateCacheForList(targetDate, maxPages, useCache) {
647
+ const hourlyIndices = new Map();
648
+ const imageIds = new Set();
649
+ for (let page = 1; page <= maxPages; page++) {
650
+ const images = await (0, api_1.listImages)(page, 100);
651
+ if (images.length === 0)
652
+ break;
653
+ let reachedLimit = false;
654
+ for (const img of images) {
655
+ const createdAt = new Date(img.created_at);
656
+ if (Number.isNaN(createdAt.getTime()))
657
+ continue;
658
+ if (createdAt > targetDate.end)
659
+ continue;
660
+ if (createdAt < targetDate.start) {
661
+ reachedLimit = true;
662
+ break;
663
+ }
664
+ const dateParts = toDateParts(createdAt);
665
+ const bucketKey = buildHourlyBucketKey(dateParts.year, dateParts.month, dateParts.day, dateParts.hour);
666
+ if (!hourlyIndices.has(bucketKey)) {
667
+ hourlyIndices.set(bucketKey, new Set());
668
+ }
669
+ hourlyIndices.get(bucketKey)?.add(img.image_id);
670
+ imageIds.add(img.image_id);
671
+ let merged = img;
672
+ const cached = useCache ? (0, storage_1.loadImageCache)(img.image_id) : null;
673
+ if (cached) {
674
+ merged = mergeImageForDisplay(img, cached);
675
+ }
676
+ (0, storage_1.saveImageCache)(img.image_id, merged);
677
+ }
678
+ if (reachedLimit)
679
+ break;
680
+ }
681
+ for (const [bucketKey, current] of hourlyIndices.entries()) {
682
+ const { year, month, day, hour } = splitHourlyBucketKey(bucketKey);
683
+ if (useCache) {
684
+ const existing = (0, storage_1.loadHourlyCache)(year, month, day, hour) || [];
685
+ for (const id of existing)
686
+ current.add(id);
687
+ }
688
+ (0, storage_1.saveHourlyCache)(year, month, day, hour, Array.from(current));
689
+ for (const id of current)
690
+ imageIds.add(id);
691
+ }
692
+ if (useCache) {
693
+ for (const id of loadImageIdsFromDateRangeCache(targetDate)) {
694
+ imageIds.add(id);
695
+ }
696
+ }
697
+ return Array.from(imageIds);
698
+ }
633
699
  async function warmDateCacheForRanking(targetDate, maxPages, useCache, metadataKind, extractValues) {
634
700
  const hourlyIndices = new Map();
635
701
  const hourlyMetadataEntries = new Map();
@@ -975,6 +1041,122 @@ function buildUploadTimeSummaryFromImageCache(imageIds, targetDate) {
975
1041
  }),
976
1042
  };
977
1043
  }
1044
+ function buildDailyUploadCountsFromHourlyCache(targetDate) {
1045
+ const dates = getDatePartsInRange(targetDate.start, targetDate.end);
1046
+ const hours = getDateHourStrings();
1047
+ const byDate = new Map();
1048
+ for (const date of dates) {
1049
+ const dateLabel = `${date.year}-${date.month}-${date.day}`;
1050
+ if (!byDate.has(dateLabel)) {
1051
+ byDate.set(dateLabel, new Set());
1052
+ }
1053
+ const ids = byDate.get(dateLabel);
1054
+ for (const hour of hours) {
1055
+ const imageIds = (0, storage_1.loadHourlyCache)(date.year, date.month, date.day, hour) || [];
1056
+ for (const imageId of imageIds)
1057
+ ids.add(imageId);
1058
+ }
1059
+ }
1060
+ return dates.map(date => {
1061
+ const dateLabel = `${date.year}-${date.month}-${date.day}`;
1062
+ return {
1063
+ date: dateLabel,
1064
+ count: byDate.get(dateLabel)?.size || 0,
1065
+ };
1066
+ });
1067
+ }
1068
+ function buildDailyUploadCountsFromImageCache(imageIds, targetDate) {
1069
+ const dates = getDatePartsInRange(targetDate.start, targetDate.end);
1070
+ const byDate = new Map();
1071
+ for (const date of dates) {
1072
+ const dateLabel = `${date.year}-${date.month}-${date.day}`;
1073
+ byDate.set(dateLabel, new Set());
1074
+ }
1075
+ for (const imageId of imageIds) {
1076
+ const image = (0, storage_1.loadImageCache)(imageId);
1077
+ const createdAtText = normalizeText(image?.created_at);
1078
+ if (!createdAtText)
1079
+ continue;
1080
+ const createdAt = new Date(createdAtText);
1081
+ if (Number.isNaN(createdAt.getTime()))
1082
+ continue;
1083
+ if (createdAt < targetDate.start || createdAt > targetDate.end)
1084
+ continue;
1085
+ const dateLabel = formatDateYmd(createdAt);
1086
+ const ids = byDate.get(dateLabel);
1087
+ if (!ids)
1088
+ continue;
1089
+ ids.add(imageId);
1090
+ }
1091
+ return dates.map(date => {
1092
+ const dateLabel = `${date.year}-${date.month}-${date.day}`;
1093
+ return {
1094
+ date: dateLabel,
1095
+ count: byDate.get(dateLabel)?.size || 0,
1096
+ };
1097
+ });
1098
+ }
1099
+ function buildDailySummariesFromImageCache(targetDate) {
1100
+ const dates = getDatePartsInRange(targetDate.start, targetDate.end);
1101
+ const hours = getDateHourStrings();
1102
+ const summaries = [];
1103
+ for (const date of dates) {
1104
+ const dateLabel = `${date.year}-${date.month}-${date.day}`;
1105
+ const imageIds = new Set();
1106
+ for (const hour of hours) {
1107
+ const ids = (0, storage_1.loadHourlyCache)(date.year, date.month, date.day, hour) || [];
1108
+ for (const id of ids)
1109
+ imageIds.add(id);
1110
+ }
1111
+ const appCounts = new Map();
1112
+ const domainCounts = new Map();
1113
+ const tagCounts = new Map();
1114
+ const locationCounts = new Map();
1115
+ for (const imageId of imageIds) {
1116
+ const image = (0, storage_1.loadImageCache)(imageId);
1117
+ if (!image)
1118
+ continue;
1119
+ for (const app of extractImageApps(image)) {
1120
+ appCounts.set(app, (appCounts.get(app) || 0) + 1);
1121
+ }
1122
+ for (const domain of extractImageDomains(image)) {
1123
+ domainCounts.set(domain, (domainCounts.get(domain) || 0) + 1);
1124
+ }
1125
+ for (const tag of extractImageTags(image)) {
1126
+ tagCounts.set(tag, (tagCounts.get(tag) || 0) + 1);
1127
+ }
1128
+ for (const location of extractImageLocations(image)) {
1129
+ locationCounts.set(location, (locationCounts.get(location) || 0) + 1);
1130
+ }
1131
+ }
1132
+ const sortEntries = (a, b) => {
1133
+ if (b[1] !== a[1])
1134
+ return b[1] - a[1];
1135
+ return a[0].localeCompare(b[0]);
1136
+ };
1137
+ const apps = Array.from(appCounts.entries())
1138
+ .sort(sortEntries)
1139
+ .map(([app, count]) => ({ app, count }));
1140
+ const domains = Array.from(domainCounts.entries())
1141
+ .sort(sortEntries)
1142
+ .map(([domain, count]) => ({ domain, count }));
1143
+ const tags = Array.from(tagCounts.entries())
1144
+ .sort(sortEntries)
1145
+ .map(([tag, count]) => ({ tag, count }));
1146
+ const locations = Array.from(locationCounts.entries())
1147
+ .sort(sortEntries)
1148
+ .map(([location, count]) => ({ location, count }));
1149
+ summaries.push({
1150
+ date: dateLabel,
1151
+ imageCount: imageIds.size,
1152
+ apps,
1153
+ domains,
1154
+ tags,
1155
+ locations,
1156
+ });
1157
+ }
1158
+ return summaries;
1159
+ }
978
1160
  function appendStatsRankSection(lines, title, rows, top) {
979
1161
  lines.push(`### ${title}`);
980
1162
  const filtered = rows.filter(row => row.count > 0).slice(0, top);
@@ -1008,6 +1190,34 @@ function renderStatsMarkdown(params) {
1008
1190
  appendStatsRankSection(lines, 'Tags', params.tags.map(item => ({ label: `#${item.tag}`, count: item.count })), params.top);
1009
1191
  return lines.join('\n').trimEnd();
1010
1192
  }
1193
+ function renderSummaryText(params) {
1194
+ const appendRankSection = (lines, title, rows, limit) => {
1195
+ lines.push(`- ${title}:`);
1196
+ const items = rows.filter(row => row.count > 0).slice(0, limit);
1197
+ if (items.length === 0) {
1198
+ lines.push(' - (none)');
1199
+ return;
1200
+ }
1201
+ for (const row of items) {
1202
+ lines.push(` - ${row.label}${row.count > 1 ? ` (${row.count})` : ''}`);
1203
+ }
1204
+ };
1205
+ const lines = [];
1206
+ lines.push('## Gyazo Summary');
1207
+ lines.push('');
1208
+ lines.push(`- Window: ${params.dateKey}`);
1209
+ lines.push('');
1210
+ for (const day of params.dailySummaries) {
1211
+ lines.push(`### ${day.date}`);
1212
+ lines.push(`- Image count: ${day.imageCount}`);
1213
+ appendRankSection(lines, 'Apps', day.apps.map(item => ({ label: item.app, count: item.count })), params.limit);
1214
+ appendRankSection(lines, 'Domains', day.domains.map(item => ({ label: item.domain, count: item.count })), params.limit);
1215
+ appendRankSection(lines, 'Tags', day.tags.map(item => ({ label: `#${item.tag}`, count: item.count })), params.limit);
1216
+ appendRankSection(lines, 'Locations', day.locations.map(item => ({ label: item.location, count: item.count })), params.limit);
1217
+ lines.push('');
1218
+ }
1219
+ return lines.join('\n').trimEnd();
1220
+ }
1011
1221
  async function readStdinBuffer() {
1012
1222
  return new Promise((resolve, reject) => {
1013
1223
  const chunks = [];
@@ -1197,6 +1407,9 @@ program
1197
1407
  .option('-l, --limit <number>', 'items per page', '20')
1198
1408
  .option('-j, --json', 'output as JSON')
1199
1409
  .option('-H, --hour <yyyy-mm-dd-hh>', 'target hour')
1410
+ .option('--date <yyyy|yyyy-mm|yyyy-mm-dd>', 'target date/range')
1411
+ .option('--today', 'target today only')
1412
+ .option('--max-pages <number>', 'max pages to scan for --date/--today mode', '100')
1200
1413
  .option('--photos', 'alias of search "has:location"')
1201
1414
  .option('--uploaded', 'alias of search "gyazocli_uploads"')
1202
1415
  .option('--no-cache', 'force fetch from API')
@@ -1204,21 +1417,69 @@ program
1204
1417
  await (0, credentials_1.ensureAccessToken)();
1205
1418
  try {
1206
1419
  const useCache = options.cache !== false;
1420
+ const page = parsePositiveIntegerOption(options.page, '--page');
1421
+ const limit = parsePositiveIntegerOption(options.limit, '--limit');
1422
+ const maxPages = parsePositiveIntegerOption(options.maxPages, '--max-pages');
1423
+ const hasDateRange = Boolean(options.date || options.today);
1424
+ const targetDate = hasDateRange
1425
+ ? (options.today ? parseDateOption() : parseDateOption(options.date))
1426
+ : undefined;
1207
1427
  if (options.photos && options.uploaded) {
1208
1428
  console.error('Error: --photos and --uploaded cannot be used together.');
1209
1429
  process.exit(1);
1210
1430
  }
1431
+ if (options.today && options.date) {
1432
+ console.error('Error: --today and --date cannot be used together.');
1433
+ process.exit(1);
1434
+ }
1211
1435
  if ((options.photos || options.uploaded) && options.hour) {
1212
1436
  console.error('Error: --photos/--uploaded and --hour cannot be used together.');
1213
1437
  process.exit(1);
1214
1438
  }
1439
+ if (options.hour && hasDateRange) {
1440
+ console.error('Error: --hour and --date/--today cannot be used together.');
1441
+ process.exit(1);
1442
+ }
1215
1443
  const aliasQuery = options.photos
1216
1444
  ? 'has:location'
1217
1445
  : options.uploaded
1218
1446
  ? 'gyazocli_uploads'
1219
1447
  : undefined;
1220
1448
  if (aliasQuery) {
1221
- const images = await (0, api_1.searchImages)(aliasQuery, parseInt(options.page, 10), parseInt(options.limit, 10));
1449
+ let images = [];
1450
+ if (targetDate) {
1451
+ const collected = [];
1452
+ for (let searchPage = 1; searchPage <= maxPages; searchPage++) {
1453
+ const pageImages = await (0, api_1.searchImages)(aliasQuery, searchPage, 100);
1454
+ if (pageImages.length === 0)
1455
+ break;
1456
+ let reachedLimit = false;
1457
+ for (const img of pageImages) {
1458
+ const createdAt = new Date(img.created_at);
1459
+ if (Number.isNaN(createdAt.getTime()))
1460
+ continue;
1461
+ if (createdAt > targetDate.end)
1462
+ continue;
1463
+ if (createdAt < targetDate.start) {
1464
+ reachedLimit = true;
1465
+ break;
1466
+ }
1467
+ collected.push(img);
1468
+ }
1469
+ if (reachedLimit)
1470
+ break;
1471
+ }
1472
+ collected.sort((a, b) => {
1473
+ const ta = new Date(a.created_at).getTime();
1474
+ const tb = new Date(b.created_at).getTime();
1475
+ return tb - ta;
1476
+ });
1477
+ const startIndex = (page - 1) * limit;
1478
+ images = collected.slice(startIndex, startIndex + limit);
1479
+ }
1480
+ else {
1481
+ images = await (0, api_1.searchImages)(aliasQuery, page, limit);
1482
+ }
1222
1483
  if (options.json) {
1223
1484
  console.log(JSON.stringify(images, null, 2));
1224
1485
  }
@@ -1232,6 +1493,50 @@ program
1232
1493
  }
1233
1494
  return;
1234
1495
  }
1496
+ if (targetDate) {
1497
+ let imageIds = [];
1498
+ if (useCache) {
1499
+ imageIds = loadImageIdsFromDateRangeCache(targetDate);
1500
+ if (imageIds.length === 0) {
1501
+ await warmDateCacheForList(targetDate, maxPages, true);
1502
+ imageIds = loadImageIdsFromDateRangeCache(targetDate);
1503
+ }
1504
+ }
1505
+ else {
1506
+ imageIds = await warmDateCacheForList(targetDate, maxPages, false);
1507
+ }
1508
+ if (imageIds.length === 0) {
1509
+ console.log(`No images found for ${targetDate.dateKey}.`);
1510
+ return;
1511
+ }
1512
+ let images = imageIds
1513
+ .map(id => (0, storage_1.loadImageCache)(id))
1514
+ .filter((img) => img !== null);
1515
+ images = images.filter((img) => {
1516
+ const createdAt = new Date(img.created_at);
1517
+ if (Number.isNaN(createdAt.getTime()))
1518
+ return false;
1519
+ return createdAt >= targetDate.start && createdAt <= targetDate.end;
1520
+ });
1521
+ images.sort((a, b) => {
1522
+ const ta = new Date(a.created_at).getTime();
1523
+ const tb = new Date(b.created_at).getTime();
1524
+ return tb - ta;
1525
+ });
1526
+ const startIndex = (page - 1) * limit;
1527
+ const pageImages = images.slice(startIndex, startIndex + limit);
1528
+ if (options.json) {
1529
+ console.log(JSON.stringify(pageImages, null, 2));
1530
+ }
1531
+ else {
1532
+ const imagesForDisplay = await prepareImagesForDisplay(pageImages, {
1533
+ enrichLocation: true,
1534
+ useCache,
1535
+ });
1536
+ printListImages(imagesForDisplay);
1537
+ }
1538
+ return;
1539
+ }
1235
1540
  if (options.hour) {
1236
1541
  const parts = options.hour.split('-');
1237
1542
  if (parts.length !== 4) {
@@ -1272,7 +1577,7 @@ program
1272
1577
  }
1273
1578
  return;
1274
1579
  }
1275
- const images = await (0, api_1.listImages)(parseInt(options.page, 10), parseInt(options.limit, 10));
1580
+ const images = await (0, api_1.listImages)(page, limit);
1276
1581
  if (options.json) {
1277
1582
  console.log(JSON.stringify(images, null, 2));
1278
1583
  }
@@ -1623,6 +1928,71 @@ program
1623
1928
  process.exit(1);
1624
1929
  }
1625
1930
  });
1931
+ program
1932
+ .command('summary')
1933
+ .description('Show weekly summary with daily uploads and metadata rankings')
1934
+ .option('--date <yyyy|yyyy-mm|yyyy-mm-dd>', 'target date/range')
1935
+ .option('--today', 'target today only (overrides default weekly range)')
1936
+ .option('-l, --limit <number>', 'maximum ranking rows (max: 10)', '10')
1937
+ .option('--max-pages <number>', 'max pages to scan before stopping', '10')
1938
+ .option('-j, --json', 'output as JSON')
1939
+ .option('--no-cache', 'force fetch from API')
1940
+ .action(async (options) => {
1941
+ await (0, credentials_1.ensureAccessToken)();
1942
+ try {
1943
+ const targetDate = resolveRankingRangeOption(options);
1944
+ const requestedLimit = parsePositiveIntegerOption(options.limit, '--limit');
1945
+ const limit = Math.min(requestedLimit, 10);
1946
+ const maxPages = parsePositiveIntegerOption(options.maxPages, '--max-pages');
1947
+ const useCache = options.cache !== false;
1948
+ let dailySummaries = [];
1949
+ if (useCache) {
1950
+ dailySummaries = buildDailySummariesFromImageCache(targetDate);
1951
+ const totalUploads = dailySummaries.reduce((sum, day) => sum + day.imageCount, 0);
1952
+ if (totalUploads === 0) {
1953
+ await warmDateCacheForTags(targetDate, maxPages, true);
1954
+ await warmDateCacheForLocations(targetDate, maxPages, true);
1955
+ dailySummaries = buildDailySummariesFromImageCache(targetDate);
1956
+ }
1957
+ else {
1958
+ const hasMetadata = dailySummaries.some(day => day.apps.length > 0 || day.domains.length > 0 || day.tags.length > 0 || day.locations.length > 0);
1959
+ if (!hasMetadata) {
1960
+ await warmDateCacheForTags(targetDate, maxPages, true);
1961
+ await warmDateCacheForLocations(targetDate, maxPages, true);
1962
+ dailySummaries = buildDailySummariesFromImageCache(targetDate);
1963
+ }
1964
+ }
1965
+ }
1966
+ else {
1967
+ await warmDateCacheForTags(targetDate, maxPages, false);
1968
+ await warmDateCacheForLocations(targetDate, maxPages, false);
1969
+ dailySummaries = buildDailySummariesFromImageCache(targetDate);
1970
+ }
1971
+ if (options.json) {
1972
+ console.log(JSON.stringify({
1973
+ date: targetDate.dateKey,
1974
+ days: dailySummaries.map(day => ({
1975
+ date: day.date,
1976
+ image_count: day.imageCount,
1977
+ apps: day.apps.slice(0, limit),
1978
+ domains: day.domains.slice(0, limit),
1979
+ tags: day.tags.slice(0, limit),
1980
+ locations: day.locations.slice(0, limit),
1981
+ })),
1982
+ }, null, 2));
1983
+ return;
1984
+ }
1985
+ console.log(renderSummaryText({
1986
+ dateKey: targetDate.dateKey,
1987
+ dailySummaries,
1988
+ limit,
1989
+ }));
1990
+ }
1991
+ catch (error) {
1992
+ console.error('Error building summary:', error.message);
1993
+ process.exit(1);
1994
+ }
1995
+ });
1626
1996
  program
1627
1997
  .command('stats')
1628
1998
  .description('Show weekly stats summary in Markdown')
@@ -47,6 +47,9 @@ Store cache files under XDG-style user cache location by default, with an enviro
47
47
  - `gyazo get <image_id>`:
48
48
  - Uses cached detail by default.
49
49
  - `--no-cache` forces API fetch and rewrites cache.
50
+ - `gyazo list --date <...>`:
51
+ - Reads image IDs from hourly index cache files in the target day/month/year range.
52
+ - If the range has no hourly cache entries, it warms cache from API list pages and then reads from cache.
50
53
  - `gyazo sync`:
51
54
  - Fetches list pages (`per_page=100`) for a bounded date range.
52
55
  - Skips detail fetch when cached record already has `ocr`.
@@ -69,6 +72,11 @@ Store cache files under XDG-style user cache location by default, with an enviro
69
72
  - Aggregates upload time bands and rankings of apps/domains/tags from hourly caches.
70
73
  - Uses the same hourly metadata extract cache mechanism as ranking commands.
71
74
  - If cache data for the target window is absent, warming is triggered once, then summary is built from cache.
75
+ - `gyazo summary`:
76
+ - Default window is from 8 days ago to yesterday (`7` days).
77
+ - Outputs day-by-day Markdown sections with `image count` plus rankings of apps/domains/tags/locations.
78
+ - Uses hourly index cache to discover images per day, then builds rankings from image cache records.
79
+ - If image-cache metadata is insufficient, warming via list/detail fetch is triggered through ranking warmers.
72
80
 
73
81
  ## Consequences
74
82
  - Cache is portable and independent from the repository working tree.
@@ -11,7 +11,7 @@ Adopt and document the existing top-level command structure.
11
11
 
12
12
  ### 1. Program Metadata
13
13
  - Binary name: `gyazo`
14
- - Version: `1.0.0`
14
+ - Version: `0.0.2`
15
15
  - Description: `Gyazo Memory CLI for AI Secretary`
16
16
 
17
17
  ### 2. Commands
@@ -28,8 +28,11 @@ Adopt and document the existing top-level command structure.
28
28
  - `-l, --limit <number>` (default: `20`)
29
29
  - `-j, --json`
30
30
  - `-H, --hour <yyyy-mm-dd-hh>` (reads hourly cache only)
31
- - `--photos` (alias of `search has:location`)
32
- - `--uploaded` (alias of `search gyazocli_uploads`)
31
+ - `--date <yyyy|yyyy-mm|yyyy-mm-dd>` (reads date range from hourly cache; warms from API if needed)
32
+ - `--today` (target today only)
33
+ - `--max-pages <number>` (default: `100`, used for `--date`/`--today` warming)
34
+ - `--photos` (alias of `search has:location`, can be combined with `--date`/`--today`)
35
+ - `--uploaded` (alias of `search gyazocli_uploads`, can be combined with `--date`/`--today`)
33
36
  - `--no-cache`
34
37
  - `gyazo get <image_id>`
35
38
  - Options:
@@ -77,6 +80,18 @@ Adopt and document the existing top-level command structure.
77
80
  - `--max-pages <number>` (default: `10`)
78
81
  - `-j, --json`
79
82
  - `--no-cache`
83
+ - `gyazo summary`
84
+ - Default range: from 8 days ago to yesterday
85
+ - Shows day-by-day Markdown sections (`### YYYY-MM-DD`) with:
86
+ - `image count`
87
+ - rankings of apps/domains/tags/locations
88
+ - Options:
89
+ - `--date <yyyy|yyyy-mm|yyyy-mm-dd>`
90
+ - `--today` (target today only)
91
+ - `-l, --limit <number>` (default: `10`, max: `10`)
92
+ - `--max-pages <number>` (default: `10`)
93
+ - `-j, --json`
94
+ - `--no-cache`
80
95
  - `gyazo stats`
81
96
  - Default range: from 8 days ago to yesterday
82
97
  - Default behavior: weekly Markdown summary.
@@ -103,7 +118,8 @@ Adopt and document the existing top-level command structure.
103
118
  - Supported types: `json`, `hourly`
104
119
 
105
120
  ### 3. Output and Behavior Notes
106
- - `-j, --json` is available on `config get`, `list`, `get`, `search`, `apps`, `domains`, `tags`, and `locations`.
121
+ - `-j, --json` is available on `config get`, `list`, `get`, `search`, `apps`, `domains`, `tags`, `locations`, and `summary`.
122
+ - `summary` default output is Markdown with headings (`## Gyazo Summary`, `### YYYY-MM-DD`) and nested bullet lists.
107
123
  - There are no global `--plain` or `--verbose` flags in current implementation.
108
124
  - Authenticated commands call token resolution before API access.
109
125
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yuiseki/gyazocli",
3
- "version": "0.0.1",
3
+ "version": "0.0.2",
4
4
  "description": "Gyazo Memory CLI for AI Secretary",
5
5
  "main": "dist/index.js",
6
6
  "bin": {