oh-my-design-cli 1.6.1 → 1.6.3
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.ko.md +14 -0
- package/README.md +16 -0
- package/data/reference-fingerprints.json +979 -402
- package/dist/bin/oh-my-design.js +5 -3
- package/dist/bin/oh-my-design.js.map +1 -1
- package/dist/{install-skills-UKEVE3KT.js → install-skills-52LCRBZZ.js} +125 -40
- package/dist/install-skills-52LCRBZZ.js.map +1 -0
- package/package.json +2 -1
- package/skills/claude-design/SKILL.md +385 -0
- package/skills/claude-design/references/claude-design-flow.md +425 -0
- package/skills/claude-design/references/codebase-analysis.md +373 -0
- package/skills/claude-design/scripts/analyze_codebase.py +1369 -0
- package/skills/claude-design/scripts/clickable_link.sh +48 -0
- package/skills/claude-design/scripts/collect_source.py +178 -0
- package/skills/claude-design/scripts/drive_claude_design.cjs +378 -0
- package/skills/claude-design/scripts/gather_references.py +437 -0
- package/web/references/bunjang/DESIGN.md +1 -1
- package/web/references/classting/DESIGN.md +251 -0
- package/web/references/coinone/DESIGN.md +218 -0
- package/web/references/devsisters/DESIGN.md +253 -0
- package/web/references/drnow/DESIGN.md +331 -0
- package/web/references/flo/DESIGN.md +306 -0
- package/web/references/fugle/DESIGN.md +250 -0
- package/web/references/gogolook/DESIGN.md +5 -0
- package/web/references/grip/DESIGN.md +250 -0
- package/web/references/hogangnono/DESIGN.md +308 -0
- package/web/references/hyundaicard/DESIGN.md +5 -0
- package/web/references/jkopay/DESIGN.md +249 -0
- package/web/references/jobkorea/DESIGN.md +310 -0
- package/web/references/krafton/DESIGN.md +230 -0
- package/web/references/laftel/DESIGN.md +253 -0
- package/web/references/lezhin/DESIGN.md +301 -0
- package/web/references/momoshop/DESIGN.md +279 -0
- package/web/references/mustit/DESIGN.md +282 -0
- package/web/references/payco/DESIGN.md +227 -0
- package/web/references/piccollage/DESIGN.md +277 -0
- package/web/references/riiid/DESIGN.md +228 -0
- package/web/references/trenbe/DESIGN.md +252 -0
- package/web/references/voicetube/DESIGN.md +227 -0
- package/dist/install-skills-UKEVE3KT.js.map +0 -1
|
@@ -0,0 +1,437 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Gather local brand/visual reference assets (and explicit URL references) for the claude-design skill.
|
|
3
|
+
|
|
4
|
+
Walks a root directory (default: current working directory) up to a bounded depth, pruning
|
|
5
|
+
noisy/excluded directories in place, and collects files whose extension matches a set of
|
|
6
|
+
VISUAL image asset types. By default DOCUMENT files (.pdf, .hwp/.hwpx, .doc/.docx, .ppt/.pptx,
|
|
7
|
+
.xls/.xlsx) are EXCLUDED — they are document templates, not brand visuals — unless --include-docs
|
|
8
|
+
is passed.
|
|
9
|
+
|
|
10
|
+
Ranking (v2): brand-likelihood FIRST, then modification time (newest first). Filenames that look
|
|
11
|
+
like real brand assets (logo, wordmark, symbol, icon, brand, og, hero, cover, favicon, app-icon)
|
|
12
|
+
score high and float to the top; everything else falls back to mtime. Results are then size-filtered
|
|
13
|
+
and capped at a maximum count. This fixes the prior bug where large PDF business-plan templates
|
|
14
|
+
outranked the real brand assets (logo.svg / og.png) that Claude Design actually wants.
|
|
15
|
+
|
|
16
|
+
Explicit URL references can be passed (repeatable) and are echoed back alongside the discovered files.
|
|
17
|
+
|
|
18
|
+
Designed to be robust: never crashes on a bad/unreadable path (PermissionError, FileNotFoundError,
|
|
19
|
+
OSError are all swallowed gracefully). stdlib only.
|
|
20
|
+
|
|
21
|
+
CLI:
|
|
22
|
+
python3 gather_references.py [--root DIR] [--max N] [--max-mb FLOAT] [--url URL ...]
|
|
23
|
+
[--ext .png,.jpg,...] [--exclude GLOB ...] [--depth N] [--include-docs] [--json]
|
|
24
|
+
|
|
25
|
+
Output:
|
|
26
|
+
With --json: a JSON object
|
|
27
|
+
{"root": ..., "files": [{"path","type","bytes","mtime_iso"}], "urls": [...],
|
|
28
|
+
"truncated": bool, "ranked_by": ..., "note": ...}
|
|
29
|
+
Without --json: a short human-readable summary.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
import argparse
|
|
33
|
+
import fnmatch
|
|
34
|
+
import json
|
|
35
|
+
import os
|
|
36
|
+
import re
|
|
37
|
+
import sys
|
|
38
|
+
from datetime import datetime, timezone
|
|
39
|
+
|
|
40
|
+
# ---------------------------------------------------------------------------
|
|
41
|
+
# Defaults (mirrored in the SKILL.md file contract)
|
|
42
|
+
# ---------------------------------------------------------------------------
|
|
43
|
+
|
|
44
|
+
DEFAULT_MAX_FILES = 12
|
|
45
|
+
DEFAULT_MAX_MB = 8.0
|
|
46
|
+
DEFAULT_DEPTH = 4
|
|
47
|
+
|
|
48
|
+
# v2: VISUAL-ONLY by default. Brand visuals, not document templates.
|
|
49
|
+
DEFAULT_EXTENSIONS = [
|
|
50
|
+
".png", ".jpg", ".jpeg", ".webp", ".svg", ".avif", ".gif",
|
|
51
|
+
]
|
|
52
|
+
|
|
53
|
+
# Document extensions are never visual brand assets. Excluded from the default
|
|
54
|
+
# scan; only included when --include-docs is passed (they are then *added* to the
|
|
55
|
+
# active extension set, alongside whatever visual/--ext set is in effect).
|
|
56
|
+
DOC_EXTENSIONS = [
|
|
57
|
+
".pdf",
|
|
58
|
+
".hwp", ".hwpx",
|
|
59
|
+
".doc", ".docx",
|
|
60
|
+
".ppt", ".pptx",
|
|
61
|
+
".xls", ".xlsx",
|
|
62
|
+
]
|
|
63
|
+
|
|
64
|
+
DEFAULT_EXCLUDED_DIRS = [
|
|
65
|
+
"node_modules", ".git", "dist", "build", ".next", ".nuxt", "out",
|
|
66
|
+
"venv", ".venv", "__pycache__", ".cache", "coverage", "target",
|
|
67
|
+
"Pods", ".gradle", ".idea", ".DS_Store",
|
|
68
|
+
]
|
|
69
|
+
|
|
70
|
+
# v2: brand-likelihood ranking. A filename whose basename contains one of these
|
|
71
|
+
# tokens (as a whole token, delimited by start/end of name or any non-alphanumeric
|
|
72
|
+
# separator) is treated as a likely real brand asset and floated to the top of the
|
|
73
|
+
# results, ahead of generic screenshots / incidental images.
|
|
74
|
+
#
|
|
75
|
+
# Token boundaries matter: the short "og" token (Open Graph: og.png / og-image.png)
|
|
76
|
+
# must NOT match as a substring inside ordinary words like dog/frog/blog/logout/
|
|
77
|
+
# progress/recognition/cognac/biography, and "logo" must not match "logout". The
|
|
78
|
+
# leading group consumes one separator (or start-of-string); the trailing boundary
|
|
79
|
+
# is a zero-width lookahead so adjacent tokens (e.g. "icon-logo") both still match.
|
|
80
|
+
BRAND_KEYWORD_PATTERN = re.compile(
|
|
81
|
+
r"(?:^|[^a-z0-9])"
|
|
82
|
+
r"(?:logo|wordmark|symbol|icon|brand|hero|cover|favicon|app[-_]?icon|og[-_]?image|og)"
|
|
83
|
+
r"(?=[^a-z0-9]|$)",
|
|
84
|
+
re.IGNORECASE,
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
RANKED_BY = "brand-likelihood, then mtime-desc"
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
# ---------------------------------------------------------------------------
|
|
91
|
+
# Helpers
|
|
92
|
+
# ---------------------------------------------------------------------------
|
|
93
|
+
|
|
94
|
+
def _normalize_extensions(raw):
|
|
95
|
+
"""Turn a comma-separated extension string into a lowercased set with leading dots.
|
|
96
|
+
|
|
97
|
+
Accepts forms like ".png,jpg, .JPEG" and yields {".png", ".jpg", ".jpeg"}.
|
|
98
|
+
Returns None to signal "fall back to defaults" when nothing usable is parsed.
|
|
99
|
+
"""
|
|
100
|
+
if raw is None:
|
|
101
|
+
return None
|
|
102
|
+
exts = set()
|
|
103
|
+
for part in str(raw).split(","):
|
|
104
|
+
part = part.strip().lower()
|
|
105
|
+
if not part:
|
|
106
|
+
continue
|
|
107
|
+
if not part.startswith("."):
|
|
108
|
+
part = "." + part
|
|
109
|
+
exts.add(part)
|
|
110
|
+
return exts or None
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def _ext_of(path):
|
|
114
|
+
"""Lowercased file extension including the leading dot (e.g. '.png'), or '' if none."""
|
|
115
|
+
return os.path.splitext(path)[1].lower()
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def _type_of(path):
|
|
119
|
+
"""Lowercased extension WITHOUT the leading dot (e.g. 'png'), or '' if none."""
|
|
120
|
+
return _ext_of(path).lstrip(".")
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def _mtime_iso(epoch):
|
|
124
|
+
"""Convert an epoch float to an ISO-8601 UTC string; never raises."""
|
|
125
|
+
try:
|
|
126
|
+
return datetime.fromtimestamp(epoch, tz=timezone.utc).isoformat()
|
|
127
|
+
except (OverflowError, OSError, ValueError):
|
|
128
|
+
return ""
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def _is_excluded_dir(name, exclude_globs):
|
|
132
|
+
"""True if a directory basename matches any exclude glob (or default excluded set)."""
|
|
133
|
+
for pattern in exclude_globs:
|
|
134
|
+
if fnmatch.fnmatch(name, pattern):
|
|
135
|
+
return True
|
|
136
|
+
return False
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def _brand_score(path):
|
|
140
|
+
"""Heuristic brand-likelihood score for a file path.
|
|
141
|
+
|
|
142
|
+
Higher = more likely to be a real brand asset Claude Design should reference.
|
|
143
|
+
Scored on the *filename* (the basename), case-insensitively, against
|
|
144
|
+
BRAND_KEYWORD_PATTERN (which matches brand tokens only at token boundaries, so
|
|
145
|
+
"og" matches og.png/og-image but never dog/blog/logout). Returns 1 if any brand
|
|
146
|
+
token is present, else 0.
|
|
147
|
+
|
|
148
|
+
This is intentionally a coarse binary signal: brand-likelihood is the PRIMARY
|
|
149
|
+
sort key, with mtime as the tie-breaker, so we only need to separate "looks
|
|
150
|
+
like a brand asset" from "everything else". Never raises.
|
|
151
|
+
"""
|
|
152
|
+
try:
|
|
153
|
+
name = os.path.basename(path)
|
|
154
|
+
except Exception:
|
|
155
|
+
return 0
|
|
156
|
+
return 1 if BRAND_KEYWORD_PATTERN.search(name) else 0
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def gather(root, max_files, max_mb, extensions, exclude_globs, depth):
|
|
160
|
+
"""Walk `root` up to `depth` levels, collecting matching files.
|
|
161
|
+
|
|
162
|
+
Returns (files, truncated, note) where:
|
|
163
|
+
- files: list of dicts {path, type, bytes, mtime_iso}, ranked by brand-likelihood
|
|
164
|
+
first then mtime-desc, size-filtered, and capped.
|
|
165
|
+
- truncated: True if matching files were dropped because of the cap. A negative
|
|
166
|
+
`max_files` means "keep everything", so it is never truncated.
|
|
167
|
+
- note: optional human note (e.g. about an unreadable root); '' otherwise.
|
|
168
|
+
"""
|
|
169
|
+
note = ""
|
|
170
|
+
matched = [] # list of (brand_score, mtime, path, size)
|
|
171
|
+
|
|
172
|
+
try:
|
|
173
|
+
abs_root = os.path.abspath(root)
|
|
174
|
+
except (OSError, ValueError):
|
|
175
|
+
return [], False, "Could not resolve root path: %r" % (root,)
|
|
176
|
+
|
|
177
|
+
if not os.path.exists(abs_root):
|
|
178
|
+
return [], False, "Root path does not exist: %s" % abs_root
|
|
179
|
+
if not os.path.isdir(abs_root):
|
|
180
|
+
return [], False, "Root path is not a directory: %s" % abs_root
|
|
181
|
+
|
|
182
|
+
max_bytes = None
|
|
183
|
+
if max_mb is not None and max_mb >= 0:
|
|
184
|
+
max_bytes = int(max_mb * 1024 * 1024)
|
|
185
|
+
|
|
186
|
+
root_depth = abs_root.rstrip(os.sep).count(os.sep)
|
|
187
|
+
|
|
188
|
+
try:
|
|
189
|
+
# os.walk does NOT follow directory symlinks by default, so symlink loops cannot hang us.
|
|
190
|
+
walker = os.walk(abs_root, topdown=True, onerror=lambda err: None)
|
|
191
|
+
for current_dir, dirnames, filenames in walker:
|
|
192
|
+
# Enforce bounded depth by pruning descent once we exceed the limit.
|
|
193
|
+
try:
|
|
194
|
+
current_depth = current_dir.rstrip(os.sep).count(os.sep) - root_depth
|
|
195
|
+
except Exception:
|
|
196
|
+
current_depth = 0
|
|
197
|
+
if current_depth >= depth:
|
|
198
|
+
# Do not descend any deeper.
|
|
199
|
+
dirnames[:] = []
|
|
200
|
+
else:
|
|
201
|
+
# Prune excluded directories IN PLACE so os.walk skips them entirely.
|
|
202
|
+
dirnames[:] = [
|
|
203
|
+
d for d in dirnames if not _is_excluded_dir(d, exclude_globs)
|
|
204
|
+
]
|
|
205
|
+
|
|
206
|
+
for fname in filenames:
|
|
207
|
+
if _ext_of(fname) not in extensions:
|
|
208
|
+
continue
|
|
209
|
+
fpath = os.path.join(current_dir, fname)
|
|
210
|
+
try:
|
|
211
|
+
st = os.stat(fpath)
|
|
212
|
+
except (PermissionError, FileNotFoundError, OSError):
|
|
213
|
+
# Unreadable entry or broken symlink; skip gracefully.
|
|
214
|
+
continue
|
|
215
|
+
# Skip anything that isn't a regular file (e.g. a directory named "foo.png").
|
|
216
|
+
if not os.path.isfile(fpath):
|
|
217
|
+
continue
|
|
218
|
+
size = st.st_size
|
|
219
|
+
if max_bytes is not None and size > max_bytes:
|
|
220
|
+
continue
|
|
221
|
+
matched.append((_brand_score(fpath), st.st_mtime, fpath, size))
|
|
222
|
+
except (PermissionError, FileNotFoundError, OSError) as exc:
|
|
223
|
+
note = "Walk interrupted: %s" % exc
|
|
224
|
+
|
|
225
|
+
# v2 ranking: brand-likelihood FIRST (high score first), then newest first.
|
|
226
|
+
matched.sort(key=lambda item: (item[0], item[1]), reverse=True)
|
|
227
|
+
|
|
228
|
+
if max_files >= 0:
|
|
229
|
+
truncated = len(matched) > max_files
|
|
230
|
+
kept = matched[:max_files]
|
|
231
|
+
else:
|
|
232
|
+
# Negative cap means "keep everything"; nothing is dropped.
|
|
233
|
+
truncated = False
|
|
234
|
+
kept = matched
|
|
235
|
+
|
|
236
|
+
files = []
|
|
237
|
+
for _score, mtime, fpath, size in kept:
|
|
238
|
+
files.append({
|
|
239
|
+
"path": fpath,
|
|
240
|
+
"type": _type_of(fpath),
|
|
241
|
+
"bytes": size,
|
|
242
|
+
"mtime_iso": _mtime_iso(mtime),
|
|
243
|
+
})
|
|
244
|
+
|
|
245
|
+
return files, truncated, note
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
# ---------------------------------------------------------------------------
|
|
249
|
+
# CLI
|
|
250
|
+
# ---------------------------------------------------------------------------
|
|
251
|
+
|
|
252
|
+
def build_parser():
|
|
253
|
+
parser = argparse.ArgumentParser(
|
|
254
|
+
prog="gather_references.py",
|
|
255
|
+
description=(
|
|
256
|
+
"Scan a directory for brand/visual reference assets and collect explicit URL "
|
|
257
|
+
"references for the claude-design skill. Visual-only by default; documents "
|
|
258
|
+
"(.pdf/.hwp/.doc/.ppt/.xls) are excluded unless --include-docs is given. "
|
|
259
|
+
"Results are ranked by brand-likelihood first, then by modification time."
|
|
260
|
+
),
|
|
261
|
+
)
|
|
262
|
+
parser.add_argument(
|
|
263
|
+
"--root",
|
|
264
|
+
default=os.getcwd(),
|
|
265
|
+
help="Directory to scan (default: current working directory).",
|
|
266
|
+
)
|
|
267
|
+
parser.add_argument(
|
|
268
|
+
"--max",
|
|
269
|
+
dest="max_files",
|
|
270
|
+
type=int,
|
|
271
|
+
default=DEFAULT_MAX_FILES,
|
|
272
|
+
help="Maximum number of files to return (default: %d)." % DEFAULT_MAX_FILES,
|
|
273
|
+
)
|
|
274
|
+
parser.add_argument(
|
|
275
|
+
"--max-mb",
|
|
276
|
+
dest="max_mb",
|
|
277
|
+
type=float,
|
|
278
|
+
default=DEFAULT_MAX_MB,
|
|
279
|
+
help="Skip files larger than this many megabytes (default: %s)." % DEFAULT_MAX_MB,
|
|
280
|
+
)
|
|
281
|
+
parser.add_argument(
|
|
282
|
+
"--url",
|
|
283
|
+
dest="urls",
|
|
284
|
+
action="append",
|
|
285
|
+
default=[],
|
|
286
|
+
metavar="URL",
|
|
287
|
+
help="Explicit URL reference (repeatable).",
|
|
288
|
+
)
|
|
289
|
+
parser.add_argument(
|
|
290
|
+
"--ext",
|
|
291
|
+
dest="ext",
|
|
292
|
+
default=None,
|
|
293
|
+
metavar=".png,.jpg,...",
|
|
294
|
+
help=(
|
|
295
|
+
"Comma-separated list of extensions to match. Overrides the visual-only "
|
|
296
|
+
"default (%s). If --include-docs is also passed, document extensions are "
|
|
297
|
+
"added on top of this set." % ",".join(DEFAULT_EXTENSIONS)
|
|
298
|
+
),
|
|
299
|
+
)
|
|
300
|
+
parser.add_argument(
|
|
301
|
+
"--include-docs",
|
|
302
|
+
dest="include_docs",
|
|
303
|
+
action="store_true",
|
|
304
|
+
help=(
|
|
305
|
+
"Also include document files (%s). These are EXCLUDED by default because "
|
|
306
|
+
"they are document templates, not brand visuals." % ",".join(DOC_EXTENSIONS)
|
|
307
|
+
),
|
|
308
|
+
)
|
|
309
|
+
parser.add_argument(
|
|
310
|
+
"--exclude",
|
|
311
|
+
dest="exclude",
|
|
312
|
+
action="append",
|
|
313
|
+
default=[],
|
|
314
|
+
metavar="GLOB",
|
|
315
|
+
help="Additional directory-name glob(s) to exclude (repeatable).",
|
|
316
|
+
)
|
|
317
|
+
parser.add_argument(
|
|
318
|
+
"--depth",
|
|
319
|
+
type=int,
|
|
320
|
+
default=DEFAULT_DEPTH,
|
|
321
|
+
help="Maximum directory depth to descend (default: %d)." % DEFAULT_DEPTH,
|
|
322
|
+
)
|
|
323
|
+
parser.add_argument(
|
|
324
|
+
"--json",
|
|
325
|
+
action="store_true",
|
|
326
|
+
help="Emit a JSON object instead of a human-readable summary.",
|
|
327
|
+
)
|
|
328
|
+
return parser
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
def _human_size(num_bytes):
|
|
332
|
+
"""Render a byte count as a compact human-readable string."""
|
|
333
|
+
try:
|
|
334
|
+
value = float(num_bytes)
|
|
335
|
+
except (TypeError, ValueError):
|
|
336
|
+
return "?"
|
|
337
|
+
for unit in ("B", "KB", "MB", "GB", "TB"):
|
|
338
|
+
if value < 1024.0 or unit == "TB":
|
|
339
|
+
if unit == "B":
|
|
340
|
+
return "%d %s" % (int(value), unit)
|
|
341
|
+
return "%.1f %s" % (value, unit)
|
|
342
|
+
value /= 1024.0
|
|
343
|
+
return "%.1f TB" % value
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
def main(argv=None):
|
|
347
|
+
parser = build_parser()
|
|
348
|
+
args = parser.parse_args(argv)
|
|
349
|
+
|
|
350
|
+
# Resolve the active extension set:
|
|
351
|
+
# - --ext (if given) replaces the visual-only default;
|
|
352
|
+
# - otherwise use the visual-only default;
|
|
353
|
+
# - --include-docs ADDS document extensions on top of whichever set is active.
|
|
354
|
+
extensions = _normalize_extensions(args.ext)
|
|
355
|
+
if extensions is None:
|
|
356
|
+
extensions = set(DEFAULT_EXTENSIONS)
|
|
357
|
+
if args.include_docs:
|
|
358
|
+
extensions = set(extensions) | set(DOC_EXTENSIONS)
|
|
359
|
+
|
|
360
|
+
exclude_globs = list(DEFAULT_EXCLUDED_DIRS) + list(args.exclude or [])
|
|
361
|
+
|
|
362
|
+
depth = args.depth if args.depth is not None and args.depth >= 0 else DEFAULT_DEPTH
|
|
363
|
+
|
|
364
|
+
files, truncated, note = gather(
|
|
365
|
+
root=args.root,
|
|
366
|
+
max_files=args.max_files,
|
|
367
|
+
max_mb=args.max_mb,
|
|
368
|
+
extensions=extensions,
|
|
369
|
+
exclude_globs=exclude_globs,
|
|
370
|
+
depth=depth,
|
|
371
|
+
)
|
|
372
|
+
|
|
373
|
+
urls = list(args.urls or [])
|
|
374
|
+
|
|
375
|
+
try:
|
|
376
|
+
abs_root = os.path.abspath(args.root)
|
|
377
|
+
except (OSError, ValueError):
|
|
378
|
+
abs_root = args.root
|
|
379
|
+
|
|
380
|
+
if args.json:
|
|
381
|
+
payload = {
|
|
382
|
+
"root": abs_root,
|
|
383
|
+
"files": files,
|
|
384
|
+
"urls": urls,
|
|
385
|
+
"truncated": truncated,
|
|
386
|
+
"ranked_by": RANKED_BY,
|
|
387
|
+
"note": note,
|
|
388
|
+
}
|
|
389
|
+
print(json.dumps(payload, ensure_ascii=False, indent=2))
|
|
390
|
+
return 0
|
|
391
|
+
|
|
392
|
+
# Human-readable summary.
|
|
393
|
+
lines = []
|
|
394
|
+
lines.append("Reference scan root: %s" % abs_root)
|
|
395
|
+
lines.append(
|
|
396
|
+
"Found %d file(s) (cap=%d, max-mb=%s, depth=%d, docs=%s)%s"
|
|
397
|
+
% (
|
|
398
|
+
len(files),
|
|
399
|
+
args.max_files,
|
|
400
|
+
args.max_mb,
|
|
401
|
+
depth,
|
|
402
|
+
"included" if args.include_docs else "excluded",
|
|
403
|
+
" [truncated: more matched than shown]" if truncated else "",
|
|
404
|
+
)
|
|
405
|
+
)
|
|
406
|
+
lines.append("Ranked by: %s" % RANKED_BY)
|
|
407
|
+
if files:
|
|
408
|
+
lines.append("")
|
|
409
|
+
for entry in files:
|
|
410
|
+
lines.append(
|
|
411
|
+
" - %s [%s, %s, %s]"
|
|
412
|
+
% (
|
|
413
|
+
entry["path"],
|
|
414
|
+
entry["type"] or "?",
|
|
415
|
+
_human_size(entry["bytes"]),
|
|
416
|
+
entry["mtime_iso"] or "?",
|
|
417
|
+
)
|
|
418
|
+
)
|
|
419
|
+
else:
|
|
420
|
+
lines.append(" (no matching brand/visual assets found)")
|
|
421
|
+
|
|
422
|
+
if urls:
|
|
423
|
+
lines.append("")
|
|
424
|
+
lines.append("URL references (%d):" % len(urls))
|
|
425
|
+
for url in urls:
|
|
426
|
+
lines.append(" - %s" % url)
|
|
427
|
+
|
|
428
|
+
if note:
|
|
429
|
+
lines.append("")
|
|
430
|
+
lines.append("Note: %s" % note)
|
|
431
|
+
|
|
432
|
+
print("\n".join(lines))
|
|
433
|
+
return 0
|
|
434
|
+
|
|
435
|
+
|
|
436
|
+
if __name__ == "__main__":
|
|
437
|
+
sys.exit(main())
|
|
@@ -8,7 +8,7 @@ homepage: "https://m.bunjang.co.kr"
|
|
|
8
8
|
primary_color: "#d80c18"
|
|
9
9
|
logo:
|
|
10
10
|
type: favicon
|
|
11
|
-
slug: "https://
|
|
11
|
+
slug: "https://static.bunjang.co.kr/web/ui/favicon.ico"
|
|
12
12
|
verified: "2026-05-14"
|
|
13
13
|
omd: "0.1"
|
|
14
14
|
---
|