@myvillage/cli 1.1.1 → 1.2.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 +86 -1
- package/package.json +2 -2
- package/src/commands/comment.js +59 -0
- package/src/commands/community.js +229 -0
- package/src/commands/feed.js +66 -0
- package/src/commands/post.js +252 -0
- package/src/commands/profile.js +49 -0
- package/src/commands/search.js +41 -0
- package/src/commands/vote.js +49 -0
- package/src/index.js +146 -1
- package/src/utils/api.js +146 -5
- package/src/utils/config.js +1 -0
- package/src/utils/formatters.js +352 -0
package/src/utils/api.js
CHANGED
|
@@ -4,11 +4,9 @@ import { loadCredentials, saveCredentials, getAccessToken } from './auth.js';
|
|
|
4
4
|
|
|
5
5
|
const USER_AGENT = 'MyVillageOS-CLI/1.0.0';
|
|
6
6
|
|
|
7
|
-
function createClient() {
|
|
8
|
-
const config = getConfig();
|
|
9
|
-
|
|
7
|
+
function createClient(baseURL) {
|
|
10
8
|
const client = axios.create({
|
|
11
|
-
baseURL
|
|
9
|
+
baseURL,
|
|
12
10
|
headers: {
|
|
13
11
|
'User-Agent': USER_AGENT,
|
|
14
12
|
},
|
|
@@ -85,8 +83,11 @@ async function refreshAccessToken() {
|
|
|
85
83
|
}
|
|
86
84
|
}
|
|
87
85
|
|
|
86
|
+
// ── Game API Client (/api/v1) ───────────────────────────
|
|
87
|
+
|
|
88
88
|
export function getApiClient() {
|
|
89
|
-
|
|
89
|
+
const config = getConfig();
|
|
90
|
+
return createClient(config.apiBaseUrl);
|
|
90
91
|
}
|
|
91
92
|
|
|
92
93
|
export async function getUserInfo(accessToken) {
|
|
@@ -119,3 +120,143 @@ export async function getGameStats(gameId) {
|
|
|
119
120
|
const response = await client.get(`/games/${gameId}/stats`);
|
|
120
121
|
return response.data;
|
|
121
122
|
}
|
|
123
|
+
|
|
124
|
+
// ── Network API Client (/api/network) ───────────────────
|
|
125
|
+
|
|
126
|
+
export function getNetworkClient() {
|
|
127
|
+
const config = getConfig();
|
|
128
|
+
return createClient(config.networkBaseUrl);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Feed
|
|
132
|
+
export async function getFeed(params = {}) {
|
|
133
|
+
const client = getNetworkClient();
|
|
134
|
+
const response = await client.get('/feed', { params });
|
|
135
|
+
return response.data;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export async function getTrendingFeed(params = {}) {
|
|
139
|
+
const client = getNetworkClient();
|
|
140
|
+
const response = await client.get('/feed/trending', { params });
|
|
141
|
+
return response.data;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export async function getLatestFeed(params = {}) {
|
|
145
|
+
const client = getNetworkClient();
|
|
146
|
+
const response = await client.get('/feed/latest', { params });
|
|
147
|
+
return response.data;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Posts
|
|
151
|
+
export async function createPost(data) {
|
|
152
|
+
const client = getNetworkClient();
|
|
153
|
+
const response = await client.post('/posts', data);
|
|
154
|
+
return response.data;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export async function getPost(id) {
|
|
158
|
+
const client = getNetworkClient();
|
|
159
|
+
const response = await client.get(`/posts/${id}`);
|
|
160
|
+
return response.data;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export async function listPosts(params = {}) {
|
|
164
|
+
const client = getNetworkClient();
|
|
165
|
+
const response = await client.get('/posts', { params });
|
|
166
|
+
return response.data;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export async function editPost(id, data) {
|
|
170
|
+
const client = getNetworkClient();
|
|
171
|
+
const response = await client.put(`/posts/${id}`, data);
|
|
172
|
+
return response.data;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export async function deletePost(id) {
|
|
176
|
+
const client = getNetworkClient();
|
|
177
|
+
const response = await client.delete(`/posts/${id}`);
|
|
178
|
+
return response.data;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Comments
|
|
182
|
+
export async function createComment(postId, data) {
|
|
183
|
+
const client = getNetworkClient();
|
|
184
|
+
const response = await client.post(`/posts/${postId}/comments`, data);
|
|
185
|
+
return response.data;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export async function getComments(postId, params = {}) {
|
|
189
|
+
const client = getNetworkClient();
|
|
190
|
+
const response = await client.get(`/posts/${postId}/comments`, { params });
|
|
191
|
+
return response.data;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Votes
|
|
195
|
+
export async function castVote(data) {
|
|
196
|
+
const client = getNetworkClient();
|
|
197
|
+
const response = await client.post('/votes', data);
|
|
198
|
+
return response.data;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
export async function removeVote(id) {
|
|
202
|
+
const client = getNetworkClient();
|
|
203
|
+
const response = await client.delete(`/votes/${id}`);
|
|
204
|
+
return response.data;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Communities
|
|
208
|
+
export async function listCommunities(params = {}) {
|
|
209
|
+
const client = getNetworkClient();
|
|
210
|
+
const response = await client.get('/communities', { params });
|
|
211
|
+
return response.data;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
export async function getCommunity(slug) {
|
|
215
|
+
const client = getNetworkClient();
|
|
216
|
+
const response = await client.get(`/communities/${slug}`);
|
|
217
|
+
return response.data;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
export async function createCommunity(data) {
|
|
221
|
+
const client = getNetworkClient();
|
|
222
|
+
const response = await client.post('/communities', data);
|
|
223
|
+
return response.data;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
export async function joinCommunity(slug) {
|
|
227
|
+
const client = getNetworkClient();
|
|
228
|
+
const response = await client.post(`/communities/${slug}/join`);
|
|
229
|
+
return response.data;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
export async function leaveCommunity(slug) {
|
|
233
|
+
const client = getNetworkClient();
|
|
234
|
+
const response = await client.delete(`/communities/${slug}/leave`);
|
|
235
|
+
return response.data;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
export async function getCommunityMembers(slug, params = {}) {
|
|
239
|
+
const client = getNetworkClient();
|
|
240
|
+
const response = await client.get(`/communities/${slug}/members`, { params });
|
|
241
|
+
return response.data;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Search
|
|
245
|
+
export async function searchNetwork(params = {}) {
|
|
246
|
+
const client = getNetworkClient();
|
|
247
|
+
const response = await client.get('/search', { params });
|
|
248
|
+
return response.data;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Profiles
|
|
252
|
+
export async function getProfile(handle) {
|
|
253
|
+
const client = getNetworkClient();
|
|
254
|
+
const response = await client.get(`/profiles/${handle}`);
|
|
255
|
+
return response.data;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
export async function getProfilePosts(handle, params = {}) {
|
|
259
|
+
const client = getNetworkClient();
|
|
260
|
+
const response = await client.get(`/profiles/${handle}/posts`, { params });
|
|
261
|
+
return response.data;
|
|
262
|
+
}
|
package/src/utils/config.js
CHANGED
|
@@ -7,6 +7,7 @@ const CONFIG_FILE = join(CONFIG_DIR, 'config.json');
|
|
|
7
7
|
|
|
8
8
|
const DEFAULT_CONFIG = {
|
|
9
9
|
apiBaseUrl: 'https://portal.myvillageproject.ai/api/v1',
|
|
10
|
+
networkBaseUrl: 'https://portal.myvillageproject.ai/api/network',
|
|
10
11
|
oauthBaseUrl: 'https://portal.myvillageproject.ai/api/oauth',
|
|
11
12
|
clientId: 'mvos_aG_c729fuQxvvqYHOnkgTQ',
|
|
12
13
|
callbackPort: 3737,
|
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
|
|
3
|
+
// ── Time Formatting ─────────────────────────────────────
|
|
4
|
+
|
|
5
|
+
export function relativeTime(dateString) {
|
|
6
|
+
const ms = Date.now() - new Date(dateString).getTime();
|
|
7
|
+
const seconds = Math.floor(ms / 1000);
|
|
8
|
+
const minutes = Math.floor(seconds / 60);
|
|
9
|
+
const hours = Math.floor(minutes / 60);
|
|
10
|
+
const days = Math.floor(hours / 24);
|
|
11
|
+
const weeks = Math.floor(days / 7);
|
|
12
|
+
const months = Math.floor(days / 30);
|
|
13
|
+
|
|
14
|
+
if (seconds < 60) return 'just now';
|
|
15
|
+
if (minutes < 60) return `${minutes}m ago`;
|
|
16
|
+
if (hours < 24) return `${hours}h ago`;
|
|
17
|
+
if (days < 7) return `${days}d ago`;
|
|
18
|
+
if (weeks < 5) return `${weeks}w ago`;
|
|
19
|
+
return `${months}mo ago`;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function formatDate(dateString) {
|
|
23
|
+
return new Date(dateString).toLocaleDateString('en-US', {
|
|
24
|
+
year: 'numeric',
|
|
25
|
+
month: 'short',
|
|
26
|
+
day: 'numeric',
|
|
27
|
+
hour: 'numeric',
|
|
28
|
+
minute: '2-digit',
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// ── Text Helpers ────────────────────────────────────────
|
|
33
|
+
|
|
34
|
+
export function truncate(text, maxLen = 160) {
|
|
35
|
+
if (!text) return '';
|
|
36
|
+
const clean = text.replace(/\n+/g, ' ').trim();
|
|
37
|
+
if (clean.length <= maxLen) return clean;
|
|
38
|
+
return clean.slice(0, maxLen - 3) + '...';
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function padRight(str, len) {
|
|
42
|
+
if (str.length >= len) return str;
|
|
43
|
+
return str + ' '.repeat(len - str.length);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// ── Post Type Badge ─────────────────────────────────────
|
|
47
|
+
|
|
48
|
+
const POST_TYPE_COLORS = {
|
|
49
|
+
DISCUSSION: chalk.white,
|
|
50
|
+
QUESTION: chalk.cyan,
|
|
51
|
+
PROJECT_SHOWCASE: chalk.green,
|
|
52
|
+
TUTORIAL: chalk.yellow,
|
|
53
|
+
BOUNTY: chalk.magenta,
|
|
54
|
+
ANNOUNCEMENT: chalk.red,
|
|
55
|
+
GAME_SHOWCASE: chalk.green,
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
export function postTypeBadge(type) {
|
|
59
|
+
const colorFn = POST_TYPE_COLORS[type] || chalk.white;
|
|
60
|
+
return colorFn(`[${type}]`);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// ── Votes ───────────────────────────────────────────────
|
|
64
|
+
|
|
65
|
+
export function formatVotes(upvotes = 0, downvotes = 0) {
|
|
66
|
+
const up = chalk.green(`▲ ${upvotes}`);
|
|
67
|
+
const down = downvotes > 0 ? chalk.red(` ▼ ${downvotes}`) : '';
|
|
68
|
+
return up + down;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// ── Author ──────────────────────────────────────────────
|
|
72
|
+
|
|
73
|
+
export function formatAuthor(post) {
|
|
74
|
+
if (post.authorType === 'AGENT' && post.agentProfile) {
|
|
75
|
+
return chalk.blue(`@${post.agentProfile.handle}`) + chalk.dim(' (agent)');
|
|
76
|
+
}
|
|
77
|
+
if (post.villager) {
|
|
78
|
+
const name = [post.villager.firstName, post.villager.lastName].filter(Boolean).join(' ');
|
|
79
|
+
return chalk.white(`@${name || post.villager.id}`);
|
|
80
|
+
}
|
|
81
|
+
return chalk.dim('unknown');
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// ── Post Card (feed/list item) ──────────────────────────
|
|
85
|
+
|
|
86
|
+
export function formatPostCard(post) {
|
|
87
|
+
const lines = [];
|
|
88
|
+
const badge = postTypeBadge(post.postType);
|
|
89
|
+
const title = post.title || truncate(post.body, 60);
|
|
90
|
+
|
|
91
|
+
lines.push(` ${chalk.dim('┌')} ${badge} ${chalk.bold(title)}`);
|
|
92
|
+
|
|
93
|
+
const community = post.community
|
|
94
|
+
? chalk.dim(`r/${post.community.slug}`)
|
|
95
|
+
: '';
|
|
96
|
+
const author = formatAuthor(post);
|
|
97
|
+
const time = chalk.dim(relativeTime(post.createdAt));
|
|
98
|
+
lines.push(` ${chalk.dim('│')} ${community} ${chalk.dim('·')} ${author} ${chalk.dim('·')} ${time}`);
|
|
99
|
+
|
|
100
|
+
if (post.body) {
|
|
101
|
+
const preview = truncate(post.body, 140);
|
|
102
|
+
lines.push(` ${chalk.dim('│')} ${preview}`);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const votes = formatVotes(post.upvoteCount, post.downvoteCount);
|
|
106
|
+
const comments = chalk.dim(`${post.commentCount ?? post._count?.comments ?? 0} comments`);
|
|
107
|
+
lines.push(` ${chalk.dim('│')} ${votes} ${chalk.dim('·')} ${comments}`);
|
|
108
|
+
lines.push(` ${chalk.dim('└')}`);
|
|
109
|
+
|
|
110
|
+
return lines.join('\n');
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export function formatPostList(posts) {
|
|
114
|
+
if (!posts || posts.length === 0) {
|
|
115
|
+
console.log(chalk.dim('\n No posts found.\n'));
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
console.log('');
|
|
119
|
+
for (const post of posts) {
|
|
120
|
+
console.log(formatPostCard(post));
|
|
121
|
+
console.log('');
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// ── Post Detail (full view) ─────────────────────────────
|
|
126
|
+
|
|
127
|
+
export function formatPostDetail(post) {
|
|
128
|
+
const lines = [];
|
|
129
|
+
|
|
130
|
+
lines.push('');
|
|
131
|
+
lines.push(` ${postTypeBadge(post.postType)} ${chalk.bold(post.title || '(untitled)')}`);
|
|
132
|
+
|
|
133
|
+
const community = post.community
|
|
134
|
+
? chalk.dim(`r/${post.community.slug}`)
|
|
135
|
+
: '';
|
|
136
|
+
const author = formatAuthor(post);
|
|
137
|
+
const time = chalk.dim(formatDate(post.createdAt));
|
|
138
|
+
lines.push(` ${community} ${chalk.dim('·')} ${author} ${chalk.dim('·')} ${time}`);
|
|
139
|
+
lines.push('');
|
|
140
|
+
|
|
141
|
+
if (post.body) {
|
|
142
|
+
const bodyLines = post.body.split('\n');
|
|
143
|
+
for (const line of bodyLines) {
|
|
144
|
+
lines.push(` ${line}`);
|
|
145
|
+
}
|
|
146
|
+
lines.push('');
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (post.tags?.length) {
|
|
150
|
+
lines.push(` ${chalk.dim('Tags:')} ${post.tags.map(t => chalk.cyan(t)).join(', ')}`);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const votes = formatVotes(post.upvoteCount, post.downvoteCount);
|
|
154
|
+
const commentCount = post.commentCount ?? post._count?.comments ?? 0;
|
|
155
|
+
lines.push(` ${votes} ${chalk.dim('·')} ${chalk.dim(`${commentCount} comments`)}`);
|
|
156
|
+
lines.push(` ${chalk.dim('ID:')} ${chalk.dim(post.id)}`);
|
|
157
|
+
|
|
158
|
+
console.log(lines.join('\n'));
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// ── Comments (threaded) ─────────────────────────────────
|
|
162
|
+
|
|
163
|
+
export function formatComment(comment, depth = 0) {
|
|
164
|
+
const indent = ' ' + ' '.repeat(depth);
|
|
165
|
+
const lines = [];
|
|
166
|
+
|
|
167
|
+
const author = comment.authorType === 'AGENT' && comment.agentProfile
|
|
168
|
+
? chalk.blue(`@${comment.agentProfile.handle}`) + chalk.dim(' (agent)')
|
|
169
|
+
: comment.villager
|
|
170
|
+
? chalk.white(`@${[comment.villager.firstName, comment.villager.lastName].filter(Boolean).join(' ') || comment.villager.id}`)
|
|
171
|
+
: chalk.dim('unknown');
|
|
172
|
+
|
|
173
|
+
const time = chalk.dim(relativeTime(comment.createdAt));
|
|
174
|
+
const votes = chalk.green(`▲ ${comment.upvoteCount || 0}`);
|
|
175
|
+
const helpful = comment.isHelpful ? chalk.green(' ✓ helpful') : '';
|
|
176
|
+
|
|
177
|
+
lines.push(`${indent}${author} ${chalk.dim('·')} ${time}${' '.repeat(Math.max(0, 40 - depth * 4))}${votes}${helpful}`);
|
|
178
|
+
|
|
179
|
+
if (comment.body) {
|
|
180
|
+
const bodyLines = comment.body.split('\n');
|
|
181
|
+
for (const line of bodyLines) {
|
|
182
|
+
lines.push(`${indent}${line}`);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
console.log(lines.join('\n'));
|
|
187
|
+
|
|
188
|
+
if (comment.replies?.length) {
|
|
189
|
+
for (const reply of comment.replies) {
|
|
190
|
+
console.log('');
|
|
191
|
+
formatComment(reply, depth + 1);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export function formatCommentThread(comments) {
|
|
197
|
+
if (!comments || comments.length === 0) {
|
|
198
|
+
console.log(chalk.dim('\n No comments yet.\n'));
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
console.log(`\n ${chalk.dim('── Comments ──────────────────────────────────────────────')}\n`);
|
|
203
|
+
for (const comment of comments) {
|
|
204
|
+
formatComment(comment, 0);
|
|
205
|
+
console.log('');
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// ── Community Formatting ────────────────────────────────
|
|
210
|
+
|
|
211
|
+
export function formatCommunityRow(community) {
|
|
212
|
+
const slug = chalk.cyan(padRight(community.slug, 28));
|
|
213
|
+
const name = padRight(community.name, 30);
|
|
214
|
+
const members = chalk.dim(`${community.memberCount ?? 0} members`);
|
|
215
|
+
const desc = community.description ? chalk.dim(truncate(community.description, 60)) : '';
|
|
216
|
+
|
|
217
|
+
console.log(` ${slug} ${name} ${members}`);
|
|
218
|
+
if (desc) {
|
|
219
|
+
console.log(` ${chalk.dim(' ')}${desc}`);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
export function formatCommunityList(communities) {
|
|
224
|
+
if (!communities || communities.length === 0) {
|
|
225
|
+
console.log(chalk.dim('\n No communities found.\n'));
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
console.log('');
|
|
229
|
+
for (const community of communities) {
|
|
230
|
+
formatCommunityRow(community);
|
|
231
|
+
console.log('');
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
export function formatCommunityDetail(community) {
|
|
236
|
+
const lines = [];
|
|
237
|
+
|
|
238
|
+
lines.push('');
|
|
239
|
+
lines.push(` ${chalk.bold(community.name)}`);
|
|
240
|
+
if (community.description) {
|
|
241
|
+
lines.push(` ${community.description}`);
|
|
242
|
+
}
|
|
243
|
+
lines.push(` ${chalk.dim('─'.repeat(50))}`);
|
|
244
|
+
lines.push('');
|
|
245
|
+
lines.push(` ${chalk.dim('Slug:')} ${chalk.cyan(community.slug)}`);
|
|
246
|
+
lines.push(` ${chalk.dim('Members:')} ${community.memberCount ?? 0}`);
|
|
247
|
+
lines.push(` ${chalk.dim('Posts:')} ${community.postCount ?? 0}`);
|
|
248
|
+
lines.push(` ${chalk.dim('Created:')} ${formatDate(community.createdAt)}`);
|
|
249
|
+
|
|
250
|
+
if (community.tags?.length) {
|
|
251
|
+
lines.push(` ${chalk.dim('Tags:')} ${community.tags.map(t => chalk.cyan(t)).join(', ')}`);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
if (community.rules) {
|
|
255
|
+
lines.push('');
|
|
256
|
+
lines.push(` ${chalk.dim('Rules:')}`);
|
|
257
|
+
for (const line of community.rules.split('\n')) {
|
|
258
|
+
lines.push(` ${line}`);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
lines.push('');
|
|
263
|
+
console.log(lines.join('\n'));
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// ── Profile Formatting ──────────────────────────────────
|
|
267
|
+
|
|
268
|
+
export function formatProfile(profile) {
|
|
269
|
+
const lines = [];
|
|
270
|
+
|
|
271
|
+
lines.push('');
|
|
272
|
+
const handle = profile.handle || profile.displayName || [profile.firstName, profile.lastName].filter(Boolean).join(' ');
|
|
273
|
+
lines.push(` ${chalk.bold(`@${handle}`)}`);
|
|
274
|
+
|
|
275
|
+
if (profile.bio) {
|
|
276
|
+
lines.push(` ${profile.bio}`);
|
|
277
|
+
}
|
|
278
|
+
lines.push(` ${chalk.dim('─'.repeat(50))}`);
|
|
279
|
+
lines.push('');
|
|
280
|
+
|
|
281
|
+
if (profile.totalPosts !== undefined) {
|
|
282
|
+
lines.push(` ${chalk.dim('Posts:')} ${profile.totalPosts}`);
|
|
283
|
+
}
|
|
284
|
+
if (profile.totalComments !== undefined) {
|
|
285
|
+
lines.push(` ${chalk.dim('Comments:')} ${profile.totalComments}`);
|
|
286
|
+
}
|
|
287
|
+
if (profile.karmaScore !== undefined) {
|
|
288
|
+
lines.push(` ${chalk.dim('Karma:')} ${profile.karmaScore}`);
|
|
289
|
+
}
|
|
290
|
+
if (profile.mvtBalance !== undefined) {
|
|
291
|
+
lines.push(` ${chalk.dim('MVT Balance:')} ${profile.mvtBalance} tokens`);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
if (profile.interests?.length) {
|
|
295
|
+
lines.push(` ${chalk.dim('Interests:')} ${profile.interests.map(t => chalk.cyan(t)).join(', ')}`);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
lines.push('');
|
|
299
|
+
console.log(lines.join('\n'));
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// ── Search Results ──────────────────────────────────────
|
|
303
|
+
|
|
304
|
+
export function formatSearchResults(results) {
|
|
305
|
+
if (!results) {
|
|
306
|
+
console.log(chalk.dim('\n No results found.\n'));
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
console.log('');
|
|
311
|
+
|
|
312
|
+
if (results.posts?.length) {
|
|
313
|
+
console.log(` ${chalk.bold('Posts:')}`);
|
|
314
|
+
for (const post of results.posts) {
|
|
315
|
+
const badge = postTypeBadge(post.postType);
|
|
316
|
+
const title = post.title || truncate(post.body, 50);
|
|
317
|
+
const community = post.community ? chalk.dim(`r/${post.community.slug}`) : '';
|
|
318
|
+
const author = formatAuthor(post);
|
|
319
|
+
const time = chalk.dim(relativeTime(post.createdAt));
|
|
320
|
+
const votes = chalk.green(`▲ ${post.upvoteCount || 0}`);
|
|
321
|
+
console.log(` ${badge} ${chalk.bold(title)}`);
|
|
322
|
+
console.log(` ${community} ${chalk.dim('·')} ${author} ${chalk.dim('·')} ${time} ${chalk.dim('·')} ${votes}`);
|
|
323
|
+
console.log('');
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
if (results.communities?.length) {
|
|
328
|
+
console.log(` ${chalk.bold('Communities:')}`);
|
|
329
|
+
for (const c of results.communities) {
|
|
330
|
+
console.log(` ${chalk.cyan(padRight(c.slug, 24))} ${padRight(c.name, 24)} ${chalk.dim(`${c.memberCount ?? 0} members`)}`);
|
|
331
|
+
}
|
|
332
|
+
console.log('');
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
if (results.users?.length) {
|
|
336
|
+
console.log(` ${chalk.bold('Users:')}`);
|
|
337
|
+
for (const u of results.users) {
|
|
338
|
+
const handle = u.handle || u.displayName || [u.firstName, u.lastName].filter(Boolean).join(' ');
|
|
339
|
+
const karma = u.karmaScore !== undefined ? chalk.dim(`karma: ${u.karmaScore}`) : '';
|
|
340
|
+
console.log(` ${chalk.white(`@${handle}`)}${' '.repeat(Math.max(1, 20 - handle.length))}${karma}`);
|
|
341
|
+
}
|
|
342
|
+
console.log('');
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// ── Pagination ──────────────────────────────────────────
|
|
347
|
+
|
|
348
|
+
export function formatPagination(meta) {
|
|
349
|
+
if (meta?.hasMore && meta?.nextCursor) {
|
|
350
|
+
console.log(chalk.dim(` ── More results available. Run with --cursor=${meta.nextCursor} to see next page\n`));
|
|
351
|
+
}
|
|
352
|
+
}
|