imgstat 3.0.3 → 3.1.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 CHANGED
@@ -14,6 +14,26 @@ can actually read — without guessing, without vision tokens, without touching
14
14
  npm install -g imgstat
15
15
  ```
16
16
 
17
+ ## usage
18
+
19
+ imgstat infers your intent automatically based on the target you provide.
20
+
21
+ ```bash
22
+ # Print dimensions of images from a web URL
23
+ imgstat https://example.com/logo.png
24
+
25
+ # Inspect a local directory
26
+ imgstat ./public/images
27
+
28
+ # Output strict, flat JSON (ideal for AI parsing)
29
+ imgstat https://example.com/images --json
30
+
31
+ # Rename local images to append dimensions (e.g., image-800x600.jpg)
32
+ imgstat rename ./public/images -y
33
+ ```
34
+
35
+ When run in non-interactive environments or with flags, imgstat routes all progress logs to standard error, ensuring `stdout` remains clean for pipeline parsing and LLM usage.
36
+
17
37
  ## Contribution Rules
18
38
 
19
39
  Keep the tool small. If you are considering adding a feature, ask: **does this help AI understand images faster?** If the answer is not clearly yes, it probably does not belong here.
package/bin/imgstat CHANGED
@@ -45,30 +45,36 @@ while [[ $# -gt 0 ]]; do
45
45
  --help|-h)
46
46
  echo "imgstat — Embed image dimensions directly into filenames."
47
47
  echo "Usage: imgstat [mode] [target] [options]"
48
+ echo " imgstat <url> [--json] (Auto-infers remote mode)"
49
+ echo " imgstat <path> [--json] (Auto-infers inspect mode)"
48
50
  echo ""
49
51
  echo "Modes:"
50
- echo " (none) Interactive menu"
52
+ echo " (none) Interactive menu (Not recommended for AI agents)"
51
53
  echo " inspect Print dimensions of local directory images"
52
54
  echo " rename Rename local files (e.g. image-800x600.jpg)"
53
55
  echo " remote Print dimensions of images from a URL"
54
56
  echo " analyze Scan codebase for URLs, write dimension report for your IDE"
55
57
  echo ""
56
- echo "Options:"
57
- echo " --format Set output format (e.g. --format json)"
58
- echo " --dry-run Show what would happen without making changes"
59
- echo " --yes, -y Skip confirmation prompts (for rename)"
60
- echo " --help, -h Show this help message"
58
+ echo "Options for AI Agents:"
59
+ echo " --json Output strictly flat JSON to stdout (Recommended)"
60
+ echo " --yes, -y Skip all confirmation prompts"
61
+ echo " --dry-run Safe preview of changes"
62
+ echo " --help Show this help message"
61
63
  exit 0
62
64
  ;;
63
65
  --format)
64
66
  shift
65
67
  FORMAT="$1"
66
68
  if [[ "$FORMAT" != "json" ]]; then
67
- echo "Error: unsupported format '$FORMAT'. Only 'json' is supported."
69
+ echo "Error: unsupported format '$FORMAT'. Only 'json' is supported." >&2
68
70
  exit 1
69
71
  fi
70
72
  shift
71
73
  ;;
74
+ --json)
75
+ FORMAT="json"
76
+ shift
77
+ ;;
72
78
  -*)
73
79
  echo "Unknown flag: $1"
74
80
  exit 1
@@ -87,13 +93,16 @@ done
87
93
 
88
94
  if [[ -z "$MODE" ]]; then
89
95
  if [[ -n "$TARGET" ]]; then
90
- echo "Error: A target was provided without a mode."
91
- echo "Usage: imgstat [mode] [target] [--dry-run] [--yes]"
92
- exit 1
96
+ if [[ "$TARGET" =~ ^https?:// ]]; then
97
+ MODE="remote"
98
+ else
99
+ MODE="inspect"
100
+ fi
101
+ else
102
+ source "$LIB_DIR/ui.sh"
103
+ cmd_ui
104
+ exit 0
93
105
  fi
94
- source "$LIB_DIR/ui.sh"
95
- cmd_ui
96
- exit 0
97
106
  fi
98
107
 
99
108
  # Fallback target if none provided
@@ -104,7 +113,7 @@ fi
104
113
  case "$MODE" in
105
114
  inspect)
106
115
  source "$LIB_DIR/inspect.sh"
107
- cmd_inspect "$TARGET"
116
+ cmd_inspect "$TARGET" "$FORMAT"
108
117
  ;;
109
118
  rename)
110
119
  source "$LIB_DIR/rename.sh"
@@ -116,7 +125,7 @@ case "$MODE" in
116
125
  exit 1
117
126
  fi
118
127
  source "$LIB_DIR/remote.sh"
119
- cmd_remote "$TARGET" "$DRY_RUN"
128
+ cmd_remote "$TARGET" "$DRY_RUN" "$FORMAT"
120
129
  ;;
121
130
  analyze)
122
131
  source "$LIB_DIR/analyze.sh"
package/lib/analyze.sh CHANGED
@@ -6,7 +6,7 @@ cmd_analyze() {
6
6
  local dir="$1"
7
7
  local target_option="${2:-1}"
8
8
  local json_format="$3"
9
- echo "Analyzing codebase for remote image references in $dir..."
9
+ echo "Analyzing codebase for remote image references in $dir..." >&2
10
10
 
11
11
  # ── Step 1: Extract all http/https URLs from common code files ──────────────
12
12
  local all_urls=()
@@ -18,12 +18,12 @@ cmd_analyze() {
18
18
  -exec grep -hoE "https?://[^\"')[:space:]]+" {} + 2>/dev/null | sort -u)
19
19
 
20
20
  if [[ ${#all_urls[@]} -eq 0 ]]; then
21
- echo "No remote URLs found in code files."
21
+ echo "No remote URLs found in code files." >&2
22
22
  return 0
23
23
  fi
24
24
 
25
- echo "Found ${#all_urls[@]} unique URL(s). Classifying..."
26
- echo ""
25
+ echo "Found ${#all_urls[@]} unique URL(s). Classifying..." >&2
26
+ echo "" >&2
27
27
 
28
28
  # ── Step 2: Classify each URL via 3-tier pipeline ──────────────────────────
29
29
  local image_extensions=("jpg" "jpeg" "png" "gif" "webp" "avif" "svg" "bmp" "tiff" "tif" "ico")
@@ -39,7 +39,7 @@ cmd_analyze() {
39
39
 
40
40
  # ── Tier 1: Obvious non-image check (fast, no network) ──────────────────
41
41
  if is_obvious_non_image "$url"; then
42
- printf " \033[2m⏭ Skip (pattern match) : %s\033[0m\n" "$url"
42
+ printf " \033[2m⏭ Skip (pattern match) : %s\033[0m\n" "$url" >&2
43
43
  continue
44
44
  fi
45
45
 
@@ -55,30 +55,30 @@ cmd_analyze() {
55
55
  done
56
56
 
57
57
  if [[ "$is_image_ext" == "true" ]]; then
58
- printf " \033[1;32m✓ Queue (extension) : %s\033[0m\n" "$url"
58
+ printf " \033[1;32m✓ Queue (extension) : %s\033[0m\n" "$url" >&2
59
59
  queued_urls+=("$url")
60
60
  continue
61
61
  fi
62
62
 
63
63
  # ── Tier 3: HTTP HEAD Content-Type check (network, no body download) ─────
64
- printf " \033[33m? Check (HEAD request) : %s\033[0m" "$url"
64
+ printf " \033[33m? Check (HEAD request) : %s\033[0m" "$url" >&2
65
65
  if check_content_type_is_image "$url"; then
66
- printf "\r \033[1;32m✓ Queue (content-type) : %s\033[0m\n" "$url"
66
+ printf "\r \033[1;32m✓ Queue (content-type) : %s\033[0m\n" "$url" >&2
67
67
  queued_urls+=("$url")
68
68
  else
69
- printf "\r \033[2m⏭ Skip (not image CT) : %s\033[0m\n" "$url"
69
+ printf "\r \033[2m⏭ Skip (not image CT) : %s\033[0m\n" "$url" >&2
70
70
  fi
71
71
  done
72
72
 
73
- echo ""
73
+ echo "" >&2
74
74
 
75
75
  if [[ ${#queued_urls[@]} -eq 0 ]]; then
76
- echo "No image URLs found after classification."
76
+ echo "No image URLs found after classification." >&2
77
77
  return 0
78
78
  fi
79
79
 
80
- echo "Fetching dimensions for ${#queued_urls[@]} image URL(s)..."
81
- echo ""
80
+ echo "Fetching dimensions for ${#queued_urls[@]} image URL(s)..." >&2
81
+ echo "" >&2
82
82
 
83
83
  # ── Step 3: Download & measure queued image URLs ───────────────────────────
84
84
  local TEMP_DIR
@@ -119,7 +119,7 @@ cmd_analyze() {
119
119
  local w="${dim% *}"
120
120
  local h="${dim#* }"
121
121
  echo "${url}|${w}|${h}" >> "$TEMP_RESULTS"
122
- printf " \033[1;32m📐 %s → %sx%s\033[0m\n" "$url" "$w" "$h"
122
+ printf " \033[1;32m📐 %s → %sx%s\033[0m\n" "$url" "$w" "$h" >&2
123
123
  processed=$((processed + 1))
124
124
  fi
125
125
  fi
@@ -128,11 +128,11 @@ cmd_analyze() {
128
128
  rm -rf "${TEMP_DIR:?}"/*
129
129
  done
130
130
 
131
- echo ""
132
- echo "Analysis complete!"
131
+ echo "" >&2
132
+ echo "Analysis complete!" >&2
133
133
 
134
134
  if [[ $processed -eq 0 ]]; then
135
- echo "No matching images with dimension info could be parsed."
135
+ echo "No matching images with dimension info could be parsed." >&2
136
136
  return 0
137
137
  fi
138
138
 
@@ -172,5 +172,5 @@ cmd_analyze() {
172
172
  ;;
173
173
  esac
174
174
 
175
- echo "This file can now be read by autonomous coding agents."
175
+ echo "This file can now be read by autonomous coding agents." >&2
176
176
  }
package/lib/inspect.sh CHANGED
@@ -5,19 +5,36 @@ source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/scan.sh"
5
5
 
6
6
  cmd_inspect() {
7
7
  local dir="$1"
8
- echo "Inspecting directory: $dir"
8
+ local format="$2"
9
+ echo "Inspecting directory: $dir" >&2
9
10
 
10
11
  local files
11
12
  mapfile -t files < <(scan_images "$dir")
12
13
 
13
14
  if [[ ${#files[@]} -eq 0 ]]; then
14
- echo "No images found in $dir."
15
+ echo "No images found in $dir." >&2
15
16
  return 0
16
17
  fi
17
18
 
18
- printf "%-50s | %-15s\n" "Filename" "Dimensions"
19
- printf "%.s-" {1..70}
20
- echo
19
+ if [[ "$format" == "json" ]]; then
20
+ local temp_res=$(mktemp)
21
+ for file in "${files[@]}"; do
22
+ local dim
23
+ dim=$(get_dimensions "$file" || true)
24
+ if [[ -n "$dim" ]]; then
25
+ local w="${dim% *}"
26
+ local h="${dim#* }"
27
+ echo "${file}|${w}|${h}" >> "$temp_res"
28
+ fi
29
+ done
30
+ bash "$(dirname "${BASH_SOURCE[0]}")/format-json.sh" "." "$temp_res" "0"
31
+ rm -f "$temp_res"
32
+ return 0
33
+ fi
34
+
35
+ printf "%-50s | %-15s\n" "Filename" "Dimensions" >&2
36
+ printf "%.s-" {1..70} >&2
37
+ echo >&2
21
38
 
22
39
  for file in "${files[@]}"; do
23
40
  local dim
@@ -27,7 +44,7 @@ cmd_inspect() {
27
44
  local h="${dim#* }"
28
45
  printf "%-50s | %s x %s\n" "$(basename "$file")" "$w" "$h"
29
46
  else
30
- printf "%-50s | %-15s\n" "$(basename "$file")" "Error reading"
47
+ printf "%-50s | %-15s\n" "$(basename "$file")" "Error reading" >&2
31
48
  fi
32
49
  done
33
50
  }
package/lib/remote.sh CHANGED
@@ -5,13 +5,14 @@ source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/utils.sh"
5
5
  cmd_remote() {
6
6
  local url="$1"
7
7
  local dry_run="$2"
8
+ local format="$3"
8
9
 
9
10
  if [[ "$dry_run" == "true" ]]; then
10
- echo "[Dry Run] Would fetch $url to extract dimensions. Nothing will be downloaded."
11
+ echo "[Dry Run] Would fetch $url to extract dimensions. Nothing will be downloaded." >&2
11
12
  return 0
12
13
  fi
13
14
 
14
- echo "Fetching images from $url..."
15
+ echo "Fetching images from $url..." >&2
15
16
 
16
17
  local TEMP_DIR
17
18
  TEMP_DIR=$(mktemp -d)
@@ -37,13 +38,30 @@ cmd_remote() {
37
38
  done
38
39
 
39
40
  if [[ ${#found_images[@]} -eq 0 ]]; then
40
- echo "No images found at $url."
41
+ echo "No images found at $url." >&2
41
42
  return 0
42
43
  fi
43
44
 
44
- printf "%-50s | %-15s\n" "Filename" "Dimensions"
45
- printf "%.s-" {1..70}
46
- echo
45
+ if [[ "$format" == "json" ]]; then
46
+ local temp_res=$(mktemp)
47
+ for file in "${found_images[@]}"; do
48
+ local dim
49
+ dim=$(get_dimensions "$file" || true)
50
+ if [[ -n "$dim" ]]; then
51
+ local w="${dim% *}"
52
+ local h="${dim#* }"
53
+ # Provide base filename since it matches expected JSON output
54
+ echo "$(basename "$file")|${w}|${h}" >> "$temp_res"
55
+ fi
56
+ done
57
+ bash "$(dirname "${BASH_SOURCE[0]}")/format-json.sh" "." "$temp_res" "0"
58
+ rm -f "$temp_res"
59
+ return 0
60
+ fi
61
+
62
+ printf "%-50s | %-15s\n" "Filename" "Dimensions" >&2
63
+ printf "%.s-" {1..70} >&2
64
+ echo >&2
47
65
 
48
66
  for file in "${found_images[@]}"; do
49
67
  local dim
@@ -53,7 +71,7 @@ cmd_remote() {
53
71
  local h="${dim#* }"
54
72
  printf "%-50s | %s x %s\n" "$(basename "$file")" "$w" "$h"
55
73
  else
56
- printf "%-50s | %-15s\n" "$(basename "$file")" "Error reading"
74
+ printf "%-50s | %-15s\n" "$(basename "$file")" "Error reading" >&2
57
75
  fi
58
76
  done
59
77
  }
package/lib/rename.sh CHANGED
@@ -12,7 +12,7 @@ cmd_rename() {
12
12
  mapfile -t files < <(scan_images "$dir")
13
13
 
14
14
  if [[ ${#files[@]} -eq 0 ]]; then
15
- echo "No images found in $dir."
15
+ echo "No images found in $dir." >&2
16
16
  return 0
17
17
  fi
18
18
 
@@ -38,26 +38,26 @@ cmd_rename() {
38
38
  done
39
39
 
40
40
  if [[ ${#to_rename[@]} -eq 0 ]]; then
41
- echo "All images already have dimensions in their filenames. Nothing to do."
41
+ echo "All images already have dimensions in their filenames. Nothing to do." >&2
42
42
  return 0
43
43
  fi
44
44
 
45
- echo "Found ${#to_rename[@]} file(s) to rename:"
45
+ echo "Found ${#to_rename[@]} file(s) to rename:" >&2
46
46
  for i in "${!to_rename[@]}"; do
47
- echo " $(basename "${to_rename[$i]}") -> $(basename "${new_names[$i]}")"
47
+ echo " $(basename "${to_rename[$i]}") -> $(basename "${new_names[$i]}")" >&2
48
48
  done
49
49
 
50
50
  if [[ "$dry_run" == "true" ]]; then
51
- echo
52
- echo "[Dry Run] No files were changed."
51
+ echo >&2
52
+ echo "[Dry Run] No files were changed." >&2
53
53
  return 0
54
54
  fi
55
55
 
56
56
  if [[ "$auto_yes" != "true" ]]; then
57
- echo
57
+ echo >&2
58
58
  read -r -p "Proceed with renaming? [y/N] " response
59
59
  if [[ ! "$response" =~ ^([yY][eE][sS]|[yY])$ ]]; then
60
- echo "Aborted."
60
+ echo "Aborted." >&2
61
61
  return 1
62
62
  fi
63
63
  fi
@@ -66,5 +66,5 @@ cmd_rename() {
66
66
  mv "${to_rename[$i]}" "${new_names[$i]}"
67
67
  done
68
68
 
69
- echo "Renaming complete."
69
+ echo "Renaming complete." >&2
70
70
  }
package/lib/ui.sh CHANGED
@@ -6,6 +6,11 @@ source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/remote.sh"
6
6
  source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/analyze.sh"
7
7
 
8
8
  cmd_ui() {
9
+ if [ ! -t 0 ]; then
10
+ echo "Error: Non-interactive terminal detected. Interactive menus are not supported here." >&2
11
+ echo "Please provide arguments instead. Run 'imgstat --help' for usage." >&2
12
+ exit 1
13
+ fi
9
14
  local options=("Inspect local directory" "Rename local files" "Analyze remote URL" "Analyze codebase (.agent/rules)" "Quit")
10
15
  local selected=0
11
16
 
package/llms.txt ADDED
@@ -0,0 +1,45 @@
1
+ # imgstat CLI (Agent Reference)
2
+
3
+ `imgstat` is a specialized tool that extracts dimensions (width and height) from local images or remote URLs to provide context.
4
+
5
+ > **CRITICAL RULE FOR AI AGENTS:** Always use the `--json` flag to receive structured output. Never run this tool without flags, as the interactive fallback will block execution. Always pass `-y` to skip user prompts on state-mutating commands.
6
+
7
+ ## Usage
8
+
9
+ ```bash
10
+ # Recommended one-shot remote inspection
11
+ npx imgstat https://example.com/image.jpg --json
12
+
13
+ # Recommended one-shot local inspection
14
+ npx imgstat ./public/images --json
15
+
16
+ # Rename all local images to append their dimensions (e.g. image-800x600.jpg)
17
+ npx imgstat rename ./public/images -y
18
+ ```
19
+
20
+ ## Available Modes
21
+ If you just pass a single argument path or URL, imgstat will automatically infer `inspect` or `remote` respectively.
22
+
23
+ - `remote`: Downloads a URL (or shallow scrapes an HTML page) and measures any embedded images.
24
+ - `inspect`: Scans a specific local directory for images and prints their dimensions.
25
+ - `rename`: Actually modifies files on the file system to append dimensions. (Requires `-y` to bypass confirmation).
26
+ - `analyze`: Scrapes an entire codebase for external URL references and builds a report.
27
+
28
+ ## Required Flags for Autonomous Agents
29
+ - `--json`: (Highly Recommended). Formats output as flat JSON. All logs, spinners, and debug text are routed to `stderr`, leaving only the pure JSON on `stdout`.
30
+ - `-y` or `--yes`: Skip confirmation prompts (vital for `rename`).
31
+ - `--dry-run`: Performs read operations and displays actions it *will* perform without writing.
32
+
33
+ ## Output Schema (When using --json)
34
+ ```json
35
+ {
36
+ "generated_at": "2026-04-17T12:00:00Z",
37
+ "images": [
38
+ {
39
+ "path": "hero-bg.jpg",
40
+ "width": 1920,
41
+ "height": 1080
42
+ }
43
+ ]
44
+ }
45
+ ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "imgstat",
3
- "version": "3.0.3",
3
+ "version": "3.1.0",
4
4
  "description": "Embeds image dimensions directly into filenames for natural AI context.",
5
5
  "scripts": {
6
6
  "test": "echo \"Error: no test specified\" && exit 1"