@myvillage/cli 1.2.2 → 1.5.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 +14 -199
- package/package.json +6 -2
- package/src/agent-runtime/context.js +99 -0
- package/src/agent-runtime/daemon-entry.js +66 -0
- package/src/agent-runtime/daemon.js +65 -0
- package/src/agent-runtime/loop.js +281 -0
- package/src/agent-runtime/mcp-client.js +400 -0
- package/src/agent-runtime/scheduler.js +53 -0
- package/src/commands/agent-local.js +607 -0
- package/src/commands/agent.js +540 -0
- package/src/commands/bizreqs.js +965 -0
- package/src/commands/comment.js +29 -3
- package/src/commands/community.js +13 -12
- package/src/commands/create-app.js +253 -0
- package/src/commands/create-game.js +9 -8
- package/src/commands/deploy.js +101 -23
- package/src/commands/feed.js +4 -3
- package/src/commands/login.js +7 -6
- package/src/commands/logout.js +3 -2
- package/src/commands/post.js +67 -11
- package/src/commands/profile.js +4 -3
- package/src/commands/search.js +3 -2
- package/src/commands/status.js +64 -28
- package/src/commands/vote.js +46 -18
- package/src/index.js +179 -1
- package/src/utils/agent-scaffolder.js +165 -0
- package/src/utils/api.js +175 -11
- package/src/utils/app-templates.js +2983 -0
- package/src/utils/brand.js +107 -0
- package/src/utils/config.js +16 -1
- package/src/utils/formatters.js +404 -11
- package/src/utils/local-agent.js +168 -0
package/src/commands/login.js
CHANGED
|
@@ -2,6 +2,7 @@ import { createServer } from 'http';
|
|
|
2
2
|
import { randomBytes, createHash } from 'crypto';
|
|
3
3
|
import chalk from 'chalk';
|
|
4
4
|
import ora from 'ora';
|
|
5
|
+
import { villageSpinner, brand } from '../utils/brand.js';
|
|
5
6
|
import open from 'open';
|
|
6
7
|
import axios from 'axios';
|
|
7
8
|
import { getConfig } from '../utils/config.js';
|
|
@@ -27,11 +28,11 @@ export async function loginCommand() {
|
|
|
27
28
|
const existing = loadCredentials();
|
|
28
29
|
if (existing?.access_token) {
|
|
29
30
|
console.log(chalk.yellow('You are already logged in.'));
|
|
30
|
-
console.log(
|
|
31
|
+
console.log(brand.teal('Run "myvillage logout" first to log in with a different account.'));
|
|
31
32
|
return;
|
|
32
33
|
}
|
|
33
34
|
|
|
34
|
-
const spinner =
|
|
35
|
+
const spinner = villageSpinner('Preparing authentication...').start();
|
|
35
36
|
|
|
36
37
|
// Generate PKCE values and state token
|
|
37
38
|
const state = randomBytes(16).toString('hex');
|
|
@@ -117,8 +118,8 @@ export async function loginCommand() {
|
|
|
117
118
|
open(authUrl).catch(() => {
|
|
118
119
|
spinner.stop();
|
|
119
120
|
console.log(chalk.yellow('\nCould not open browser automatically.'));
|
|
120
|
-
console.log(
|
|
121
|
-
console.log(
|
|
121
|
+
console.log(brand.teal('Please open this URL in your browser:'));
|
|
122
|
+
console.log(brand.gold(authUrl));
|
|
122
123
|
});
|
|
123
124
|
|
|
124
125
|
spinner.text = 'Waiting for authentication in browser...';
|
|
@@ -184,12 +185,12 @@ export async function loginCommand() {
|
|
|
184
185
|
|
|
185
186
|
spinner.succeed('Authentication complete!');
|
|
186
187
|
console.log();
|
|
187
|
-
console.log(
|
|
188
|
+
console.log(brand.green(` \u2713 Successfully logged in as ${userInfo.email}`));
|
|
188
189
|
|
|
189
190
|
if (userInfo.villager) {
|
|
190
191
|
const v = userInfo.villager;
|
|
191
192
|
if (v.mvtBalance !== undefined) {
|
|
192
|
-
console.log(
|
|
193
|
+
console.log(brand.teal(` MVT Balance: ${v.mvtBalance} tokens`));
|
|
193
194
|
}
|
|
194
195
|
}
|
|
195
196
|
|
package/src/commands/logout.js
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
|
+
import { brand } from '../utils/brand.js';
|
|
2
3
|
import { clearCredentials } from '../utils/auth.js';
|
|
3
4
|
|
|
4
5
|
export async function logoutCommand() {
|
|
5
6
|
const removed = clearCredentials();
|
|
6
7
|
|
|
7
8
|
if (removed) {
|
|
8
|
-
console.log(
|
|
9
|
+
console.log(brand.green(' \u2713 Successfully logged out.'));
|
|
9
10
|
} else {
|
|
10
|
-
console.log(
|
|
11
|
+
console.log(brand.teal(' No stored credentials found. You are not logged in.'));
|
|
11
12
|
}
|
|
12
13
|
}
|
package/src/commands/post.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
2
|
import ora from 'ora';
|
|
3
|
+
import { villageSpinner, brand } from '../utils/brand.js';
|
|
3
4
|
import inquirer from 'inquirer';
|
|
4
5
|
import { isAuthenticated } from '../utils/auth.js';
|
|
5
6
|
import {
|
|
@@ -9,6 +10,7 @@ import {
|
|
|
9
10
|
editPost as apiEditPost,
|
|
10
11
|
deletePost as apiDeletePost,
|
|
11
12
|
listCommunities,
|
|
13
|
+
listMyAgents,
|
|
12
14
|
} from '../utils/api.js';
|
|
13
15
|
import {
|
|
14
16
|
formatPostDetail,
|
|
@@ -37,7 +39,7 @@ export async function postViewCommand(id) {
|
|
|
37
39
|
return;
|
|
38
40
|
}
|
|
39
41
|
|
|
40
|
-
const spinner =
|
|
42
|
+
const spinner = villageSpinner('Loading post...').start();
|
|
41
43
|
|
|
42
44
|
try {
|
|
43
45
|
const result = await getPost(id);
|
|
@@ -55,7 +57,7 @@ export async function postViewCommand(id) {
|
|
|
55
57
|
}
|
|
56
58
|
}
|
|
57
59
|
|
|
58
|
-
export async function postCreateCommand() {
|
|
60
|
+
export async function postCreateCommand(options = {}) {
|
|
59
61
|
if (!isAuthenticated()) {
|
|
60
62
|
console.log(chalk.red(' ✗ Authentication required. Run \'myvillage login\' first.'));
|
|
61
63
|
return;
|
|
@@ -63,7 +65,7 @@ export async function postCreateCommand() {
|
|
|
63
65
|
|
|
64
66
|
try {
|
|
65
67
|
// Fetch communities for selection
|
|
66
|
-
const commSpinner =
|
|
68
|
+
const commSpinner = villageSpinner('Loading your communities...').start();
|
|
67
69
|
let communities = [];
|
|
68
70
|
try {
|
|
69
71
|
const result = await listCommunities({ pageSize: 50 });
|
|
@@ -73,6 +75,53 @@ export async function postCreateCommand() {
|
|
|
73
75
|
}
|
|
74
76
|
commSpinner.stop();
|
|
75
77
|
|
|
78
|
+
// Resolve agent identity
|
|
79
|
+
let agentProfileId = null;
|
|
80
|
+
|
|
81
|
+
if (options.as) {
|
|
82
|
+
// --as flag provided: resolve handle to agent ID
|
|
83
|
+
const agentsSpinner = villageSpinner('Resolving agent...').start();
|
|
84
|
+
try {
|
|
85
|
+
const agentsResult = await listMyAgents();
|
|
86
|
+
const agents = agentsResult.data || agentsResult;
|
|
87
|
+
const agent = Array.isArray(agents) ? agents.find(a => a.handle === options.as) : null;
|
|
88
|
+
agentsSpinner.stop();
|
|
89
|
+
|
|
90
|
+
if (!agent) {
|
|
91
|
+
console.log(chalk.red(` ✗ Agent @${options.as} not found. Run 'myvillage agent' to see your agents.`));
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
agentProfileId = agent.id;
|
|
95
|
+
console.log(brand.teal(` Posting as agent @${agent.handle}\n`));
|
|
96
|
+
} catch {
|
|
97
|
+
agentsSpinner.stop();
|
|
98
|
+
}
|
|
99
|
+
} else {
|
|
100
|
+
// No --as flag: check if user has agents and prompt
|
|
101
|
+
try {
|
|
102
|
+
const agentsResult = await listMyAgents();
|
|
103
|
+
const agents = agentsResult.data || agentsResult;
|
|
104
|
+
|
|
105
|
+
if (Array.isArray(agents) && agents.length > 0) {
|
|
106
|
+
const { postAs } = await inquirer.prompt([
|
|
107
|
+
{
|
|
108
|
+
type: 'list',
|
|
109
|
+
name: 'postAs',
|
|
110
|
+
message: 'Post as:',
|
|
111
|
+
choices: [
|
|
112
|
+
{ name: 'Yourself', value: null },
|
|
113
|
+
...agents.map(a => ({ name: `@${a.handle} - ${a.displayName}`, value: a.id })),
|
|
114
|
+
],
|
|
115
|
+
},
|
|
116
|
+
]);
|
|
117
|
+
|
|
118
|
+
agentProfileId = postAs;
|
|
119
|
+
}
|
|
120
|
+
} catch {
|
|
121
|
+
// If fetching agents fails, just proceed as the user
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
76
125
|
const communityChoices = Array.isArray(communities) && communities.length > 0
|
|
77
126
|
? communities.map(c => ({ name: `r/${c.slug} - ${c.name}`, value: c.slug }))
|
|
78
127
|
: null;
|
|
@@ -123,7 +172,7 @@ export async function postCreateCommand() {
|
|
|
123
172
|
},
|
|
124
173
|
]);
|
|
125
174
|
|
|
126
|
-
const spinner =
|
|
175
|
+
const spinner = villageSpinner('Creating post...').start();
|
|
127
176
|
|
|
128
177
|
const data = {
|
|
129
178
|
communitySlug: answers.communitySlug.trim(),
|
|
@@ -134,13 +183,20 @@ export async function postCreateCommand() {
|
|
|
134
183
|
if (answers.title?.trim()) {
|
|
135
184
|
data.title = answers.title.trim();
|
|
136
185
|
}
|
|
186
|
+
if (agentProfileId) {
|
|
187
|
+
data.agentProfileId = agentProfileId;
|
|
188
|
+
}
|
|
137
189
|
|
|
138
190
|
const result = await apiCreatePost(data);
|
|
139
191
|
spinner.succeed('Post created!');
|
|
140
192
|
|
|
141
193
|
const post = result.data || result;
|
|
142
|
-
|
|
143
|
-
|
|
194
|
+
if (agentProfileId) {
|
|
195
|
+
console.log(brand.green(` ✓ Post published in r/${answers.communitySlug} as agent`));
|
|
196
|
+
} else {
|
|
197
|
+
console.log(brand.green(` ✓ Post published in r/${answers.communitySlug}`));
|
|
198
|
+
}
|
|
199
|
+
console.log(brand.teal(` ID: ${post.id}\n`));
|
|
144
200
|
} catch (err) {
|
|
145
201
|
if (err.isTtyError) {
|
|
146
202
|
console.log(chalk.red(' ✗ Prompts cannot be rendered in this environment.\n'));
|
|
@@ -157,7 +213,7 @@ export async function postListCommand(options) {
|
|
|
157
213
|
return;
|
|
158
214
|
}
|
|
159
215
|
|
|
160
|
-
const spinner =
|
|
216
|
+
const spinner = villageSpinner('Loading posts...').start();
|
|
161
217
|
|
|
162
218
|
try {
|
|
163
219
|
const params = {
|
|
@@ -193,7 +249,7 @@ export async function postEditCommand(id) {
|
|
|
193
249
|
return;
|
|
194
250
|
}
|
|
195
251
|
|
|
196
|
-
const loadSpinner =
|
|
252
|
+
const loadSpinner = villageSpinner('Loading post...').start();
|
|
197
253
|
|
|
198
254
|
try {
|
|
199
255
|
const result = await getPost(id);
|
|
@@ -211,7 +267,7 @@ export async function postEditCommand(id) {
|
|
|
211
267
|
},
|
|
212
268
|
]);
|
|
213
269
|
|
|
214
|
-
const spinner =
|
|
270
|
+
const spinner = villageSpinner('Saving changes...').start();
|
|
215
271
|
await apiEditPost(id, { body: answers.body.trim() });
|
|
216
272
|
spinner.succeed('Post updated!');
|
|
217
273
|
} catch (err) {
|
|
@@ -238,11 +294,11 @@ export async function postDeleteCommand(id) {
|
|
|
238
294
|
]);
|
|
239
295
|
|
|
240
296
|
if (!confirm) {
|
|
241
|
-
console.log(
|
|
297
|
+
console.log(brand.teal(' Cancelled.\n'));
|
|
242
298
|
return;
|
|
243
299
|
}
|
|
244
300
|
|
|
245
|
-
const spinner =
|
|
301
|
+
const spinner = villageSpinner('Deleting post...').start();
|
|
246
302
|
await apiDeletePost(id);
|
|
247
303
|
spinner.succeed('Post deleted.');
|
|
248
304
|
} catch (err) {
|
package/src/commands/profile.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
2
|
import ora from 'ora';
|
|
3
|
+
import { villageSpinner, brand } from '../utils/brand.js';
|
|
3
4
|
import { isAuthenticated, loadCredentials } from '../utils/auth.js';
|
|
4
5
|
import { getProfile, getProfilePosts } from '../utils/api.js';
|
|
5
6
|
import { formatProfile, formatPostList, formatPagination } from '../utils/formatters.js';
|
|
@@ -20,7 +21,7 @@ export async function profileCommand(handle, options) {
|
|
|
20
21
|
return;
|
|
21
22
|
}
|
|
22
23
|
}
|
|
23
|
-
const spinner =
|
|
24
|
+
const spinner = villageSpinner('Loading profile...').start();
|
|
24
25
|
|
|
25
26
|
try {
|
|
26
27
|
const result = await getProfile(target);
|
|
@@ -37,7 +38,7 @@ export async function profileCommand(handle, options) {
|
|
|
37
38
|
|
|
38
39
|
// If --posts flag, also fetch posts
|
|
39
40
|
if (options.posts) {
|
|
40
|
-
const postsSpinner =
|
|
41
|
+
const postsSpinner = villageSpinner('Loading posts...').start();
|
|
41
42
|
const postsResult = await getProfilePosts(target, { pageSize: 10 });
|
|
42
43
|
postsSpinner.stop();
|
|
43
44
|
|
|
@@ -47,7 +48,7 @@ export async function profileCommand(handle, options) {
|
|
|
47
48
|
formatPostList(posts);
|
|
48
49
|
formatPagination(postsResult.meta);
|
|
49
50
|
} else {
|
|
50
|
-
console.log(
|
|
51
|
+
console.log(brand.teal(' No posts yet.\n'));
|
|
51
52
|
}
|
|
52
53
|
}
|
|
53
54
|
} catch (err) {
|
package/src/commands/search.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
2
|
import ora from 'ora';
|
|
3
|
+
import { villageSpinner, brand } from '../utils/brand.js';
|
|
3
4
|
import { isAuthenticated } from '../utils/auth.js';
|
|
4
5
|
import { searchNetwork } from '../utils/api.js';
|
|
5
6
|
import { formatSearchResults, formatPagination } from '../utils/formatters.js';
|
|
@@ -10,7 +11,7 @@ export async function searchCommand(query, options) {
|
|
|
10
11
|
return;
|
|
11
12
|
}
|
|
12
13
|
|
|
13
|
-
const spinner =
|
|
14
|
+
const spinner = villageSpinner(`Searching "${query}"...`).start();
|
|
14
15
|
|
|
15
16
|
try {
|
|
16
17
|
const params = {
|
|
@@ -30,7 +31,7 @@ export async function searchCommand(query, options) {
|
|
|
30
31
|
}
|
|
31
32
|
|
|
32
33
|
const total = result.meta?.total ?? '';
|
|
33
|
-
console.log(`\n ${chalk.bold(`Search results for "${query}"`)}${total ?
|
|
34
|
+
console.log(`\n ${chalk.bold(`Search results for "${query}"`)}${total ? brand.teal(` (${total} results)`) : ''}\n`);
|
|
34
35
|
|
|
35
36
|
formatSearchResults(data);
|
|
36
37
|
formatPagination(result.meta);
|
package/src/commands/status.js
CHANGED
|
@@ -2,8 +2,9 @@ import { existsSync, readFileSync } from 'fs';
|
|
|
2
2
|
import { join, resolve } from 'path';
|
|
3
3
|
import chalk from 'chalk';
|
|
4
4
|
import ora from 'ora';
|
|
5
|
+
import { villageSpinner, brand } from '../utils/brand.js';
|
|
5
6
|
import { isAuthenticated } from '../utils/auth.js';
|
|
6
|
-
import { getMyGames,
|
|
7
|
+
import { getMyGames, getGameStatus } from '../utils/api.js';
|
|
7
8
|
|
|
8
9
|
function readPackageJson(dir) {
|
|
9
10
|
const pkgPath = join(dir, 'package.json');
|
|
@@ -48,33 +49,49 @@ export async function statusCommand() {
|
|
|
48
49
|
}
|
|
49
50
|
|
|
50
51
|
async function showGameStatus(gameId) {
|
|
51
|
-
const spinner =
|
|
52
|
+
const spinner = villageSpinner('Fetching game status...').start();
|
|
52
53
|
|
|
53
54
|
try {
|
|
54
|
-
const
|
|
55
|
+
const data = await getGameStatus(gameId);
|
|
56
|
+
const game = data.game || data;
|
|
55
57
|
|
|
56
58
|
spinner.stop();
|
|
57
59
|
|
|
58
60
|
console.log();
|
|
59
|
-
console.log(chalk.bold(` ${
|
|
61
|
+
console.log(chalk.bold(` ${game.title || game.name || gameId}`));
|
|
60
62
|
console.log();
|
|
61
|
-
console.log(` Status: ${formatStatus(
|
|
62
|
-
console.log(` Play Count: ${
|
|
63
|
-
console.log(` MVT Earned: ${chalk.yellow(
|
|
64
|
-
console.log(` Last Updated: ${
|
|
63
|
+
console.log(` Status: ${formatStatus(game.status)}`);
|
|
64
|
+
console.log(` Play Count: ${brand.gold(game.playCount ?? 0)}`);
|
|
65
|
+
console.log(` MVT Earned: ${chalk.yellow(game.mvtAwarded ?? 0)} tokens`);
|
|
66
|
+
console.log(` Last Updated: ${brand.teal(formatDate(game.updatedAt || game.lastUpdated))}`);
|
|
65
67
|
|
|
66
|
-
if (
|
|
67
|
-
console.log(`
|
|
68
|
+
if (game.slug) {
|
|
69
|
+
console.log(` Slug: ${brand.teal(game.slug)}`);
|
|
68
70
|
}
|
|
71
|
+
|
|
72
|
+
// Show review history if available
|
|
73
|
+
if (game.reviews?.length > 0) {
|
|
74
|
+
console.log();
|
|
75
|
+
console.log(chalk.bold(' Review History:'));
|
|
76
|
+
for (const review of game.reviews) {
|
|
77
|
+
const action = formatReviewAction(review.action);
|
|
78
|
+
const date = formatDate(review.createdAt);
|
|
79
|
+
console.log(` ${action} - ${brand.teal(date)}`);
|
|
80
|
+
if (review.notes) {
|
|
81
|
+
console.log(` ${brand.teal(review.notes)}`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
69
86
|
console.log();
|
|
70
87
|
} catch (err) {
|
|
71
|
-
const message = err.response?.data?.message || err.message;
|
|
88
|
+
const message = err.response?.data?.error || err.response?.data?.message || err.message;
|
|
72
89
|
spinner.fail(`Failed to fetch game status: ${message}`);
|
|
73
90
|
}
|
|
74
91
|
}
|
|
75
92
|
|
|
76
93
|
async function showAllGames() {
|
|
77
|
-
const spinner =
|
|
94
|
+
const spinner = villageSpinner('Fetching your games...').start();
|
|
78
95
|
|
|
79
96
|
try {
|
|
80
97
|
const data = await getMyGames();
|
|
@@ -84,8 +101,8 @@ async function showAllGames() {
|
|
|
84
101
|
|
|
85
102
|
if (!games.length) {
|
|
86
103
|
console.log();
|
|
87
|
-
console.log(
|
|
88
|
-
console.log(
|
|
104
|
+
console.log(brand.teal(' No deployed games found.'));
|
|
105
|
+
console.log(brand.teal(' Create a game with "myvillage create-game" and deploy it!'));
|
|
89
106
|
console.log();
|
|
90
107
|
return;
|
|
91
108
|
}
|
|
@@ -95,32 +112,51 @@ async function showAllGames() {
|
|
|
95
112
|
console.log();
|
|
96
113
|
|
|
97
114
|
for (const game of games) {
|
|
98
|
-
console.log(` ${chalk.yellow(game.name || game.
|
|
99
|
-
console.log(` Status: ${formatStatus(game.status)} | Plays: ${
|
|
115
|
+
console.log(` ${chalk.yellow(game.title || game.name || game.id)}`);
|
|
116
|
+
console.log(` Status: ${formatStatus(game.status)} | Plays: ${brand.gold(game.playCount ?? 0)} | MVT: ${chalk.yellow(game.mvtAwarded ?? 0)}`);
|
|
100
117
|
|
|
101
|
-
if (game.
|
|
102
|
-
console.log(` ${
|
|
118
|
+
if (game.slug) {
|
|
119
|
+
console.log(` ${brand.teal(game.slug)}`);
|
|
103
120
|
}
|
|
104
121
|
|
|
105
122
|
console.log();
|
|
106
123
|
}
|
|
107
124
|
} catch (err) {
|
|
108
|
-
const message = err.response?.data?.message || err.message;
|
|
125
|
+
const message = err.response?.data?.error || err.response?.data?.message || err.message;
|
|
109
126
|
spinner.fail(`Failed to fetch games: ${message}`);
|
|
110
127
|
}
|
|
111
128
|
}
|
|
112
129
|
|
|
113
130
|
function formatStatus(status) {
|
|
114
131
|
switch (status) {
|
|
115
|
-
case '
|
|
116
|
-
return
|
|
117
|
-
case '
|
|
118
|
-
return chalk.yellow('
|
|
119
|
-
case '
|
|
120
|
-
return chalk.
|
|
121
|
-
case '
|
|
122
|
-
return chalk.
|
|
132
|
+
case 'DRAFT':
|
|
133
|
+
return brand.teal('Draft');
|
|
134
|
+
case 'SUBMITTED':
|
|
135
|
+
return chalk.yellow('Submitted');
|
|
136
|
+
case 'UNDER_REVIEW':
|
|
137
|
+
return chalk.yellow('Under Review');
|
|
138
|
+
case 'APPROVED':
|
|
139
|
+
return chalk.green('Approved');
|
|
140
|
+
case 'REJECTED':
|
|
141
|
+
return chalk.red('Rejected');
|
|
142
|
+
case 'PUBLISHED':
|
|
143
|
+
return chalk.green('Published');
|
|
144
|
+
case 'ARCHIVED':
|
|
145
|
+
return brand.teal('Archived');
|
|
146
|
+
default:
|
|
147
|
+
return brand.teal(status || 'Unknown');
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function formatReviewAction(action) {
|
|
152
|
+
switch (action) {
|
|
153
|
+
case 'APPROVE':
|
|
154
|
+
return chalk.green('Approved');
|
|
155
|
+
case 'REJECT':
|
|
156
|
+
return chalk.red('Rejected');
|
|
157
|
+
case 'REQUEST_CHANGES':
|
|
158
|
+
return chalk.yellow('Changes Requested');
|
|
123
159
|
default:
|
|
124
|
-
return
|
|
160
|
+
return brand.teal(action || 'Unknown');
|
|
125
161
|
}
|
|
126
162
|
}
|
package/src/commands/vote.js
CHANGED
|
@@ -1,25 +1,35 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
2
|
import ora from 'ora';
|
|
3
|
+
import { villageSpinner, brand } from '../utils/brand.js';
|
|
3
4
|
import { isAuthenticated } from '../utils/auth.js';
|
|
4
|
-
import { castVote,
|
|
5
|
+
import { castVote, listMyAgents } from '../utils/api.js';
|
|
5
6
|
|
|
6
7
|
export async function voteCommand(options) {
|
|
7
8
|
if (!isAuthenticated()) {
|
|
8
|
-
console.log(chalk.red('
|
|
9
|
+
console.log(chalk.red(' \u2717 Authentication required. Run \'myvillage login\' first.'));
|
|
9
10
|
return;
|
|
10
11
|
}
|
|
11
12
|
|
|
12
|
-
//
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
// Resolve agent identity if --as flag provided
|
|
14
|
+
let agentProfileId = null;
|
|
15
|
+
|
|
16
|
+
if (options.as) {
|
|
15
17
|
try {
|
|
16
|
-
await
|
|
17
|
-
|
|
18
|
+
const agentsResult = await listMyAgents();
|
|
19
|
+
const agents = agentsResult.data || agentsResult;
|
|
20
|
+
const agent = Array.isArray(agents) ? agents.find(a => a.handle === options.as) : null;
|
|
21
|
+
|
|
22
|
+
if (!agent) {
|
|
23
|
+
console.log(chalk.red(` \u2717 Agent @${options.as} not found. Run 'myvillage agent' to see your agents.\n`));
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
agentProfileId = agent.id;
|
|
27
|
+
console.log(brand.teal(` Voting as agent @${agent.handle}\n`));
|
|
18
28
|
} catch (err) {
|
|
19
29
|
const message = err.response?.data?.error || err.response?.data?.message || err.message;
|
|
20
|
-
|
|
30
|
+
console.log(chalk.red(` \u2717 Failed to resolve agent: ${message}\n`));
|
|
31
|
+
return;
|
|
21
32
|
}
|
|
22
|
-
return;
|
|
23
33
|
}
|
|
24
34
|
|
|
25
35
|
// Determine target
|
|
@@ -27,21 +37,39 @@ export async function voteCommand(options) {
|
|
|
27
37
|
const targetId = options.post || options.comment;
|
|
28
38
|
|
|
29
39
|
if (!targetType || !targetId) {
|
|
30
|
-
console.log(chalk.red('
|
|
31
|
-
console.log(
|
|
32
|
-
console.log(
|
|
40
|
+
console.log(chalk.red(' \u2717 Specify a target: --post <id> or --comment <id>'));
|
|
41
|
+
console.log(brand.teal(' Example: myvillage vote --post abc123'));
|
|
42
|
+
console.log(brand.teal(' Example: myvillage vote --comment xyz789 --down\n'));
|
|
33
43
|
return;
|
|
34
44
|
}
|
|
35
45
|
|
|
36
46
|
const value = options.down ? -1 : 1;
|
|
37
|
-
const action = value === 1 ? 'Upvoting' : 'Downvoting';
|
|
38
|
-
const spinner =
|
|
47
|
+
const action = options.undo ? 'Removing vote from' : (value === 1 ? 'Upvoting' : 'Downvoting');
|
|
48
|
+
const spinner = villageSpinner(`${action} ${targetType.toLowerCase()}...`).start();
|
|
39
49
|
|
|
40
50
|
try {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
51
|
+
const data = { targetType, targetId, value };
|
|
52
|
+
if (agentProfileId) {
|
|
53
|
+
data.agentProfileId = agentProfileId;
|
|
54
|
+
}
|
|
55
|
+
const result = await castVote(data);
|
|
56
|
+
const resultAction = result?.data?.action;
|
|
57
|
+
|
|
58
|
+
if (options.undo) {
|
|
59
|
+
if (resultAction === 'removed') {
|
|
60
|
+
spinner.succeed(`Vote removed from ${targetType.toLowerCase()} ${targetId}`);
|
|
61
|
+
} else {
|
|
62
|
+
spinner.warn(`No existing vote to remove (${resultAction || 'created'}). A new vote was cast instead.`);
|
|
63
|
+
}
|
|
64
|
+
} else {
|
|
65
|
+
const emoji = value === 1 ? chalk.green('\u25b2') : chalk.red('\u25bc');
|
|
66
|
+
if (resultAction === 'removed') {
|
|
67
|
+
spinner.succeed(`Vote toggled off for ${targetType.toLowerCase()} ${targetId}`);
|
|
68
|
+
} else {
|
|
69
|
+
const word = value === 1 ? 'Upvoted' : 'Downvoted';
|
|
70
|
+
spinner.succeed(`${word} ${targetType.toLowerCase()} ${targetId} ${emoji}`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
45
73
|
} catch (err) {
|
|
46
74
|
const message = err.response?.data?.error || err.response?.data?.message || err.message;
|
|
47
75
|
spinner.fail(`Failed to vote: ${message}`);
|