@yuiseki/gyazocli 0.5.0 → 0.5.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
@@ -122,15 +122,26 @@ results back from the detail endpoint to confirm the filter had applied:
122
122
 
123
123
  | Operator | Matches |
124
124
  | --- | --- |
125
+ | `has:exif` | photographs rather than screenshots |
126
+ | `has:location` | captures with coordinates |
125
127
  | `address:広島`, `address:Hiroshima`, `address:730-0041` | the reverse-geocoded address of a capture with GPS, in any language or case, postal codes included |
126
128
  | `app:"Gyazo Android"` | the application the capture came from |
127
129
  | `title:`, `url:`, `desc:` | the page it was captured from |
128
130
  | `ocr:` | the text in the image |
129
131
  | `type:png` | the file type |
130
- | `has:location` | only captures with coordinates |
131
- | `has:exif` | only captures with EXIF, which is not the same thing |
132
132
  | `since:2026-08-30 until:2026-08-31` | the upload date |
133
133
  | `-address:広島` | negation |
134
+ | `OR` | alternation; terms are ANDed otherwise |
135
+
136
+ To narrow to photographs, reach for `has:exif`. The application does not tell
137
+ them apart: `app:"Gyazo Android"` includes screenshots and screen recordings
138
+ from the same phone, and one page of `app:"Gyazo Android" -has:exif` came back
139
+ as 68 gif and 30 png against 2 jpg.
140
+
141
+ `has:exif` and `has:location` overlap without either containing the other. A
142
+ photo taken indoors has EXIF and no coordinates; 86 captures here carry
143
+ coordinates without the EXIF flag. `has:exif OR has:location` is the widest
144
+ reading of "a photo".
134
145
 
135
146
  There is no coordinate or radius search. `location:`, `geo:`, `near:`,
136
147
  `bbox:`, `city:`, `lat:` and the like all return nothing, exactly as an
package/dist/api.js CHANGED
@@ -119,7 +119,11 @@ async function listCollectionImages(collectionId, page = 1, per = 100) {
119
119
  */
120
120
  async function fetchImageRendition(imageId, width, format = 'webp') {
121
121
  const extension = format === 'jpeg' ? 'jpg' : 'webp';
122
- const url = `${imageOrigin()}/thumb/${width}_w/${imageId}.${extension}`;
122
+ // The `-jpg` before the extension is a source-type marker, and it has to be
123
+ // there: without it the route answers 404 for any capture whose derivative
124
+ // is not already stored, which is most of them. Its value is ignored, and
125
+ // the extension alone decides what comes back, so a constant will do.
126
+ const url = `${imageOrigin()}/thumb/${width}_w/${imageId}-jpg.${extension}`;
123
127
  const response = await axios_1.default.get(url, { responseType: 'arraybuffer' });
124
128
  const data = Buffer.from(response.data);
125
129
  const contentType = String(response.headers['content-type'] || '').split(';')[0].trim();
package/dist/index.js CHANGED
@@ -26,7 +26,7 @@ program
26
26
  .name('gyazo')
27
27
  .description('Gyazo Memory CLI for AI Secretary')
28
28
  .option('--mcp-server', 'run as a Model Context Protocol server over stdio')
29
- .version('0.5.0');
29
+ .version('0.5.2');
30
30
  (0, config_1.registerConfigCommand)(program);
31
31
  (0, list_1.registerListCommand)(program);
32
32
  (0, get_1.registerGetCommand)(program);
package/dist/mcp.js CHANGED
@@ -34,20 +34,26 @@ const collections_1 = require("./services/collections");
34
34
  */
35
35
  const SEARCH_QUERY_DESCRIPTION = [
36
36
  'Search keyword, up to 200 characters. Bare words match the OCR text, title and',
37
- 'description.',
38
- 'Operators, all confirmed to work: address: matches the reverse-geocoded address of',
39
- 'a capture with GPS, in any language and case, and also matches postal codes, so',
40
- 'address:広島 and address:Hiroshima and address:730-0041 all find the same photos;',
41
- 'app: the application it came from, as app:"Gyazo Android"; title:, url: and desc:',
42
- 'the page it was captured from; ocr: the text in the image; type: the file type, as',
43
- 'type:png; has:location only captures with coordinates; has:exif only captures with',
44
- 'EXIF, which is not the same thing; since: and until: bound the upload date, as',
45
- 'since:2026-08-30 until:2026-08-31. A leading - negates, as -address:広島. Quote a',
46
- 'value that contains spaces.',
47
- 'There is no coordinate or radius search: location:, geo:, near:, bbox:, city: and',
48
- 'the like all return nothing. To search by place, use address: with a place name.',
49
- 'If nothing suitable comes back, rephrase the query to match what the user meant and',
50
- 'search again rather than giving up on the first attempt.',
37
+ 'description. Terms are ANDed; OR in capitals is honoured, and a leading - negates.',
38
+ 'To narrow to photographs rather than screenshots, use has:exif. The application is',
39
+ 'not the distinction: app:"Gyazo Android" includes screenshots and screen recordings',
40
+ 'from the same phone, and of one such page 68 were gif and 30 png against 2 jpg.',
41
+ 'has:location narrows to captures with coordinates. It is not a subset of has:exif',
42
+ 'and does not contain it either: a photo taken indoors has EXIF and no coordinates,',
43
+ 'and some photos carry coordinates without the EXIF flag, so has:exif OR has:location',
44
+ 'is the widest reading of "a photo".',
45
+ 'address: matches the reverse-geocoded address of a capture with GPS, in any language',
46
+ 'and case, and matches postal codes too, so address:広島 and address:Hiroshima and',
47
+ 'address:730-0041 all find the same photos.',
48
+ 'Also: app: the application it came from; title:, url: and desc: the page it was',
49
+ 'captured from; ocr: the text in the image; type: the file type, as type:png; since:',
50
+ 'and until: bound the upload date, as since:2026-08-30 until:2026-08-31. Quote a value',
51
+ 'that contains spaces.',
52
+ 'There is no coordinate or radius search: location:, geo:, near:, bbox:, city: and the',
53
+ 'like all return nothing. To search by place, use address: with a place name.',
54
+ 'An operator Gyazo does not know returns nothing rather than matching as text, so an',
55
+ 'invented operator looks exactly like "no such captures". If nothing suitable comes',
56
+ 'back, rephrase the query rather than giving up on the first attempt.',
51
57
  ].join(' ');
52
58
  function serverVersion() {
53
59
  // The published tarball always contains package.json, and dist/ sits one
@@ -187,16 +193,35 @@ const INCLUDE_LOCATION = zod_1.z
187
193
  .boolean()
188
194
  .default(true)
189
195
  .describe('Fill in the location, which the listing and search endpoints leave out. Costs one ' +
190
- 'extra lookup per capture the local cache does not already hold. Set false when the ' +
191
- 'coordinates do not matter');
196
+ 'extra lookup per capture the local cache does not already hold, so a page of 40 ' +
197
+ 'uncached captures adds a few seconds; the next look at the same captures is free. ' +
198
+ 'Set false when the coordinates do not matter');
199
+ /**
200
+ * Fill in the locations, and say so when the per-call limit stopped it part
201
+ * way: past that point a capture without a location is indistinguishable from
202
+ * one that has none, which is exactly the sort of silence worth breaking.
203
+ */
192
204
  async function withLocations(images, includeLocation) {
193
- return includeLocation ? (0, memory_1.enrichImageLocations)(images) : images;
205
+ if (!includeLocation)
206
+ return { images };
207
+ const result = await (0, memory_1.enrichImageLocations)(images);
208
+ if (result.skipped === 0)
209
+ return { images: result.images };
210
+ return {
211
+ images: result.images,
212
+ note: `The location was filled in for ${result.considered} captures; ${result.skipped} were ` +
213
+ 'left as the listing returned them, so their missing location means "not looked up" ' +
214
+ 'rather than "no location". Ask for fewer at a time to cover them all.',
215
+ };
194
216
  }
195
- function asMetadataListResult(images) {
217
+ function asMetadataListResult(images, note) {
196
218
  if (!images || images.length === 0) {
197
219
  return NO_IMAGES;
198
220
  }
199
- return asJsonResult(images.map(toMetadata));
221
+ const json = asJsonResult(images.map(toMetadata));
222
+ if (!note)
223
+ return json;
224
+ return { content: [...json.content, { type: 'text', text: note }] };
200
225
  }
201
226
  /**
202
227
  * A date argument, refused by throwing. The CLI reports and exits here, which
@@ -243,7 +268,8 @@ function createMcpServer() {
243
268
  if (!images || images.length === 0) {
244
269
  return NO_IMAGES;
245
270
  }
246
- return asMetadataListResult(await withLocations(images, includeLocation));
271
+ const withLocation = await withLocations(images, includeLocation);
272
+ return asMetadataListResult(withLocation.images, withLocation.note);
247
273
  }));
248
274
  server.registerTool('gyazo_image', {
249
275
  title: 'Describe one Gyazo capture',
@@ -359,7 +385,8 @@ function createMcpServer() {
359
385
  hour: hour || undefined,
360
386
  alias,
361
387
  });
362
- return asMetadataListResult(await withLocations(images, args.include_location));
388
+ const withLocation = await withLocations(images, args.include_location);
389
+ return asMetadataListResult(withLocation.images, withLocation.note);
363
390
  }));
364
391
  server.registerTool('gyazo_summary', {
365
392
  title: 'Summarise a stretch of Gyazo captures',
@@ -529,7 +556,8 @@ function createMcpServer() {
529
556
  'recent pages of captures. It may be older than that, or belong to another ' +
530
557
  'account. Ask for a window in minutes instead, or raise max_pages.');
531
558
  }
532
- return asMetadataListResult(await withLocations(result.images, args.include_location));
559
+ const withLocation = await withLocations(result.images, args.include_location);
560
+ return asMetadataListResult(withLocation.images, withLocation.note);
533
561
  }));
534
562
  server.registerTool('gyazo_collections', {
535
563
  title: 'Find a Gyazo collection by name',
@@ -416,26 +416,23 @@ async function listCapturesSince(options) {
416
416
  }
417
417
  return { images: collected.slice(0, limit), pagesWalked };
418
418
  }
419
- /**
420
- * Fill in what the lean endpoints leave out.
421
- *
422
- * The listing and the search endpoints return an image without its
423
- * coordinates or its address, whatever the capture actually carries; only the
424
- * detail endpoint has them. So a caller that needs a location has to ask again
425
- * per image, which is what this does: the cache first, the API for the rest,
426
- * a few at a time, writing what it fetches back to the cache so the next look
427
- * is free.
428
- */
429
419
  async function enrichImageLocations(images, options = {}) {
430
420
  const useCache = options.useCache !== false;
431
421
  const limit = options.limit ?? 40;
432
- const concurrency = Math.max(1, options.concurrency ?? 5);
422
+ const concurrency = Math.max(1, options.concurrency ?? 10);
433
423
  const enriched = [...images];
434
424
  const pending = [];
435
- for (let index = 0; index < enriched.length && pending.length < limit; index++) {
425
+ let considered = 0;
426
+ let skipped = 0;
427
+ for (let index = 0; index < enriched.length; index++) {
436
428
  const image = enriched[index];
437
429
  if (image?.metadata?.exif_normalized || image?.metadata?.exif_address)
438
430
  continue;
431
+ if (considered >= limit) {
432
+ skipped++;
433
+ continue;
434
+ }
435
+ considered++;
439
436
  if (useCache) {
440
437
  const cached = (0, storage_1.loadImageCache)(image?.image_id);
441
438
  if (cached) {
@@ -459,5 +456,5 @@ async function enrichImageLocations(images, options = {}) {
459
456
  }
460
457
  }));
461
458
  }
462
- return enriched;
459
+ return { images: enriched, considered, skipped };
463
460
  }
@@ -13,7 +13,7 @@ Adopt and document the existing top-level command structure.
13
13
 
14
14
  ### 1. Program Metadata
15
15
  - Binary name: `gyazo`
16
- - Version: `0.5.0`
16
+ - Version: `0.5.2`
17
17
  - Description: `Gyazo Memory CLI for AI Secretary`
18
18
 
19
19
  ### 2. Commands
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yuiseki/gyazocli",
3
- "version": "0.5.0",
3
+ "version": "0.5.2",
4
4
  "description": "Gyazo Memory CLI for AI Secretary",
5
5
  "repository": {
6
6
  "type": "git",