@myvillage/cli 1.1.2 → 1.2.2
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 +85 -0
- package/package.json +1 -1
- package/src/commands/comment.js +59 -0
- package/src/commands/community.js +229 -0
- package/src/commands/feed.js +66 -0
- package/src/commands/login.js +1 -0
- package/src/commands/post.js +252 -0
- package/src/commands/profile.js +57 -0
- package/src/commands/search.js +41 -0
- package/src/commands/vote.js +49 -0
- package/src/index.js +145 -0
- package/src/utils/api.js +152 -8
- package/src/utils/auth.js +7 -0
- package/src/utils/config.js +1 -0
- package/src/utils/formatters.js +352 -0
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import ora from 'ora';
|
|
3
|
+
import inquirer from 'inquirer';
|
|
4
|
+
import { isAuthenticated } from '../utils/auth.js';
|
|
5
|
+
import {
|
|
6
|
+
createPost as apiCreatePost,
|
|
7
|
+
getPost,
|
|
8
|
+
listPosts,
|
|
9
|
+
editPost as apiEditPost,
|
|
10
|
+
deletePost as apiDeletePost,
|
|
11
|
+
listCommunities,
|
|
12
|
+
} from '../utils/api.js';
|
|
13
|
+
import {
|
|
14
|
+
formatPostDetail,
|
|
15
|
+
formatPostList,
|
|
16
|
+
formatCommentThread,
|
|
17
|
+
formatPagination,
|
|
18
|
+
} from '../utils/formatters.js';
|
|
19
|
+
|
|
20
|
+
export async function postCommand(id) {
|
|
21
|
+
if (!isAuthenticated()) {
|
|
22
|
+
console.log(chalk.red(' ✗ Authentication required. Run \'myvillage login\' first.'));
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (id) {
|
|
27
|
+
return postViewCommand(id);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// No args: interactive create
|
|
31
|
+
return postCreateCommand();
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export async function postViewCommand(id) {
|
|
35
|
+
if (!isAuthenticated()) {
|
|
36
|
+
console.log(chalk.red(' ✗ Authentication required. Run \'myvillage login\' first.'));
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const spinner = ora('Loading post...').start();
|
|
41
|
+
|
|
42
|
+
try {
|
|
43
|
+
const result = await getPost(id);
|
|
44
|
+
spinner.stop();
|
|
45
|
+
|
|
46
|
+
const post = result.data || result;
|
|
47
|
+
formatPostDetail(post);
|
|
48
|
+
|
|
49
|
+
if (post.comments?.length) {
|
|
50
|
+
formatCommentThread(post.comments);
|
|
51
|
+
}
|
|
52
|
+
} catch (err) {
|
|
53
|
+
const message = err.response?.data?.error || err.response?.data?.message || err.message;
|
|
54
|
+
spinner.fail(`Failed to load post: ${message}`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export async function postCreateCommand() {
|
|
59
|
+
if (!isAuthenticated()) {
|
|
60
|
+
console.log(chalk.red(' ✗ Authentication required. Run \'myvillage login\' first.'));
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
try {
|
|
65
|
+
// Fetch communities for selection
|
|
66
|
+
const commSpinner = ora('Loading your communities...').start();
|
|
67
|
+
let communities = [];
|
|
68
|
+
try {
|
|
69
|
+
const result = await listCommunities({ pageSize: 50 });
|
|
70
|
+
communities = result.data || result;
|
|
71
|
+
} catch {
|
|
72
|
+
// Fall back to manual entry if fetch fails
|
|
73
|
+
}
|
|
74
|
+
commSpinner.stop();
|
|
75
|
+
|
|
76
|
+
const communityChoices = Array.isArray(communities) && communities.length > 0
|
|
77
|
+
? communities.map(c => ({ name: `r/${c.slug} - ${c.name}`, value: c.slug }))
|
|
78
|
+
: null;
|
|
79
|
+
|
|
80
|
+
const answers = await inquirer.prompt([
|
|
81
|
+
communityChoices
|
|
82
|
+
? {
|
|
83
|
+
type: 'list',
|
|
84
|
+
name: 'communitySlug',
|
|
85
|
+
message: 'Select community:',
|
|
86
|
+
choices: communityChoices,
|
|
87
|
+
}
|
|
88
|
+
: {
|
|
89
|
+
type: 'input',
|
|
90
|
+
name: 'communitySlug',
|
|
91
|
+
message: 'Community slug:',
|
|
92
|
+
validate: (input) => input.trim().length > 0 || 'Community is required',
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
type: 'list',
|
|
96
|
+
name: 'postType',
|
|
97
|
+
message: 'Post type:',
|
|
98
|
+
choices: [
|
|
99
|
+
{ name: 'Discussion', value: 'DISCUSSION' },
|
|
100
|
+
{ name: 'Question', value: 'QUESTION' },
|
|
101
|
+
{ name: 'Project Showcase', value: 'PROJECT_SHOWCASE' },
|
|
102
|
+
{ name: 'Tutorial', value: 'TUTORIAL' },
|
|
103
|
+
{ name: 'Game Showcase', value: 'GAME_SHOWCASE' },
|
|
104
|
+
{ name: 'Bounty', value: 'BOUNTY' },
|
|
105
|
+
{ name: 'Announcement', value: 'ANNOUNCEMENT' },
|
|
106
|
+
],
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
type: 'input',
|
|
110
|
+
name: 'title',
|
|
111
|
+
message: 'Title (optional):',
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
type: 'input',
|
|
115
|
+
name: 'body',
|
|
116
|
+
message: 'Post body:',
|
|
117
|
+
validate: (input) => input.trim().length > 0 || 'Body is required',
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
type: 'input',
|
|
121
|
+
name: 'tags',
|
|
122
|
+
message: 'Tags (comma-separated):',
|
|
123
|
+
},
|
|
124
|
+
]);
|
|
125
|
+
|
|
126
|
+
const spinner = ora('Creating post...').start();
|
|
127
|
+
|
|
128
|
+
const data = {
|
|
129
|
+
communitySlug: answers.communitySlug.trim(),
|
|
130
|
+
postType: answers.postType,
|
|
131
|
+
body: answers.body.trim(),
|
|
132
|
+
tags: answers.tags ? answers.tags.split(',').map(t => t.trim()).filter(Boolean) : [],
|
|
133
|
+
};
|
|
134
|
+
if (answers.title?.trim()) {
|
|
135
|
+
data.title = answers.title.trim();
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const result = await apiCreatePost(data);
|
|
139
|
+
spinner.succeed('Post created!');
|
|
140
|
+
|
|
141
|
+
const post = result.data || result;
|
|
142
|
+
console.log(chalk.green(` ✓ Post published in r/${answers.communitySlug}`));
|
|
143
|
+
console.log(chalk.dim(` ID: ${post.id}\n`));
|
|
144
|
+
} catch (err) {
|
|
145
|
+
if (err.isTtyError) {
|
|
146
|
+
console.log(chalk.red(' ✗ Prompts cannot be rendered in this environment.\n'));
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
const message = err.response?.data?.error || err.response?.data?.message || err.message;
|
|
150
|
+
console.log(chalk.red(` ✗ Failed to create post: ${message}\n`));
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export async function postListCommand(options) {
|
|
155
|
+
if (!isAuthenticated()) {
|
|
156
|
+
console.log(chalk.red(' ✗ Authentication required. Run \'myvillage login\' first.'));
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const spinner = ora('Loading posts...').start();
|
|
161
|
+
|
|
162
|
+
try {
|
|
163
|
+
const params = {
|
|
164
|
+
pageSize: parseInt(options.limit) || 10,
|
|
165
|
+
};
|
|
166
|
+
if (options.community) params.communitySlug = options.community;
|
|
167
|
+
if (options.author) params.authorId = options.author;
|
|
168
|
+
if (options.type) params.postType = options.type;
|
|
169
|
+
if (options.sort) params.sort = options.sort;
|
|
170
|
+
if (options.cursor) params.cursor = options.cursor;
|
|
171
|
+
|
|
172
|
+
const result = await listPosts(params);
|
|
173
|
+
spinner.stop();
|
|
174
|
+
|
|
175
|
+
const posts = result.data || result;
|
|
176
|
+
|
|
177
|
+
if (options.json) {
|
|
178
|
+
console.log(JSON.stringify(result, null, 2));
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
formatPostList(posts);
|
|
183
|
+
formatPagination(result.meta);
|
|
184
|
+
} catch (err) {
|
|
185
|
+
const message = err.response?.data?.error || err.response?.data?.message || err.message;
|
|
186
|
+
spinner.fail(`Failed to load posts: ${message}`);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export async function postEditCommand(id) {
|
|
191
|
+
if (!isAuthenticated()) {
|
|
192
|
+
console.log(chalk.red(' ✗ Authentication required. Run \'myvillage login\' first.'));
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const loadSpinner = ora('Loading post...').start();
|
|
197
|
+
|
|
198
|
+
try {
|
|
199
|
+
const result = await getPost(id);
|
|
200
|
+
loadSpinner.stop();
|
|
201
|
+
|
|
202
|
+
const post = result.data || result;
|
|
203
|
+
|
|
204
|
+
const answers = await inquirer.prompt([
|
|
205
|
+
{
|
|
206
|
+
type: 'input',
|
|
207
|
+
name: 'body',
|
|
208
|
+
message: 'Edit post body:',
|
|
209
|
+
default: post.body,
|
|
210
|
+
validate: (input) => input.trim().length > 0 || 'Body is required',
|
|
211
|
+
},
|
|
212
|
+
]);
|
|
213
|
+
|
|
214
|
+
const spinner = ora('Saving changes...').start();
|
|
215
|
+
await apiEditPost(id, { body: answers.body.trim() });
|
|
216
|
+
spinner.succeed('Post updated!');
|
|
217
|
+
} catch (err) {
|
|
218
|
+
const message = err.response?.data?.error || err.response?.data?.message || err.message;
|
|
219
|
+
loadSpinner.stop();
|
|
220
|
+
console.log(chalk.red(` ✗ Failed to edit post: ${message}\n`));
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
export async function postDeleteCommand(id) {
|
|
225
|
+
if (!isAuthenticated()) {
|
|
226
|
+
console.log(chalk.red(' ✗ Authentication required. Run \'myvillage login\' first.'));
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
try {
|
|
231
|
+
const { confirm } = await inquirer.prompt([
|
|
232
|
+
{
|
|
233
|
+
type: 'confirm',
|
|
234
|
+
name: 'confirm',
|
|
235
|
+
message: `Delete post ${id}? This cannot be undone.`,
|
|
236
|
+
default: false,
|
|
237
|
+
},
|
|
238
|
+
]);
|
|
239
|
+
|
|
240
|
+
if (!confirm) {
|
|
241
|
+
console.log(chalk.dim(' Cancelled.\n'));
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const spinner = ora('Deleting post...').start();
|
|
246
|
+
await apiDeletePost(id);
|
|
247
|
+
spinner.succeed('Post deleted.');
|
|
248
|
+
} catch (err) {
|
|
249
|
+
const message = err.response?.data?.error || err.response?.data?.message || err.message;
|
|
250
|
+
console.log(chalk.red(` ✗ Failed to delete post: ${message}\n`));
|
|
251
|
+
}
|
|
252
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import ora from 'ora';
|
|
3
|
+
import { isAuthenticated, loadCredentials } 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
|
+
// Resolve handle: default to the logged-in user's villagerId
|
|
14
|
+
let target = handle;
|
|
15
|
+
if (!target) {
|
|
16
|
+
const creds = loadCredentials();
|
|
17
|
+
target = creds?.villager_id;
|
|
18
|
+
if (!target) {
|
|
19
|
+
console.log(chalk.red(' ✗ No villager ID found. Try logging out and back in, or provide a handle: myvillage profile <handle>'));
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
const spinner = ora('Loading profile...').start();
|
|
24
|
+
|
|
25
|
+
try {
|
|
26
|
+
const result = await getProfile(target);
|
|
27
|
+
spinner.stop();
|
|
28
|
+
|
|
29
|
+
const profile = result.data || result;
|
|
30
|
+
|
|
31
|
+
if (options.json) {
|
|
32
|
+
console.log(JSON.stringify(result, null, 2));
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
formatProfile(profile);
|
|
37
|
+
|
|
38
|
+
// If --posts flag, also fetch posts
|
|
39
|
+
if (options.posts) {
|
|
40
|
+
const postsSpinner = ora('Loading posts...').start();
|
|
41
|
+
const postsResult = await getProfilePosts(target, { pageSize: 10 });
|
|
42
|
+
postsSpinner.stop();
|
|
43
|
+
|
|
44
|
+
const posts = postsResult.data || postsResult;
|
|
45
|
+
if (Array.isArray(posts) && posts.length > 0) {
|
|
46
|
+
console.log(` ${chalk.bold('Recent Posts')}`);
|
|
47
|
+
formatPostList(posts);
|
|
48
|
+
formatPagination(postsResult.meta);
|
|
49
|
+
} else {
|
|
50
|
+
console.log(chalk.dim(' No posts yet.\n'));
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
} catch (err) {
|
|
54
|
+
const message = err.response?.data?.error || err.response?.data?.message || err.message;
|
|
55
|
+
spinner.fail(`Failed to load profile: ${message}`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -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
|
}
|