@yuiseki/gyazocli 0.5.1 → 0.6.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/README.md +18 -1
- package/dist/api.js +5 -1
- package/dist/index.js +1 -1
- package/dist/mcp.js +34 -11
- package/dist/services/memory.js +10 -13
- package/docs/ADR/003-cli-structure.md +1 -1
- package/package.json +2 -1
- package/skills/gyazo/SKILL.md +128 -0
- package/skills/gyazo/references/search-syntax.md +79 -0
package/README.md
CHANGED
|
@@ -129,7 +129,8 @@ results back from the detail endpoint to confirm the filter had applied:
|
|
|
129
129
|
| `title:`, `url:`, `desc:` | the page it was captured from |
|
|
130
130
|
| `ocr:` | the text in the image |
|
|
131
131
|
| `type:png` | the file type |
|
|
132
|
-
| `
|
|
132
|
+
| `date:2026-08-30`, `date:2026-08`, `date:2026` | the upload date, by day, month or year |
|
|
133
|
+
| `since:2026-08-30 until:2026-08-31` | the upload date, as a range |
|
|
133
134
|
| `-address:広島` | negation |
|
|
134
135
|
| `OR` | alternation; terms are ANDed otherwise |
|
|
135
136
|
|
|
@@ -195,6 +196,22 @@ already configured against that server can point at this one instead. Its
|
|
|
195
196
|
`gyazo_upload` is deliberately absent: nothing here can write to your Gyazo
|
|
196
197
|
account until there is a reason for it to.
|
|
197
198
|
|
|
199
|
+
## Agent skill
|
|
200
|
+
|
|
201
|
+
`skills/gyazo/` is a skill for coding agents that drive the CLI: what the
|
|
202
|
+
commands are, what the search syntax actually accepts, and the judgement calls
|
|
203
|
+
that keep a capture from becoming a claim it does not support. It ships in the
|
|
204
|
+
npm package.
|
|
205
|
+
|
|
206
|
+
Install it for Claude Code by copying it where the agent looks for skills:
|
|
207
|
+
|
|
208
|
+
```bash
|
|
209
|
+
mkdir -p ~/.claude/skills
|
|
210
|
+
cp -r "$(npm root -g)/@yuiseki/gyazocli/skills/gyazo" ~/.claude/skills/
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
Per project instead of per user, copy it to `.claude/skills/` in the project.
|
|
214
|
+
|
|
198
215
|
## Development
|
|
199
216
|
|
|
200
217
|
### Build
|
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
|
-
|
|
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.
|
|
29
|
+
.version('0.6.0');
|
|
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
|
@@ -46,9 +46,10 @@ const SEARCH_QUERY_DESCRIPTION = [
|
|
|
46
46
|
'and case, and matches postal codes too, so address:広島 and address:Hiroshima and',
|
|
47
47
|
'address:730-0041 all find the same photos.',
|
|
48
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;
|
|
50
|
-
'
|
|
51
|
-
'
|
|
49
|
+
'captured from; ocr: the text in the image; type: the file type, as type:png; date:',
|
|
50
|
+
'a day, a month or a year of upload, as date:2026-08-30, date:2026-08 or date:2026;',
|
|
51
|
+
'since: and until: bound the upload date, as since:2026-08-30 until:2026-08-31. Quote a',
|
|
52
|
+
'value that contains spaces.',
|
|
52
53
|
'There is no coordinate or radius search: location:, geo:, near:, bbox:, city: and the',
|
|
53
54
|
'like all return nothing. To search by place, use address: with a place name.',
|
|
54
55
|
'An operator Gyazo does not know returns nothing rather than matching as text, so an',
|
|
@@ -193,16 +194,35 @@ const INCLUDE_LOCATION = zod_1.z
|
|
|
193
194
|
.boolean()
|
|
194
195
|
.default(true)
|
|
195
196
|
.describe('Fill in the location, which the listing and search endpoints leave out. Costs one ' +
|
|
196
|
-
'extra lookup per capture the local cache does not already hold
|
|
197
|
-
'
|
|
197
|
+
'extra lookup per capture the local cache does not already hold, so a page of 40 ' +
|
|
198
|
+
'uncached captures adds a few seconds; the next look at the same captures is free. ' +
|
|
199
|
+
'Set false when the coordinates do not matter');
|
|
200
|
+
/**
|
|
201
|
+
* Fill in the locations, and say so when the per-call limit stopped it part
|
|
202
|
+
* way: past that point a capture without a location is indistinguishable from
|
|
203
|
+
* one that has none, which is exactly the sort of silence worth breaking.
|
|
204
|
+
*/
|
|
198
205
|
async function withLocations(images, includeLocation) {
|
|
199
|
-
|
|
206
|
+
if (!includeLocation)
|
|
207
|
+
return { images };
|
|
208
|
+
const result = await (0, memory_1.enrichImageLocations)(images);
|
|
209
|
+
if (result.skipped === 0)
|
|
210
|
+
return { images: result.images };
|
|
211
|
+
return {
|
|
212
|
+
images: result.images,
|
|
213
|
+
note: `The location was filled in for ${result.considered} captures; ${result.skipped} were ` +
|
|
214
|
+
'left as the listing returned them, so their missing location means "not looked up" ' +
|
|
215
|
+
'rather than "no location". Ask for fewer at a time to cover them all.',
|
|
216
|
+
};
|
|
200
217
|
}
|
|
201
|
-
function asMetadataListResult(images) {
|
|
218
|
+
function asMetadataListResult(images, note) {
|
|
202
219
|
if (!images || images.length === 0) {
|
|
203
220
|
return NO_IMAGES;
|
|
204
221
|
}
|
|
205
|
-
|
|
222
|
+
const json = asJsonResult(images.map(toMetadata));
|
|
223
|
+
if (!note)
|
|
224
|
+
return json;
|
|
225
|
+
return { content: [...json.content, { type: 'text', text: note }] };
|
|
206
226
|
}
|
|
207
227
|
/**
|
|
208
228
|
* A date argument, refused by throwing. The CLI reports and exits here, which
|
|
@@ -249,7 +269,8 @@ function createMcpServer() {
|
|
|
249
269
|
if (!images || images.length === 0) {
|
|
250
270
|
return NO_IMAGES;
|
|
251
271
|
}
|
|
252
|
-
|
|
272
|
+
const withLocation = await withLocations(images, includeLocation);
|
|
273
|
+
return asMetadataListResult(withLocation.images, withLocation.note);
|
|
253
274
|
}));
|
|
254
275
|
server.registerTool('gyazo_image', {
|
|
255
276
|
title: 'Describe one Gyazo capture',
|
|
@@ -365,7 +386,8 @@ function createMcpServer() {
|
|
|
365
386
|
hour: hour || undefined,
|
|
366
387
|
alias,
|
|
367
388
|
});
|
|
368
|
-
|
|
389
|
+
const withLocation = await withLocations(images, args.include_location);
|
|
390
|
+
return asMetadataListResult(withLocation.images, withLocation.note);
|
|
369
391
|
}));
|
|
370
392
|
server.registerTool('gyazo_summary', {
|
|
371
393
|
title: 'Summarise a stretch of Gyazo captures',
|
|
@@ -535,7 +557,8 @@ function createMcpServer() {
|
|
|
535
557
|
'recent pages of captures. It may be older than that, or belong to another ' +
|
|
536
558
|
'account. Ask for a window in minutes instead, or raise max_pages.');
|
|
537
559
|
}
|
|
538
|
-
|
|
560
|
+
const withLocation = await withLocations(result.images, args.include_location);
|
|
561
|
+
return asMetadataListResult(withLocation.images, withLocation.note);
|
|
539
562
|
}));
|
|
540
563
|
server.registerTool('gyazo_collections', {
|
|
541
564
|
title: 'Find a Gyazo collection by name',
|
package/dist/services/memory.js
CHANGED
|
@@ -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 ??
|
|
422
|
+
const concurrency = Math.max(1, options.concurrency ?? 10);
|
|
433
423
|
const enriched = [...images];
|
|
434
424
|
const pending = [];
|
|
435
|
-
|
|
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
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yuiseki/gyazocli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Gyazo Memory CLI for AI Secretary",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
},
|
|
17
17
|
"files": [
|
|
18
18
|
"dist",
|
|
19
|
+
"skills",
|
|
19
20
|
"README.md",
|
|
20
21
|
"docs"
|
|
21
22
|
],
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: gyazo
|
|
3
|
+
description: Search, read and summarise a person's Gyazo captures from the command line with the `gyazo` CLI - screenshots and phone photos, their OCR text, the application or page they came from, and where they were taken. Use when asked what someone saw, captured, read, bought, visited or worked on, when a question needs evidence from their screen history, or when a Gyazo image ID, a gyazo.com URL or a collection needs to be read.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Gyazo CLI
|
|
7
|
+
|
|
8
|
+
`gyazo` reads a Gyazo account: screenshots, phone photos, their OCR text, the
|
|
9
|
+
application and page a capture came from, and the coordinates and address of a
|
|
10
|
+
photo. It caches everything it reads under `~/.cache/gyazocli`, so repeated
|
|
11
|
+
questions about the same period are answered locally.
|
|
12
|
+
|
|
13
|
+
Check `gyazo config get me` first when a token might be missing; it prints the
|
|
14
|
+
account or exits non-zero. Set one with `gyazo config set token <token>`.
|
|
15
|
+
|
|
16
|
+
Every command exits non-zero on failure, so `&&` chains and `set -e` behave.
|
|
17
|
+
|
|
18
|
+
## Reading captures
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
gyazo ls --limit 10 # most recent, newest first
|
|
22
|
+
gyazo ls --date 2026-08-30 # a local day; also yyyy-mm and yyyy
|
|
23
|
+
gyazo ls --today
|
|
24
|
+
gyazo ls --hour 2026-08-30-14 # one hour, from the cache only
|
|
25
|
+
gyazo ls --photos # shorthand for has:location
|
|
26
|
+
gyazo get <image_id> # one capture in detail
|
|
27
|
+
gyazo get <image_id> --ocr # just the OCR text
|
|
28
|
+
gyazo <image_id> # same as get
|
|
29
|
+
gyazo <https://gyazo.com/...> # same as get
|
|
30
|
+
gyazo ./screenshot.png # an existing file uploads instead
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Add `-j`/`--json` to any of these when the output is going to be parsed rather
|
|
34
|
+
than read. Add `--no-cache` when the answer must come from the API.
|
|
35
|
+
|
|
36
|
+
## Searching
|
|
37
|
+
|
|
38
|
+
`gyazo search <query>` takes Gyazo's own query language. The operators below
|
|
39
|
+
were confirmed against the live API; see [references/search-syntax.md](references/search-syntax.md)
|
|
40
|
+
for the measurements behind them and the ones that do not exist.
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
gyazo search "お好み焼" # OCR text, title and description
|
|
44
|
+
gyazo search "has:exif" # photographs, not screenshots
|
|
45
|
+
gyazo search "address:広島" # where a photo was taken
|
|
46
|
+
gyazo search "date:2026-08-30" # a day, a month or a year
|
|
47
|
+
gyazo search 'app:"Gyazo Android"' # the application it came from
|
|
48
|
+
gyazo search "ocr:Wi-Fi has:exif" # terms are ANDed
|
|
49
|
+
gyazo search "address:広島 OR address:京都" # capital OR
|
|
50
|
+
gyazo search "has:location -app:Chrome" # leading - negates
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Three things worth knowing before composing a query:
|
|
54
|
+
|
|
55
|
+
- **An operator Gyazo does not know returns nothing**, rather than falling back
|
|
56
|
+
to a text search. A guessed operator looks exactly like "no such captures".
|
|
57
|
+
- **`has:exif` is how to narrow to photographs.** The application does not tell
|
|
58
|
+
them apart: `app:"Gyazo Android"` includes screenshots and screen recordings
|
|
59
|
+
from the same phone.
|
|
60
|
+
- **There is no coordinate or radius search.** Search by place with `address:`,
|
|
61
|
+
which matches the address in any language and matches postal codes too.
|
|
62
|
+
|
|
63
|
+
## Summaries and rankings
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
gyazo summary # the week to yesterday, day by day
|
|
67
|
+
gyazo summary --date 2026-08-30
|
|
68
|
+
gyazo stats --days 30 # one markdown report
|
|
69
|
+
gyazo apps --date 2026-08 # what applications, most used first
|
|
70
|
+
gyazo domains --today
|
|
71
|
+
gyazo tags --date 2026
|
|
72
|
+
gyazo locations --date 2026-08-30
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
The ranking commands take `--date`, `--today`, `--limit`, `--max-pages`,
|
|
76
|
+
`--json` and `--no-cache`. Only `stats` takes `--days`.
|
|
77
|
+
|
|
78
|
+
## Collections
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
gyazo collection <collection_id>
|
|
82
|
+
gyazo collection https://gyazo.com/collections/<id>
|
|
83
|
+
gyazo collection <id> --sort captured # added | created | captured
|
|
84
|
+
gyazo collection <id> --anonymous # read a public one without the token
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
A collection ID and an image ID are both 32 hex characters and cannot be told
|
|
88
|
+
apart on their own, which is why `gyazo <bare id>` reads it as an image. Pass
|
|
89
|
+
the `/collections/<id>` URL when the ID is a collection.
|
|
90
|
+
|
|
91
|
+
A collection larger than 100 images is reported as truncated: the public
|
|
92
|
+
endpoint returns the first 100 and cannot page.
|
|
93
|
+
|
|
94
|
+
## Filling the cache
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
gyazo sync --days 7 # yesterday back through 7 days
|
|
98
|
+
gyazo sync --date 2026-08 # a whole month
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
`sync` covers yesterday backwards and never today, because today is still
|
|
102
|
+
happening. For anything from today use `ls --today`, `search`, or a ranking
|
|
103
|
+
command with `--today`.
|
|
104
|
+
|
|
105
|
+
## Answering questions with captures
|
|
106
|
+
|
|
107
|
+
- A day is a local day. `--date 2026-08-30` means that date in this machine's
|
|
108
|
+
timezone, which is also how the cache is laid out.
|
|
109
|
+
- Prefer `search` with an operator over `ls` plus filtering. The API does the
|
|
110
|
+
work, and `ls --date` over a wide range walks many pages.
|
|
111
|
+
- OCR text is noisy: it comes from screenshots at whatever resolution, and
|
|
112
|
+
`locale` is often `und`. Treat it as a hint, not a transcript.
|
|
113
|
+
- `get --objects` prints detected objects, but the API no longer returns the
|
|
114
|
+
field it reads, so it exits non-zero with "Object annotations not found" on
|
|
115
|
+
every capture tested. Use the OCR text instead.
|
|
116
|
+
- **Do not turn a capture into a claim it does not support.** A product page or
|
|
117
|
+
a cart is interest; an order confirmation or a payment receipt is a purchase.
|
|
118
|
+
Say which capture the conclusion rests on.
|
|
119
|
+
- A phone photo carries coordinates and a reverse-geocoded address in
|
|
120
|
+
`metadata.exif_normalized` and `metadata.exif_address` of the `--json`
|
|
121
|
+
output; the top-level `exif_normalized` is always null. Screenshots carry
|
|
122
|
+
neither.
|
|
123
|
+
|
|
124
|
+
## Serving the same data over MCP
|
|
125
|
+
|
|
126
|
+
`gyazo --mcp-server` runs the same functionality as a Model Context Protocol
|
|
127
|
+
server over stdio, for a client that speaks MCP rather than shell. Everything
|
|
128
|
+
above is the shell path.
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# Gyazo search syntax, as measured
|
|
2
|
+
|
|
3
|
+
Every operator here was run against the live API with a value that should
|
|
4
|
+
match, and the results were read back from the image detail endpoint to confirm
|
|
5
|
+
the filter had actually applied. Counting results is not enough: a lean search
|
|
6
|
+
response does not carry the field being filtered on, so a count can look right
|
|
7
|
+
while the filter does nothing.
|
|
8
|
+
|
|
9
|
+
Measured on 2026-09-09 and 2026-09-12.
|
|
10
|
+
|
|
11
|
+
## Contents
|
|
12
|
+
|
|
13
|
+
- [Operators that work](#operators-that-work)
|
|
14
|
+
- [Operators that do not exist](#operators-that-do-not-exist)
|
|
15
|
+
- [Combining terms](#combining-terms)
|
|
16
|
+
- [Photographs versus screenshots](#photographs-versus-screenshots)
|
|
17
|
+
- [What a search result does not contain](#what-a-search-result-does-not-contain)
|
|
18
|
+
|
|
19
|
+
## Operators that work
|
|
20
|
+
|
|
21
|
+
| Operator | Matches | Notes |
|
|
22
|
+
| --- | --- | --- |
|
|
23
|
+
| bare words | OCR text, title, description | `お好み焼` |
|
|
24
|
+
| `address:` | the reverse-geocoded address of a capture with GPS | any language, any case, postal codes too: `address:広島`, `address:Hiroshima`, `address:730-0041` all find the same photos |
|
|
25
|
+
| `date:` | upload date | `date:2026-08-30`, `date:2026-08`, `date:2026` |
|
|
26
|
+
| `since:` / `until:` | upload date range | `since:2026-08-30 until:2026-08-31` |
|
|
27
|
+
| `app:` | the application the capture came from | quote a value with spaces: `app:"Gyazo Android"` |
|
|
28
|
+
| `title:`, `url:`, `desc:` | the page it was captured from | |
|
|
29
|
+
| `ocr:` | the text in the image | |
|
|
30
|
+
| `type:` | the file type | `type:png` |
|
|
31
|
+
| `has:location` | captures with coordinates | |
|
|
32
|
+
| `has:exif` | captures with EXIF | not the same set as `has:location` |
|
|
33
|
+
|
|
34
|
+
## Operators that do not exist
|
|
35
|
+
|
|
36
|
+
`location:`, `geo:`, `near:`, `bbox:`, `latlng:`, `city:`, `pref:`,
|
|
37
|
+
`locality:`, `admin1:`, `country:`, `postal:`, `zip:`, `lat:`, `lng:`,
|
|
38
|
+
`place:`, `around:`, `within:`, `radius:`, `before:`, `after:`, `day:`,
|
|
39
|
+
`has:ocr`, `has:address`.
|
|
40
|
+
|
|
41
|
+
All of them return zero results, exactly as an invented operator does
|
|
42
|
+
(`zzz:広島` returns nothing while `広島` returns many). **Zero results from an
|
|
43
|
+
unfamiliar operator means the operator, not the account.** Retry with a known
|
|
44
|
+
one before concluding the captures do not exist.
|
|
45
|
+
|
|
46
|
+
There is no coordinate or radius search of any kind. To search by place, use
|
|
47
|
+
`address:` with a place name.
|
|
48
|
+
|
|
49
|
+
## Combining terms
|
|
50
|
+
|
|
51
|
+
- Terms are ANDed: `address:広島 address:東京` returns nothing, because no
|
|
52
|
+
capture is in both places.
|
|
53
|
+
- `OR` in capitals works: `address:広島 OR address:東京`.
|
|
54
|
+
- `|` does not work.
|
|
55
|
+
- A leading `-` negates: `has:location -address:広島`.
|
|
56
|
+
|
|
57
|
+
## Photographs versus screenshots
|
|
58
|
+
|
|
59
|
+
`has:exif` is the filter that means photographs. The application does not tell
|
|
60
|
+
them apart: one page of `app:"Gyazo Android" -has:exif` came back as 68 gif and
|
|
61
|
+
30 png against 2 jpg, which is screen recordings and screenshots from the same
|
|
62
|
+
phone.
|
|
63
|
+
|
|
64
|
+
`has:exif` and `has:location` overlap without either containing the other. A
|
|
65
|
+
photo taken indoors has EXIF and no coordinates; 86 captures in the account
|
|
66
|
+
tested carried coordinates without the EXIF flag. `has:exif OR has:location` is
|
|
67
|
+
the widest reading of "a photo".
|
|
68
|
+
|
|
69
|
+
## What a search result does not contain
|
|
70
|
+
|
|
71
|
+
The search and listing endpoints return a lean image: `metadata` holds `app`,
|
|
72
|
+
`desc`, `title`, `url` and `original_*`, and nothing else. No coordinates, no
|
|
73
|
+
address, no matter what the capture carries. Checked against a full page of 100
|
|
74
|
+
where not one item had either field.
|
|
75
|
+
|
|
76
|
+
Coordinates and addresses only come back from the detail endpoint, which is
|
|
77
|
+
what `gyazo get <image_id> --json` calls. To answer "where were these taken",
|
|
78
|
+
search for the IDs and then read each one; `gyazo` caches each detail, so the
|
|
79
|
+
second pass over the same captures is local.
|