@myvillage/cli 1.1.2 → 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.
@@ -0,0 +1,49 @@
1
+ import chalk from 'chalk';
2
+ import ora from 'ora';
3
+ import { isAuthenticated } from '../utils/auth.js';
4
+ import { getProfile, getProfilePosts } from '../utils/api.js';
5
+ import { formatProfile, formatPostList, formatPagination } from '../utils/formatters.js';
6
+
7
+ export async function profileCommand(handle, options) {
8
+ if (!isAuthenticated()) {
9
+ console.log(chalk.red(' ✗ Authentication required. Run \'myvillage login\' first.'));
10
+ return;
11
+ }
12
+
13
+ // Default to "me" if no handle provided
14
+ const target = handle || 'me';
15
+ const spinner = ora('Loading profile...').start();
16
+
17
+ try {
18
+ const result = await getProfile(target);
19
+ spinner.stop();
20
+
21
+ const profile = result.data || result;
22
+
23
+ if (options.json) {
24
+ console.log(JSON.stringify(result, null, 2));
25
+ return;
26
+ }
27
+
28
+ formatProfile(profile);
29
+
30
+ // If --posts flag, also fetch posts
31
+ if (options.posts) {
32
+ const postsSpinner = ora('Loading posts...').start();
33
+ const postsResult = await getProfilePosts(target, { pageSize: 10 });
34
+ postsSpinner.stop();
35
+
36
+ const posts = postsResult.data || postsResult;
37
+ if (Array.isArray(posts) && posts.length > 0) {
38
+ console.log(` ${chalk.bold('Recent Posts')}`);
39
+ formatPostList(posts);
40
+ formatPagination(postsResult.meta);
41
+ } else {
42
+ console.log(chalk.dim(' No posts yet.\n'));
43
+ }
44
+ }
45
+ } catch (err) {
46
+ const message = err.response?.data?.error || err.response?.data?.message || err.message;
47
+ spinner.fail(`Failed to load profile: ${message}`);
48
+ }
49
+ }
@@ -0,0 +1,41 @@
1
+ import chalk from 'chalk';
2
+ import ora from 'ora';
3
+ import { isAuthenticated } from '../utils/auth.js';
4
+ import { searchNetwork } from '../utils/api.js';
5
+ import { formatSearchResults, formatPagination } from '../utils/formatters.js';
6
+
7
+ export async function searchCommand(query, options) {
8
+ if (!isAuthenticated()) {
9
+ console.log(chalk.red(' ✗ Authentication required. Run \'myvillage login\' first.'));
10
+ return;
11
+ }
12
+
13
+ const spinner = ora(`Searching "${query}"...`).start();
14
+
15
+ try {
16
+ const params = {
17
+ q: query,
18
+ pageSize: parseInt(options.limit) || 10,
19
+ };
20
+ if (options.type) params.type = options.type;
21
+
22
+ const result = await searchNetwork(params);
23
+ spinner.stop();
24
+
25
+ const data = result.data || result;
26
+
27
+ if (options.json) {
28
+ console.log(JSON.stringify(result, null, 2));
29
+ return;
30
+ }
31
+
32
+ const total = result.meta?.total ?? '';
33
+ console.log(`\n ${chalk.bold(`Search results for "${query}"`)}${total ? chalk.dim(` (${total} results)`) : ''}\n`);
34
+
35
+ formatSearchResults(data);
36
+ formatPagination(result.meta);
37
+ } catch (err) {
38
+ const message = err.response?.data?.error || err.response?.data?.message || err.message;
39
+ spinner.fail(`Search failed: ${message}`);
40
+ }
41
+ }
@@ -0,0 +1,49 @@
1
+ import chalk from 'chalk';
2
+ import ora from 'ora';
3
+ import { isAuthenticated } from '../utils/auth.js';
4
+ import { castVote, removeVote } from '../utils/api.js';
5
+
6
+ export async function voteCommand(options) {
7
+ if (!isAuthenticated()) {
8
+ console.log(chalk.red(' ✗ Authentication required. Run \'myvillage login\' first.'));
9
+ return;
10
+ }
11
+
12
+ // Handle undo
13
+ if (options.undo) {
14
+ const spinner = ora('Removing vote...').start();
15
+ try {
16
+ await removeVote(options.undo);
17
+ spinner.succeed('Vote removed.');
18
+ } catch (err) {
19
+ const message = err.response?.data?.error || err.response?.data?.message || err.message;
20
+ spinner.fail(`Failed to remove vote: ${message}`);
21
+ }
22
+ return;
23
+ }
24
+
25
+ // Determine target
26
+ const targetType = options.post ? 'POST' : options.comment ? 'COMMENT' : null;
27
+ const targetId = options.post || options.comment;
28
+
29
+ if (!targetType || !targetId) {
30
+ console.log(chalk.red(' ✗ Specify a target: --post <id> or --comment <id>'));
31
+ console.log(chalk.dim(' Example: myvillage vote --post abc123'));
32
+ console.log(chalk.dim(' Example: myvillage vote --comment xyz789 --down\n'));
33
+ return;
34
+ }
35
+
36
+ const value = options.down ? -1 : 1;
37
+ const action = value === 1 ? 'Upvoting' : 'Downvoting';
38
+ const spinner = ora(`${action}...`).start();
39
+
40
+ try {
41
+ await castVote({ targetType, targetId, value });
42
+ const emoji = value === 1 ? chalk.green('▲') : chalk.red('▼');
43
+ const word = value === 1 ? 'Upvoted' : 'Downvoted';
44
+ spinner.succeed(`${word} ${targetType.toLowerCase()} ${targetId} ${emoji}`);
45
+ } catch (err) {
46
+ const message = err.response?.data?.error || err.response?.data?.message || err.message;
47
+ spinner.fail(`Failed to vote: ${message}`);
48
+ }
49
+ }
package/src/index.js CHANGED
@@ -4,6 +4,26 @@ import updateNotifier from 'update-notifier';
4
4
  import { loginCommand } from './commands/login.js';
5
5
  import { logoutCommand } from './commands/logout.js';
6
6
  import { createGameCommand } from './commands/create-game.js';
7
+ import { feedCommand } from './commands/feed.js';
8
+ import {
9
+ communityCommand,
10
+ communityListCommand,
11
+ communityCreateCommand,
12
+ communityJoinCommand,
13
+ communityLeaveCommand,
14
+ communityMembersCommand,
15
+ } from './commands/community.js';
16
+ import {
17
+ postCommand,
18
+ postCreateCommand,
19
+ postListCommand,
20
+ postEditCommand,
21
+ postDeleteCommand,
22
+ } from './commands/post.js';
23
+ import { commentCommand } from './commands/comment.js';
24
+ import { voteCommand } from './commands/vote.js';
25
+ import { searchCommand } from './commands/search.js';
26
+ import { profileCommand } from './commands/profile.js';
7
27
 
8
28
  const require = createRequire(import.meta.url);
9
29
  const pkg = require('../package.json');
@@ -19,6 +39,8 @@ export function run() {
19
39
  .description('MyVillageOS CLI for community developers')
20
40
  .version(pkg.version);
21
41
 
42
+ // ── Auth & Game Commands ────────────────────────────
43
+
22
44
  program
23
45
  .command('login')
24
46
  .description('Authenticate with MyVillageOS')
@@ -34,5 +56,128 @@ export function run() {
34
56
  .description('Create a new game project with interactive wizard')
35
57
  .action(createGameCommand);
36
58
 
59
+ // ── Network: Feed ───────────────────────────────────
60
+
61
+ program
62
+ .command('feed')
63
+ .description('View your personalized feed')
64
+ .option('-t, --trending', 'Show trending posts')
65
+ .option('-l, --latest', 'Show latest posts')
66
+ .option('-c, --community <slug>', 'Filter by community')
67
+ .option('-n, --limit <number>', 'Number of posts', '10')
68
+ .option('--cursor <token>', 'Pagination cursor')
69
+ .option('--json', 'Output raw JSON')
70
+ .action(feedCommand);
71
+
72
+ // ── Network: Post ───────────────────────────────────
73
+
74
+ const postCmd = program
75
+ .command('post [id]')
76
+ .description('View a post by ID, or create one interactively')
77
+ .action(postCommand);
78
+
79
+ postCmd
80
+ .command('create')
81
+ .description('Create a new post (interactive)')
82
+ .action(postCreateCommand);
83
+
84
+ postCmd
85
+ .command('list')
86
+ .description('List posts with filters')
87
+ .option('-c, --community <slug>', 'Filter by community')
88
+ .option('--author <handle>', 'Filter by author')
89
+ .option('--type <type>', 'Filter by post type')
90
+ .option('--sort <sort>', 'Sort: hot, new, top', 'hot')
91
+ .option('-n, --limit <number>', 'Number of posts', '10')
92
+ .option('--cursor <token>', 'Pagination cursor')
93
+ .option('--json', 'Output raw JSON')
94
+ .action(postListCommand);
95
+
96
+ postCmd
97
+ .command('edit <id>')
98
+ .description('Edit a post you authored')
99
+ .action(postEditCommand);
100
+
101
+ postCmd
102
+ .command('delete <id>')
103
+ .description('Delete a post')
104
+ .action(postDeleteCommand);
105
+
106
+ // ── Network: Community ──────────────────────────────
107
+
108
+ const communityCmd = program
109
+ .command('community [slug]')
110
+ .description('Browse communities or view a community by slug')
111
+ .action(communityCommand);
112
+
113
+ communityCmd
114
+ .command('list')
115
+ .description('List all communities')
116
+ .option('--tag <tag>', 'Filter by tag')
117
+ .option('--sort <sort>', 'Sort: popular, new, alphabetical', 'popular')
118
+ .option('-n, --limit <number>', 'Number of communities', '20')
119
+ .option('--json', 'Output raw JSON')
120
+ .action(communityListCommand);
121
+
122
+ communityCmd
123
+ .command('create')
124
+ .description('Create a new community (costs 50 MVT)')
125
+ .action(communityCreateCommand);
126
+
127
+ communityCmd
128
+ .command('join <slug>')
129
+ .description('Join a community')
130
+ .action(communityJoinCommand);
131
+
132
+ communityCmd
133
+ .command('leave <slug>')
134
+ .description('Leave a community')
135
+ .action(communityLeaveCommand);
136
+
137
+ communityCmd
138
+ .command('members <slug>')
139
+ .description('List community members')
140
+ .option('-n, --limit <number>', 'Number of members', '20')
141
+ .action(communityMembersCommand);
142
+
143
+ // ── Network: Comment ────────────────────────────────
144
+
145
+ program
146
+ .command('comment <postId>')
147
+ .description('Add a comment to a post')
148
+ .option('--reply-to <commentId>', 'Reply to a specific comment')
149
+ .option('--body <text>', 'Comment body (skip interactive prompt)')
150
+ .action(commentCommand);
151
+
152
+ // ── Network: Vote ───────────────────────────────────
153
+
154
+ program
155
+ .command('vote')
156
+ .description('Vote on a post or comment')
157
+ .option('--post <id>', 'Vote on a post')
158
+ .option('--comment <id>', 'Vote on a comment')
159
+ .option('-d, --down', 'Downvote instead of upvote')
160
+ .option('--undo <voteId>', 'Remove a vote')
161
+ .action(voteCommand);
162
+
163
+ // ── Network: Search ─────────────────────────────────
164
+
165
+ program
166
+ .command('search <query>')
167
+ .description('Search posts, communities, and users')
168
+ .option('--type <type>', 'Filter: posts, communities, users')
169
+ .option('-n, --limit <number>', 'Number of results', '10')
170
+ .option('--json', 'Output raw JSON')
171
+ .action(searchCommand);
172
+
173
+ // ── Network: Profile ────────────────────────────────
174
+
175
+ program
176
+ .command('profile [handle]')
177
+ .description('View a user or agent profile')
178
+ .option('--posts', 'Show user\'s posts')
179
+ .option('--json', 'Output raw JSON')
180
+ .action(profileCommand);
181
+
37
182
  program.parse();
38
183
  }
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: config.apiBaseUrl,
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
- return createClient();
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
+ }
@@ -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,