zephinax 1.0.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 -0
- package/bin/index.js +175 -0
- package/package.json +21 -0
package/README.md
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
<h2>Zephinax's Stats</h2>
|
|
3
|
+
</div>
|
|
4
|
+
|
|
5
|
+
<br>
|
|
6
|
+
|
|
7
|
+
<p align="center">
|
|
8
|
+
<img alt="Mark streak" src="https://streak-stats.demolab.com/?user=Zephinax&hide_border=true&theme=transparent"/>
|
|
9
|
+
</p>
|
|
10
|
+
|
|
11
|
+
<!-- <div align="center">
|
|
12
|
+
<img align="center" src="http://github-profile-summary-cards.vercel.app/api/cards/stats?username=Zephinax&theme=transparent" height="180em" alt="Stats"/>
|
|
13
|
+
<img align="center" src="http://github-profile-summary-cards.vercel.app/api/cards/productive-time?username=Zephinax&theme=transparent&utcOffset=3.5" height="180em" alt="Time Graph"/>
|
|
14
|
+
<img align="center" src="http://github-profile-summary-cards.vercel.app/api/cards/profile-details?username=Zephinax&theme=transparent" height="180em" alt="Commitments"/>
|
|
15
|
+
</div> -->
|
|
16
|
+
|
|
17
|
+
<img src="./profile-3d-contrib/profile-3d-contrib.svg" alt="profile" >
|
|
18
|
+
|
package/bin/index.js
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
|
|
4
|
+
const https = require("https");
|
|
5
|
+
|
|
6
|
+
const API_URL = process.env.ZEPHINAX_API_URL || "https://zephinax.com/api/user";
|
|
7
|
+
|
|
8
|
+
const color = {
|
|
9
|
+
cyan: (str) => `\x1b[36m${str}\x1b[0m`,
|
|
10
|
+
magenta: (str) => `\x1b[35m${str}\x1b[0m`,
|
|
11
|
+
yellow: (str) => `\x1b[33m${str}\x1b[0m`,
|
|
12
|
+
bold: (str) => `\x1b[1m${str}\x1b[0m`,
|
|
13
|
+
dim: (str) => `\x1b[2m${str}\x1b[0m`
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const CONTENT_MARGIN = 1;
|
|
17
|
+
|
|
18
|
+
async function fetchProfile(url) {
|
|
19
|
+
if (typeof fetch === "function") {
|
|
20
|
+
const res = await fetch(url);
|
|
21
|
+
if (!res.ok) throw new Error(`Request failed with status ${res.status}`);
|
|
22
|
+
return res.json();
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Fallback for environments without global fetch.
|
|
26
|
+
return new Promise((resolve, reject) => {
|
|
27
|
+
https
|
|
28
|
+
.get(url, (res) => {
|
|
29
|
+
let data = "";
|
|
30
|
+
res.on("data", (chunk) => (data += chunk));
|
|
31
|
+
res.on("end", () => {
|
|
32
|
+
if (res.statusCode < 200 || res.statusCode >= 300) {
|
|
33
|
+
return reject(new Error(`Request failed with status ${res.statusCode}`));
|
|
34
|
+
}
|
|
35
|
+
try {
|
|
36
|
+
resolve(JSON.parse(data));
|
|
37
|
+
} catch (err) {
|
|
38
|
+
reject(err);
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
})
|
|
42
|
+
.on("error", reject);
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function clean(text) {
|
|
47
|
+
if (!text) return "";
|
|
48
|
+
return text
|
|
49
|
+
.replace(/\*\*/g, "") // remove bold markers
|
|
50
|
+
.replace(/^>\s?/gm, "") // strip block quotes
|
|
51
|
+
.replace(/\r?\n\s*/g, "\n") // normalize whitespace
|
|
52
|
+
.trim();
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function wrapText(text, width, indent) {
|
|
56
|
+
if (!text) return [];
|
|
57
|
+
const indentStr = " ".repeat(indent);
|
|
58
|
+
return text
|
|
59
|
+
.split(/\r?\n/)
|
|
60
|
+
.map((para) => para.trim())
|
|
61
|
+
.filter(Boolean)
|
|
62
|
+
.flatMap((para) => {
|
|
63
|
+
const words = para.split(/\s+/);
|
|
64
|
+
const lines = [];
|
|
65
|
+
let current = "";
|
|
66
|
+
|
|
67
|
+
words.forEach((word) => {
|
|
68
|
+
const candidate = current ? `${current} ${word}` : word;
|
|
69
|
+
if (candidate.length > width) {
|
|
70
|
+
if (current) lines.push(current);
|
|
71
|
+
current = word;
|
|
72
|
+
} else {
|
|
73
|
+
current = candidate;
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
if (current) lines.push(current);
|
|
77
|
+
return lines.map((l) => `${indentStr}${l}`);
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function divider(char = "-", length = 60) {
|
|
82
|
+
return char.repeat(length);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function stripAnsi(str = "") {
|
|
86
|
+
return str.replace(
|
|
87
|
+
// eslint-disable-next-line no-control-regex
|
|
88
|
+
/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g,
|
|
89
|
+
""
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function visibleLength(str = "") {
|
|
94
|
+
return stripAnsi(str).length;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function boxTop(label, width) {
|
|
98
|
+
const title = label ? ` ${label} ` : "";
|
|
99
|
+
const dash = Math.max(0, width - 2 - title.length);
|
|
100
|
+
return `╭${title}${divider("─", dash)}╮`;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function boxBottom(width) {
|
|
104
|
+
return `╰${divider("─", Math.max(0, width - 2))}╯`;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function boxLine(text, width) {
|
|
108
|
+
const inner = Math.max(0, width - 4 - CONTENT_MARGIN * 2);
|
|
109
|
+
const content = text || "";
|
|
110
|
+
const visLen = visibleLength(content);
|
|
111
|
+
const needsTruncate = visLen > inner;
|
|
112
|
+
const raw = needsTruncate ? `${stripAnsi(content).slice(0, inner - 1)}…` : content;
|
|
113
|
+
const padding = Math.max(0, inner - visibleLength(raw));
|
|
114
|
+
const margin = " ".repeat(CONTENT_MARGIN);
|
|
115
|
+
return `│ ${margin}${raw}${" ".repeat(padding)}${margin} │`;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function boxDivider(label, width) {
|
|
119
|
+
const tag = label ? ` ${label.toUpperCase()} ` : "";
|
|
120
|
+
const lineLen = Math.max(0, width - 2 - tag.length);
|
|
121
|
+
return `├${tag}${divider("─", lineLen)}┤`;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function fieldLine(label, value, width) {
|
|
125
|
+
if (!value) return null;
|
|
126
|
+
const labelText = `${label}:`.padEnd(8);
|
|
127
|
+
return boxLine(`${color.yellow(labelText)} ${value}`, width);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function render(profile) {
|
|
131
|
+
const about = clean(profile.about || profile.bio);
|
|
132
|
+
const header =
|
|
133
|
+
`${color.bold(profile.displayName || profile.username || "Zephinax")} ${profile.pronouns ? `(${profile.pronouns})` : ""}`.trim();
|
|
134
|
+
const fullName =
|
|
135
|
+
profile.firstName || profile.lastName
|
|
136
|
+
? `${profile.firstName || ""} ${profile.lastName || ""}`.trim()
|
|
137
|
+
: null;
|
|
138
|
+
|
|
139
|
+
const width = Math.max(60, Math.min(process.stdout.columns || 80, 100));
|
|
140
|
+
const contentWidth = Math.max(0, width - 4 - CONTENT_MARGIN * 2);
|
|
141
|
+
|
|
142
|
+
const primary = color.cyan(header);
|
|
143
|
+
const secondary = fullName ? color.dim(fullName) : null;
|
|
144
|
+
const aboutLines = about ? wrapText(about, contentWidth - 2, 0) : [];
|
|
145
|
+
|
|
146
|
+
const lines = [
|
|
147
|
+
boxTop("Profile", width),
|
|
148
|
+
boxLine(primary, width),
|
|
149
|
+
secondary ? boxLine(secondary, width) : null,
|
|
150
|
+
boxDivider("details", width),
|
|
151
|
+
fieldLine("Role", profile.jobTitle, width),
|
|
152
|
+
fieldLine("Timezone", profile.timeZone, width),
|
|
153
|
+
boxDivider("contact", width),
|
|
154
|
+
fieldLine("Email", profile.email, width),
|
|
155
|
+
fieldLine("Web", profile.website, width),
|
|
156
|
+
aboutLines.length ? boxDivider("about", width) : null,
|
|
157
|
+
...aboutLines.map((l) => boxLine(l, width)),
|
|
158
|
+
boxBottom(width)
|
|
159
|
+
].filter(Boolean);
|
|
160
|
+
|
|
161
|
+
return lines.join("\n");
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
async function main() {
|
|
165
|
+
try {
|
|
166
|
+
const data = await fetchProfile(API_URL);
|
|
167
|
+
if (!data || !data.data) throw new Error("No profile data returned.");
|
|
168
|
+
console.log(render(data.data));
|
|
169
|
+
} catch (err) {
|
|
170
|
+
console.error(`${color.magenta("Error:")} ${err.message}`);
|
|
171
|
+
process.exitCode = 1;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
main();
|
package/package.json
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "zephinax",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "CLI portfolio for Zephinax. Fetches live profile data and prints it to the terminal.",
|
|
5
|
+
"bin": {
|
|
6
|
+
"zephinax": "bin/index.js"
|
|
7
|
+
},
|
|
8
|
+
"files": [
|
|
9
|
+
"bin"
|
|
10
|
+
],
|
|
11
|
+
"keywords": [
|
|
12
|
+
"portfolio",
|
|
13
|
+
"cli",
|
|
14
|
+
"zephinax"
|
|
15
|
+
],
|
|
16
|
+
"author": "Zephinax <zephinax@gmail.com>",
|
|
17
|
+
"license": "MIT",
|
|
18
|
+
"engines": {
|
|
19
|
+
"node": ">=18"
|
|
20
|
+
}
|
|
21
|
+
}
|