catime 0.2.0__py3-none-any.whl → 0.4.0__py3-none-any.whl
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.
- catime/__init__.py +1 -1
- catime/cli.py +42 -1
- catime/docs/app.js +223 -0
- catime/docs/apple-touch-icon.png +0 -0
- catime/docs/favicon-32.png +0 -0
- catime/docs/favicon.ico +0 -0
- catime/docs/icon-192.png +0 -0
- catime/docs/index.html +58 -0
- catime/docs/style.css +294 -0
- catime-0.4.0.dist-info/METADATA +45 -0
- catime-0.4.0.dist-info/RECORD +14 -0
- catime-0.2.0.dist-info/METADATA +0 -25
- catime-0.2.0.dist-info/RECORD +0 -7
- {catime-0.2.0.dist-info → catime-0.4.0.dist-info}/WHEEL +0 -0
- {catime-0.2.0.dist-info → catime-0.4.0.dist-info}/entry_points.txt +0 -0
- {catime-0.2.0.dist-info → catime-0.4.0.dist-info}/licenses/LICENSE +0 -0
catime/__init__.py
CHANGED
catime/cli.py
CHANGED
|
@@ -64,14 +64,48 @@ def filter_by_query(cats: list[dict], query: str) -> list[dict]:
|
|
|
64
64
|
return []
|
|
65
65
|
|
|
66
66
|
|
|
67
|
+
def cmd_view(args):
|
|
68
|
+
"""Serve the cat gallery locally in a browser."""
|
|
69
|
+
import http.server
|
|
70
|
+
import functools
|
|
71
|
+
import threading
|
|
72
|
+
import webbrowser
|
|
73
|
+
|
|
74
|
+
docs_dir = Path(__file__).resolve().parent / "docs"
|
|
75
|
+
if not docs_dir.exists():
|
|
76
|
+
# Fallback: project root docs/
|
|
77
|
+
docs_dir = Path(__file__).resolve().parent.parent.parent / "docs"
|
|
78
|
+
if not docs_dir.exists():
|
|
79
|
+
print("Error: docs/ directory not found.", file=sys.stderr)
|
|
80
|
+
sys.exit(1)
|
|
81
|
+
|
|
82
|
+
port = args.port
|
|
83
|
+
handler = functools.partial(http.server.SimpleHTTPRequestHandler, directory=str(docs_dir))
|
|
84
|
+
server = http.server.HTTPServer(("127.0.0.1", port), handler)
|
|
85
|
+
url = f"http://127.0.0.1:{port}"
|
|
86
|
+
print(f"Serving cat gallery at {url}")
|
|
87
|
+
threading.Timer(0.5, lambda: webbrowser.open(url)).start()
|
|
88
|
+
try:
|
|
89
|
+
server.serve_forever()
|
|
90
|
+
except KeyboardInterrupt:
|
|
91
|
+
print("\nStopped.")
|
|
92
|
+
|
|
93
|
+
|
|
67
94
|
def main():
|
|
95
|
+
# Handle 'view' subcommand separately to avoid argparse conflicts
|
|
96
|
+
if len(sys.argv) >= 2 and sys.argv[1] == "view":
|
|
97
|
+
view_parser = argparse.ArgumentParser(prog="catime view")
|
|
98
|
+
view_parser.add_argument("--port", type=int, default=8000, help="Port (default: 8000)")
|
|
99
|
+
cmd_view(view_parser.parse_args(sys.argv[2:]))
|
|
100
|
+
return
|
|
101
|
+
|
|
68
102
|
parser = argparse.ArgumentParser(
|
|
69
103
|
prog="catime",
|
|
70
104
|
description="View AI-generated hourly cat images",
|
|
71
105
|
)
|
|
72
106
|
parser.add_argument(
|
|
73
107
|
"query", nargs="?",
|
|
74
|
-
help="Cat number (e.g. 42), date (2026-01-30), date+hour (2026-01-30T05), 'today', or '
|
|
108
|
+
help="Cat number (e.g. 42), date (2026-01-30), date+hour (2026-01-30T05), 'today', 'yesterday', or 'view'.",
|
|
75
109
|
)
|
|
76
110
|
parser.add_argument("--repo", default=DEFAULT_REPO, help="GitHub repo owner/name")
|
|
77
111
|
parser.add_argument("--local", action="store_true", help="Use local catlist.json")
|
|
@@ -103,7 +137,14 @@ def main():
|
|
|
103
137
|
print(" catime yesterday List yesterday's cats")
|
|
104
138
|
print(" catime 2026-01-30 List all cats from a date")
|
|
105
139
|
print(" catime 2026-01-30T05 View the cat from a specific hour")
|
|
140
|
+
print(" catime latest View the latest cat")
|
|
106
141
|
print(" catime --list List all cats")
|
|
142
|
+
print(" catime view Open cat gallery in browser")
|
|
143
|
+
return
|
|
144
|
+
|
|
145
|
+
# latest
|
|
146
|
+
if args.query == "latest":
|
|
147
|
+
print_cat(cats[-1], len(cats))
|
|
107
148
|
return
|
|
108
149
|
|
|
109
150
|
# Try as number first
|
catime/docs/app.js
ADDED
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
(function () {
|
|
2
|
+
const CATLIST_URL = "https://raw.githubusercontent.com/yazelin/catime/main/catlist.json";
|
|
3
|
+
const PAGE_SIZE = 20;
|
|
4
|
+
|
|
5
|
+
let allCats = [];
|
|
6
|
+
let filtered = [];
|
|
7
|
+
let loaded = 0;
|
|
8
|
+
let loading = false;
|
|
9
|
+
let selectedDate = ""; // "YYYY-MM-DD" or ""
|
|
10
|
+
|
|
11
|
+
const gallery = document.getElementById("gallery");
|
|
12
|
+
const endMsg = document.getElementById("end-msg");
|
|
13
|
+
const modelSelect = document.getElementById("model-filter");
|
|
14
|
+
const timelineList = document.getElementById("timeline-list");
|
|
15
|
+
const timelineNav = document.getElementById("timeline");
|
|
16
|
+
const timelineToggle = document.getElementById("timeline-toggle");
|
|
17
|
+
const lightbox = document.getElementById("lightbox");
|
|
18
|
+
const lbImg = document.getElementById("lb-img");
|
|
19
|
+
const lbInfo = document.getElementById("lb-info");
|
|
20
|
+
const lbClose = document.getElementById("lb-close");
|
|
21
|
+
|
|
22
|
+
// Date picker elements
|
|
23
|
+
const datePickerBtn = document.getElementById("date-picker-btn");
|
|
24
|
+
const dateDropdown = document.getElementById("date-dropdown");
|
|
25
|
+
const ddPrev = document.getElementById("dd-prev");
|
|
26
|
+
const ddNext = document.getElementById("dd-next");
|
|
27
|
+
const ddMonthLabel = document.getElementById("dd-month-label");
|
|
28
|
+
const ddDays = document.getElementById("dd-days");
|
|
29
|
+
const ddClear = document.getElementById("dd-clear");
|
|
30
|
+
|
|
31
|
+
let calYear = new Date().getFullYear();
|
|
32
|
+
let calMonth = new Date().getMonth();
|
|
33
|
+
let catDates = new Set();
|
|
34
|
+
|
|
35
|
+
// Fetch data
|
|
36
|
+
fetch(CATLIST_URL)
|
|
37
|
+
.then(r => r.json())
|
|
38
|
+
.then(data => {
|
|
39
|
+
allCats = data.filter(c => c.status !== "failed").reverse();
|
|
40
|
+
allCats.forEach(c => catDates.add(c.timestamp.split(" ")[0]));
|
|
41
|
+
populateModels();
|
|
42
|
+
buildTimeline();
|
|
43
|
+
// Init calendar to latest cat's month
|
|
44
|
+
if (allCats.length) {
|
|
45
|
+
const parts = allCats[0].timestamp.split(" ")[0].split("-");
|
|
46
|
+
calYear = parseInt(parts[0], 10);
|
|
47
|
+
calMonth = parseInt(parts[1], 10) - 1;
|
|
48
|
+
}
|
|
49
|
+
applyFilter();
|
|
50
|
+
})
|
|
51
|
+
.catch(err => {
|
|
52
|
+
gallery.innerHTML = `<p style="padding:2rem;color:var(--pink)">Failed to load cat list: ${err.message}</p>`;
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
function populateModels() {
|
|
56
|
+
const models = [...new Set(allCats.map(c => c.model).filter(Boolean))].sort();
|
|
57
|
+
models.forEach(m => {
|
|
58
|
+
const opt = document.createElement("option");
|
|
59
|
+
opt.value = m; opt.textContent = m;
|
|
60
|
+
modelSelect.appendChild(opt);
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function buildTimeline() {
|
|
65
|
+
const map = {};
|
|
66
|
+
allCats.forEach(c => {
|
|
67
|
+
const [date] = c.timestamp.split(" ");
|
|
68
|
+
const [y, m] = date.split("-");
|
|
69
|
+
if (!map[y]) map[y] = new Set();
|
|
70
|
+
map[y].add(m);
|
|
71
|
+
});
|
|
72
|
+
let html = "";
|
|
73
|
+
Object.keys(map).sort().reverse().forEach(y => {
|
|
74
|
+
html += `<div class="year">${y}</div>`;
|
|
75
|
+
[...map[y]].sort().reverse().forEach(m => {
|
|
76
|
+
html += `<a href="#" data-ym="${y}-${m}">${y}-${m}</a>`;
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
timelineList.innerHTML = html;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// ── Date picker ──
|
|
83
|
+
datePickerBtn.addEventListener("click", e => {
|
|
84
|
+
e.stopPropagation();
|
|
85
|
+
dateDropdown.classList.toggle("hidden");
|
|
86
|
+
if (!dateDropdown.classList.contains("hidden")) renderCalendar();
|
|
87
|
+
});
|
|
88
|
+
document.addEventListener("click", e => {
|
|
89
|
+
if (!dateDropdown.classList.contains("hidden") && !document.getElementById("date-picker").contains(e.target)) {
|
|
90
|
+
dateDropdown.classList.add("hidden");
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
ddPrev.addEventListener("click", () => { calMonth--; if (calMonth < 0) { calMonth = 11; calYear--; } renderCalendar(); });
|
|
94
|
+
ddNext.addEventListener("click", () => { calMonth++; if (calMonth > 11) { calMonth = 0; calYear++; } renderCalendar(); });
|
|
95
|
+
ddClear.addEventListener("click", () => {
|
|
96
|
+
selectedDate = "";
|
|
97
|
+
datePickerBtn.textContent = "\u{1f4c5} All Dates";
|
|
98
|
+
datePickerBtn.classList.remove("active");
|
|
99
|
+
dateDropdown.classList.add("hidden");
|
|
100
|
+
applyFilter();
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
function renderCalendar() {
|
|
104
|
+
const months = ["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];
|
|
105
|
+
ddMonthLabel.textContent = `${months[calMonth]} ${calYear}`;
|
|
106
|
+
const first = new Date(calYear, calMonth, 1);
|
|
107
|
+
const startDay = first.getDay();
|
|
108
|
+
const daysInMonth = new Date(calYear, calMonth + 1, 0).getDate();
|
|
109
|
+
const todayStr = new Date().toISOString().slice(0, 10);
|
|
110
|
+
|
|
111
|
+
let html = "";
|
|
112
|
+
// Empty cells before first day
|
|
113
|
+
for (let i = 0; i < startDay; i++) html += `<button class="other-month" disabled></button>`;
|
|
114
|
+
for (let d = 1; d <= daysInMonth; d++) {
|
|
115
|
+
const ds = `${calYear}-${String(calMonth + 1).padStart(2, "0")}-${String(d).padStart(2, "0")}`;
|
|
116
|
+
const cls = [];
|
|
117
|
+
if (ds === todayStr) cls.push("today");
|
|
118
|
+
if (ds === selectedDate) cls.push("selected");
|
|
119
|
+
if (catDates.has(ds)) cls.push("has-cat");
|
|
120
|
+
html += `<button data-date="${ds}" class="${cls.join(" ")}">${d}</button>`;
|
|
121
|
+
}
|
|
122
|
+
ddDays.innerHTML = html;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
ddDays.addEventListener("click", e => {
|
|
126
|
+
const date = e.target.dataset.date;
|
|
127
|
+
if (!date) return;
|
|
128
|
+
selectedDate = date;
|
|
129
|
+
datePickerBtn.textContent = "\u{1f4c5} " + date;
|
|
130
|
+
datePickerBtn.classList.add("active");
|
|
131
|
+
dateDropdown.classList.add("hidden");
|
|
132
|
+
applyFilter();
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
// ── Filter ──
|
|
136
|
+
function applyFilter() {
|
|
137
|
+
const model = modelSelect.value;
|
|
138
|
+
filtered = allCats.filter(c => {
|
|
139
|
+
if (model && c.model !== model) return false;
|
|
140
|
+
if (selectedDate && !c.timestamp.startsWith(selectedDate)) return false;
|
|
141
|
+
return true;
|
|
142
|
+
});
|
|
143
|
+
loaded = 0;
|
|
144
|
+
gallery.innerHTML = "";
|
|
145
|
+
endMsg.classList.add("loading");
|
|
146
|
+
endMsg.classList.remove("hidden");
|
|
147
|
+
loadMore();
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
modelSelect.addEventListener("change", applyFilter);
|
|
151
|
+
|
|
152
|
+
// ── Render cards ──
|
|
153
|
+
function loadMore() {
|
|
154
|
+
if (loading || loaded >= filtered.length) return;
|
|
155
|
+
loading = true;
|
|
156
|
+
const slice = filtered.slice(loaded, loaded + PAGE_SIZE);
|
|
157
|
+
let lastMonth = "";
|
|
158
|
+
if (loaded > 0) {
|
|
159
|
+
const prev = filtered[loaded - 1];
|
|
160
|
+
lastMonth = prev.timestamp.slice(0, 7);
|
|
161
|
+
}
|
|
162
|
+
const frag = document.createDocumentFragment();
|
|
163
|
+
slice.forEach(cat => {
|
|
164
|
+
const month = cat.timestamp.slice(0, 7);
|
|
165
|
+
if (month !== lastMonth) {
|
|
166
|
+
const sep = document.createElement("div");
|
|
167
|
+
sep.className = "month-sep";
|
|
168
|
+
sep.id = `m-${month}`;
|
|
169
|
+
sep.textContent = month;
|
|
170
|
+
frag.appendChild(sep);
|
|
171
|
+
lastMonth = month;
|
|
172
|
+
}
|
|
173
|
+
const card = document.createElement("div");
|
|
174
|
+
card.className = "card";
|
|
175
|
+
card.innerHTML = `
|
|
176
|
+
<img src="${cat.url}" alt="Cat #${cat.number}" loading="lazy">
|
|
177
|
+
<div class="card-info">
|
|
178
|
+
<div class="time">#${cat.number} · ${cat.timestamp}</div>
|
|
179
|
+
${cat.model ? `<span class="model">${cat.model}</span>` : ""}
|
|
180
|
+
</div>`;
|
|
181
|
+
card.addEventListener("click", () => openLightbox(cat));
|
|
182
|
+
frag.appendChild(card);
|
|
183
|
+
});
|
|
184
|
+
gallery.appendChild(frag);
|
|
185
|
+
loaded += slice.length;
|
|
186
|
+
loading = false;
|
|
187
|
+
if (loaded >= filtered.length) {
|
|
188
|
+
endMsg.classList.remove("loading");
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// ── Infinite scroll ──
|
|
193
|
+
const observer = new IntersectionObserver(entries => {
|
|
194
|
+
if (entries[0].isIntersecting) loadMore();
|
|
195
|
+
}, { rootMargin: "400px" });
|
|
196
|
+
observer.observe(endMsg);
|
|
197
|
+
|
|
198
|
+
// ── Timeline click ──
|
|
199
|
+
timelineList.addEventListener("click", e => {
|
|
200
|
+
e.preventDefault();
|
|
201
|
+
const ym = e.target.dataset.ym;
|
|
202
|
+
if (!ym) return;
|
|
203
|
+
const idx = filtered.findIndex(c => c.timestamp.startsWith(ym));
|
|
204
|
+
if (idx === -1) return;
|
|
205
|
+
while (loaded <= idx && loaded < filtered.length) loadMore();
|
|
206
|
+
const el = document.getElementById(`m-${ym}`);
|
|
207
|
+
if (el) el.scrollIntoView({ behavior: "smooth", block: "start" });
|
|
208
|
+
if (window.innerWidth <= 1024) timelineNav.classList.remove("open");
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
timelineToggle.addEventListener("click", () => timelineNav.classList.toggle("open"));
|
|
212
|
+
|
|
213
|
+
// ── Lightbox ──
|
|
214
|
+
function openLightbox(cat) {
|
|
215
|
+
lbImg.src = cat.url;
|
|
216
|
+
lbInfo.textContent = `#${cat.number} \u00b7 ${cat.timestamp} \u00b7 ${cat.model || ""}`;
|
|
217
|
+
lightbox.classList.remove("hidden");
|
|
218
|
+
}
|
|
219
|
+
lbClose.addEventListener("click", () => lightbox.classList.add("hidden"));
|
|
220
|
+
lightbox.addEventListener("click", e => { if (e.target === lightbox) lightbox.classList.add("hidden"); });
|
|
221
|
+
document.addEventListener("keydown", e => { if (e.key === "Escape") lightbox.classList.add("hidden"); });
|
|
222
|
+
|
|
223
|
+
})();
|
|
Binary file
|
|
Binary file
|
catime/docs/favicon.ico
ADDED
|
Binary file
|
catime/docs/icon-192.png
ADDED
|
Binary file
|
catime/docs/index.html
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="zh-TW">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>Catime - AI Cat Gallery</title>
|
|
7
|
+
<link rel="icon" href="favicon.ico" sizes="any">
|
|
8
|
+
<link rel="icon" href="favicon-32.png" type="image/png" sizes="32x32">
|
|
9
|
+
<link rel="icon" href="icon-192.png" type="image/png" sizes="192x192">
|
|
10
|
+
<link rel="apple-touch-icon" href="apple-touch-icon.png">
|
|
11
|
+
<link rel="stylesheet" href="style.css">
|
|
12
|
+
</head>
|
|
13
|
+
<body>
|
|
14
|
+
<header id="topbar">
|
|
15
|
+
<img src="icon-192.png" alt="Catime" class="logo-icon">
|
|
16
|
+
<h1>Catime</h1>
|
|
17
|
+
<div class="filters">
|
|
18
|
+
<select id="model-filter">
|
|
19
|
+
<option value="">All Models</option>
|
|
20
|
+
</select>
|
|
21
|
+
<div class="date-picker" id="date-picker">
|
|
22
|
+
<button class="date-picker-btn" id="date-picker-btn">📅 All Dates</button>
|
|
23
|
+
<div class="date-dropdown hidden" id="date-dropdown">
|
|
24
|
+
<div class="dd-header">
|
|
25
|
+
<button class="dd-nav" id="dd-prev">‹</button>
|
|
26
|
+
<span id="dd-month-label"></span>
|
|
27
|
+
<button class="dd-nav" id="dd-next">›</button>
|
|
28
|
+
</div>
|
|
29
|
+
<div class="dd-weekdays">
|
|
30
|
+
<span>Su</span><span>Mo</span><span>Tu</span><span>We</span><span>Th</span><span>Fr</span><span>Sa</span>
|
|
31
|
+
</div>
|
|
32
|
+
<div class="dd-days" id="dd-days"></div>
|
|
33
|
+
<button class="dd-clear" id="dd-clear">Clear</button>
|
|
34
|
+
</div>
|
|
35
|
+
</div>
|
|
36
|
+
</div>
|
|
37
|
+
<a href="https://github.com/yazelin/catime" target="_blank" rel="noopener" class="github-link" title="View on GitHub">
|
|
38
|
+
<svg viewBox="0 0 16 16" width="22" height="22" fill="currentColor"><path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27s1.36.09 2 .27c1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.01 8.01 0 0016 8c0-4.42-3.58-8-8-8z"/></svg>
|
|
39
|
+
</a>
|
|
40
|
+
</header>
|
|
41
|
+
|
|
42
|
+
<nav id="timeline" aria-label="Timeline navigation">
|
|
43
|
+
<button id="timeline-toggle" aria-label="Toggle timeline">📅</button>
|
|
44
|
+
<div id="timeline-list"></div>
|
|
45
|
+
</nav>
|
|
46
|
+
|
|
47
|
+
<main id="gallery" class="masonry"></main>
|
|
48
|
+
<div id="end-msg" class="hidden">No more cats! 🐱</div>
|
|
49
|
+
|
|
50
|
+
<div id="lightbox" class="hidden" role="dialog">
|
|
51
|
+
<button id="lb-close" aria-label="Close">×</button>
|
|
52
|
+
<img id="lb-img" src="" alt="Cat">
|
|
53
|
+
<div id="lb-info"></div>
|
|
54
|
+
</div>
|
|
55
|
+
|
|
56
|
+
<script src="app.js"></script>
|
|
57
|
+
</body>
|
|
58
|
+
</html>
|
catime/docs/style.css
ADDED
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
@import url('https://fonts.googleapis.com/css2?family=Nunito:wght@400;600;700;800&display=swap');
|
|
2
|
+
|
|
3
|
+
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
|
4
|
+
|
|
5
|
+
:root {
|
|
6
|
+
--pink: #ff6b9d;
|
|
7
|
+
--pink-light: #ffa5c8;
|
|
8
|
+
--pink-pale: #fff0f5;
|
|
9
|
+
--orange: #ffb347;
|
|
10
|
+
--red: #ff6b6b;
|
|
11
|
+
--blue: #74b9ff;
|
|
12
|
+
--green: #a8e6cf;
|
|
13
|
+
--purple: #c9b1ff;
|
|
14
|
+
--bg: #fff5f9;
|
|
15
|
+
--surface: #ffffff;
|
|
16
|
+
--card-1: #fff0f5;
|
|
17
|
+
--card-2: #f0f8ff;
|
|
18
|
+
--card-3: #f5fff0;
|
|
19
|
+
--card-4: #fef5ff;
|
|
20
|
+
--card-5: #fff8f0;
|
|
21
|
+
--text: #5a4a5a;
|
|
22
|
+
--text-muted: #b8a0b8;
|
|
23
|
+
--shadow: rgba(255, 107, 157, .15);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
body {
|
|
27
|
+
font-family: 'Nunito', system-ui, sans-serif;
|
|
28
|
+
background: var(--bg);
|
|
29
|
+
background-image:
|
|
30
|
+
radial-gradient(circle at 10% 20%, rgba(255,107,157,.06) 0%, transparent 50%),
|
|
31
|
+
radial-gradient(circle at 90% 80%, rgba(116,185,255,.06) 0%, transparent 50%),
|
|
32
|
+
radial-gradient(circle at 50% 50%, rgba(201,177,255,.05) 0%, transparent 50%);
|
|
33
|
+
color: var(--text);
|
|
34
|
+
min-height: 100vh;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/* Top bar */
|
|
38
|
+
#topbar {
|
|
39
|
+
position: fixed; top: 0; left: 0; right: 0; z-index: 100;
|
|
40
|
+
display: flex; align-items: center; gap: .6rem;
|
|
41
|
+
padding: .6rem 1rem;
|
|
42
|
+
background: linear-gradient(135deg, #fff 0%, var(--pink-pale) 100%);
|
|
43
|
+
border-bottom: 3px solid var(--pink-light);
|
|
44
|
+
box-shadow: 0 2px 16px var(--shadow);
|
|
45
|
+
flex-wrap: wrap;
|
|
46
|
+
}
|
|
47
|
+
.logo-icon {
|
|
48
|
+
width: 34px; height: 34px; border-radius: 50%;
|
|
49
|
+
flex-shrink: 0;
|
|
50
|
+
}
|
|
51
|
+
#topbar h1 {
|
|
52
|
+
font-size: 1.3rem; font-weight: 800; white-space: nowrap;
|
|
53
|
+
background: linear-gradient(135deg, #9b7ec8, var(--pink), #ffb347);
|
|
54
|
+
-webkit-background-clip: text; -webkit-text-fill-color: transparent;
|
|
55
|
+
background-clip: text;
|
|
56
|
+
}
|
|
57
|
+
.filters {
|
|
58
|
+
display: flex; gap: .5rem; flex: 1; justify-content: flex-end;
|
|
59
|
+
align-items: center; flex-wrap: wrap; min-width: 0;
|
|
60
|
+
}
|
|
61
|
+
.filters select {
|
|
62
|
+
padding: .45rem .8rem; border-radius: 20px;
|
|
63
|
+
border: 2px solid var(--pink-light);
|
|
64
|
+
background: #fff; color: var(--text); font-size: .85rem;
|
|
65
|
+
font-family: inherit; cursor: pointer;
|
|
66
|
+
transition: border-color .2s, box-shadow .2s;
|
|
67
|
+
min-width: 0;
|
|
68
|
+
}
|
|
69
|
+
.filters select:focus {
|
|
70
|
+
outline: none; border-color: var(--pink);
|
|
71
|
+
box-shadow: 0 0 0 3px rgba(255,107,157,.2);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/* Custom date picker */
|
|
75
|
+
.date-picker { position: relative; }
|
|
76
|
+
.date-picker-btn {
|
|
77
|
+
padding: .45rem .8rem; border-radius: 20px;
|
|
78
|
+
border: 2px solid var(--pink-light);
|
|
79
|
+
background: #fff; color: var(--text); font-size: .85rem;
|
|
80
|
+
font-family: inherit; cursor: pointer; white-space: nowrap;
|
|
81
|
+
transition: border-color .2s, box-shadow .2s;
|
|
82
|
+
}
|
|
83
|
+
.date-picker-btn:hover { border-color: var(--pink); }
|
|
84
|
+
.date-picker-btn.active {
|
|
85
|
+
border-color: var(--pink); background: var(--pink-pale);
|
|
86
|
+
}
|
|
87
|
+
.date-dropdown {
|
|
88
|
+
position: absolute; top: calc(100% + 6px); right: 0; z-index: 150;
|
|
89
|
+
background: #fff; border-radius: 16px; padding: .8rem;
|
|
90
|
+
border: 2px solid var(--pink-light);
|
|
91
|
+
box-shadow: 0 8px 30px var(--shadow);
|
|
92
|
+
width: 260px;
|
|
93
|
+
}
|
|
94
|
+
.dd-header {
|
|
95
|
+
display: flex; align-items: center; justify-content: space-between;
|
|
96
|
+
margin-bottom: .5rem;
|
|
97
|
+
}
|
|
98
|
+
.dd-header span {
|
|
99
|
+
font-weight: 800; font-size: .9rem;
|
|
100
|
+
background: linear-gradient(135deg, var(--pink), var(--purple));
|
|
101
|
+
-webkit-background-clip: text; -webkit-text-fill-color: transparent;
|
|
102
|
+
background-clip: text;
|
|
103
|
+
}
|
|
104
|
+
.dd-nav {
|
|
105
|
+
width: 30px; height: 30px; border-radius: 50%; border: none;
|
|
106
|
+
background: var(--pink-pale); color: var(--pink);
|
|
107
|
+
font-size: 1.1rem; font-weight: 800; cursor: pointer;
|
|
108
|
+
transition: background .2s;
|
|
109
|
+
}
|
|
110
|
+
.dd-nav:hover { background: var(--pink-light); color: #fff; }
|
|
111
|
+
.dd-weekdays {
|
|
112
|
+
display: grid; grid-template-columns: repeat(7, 1fr);
|
|
113
|
+
text-align: center; font-size: .7rem; font-weight: 700;
|
|
114
|
+
color: var(--text-muted); margin-bottom: .3rem;
|
|
115
|
+
}
|
|
116
|
+
.dd-days {
|
|
117
|
+
display: grid; grid-template-columns: repeat(7, 1fr); gap: 2px;
|
|
118
|
+
}
|
|
119
|
+
.dd-days button {
|
|
120
|
+
aspect-ratio: 1; border: none; border-radius: 50%;
|
|
121
|
+
background: transparent; color: var(--text); font-size: .78rem;
|
|
122
|
+
font-family: inherit; font-weight: 600; cursor: pointer;
|
|
123
|
+
transition: all .15s;
|
|
124
|
+
}
|
|
125
|
+
.dd-days button:hover { background: var(--pink-pale); }
|
|
126
|
+
.dd-days button.today { border: 2px solid var(--pink-light); }
|
|
127
|
+
.dd-days button.selected {
|
|
128
|
+
background: linear-gradient(135deg, var(--pink), var(--purple));
|
|
129
|
+
color: #fff;
|
|
130
|
+
}
|
|
131
|
+
.dd-days button.has-cat::after {
|
|
132
|
+
content: ""; display: block; width: 4px; height: 4px;
|
|
133
|
+
background: var(--pink); border-radius: 50%;
|
|
134
|
+
margin: -2px auto 0;
|
|
135
|
+
}
|
|
136
|
+
.dd-days button.selected.has-cat::after { background: #fff; }
|
|
137
|
+
.dd-days button.other-month { color: var(--text-muted); opacity: .4; }
|
|
138
|
+
.dd-clear {
|
|
139
|
+
display: block; width: 100%; margin-top: .5rem; padding: .4rem;
|
|
140
|
+
border: none; border-radius: 20px;
|
|
141
|
+
background: var(--pink-pale); color: var(--pink);
|
|
142
|
+
font-family: inherit; font-size: .8rem; font-weight: 700;
|
|
143
|
+
cursor: pointer; transition: background .2s;
|
|
144
|
+
}
|
|
145
|
+
.dd-clear:hover { background: var(--pink-light); color: #fff; }
|
|
146
|
+
|
|
147
|
+
/* GitHub link */
|
|
148
|
+
.github-link {
|
|
149
|
+
color: var(--pink); flex-shrink: 0;
|
|
150
|
+
transition: color .2s, transform .2s;
|
|
151
|
+
display: flex; align-items: center;
|
|
152
|
+
}
|
|
153
|
+
.github-link:hover { color: var(--purple); transform: scale(1.15) rotate(-8deg); }
|
|
154
|
+
|
|
155
|
+
/* Timeline sidebar */
|
|
156
|
+
#timeline {
|
|
157
|
+
position: fixed; top: 56px; right: 0; bottom: 0; width: 130px; z-index: 90;
|
|
158
|
+
background: linear-gradient(180deg, #fff 0%, var(--pink-pale) 100%);
|
|
159
|
+
border-left: 2px solid var(--pink-light);
|
|
160
|
+
overflow-y: auto; padding: .8rem 0;
|
|
161
|
+
transition: transform .3s ease;
|
|
162
|
+
}
|
|
163
|
+
#timeline::-webkit-scrollbar { width: 4px; }
|
|
164
|
+
#timeline::-webkit-scrollbar-thumb { background: var(--pink-light); border-radius: 4px; }
|
|
165
|
+
|
|
166
|
+
#timeline-toggle {
|
|
167
|
+
display: none; position: fixed; bottom: 1.2rem; right: 1.2rem; z-index: 91;
|
|
168
|
+
width: 48px; height: 48px; border-radius: 50%; border: none;
|
|
169
|
+
background: linear-gradient(135deg, var(--pink), var(--purple));
|
|
170
|
+
color: #fff; font-size: 1.3rem; cursor: pointer;
|
|
171
|
+
box-shadow: 0 4px 15px rgba(255,107,157,.35);
|
|
172
|
+
transition: transform .2s;
|
|
173
|
+
}
|
|
174
|
+
#timeline-toggle:hover { transform: scale(1.1); }
|
|
175
|
+
|
|
176
|
+
#timeline-list a {
|
|
177
|
+
display: block; padding: .3rem .8rem; color: var(--text-muted);
|
|
178
|
+
text-decoration: none; font-size: .8rem; font-weight: 600;
|
|
179
|
+
border-right: 3px solid transparent;
|
|
180
|
+
transition: all .2s;
|
|
181
|
+
}
|
|
182
|
+
#timeline-list a:hover {
|
|
183
|
+
color: var(--pink); background: rgba(255,107,157,.08);
|
|
184
|
+
border-right-color: var(--pink);
|
|
185
|
+
}
|
|
186
|
+
#timeline-list .year {
|
|
187
|
+
font-weight: 800; color: var(--purple); margin-top: .6rem;
|
|
188
|
+
padding: .2rem .8rem; font-size: .85rem;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/* Gallery masonry */
|
|
192
|
+
.masonry {
|
|
193
|
+
margin-top: 60px; margin-right: 130px; padding: 1.2rem;
|
|
194
|
+
column-count: 3; column-gap: 1.2rem;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/* Candy color rotation for cards */
|
|
198
|
+
.card {
|
|
199
|
+
break-inside: avoid; margin-bottom: 1.2rem;
|
|
200
|
+
border-radius: 16px; overflow: hidden;
|
|
201
|
+
cursor: pointer;
|
|
202
|
+
transition: transform .2s ease, box-shadow .2s ease;
|
|
203
|
+
border: 2px solid transparent;
|
|
204
|
+
}
|
|
205
|
+
.card:nth-child(5n+1) { background: var(--card-1); border-color: rgba(255,107,157,.2); }
|
|
206
|
+
.card:nth-child(5n+2) { background: var(--card-2); border-color: rgba(116,185,255,.2); }
|
|
207
|
+
.card:nth-child(5n+3) { background: var(--card-3); border-color: rgba(168,230,207,.25); }
|
|
208
|
+
.card:nth-child(5n+4) { background: var(--card-4); border-color: rgba(201,177,255,.25); }
|
|
209
|
+
.card:nth-child(5n+5) { background: var(--card-5); border-color: rgba(255,179,71,.2); }
|
|
210
|
+
|
|
211
|
+
.card:hover {
|
|
212
|
+
transform: translateY(-4px) scale(1.01);
|
|
213
|
+
box-shadow: 0 8px 25px var(--shadow);
|
|
214
|
+
}
|
|
215
|
+
.card img { width: 100%; display: block; border-radius: 14px 14px 0 0; }
|
|
216
|
+
.card-info { padding: .6rem .8rem; font-size: .78rem; }
|
|
217
|
+
.card-info .time { color: var(--text-muted); font-weight: 600; }
|
|
218
|
+
.card-info .model {
|
|
219
|
+
display: inline-block; margin-top: .3rem; padding: .15rem .55rem;
|
|
220
|
+
border-radius: 20px; font-size: .7rem; font-weight: 700;
|
|
221
|
+
color: #fff;
|
|
222
|
+
}
|
|
223
|
+
/* Rotate model tag colors */
|
|
224
|
+
.card:nth-child(5n+1) .model { background: linear-gradient(135deg, var(--pink), var(--red)); }
|
|
225
|
+
.card:nth-child(5n+2) .model { background: linear-gradient(135deg, var(--blue), var(--purple)); }
|
|
226
|
+
.card:nth-child(5n+3) .model { background: linear-gradient(135deg, var(--green), #6bcfa8); }
|
|
227
|
+
.card:nth-child(5n+4) .model { background: linear-gradient(135deg, var(--purple), var(--pink)); }
|
|
228
|
+
.card:nth-child(5n+5) .model { background: linear-gradient(135deg, var(--orange), var(--red)); }
|
|
229
|
+
|
|
230
|
+
/* Month separator */
|
|
231
|
+
.month-sep {
|
|
232
|
+
column-span: all; padding: .5rem 0; font-size: .95rem;
|
|
233
|
+
font-weight: 800; letter-spacing: .02em;
|
|
234
|
+
background: linear-gradient(135deg, var(--pink), var(--purple));
|
|
235
|
+
-webkit-background-clip: text; -webkit-text-fill-color: transparent;
|
|
236
|
+
background-clip: text;
|
|
237
|
+
border-bottom: 2px dashed var(--pink-light);
|
|
238
|
+
margin-bottom: .8rem;
|
|
239
|
+
}
|
|
240
|
+
.month-sep::before { content: "🌸 "; -webkit-text-fill-color: initial; }
|
|
241
|
+
|
|
242
|
+
/* End / scroll sentinel */
|
|
243
|
+
#end-msg {
|
|
244
|
+
text-align: center; padding: 2.5rem; font-size: 1rem;
|
|
245
|
+
color: var(--text-muted); font-weight: 700;
|
|
246
|
+
margin-right: 130px;
|
|
247
|
+
}
|
|
248
|
+
#end-msg.loading { visibility: hidden; height: 1px; padding: 0; }
|
|
249
|
+
.hidden { display: none !important; }
|
|
250
|
+
|
|
251
|
+
/* Lightbox */
|
|
252
|
+
#lightbox {
|
|
253
|
+
position: fixed; inset: 0; z-index: 200;
|
|
254
|
+
background: rgba(90, 74, 90, .8);
|
|
255
|
+
backdrop-filter: blur(8px);
|
|
256
|
+
display: flex; flex-direction: column;
|
|
257
|
+
align-items: center; justify-content: center;
|
|
258
|
+
}
|
|
259
|
+
#lb-close {
|
|
260
|
+
position: absolute; top: 1rem; right: 1.2rem;
|
|
261
|
+
background: none; border: none; color: #fff; font-size: 2.2rem; cursor: pointer;
|
|
262
|
+
text-shadow: 0 2px 8px rgba(0,0,0,.3);
|
|
263
|
+
transition: transform .2s;
|
|
264
|
+
}
|
|
265
|
+
#lb-close:hover { transform: scale(1.2) rotate(90deg); }
|
|
266
|
+
#lb-img {
|
|
267
|
+
max-width: 90vw; max-height: 80vh;
|
|
268
|
+
border-radius: 16px;
|
|
269
|
+
box-shadow: 0 12px 40px rgba(0,0,0,.3);
|
|
270
|
+
}
|
|
271
|
+
#lb-info {
|
|
272
|
+
margin-top: .8rem; color: #fff; font-size: .9rem;
|
|
273
|
+
font-weight: 600; text-shadow: 0 1px 4px rgba(0,0,0,.3);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/* Responsive */
|
|
277
|
+
@media (max-width: 1024px) {
|
|
278
|
+
.masonry { column-count: 2; margin-right: 0; }
|
|
279
|
+
#end-msg { margin-right: 0; }
|
|
280
|
+
#timeline { transform: translateX(100%); }
|
|
281
|
+
#timeline.open { transform: translateX(0); }
|
|
282
|
+
#timeline-toggle { display: block; }
|
|
283
|
+
}
|
|
284
|
+
@media (max-width: 600px) {
|
|
285
|
+
.masonry { column-count: 1; padding: .8rem; margin-top: 100px; }
|
|
286
|
+
#topbar { gap: .4rem; padding: .5rem .6rem; }
|
|
287
|
+
#topbar h1 { font-size: 1.1rem; }
|
|
288
|
+
.logo-icon { width: 28px; height: 28px; }
|
|
289
|
+
.filters { flex: 1 1 100%; order: 1; justify-content: flex-start; gap: .4rem; }
|
|
290
|
+
.filters select { font-size: .78rem; padding: .35rem .5rem; }
|
|
291
|
+
.date-picker-btn { font-size: .78rem; padding: .35rem .5rem; }
|
|
292
|
+
.date-dropdown { width: calc(100vw - 1.2rem); max-width: 280px; right: auto; left: 0; }
|
|
293
|
+
.github-link { margin-left: auto; }
|
|
294
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: catime
|
|
3
|
+
Version: 0.4.0
|
|
4
|
+
Summary: AI-generated hourly cat images - a new cat every hour!
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
License-File: LICENSE
|
|
7
|
+
Requires-Python: >=3.10
|
|
8
|
+
Requires-Dist: httpx
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
|
|
11
|
+
# <img src="docs/icon-192.png" width="32" height="32" alt="catime icon"> catime
|
|
12
|
+
|
|
13
|
+
AI-generated hourly cat images. A new cat every hour!
|
|
14
|
+
|
|
15
|
+
Every hour, a GitHub Actions workflow generates a unique cat image using [nanobanana-py](https://pypi.org/project/nanobanana-py/) (Gemini API), uploads it as a GitHub Release asset, and posts it to a monthly issue.
|
|
16
|
+
|
|
17
|
+
## Install & Usage
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
uvx catime # Show total cat count
|
|
21
|
+
uvx catime latest # View the latest cat
|
|
22
|
+
uvx catime 42 # View cat #42
|
|
23
|
+
uvx catime today # List today's cats
|
|
24
|
+
uvx catime yesterday # List yesterday's cats
|
|
25
|
+
uvx catime 2026-01-30 # List all cats from a date
|
|
26
|
+
uvx catime 2026-01-30T05 # View the cat from a specific hour
|
|
27
|
+
uvx catime --list # List all cats
|
|
28
|
+
uvx catime view # Open cat gallery in browser (localhost:8000)
|
|
29
|
+
uvx catime view --port 3000 # Use custom port
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## How It Works
|
|
33
|
+
|
|
34
|
+
- **Image generation:** [nanobanana-py](https://pypi.org/project/nanobanana-py/) with `gemini-3-pro-image-preview` (fallback: `gemini-2.5-flash-image`)
|
|
35
|
+
- **Image hosting:** GitHub Release assets
|
|
36
|
+
- **Cat gallery:** Monthly GitHub issues (auto-created)
|
|
37
|
+
- **Metadata:** `catlist.json` in the repo (records timestamp, model used, success/failure)
|
|
38
|
+
- **Gallery:** [GitHub Pages](https://yazelin.github.io/catime) waterfall gallery (`docs/`)
|
|
39
|
+
- **Schedule:** GitHub Actions cron, every hour at :00
|
|
40
|
+
|
|
41
|
+
## Setup (for your own repo)
|
|
42
|
+
|
|
43
|
+
1. Fork or clone this repo
|
|
44
|
+
2. Add `GEMINI_API_KEY` to repo Settings → Secrets
|
|
45
|
+
3. The workflow will auto-create monthly issues and a `cats` release
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
catime/__init__.py,sha256=i8FYHW-V2UO0kNEghreL2-uQ70fLxsGQ0z5kGoNEbfk,70
|
|
2
|
+
catime/cli.py,sha256=9wvMNzCYkhvEJGOycuLCUf4MbvkpOAHw7Sfg0M1bN80,5743
|
|
3
|
+
catime/docs/app.js,sha256=PyvA4Ai7Y_ZDPbwRDAsrv8aWi1yMZV7BqUraF4uIyPE,8261
|
|
4
|
+
catime/docs/apple-touch-icon.png,sha256=ZSipNfat3Wz3Gu3S5hTGPfIpc5hT_eDFFgScStJLzwQ,38872
|
|
5
|
+
catime/docs/favicon-32.png,sha256=13byvPFWFl_u2RiFITAIVZJZk75Ljo7_N_xS6NUmX_g,2496
|
|
6
|
+
catime/docs/favicon.ico,sha256=2LLdNzyOh5dCYMkpcOVCMyvYyg9PVJPTmmZ2APubvpQ,8050
|
|
7
|
+
catime/docs/icon-192.png,sha256=hx10FySGPXgaBVWMDLR6VJd9UBOfzybOeo9fNZMZTdw,43281
|
|
8
|
+
catime/docs/index.html,sha256=CMKUjEIfaEGu8B7xayHfslDVqfnSmXH10l4Qn3buk44,2801
|
|
9
|
+
catime/docs/style.css,sha256=sS5INs7BsUvP2e5yA2x3CzFVjJ7ODQ0B1nqJRAYUEyU,10429
|
|
10
|
+
catime-0.4.0.dist-info/METADATA,sha256=MFjNZp8nIrt-7m-dsYVbQFmPZIBTljTpjRWDJtfYbE0,1814
|
|
11
|
+
catime-0.4.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
12
|
+
catime-0.4.0.dist-info/entry_points.txt,sha256=oPgi6h026vMo9YyEOH3wuNtD3A28e-s1ChJs-KWWGXw,43
|
|
13
|
+
catime-0.4.0.dist-info/licenses/LICENSE,sha256=p_h5YRMaNCwMqGXX5KDrk49_NWGpzAJ2d6IhKa67B0E,1064
|
|
14
|
+
catime-0.4.0.dist-info/RECORD,,
|
catime-0.2.0.dist-info/METADATA
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: catime
|
|
3
|
-
Version: 0.2.0
|
|
4
|
-
Summary: AI-generated hourly cat images - a new cat every hour!
|
|
5
|
-
License-Expression: MIT
|
|
6
|
-
License-File: LICENSE
|
|
7
|
-
Requires-Python: >=3.10
|
|
8
|
-
Requires-Dist: httpx
|
|
9
|
-
Description-Content-Type: text/markdown
|
|
10
|
-
|
|
11
|
-
# catime
|
|
12
|
-
|
|
13
|
-
AI-generated hourly cat images. A new cat every hour!
|
|
14
|
-
|
|
15
|
-
## Usage
|
|
16
|
-
|
|
17
|
-
```bash
|
|
18
|
-
uvx catime # Show total cat count
|
|
19
|
-
uvx catime 42 # View cat #42
|
|
20
|
-
uvx catime today # List today's cats
|
|
21
|
-
uvx catime yesterday # List yesterday's cats
|
|
22
|
-
uvx catime 2026-01-30 # List all cats from a date
|
|
23
|
-
uvx catime 2026-01-30T05 # View the cat from a specific hour
|
|
24
|
-
uvx catime --list # List all cats
|
|
25
|
-
```
|
catime-0.2.0.dist-info/RECORD
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
catime/__init__.py,sha256=f37KsktucfUui7NyCuEJRZT_WMFOIcUFCWao5y9wsyM,70
|
|
2
|
-
catime/cli.py,sha256=dDMYM6UvXQlDldni2rfQ70CsjUwIaQGPZj6Egcd4t8M,4243
|
|
3
|
-
catime-0.2.0.dist-info/METADATA,sha256=bfR_tARqCSml9MpfEOnZZnBUz1bMgGF3OTPSsX88GV8,665
|
|
4
|
-
catime-0.2.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
5
|
-
catime-0.2.0.dist-info/entry_points.txt,sha256=oPgi6h026vMo9YyEOH3wuNtD3A28e-s1ChJs-KWWGXw,43
|
|
6
|
-
catime-0.2.0.dist-info/licenses/LICENSE,sha256=p_h5YRMaNCwMqGXX5KDrk49_NWGpzAJ2d6IhKa67B0E,1064
|
|
7
|
-
catime-0.2.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|