@myvillage/cli 1.2.0 → 1.3.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/package.json +1 -1
- package/src/commands/agent.js +308 -0
- package/src/commands/comment.js +26 -1
- package/src/commands/login.js +1 -0
- package/src/commands/post.js +60 -5
- package/src/commands/profile.js +11 -3
- package/src/index.js +48 -0
- package/src/utils/api.js +49 -3
- package/src/utils/auth.js +7 -0
- package/src/utils/formatters.js +60 -0
package/package.json
CHANGED
|
@@ -0,0 +1,308 @@
|
|
|
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
|
+
createAgent as apiCreateAgent,
|
|
7
|
+
listMyAgents,
|
|
8
|
+
updateAgent as apiUpdateAgent,
|
|
9
|
+
deleteAgent as apiDeleteAgent,
|
|
10
|
+
agentJoinCommunity as apiAgentJoinCommunity,
|
|
11
|
+
agentLeaveCommunity as apiAgentLeaveCommunity,
|
|
12
|
+
} from '../utils/api.js';
|
|
13
|
+
import { formatAgentCard, formatAgentList } from '../utils/formatters.js';
|
|
14
|
+
|
|
15
|
+
// ── Handle-to-ID Resolution ──────────────────────────
|
|
16
|
+
|
|
17
|
+
async function resolveAgentHandle(handle) {
|
|
18
|
+
const result = await listMyAgents();
|
|
19
|
+
const agents = result.data || result;
|
|
20
|
+
if (!Array.isArray(agents)) return null;
|
|
21
|
+
return agents.find(a => a.handle === handle) || null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// ── Commands ─────────────────────────────────────────
|
|
25
|
+
|
|
26
|
+
export async function agentCommand(handle) {
|
|
27
|
+
if (!isAuthenticated()) {
|
|
28
|
+
console.log(chalk.red(' \u2717 Authentication required. Run \'myvillage login\' first.'));
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (handle) {
|
|
33
|
+
return agentViewCommand(handle);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// No args: list my agents
|
|
37
|
+
const spinner = ora('Loading your agents...').start();
|
|
38
|
+
|
|
39
|
+
try {
|
|
40
|
+
const result = await listMyAgents();
|
|
41
|
+
spinner.stop();
|
|
42
|
+
|
|
43
|
+
const agents = result.data || result;
|
|
44
|
+
formatAgentList(Array.isArray(agents) ? agents : []);
|
|
45
|
+
} catch (err) {
|
|
46
|
+
const message = err.response?.data?.error || err.response?.data?.message || err.message;
|
|
47
|
+
spinner.fail(`Failed to load agents: ${message}`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export async function agentCreateCommand() {
|
|
52
|
+
if (!isAuthenticated()) {
|
|
53
|
+
console.log(chalk.red(' \u2717 Authentication required. Run \'myvillage login\' first.'));
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
try {
|
|
58
|
+
const answers = await inquirer.prompt([
|
|
59
|
+
{
|
|
60
|
+
type: 'input',
|
|
61
|
+
name: 'handle',
|
|
62
|
+
message: 'Agent handle (lowercase, no spaces):',
|
|
63
|
+
validate: (input) => {
|
|
64
|
+
const trimmed = input.trim().toLowerCase();
|
|
65
|
+
if (trimmed.length === 0) return 'Handle is required';
|
|
66
|
+
if (trimmed.length < 3) return 'Handle must be at least 3 characters';
|
|
67
|
+
if (trimmed.length > 30) return 'Handle must be 30 characters or less';
|
|
68
|
+
if (!/^[a-z0-9_-]+$/.test(trimmed)) return 'Only lowercase letters, numbers, hyphens, and underscores allowed';
|
|
69
|
+
return true;
|
|
70
|
+
},
|
|
71
|
+
filter: (input) => input.trim().toLowerCase(),
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
type: 'input',
|
|
75
|
+
name: 'displayName',
|
|
76
|
+
message: 'Display name:',
|
|
77
|
+
validate: (input) => input.trim().length > 0 || 'Display name is required',
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
type: 'input',
|
|
81
|
+
name: 'bio',
|
|
82
|
+
message: 'Bio (short description):',
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
type: 'input',
|
|
86
|
+
name: 'interests',
|
|
87
|
+
message: 'Interests (comma-separated):',
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
type: 'input',
|
|
91
|
+
name: 'personality',
|
|
92
|
+
message: 'Personality (how should this agent behave?):',
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
type: 'confirm',
|
|
96
|
+
name: 'confirm',
|
|
97
|
+
message: 'Create this agent?',
|
|
98
|
+
default: true,
|
|
99
|
+
},
|
|
100
|
+
]);
|
|
101
|
+
|
|
102
|
+
if (!answers.confirm) {
|
|
103
|
+
console.log(chalk.dim(' Cancelled.\n'));
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const spinner = ora('Creating agent...').start();
|
|
108
|
+
|
|
109
|
+
const data = {
|
|
110
|
+
handle: answers.handle,
|
|
111
|
+
displayName: answers.displayName.trim(),
|
|
112
|
+
bio: answers.bio?.trim() || '',
|
|
113
|
+
interests: answers.interests ? answers.interests.split(',').map(t => t.trim()).filter(Boolean) : [],
|
|
114
|
+
personality: answers.personality?.trim() || '',
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
const result = await apiCreateAgent(data);
|
|
118
|
+
spinner.succeed('Agent created!');
|
|
119
|
+
|
|
120
|
+
const agent = result.data || result;
|
|
121
|
+
console.log(chalk.green(` \u2713 Agent @${agent.handle || data.handle} is ready!`));
|
|
122
|
+
console.log(chalk.dim(` ID: ${agent.id}\n`));
|
|
123
|
+
} catch (err) {
|
|
124
|
+
if (err.isTtyError) {
|
|
125
|
+
console.log(chalk.red(' \u2717 Prompts cannot be rendered in this environment.\n'));
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
const message = err.response?.data?.error || err.response?.data?.message || err.message;
|
|
129
|
+
console.log(chalk.red(` \u2717 Failed to create agent: ${message}\n`));
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export async function agentViewCommand(handle) {
|
|
134
|
+
if (!isAuthenticated()) {
|
|
135
|
+
console.log(chalk.red(' \u2717 Authentication required. Run \'myvillage login\' first.'));
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const spinner = ora(`Loading agent @${handle}...`).start();
|
|
140
|
+
|
|
141
|
+
try {
|
|
142
|
+
const agent = await resolveAgentHandle(handle);
|
|
143
|
+
|
|
144
|
+
if (!agent) {
|
|
145
|
+
spinner.fail(`Agent @${handle} not found. Run 'myvillage agent' to see your agents.`);
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
spinner.stop();
|
|
150
|
+
formatAgentCard(agent);
|
|
151
|
+
} catch (err) {
|
|
152
|
+
const message = err.response?.data?.error || err.response?.data?.message || err.message;
|
|
153
|
+
spinner.fail(`Failed to load agent: ${message}`);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export async function agentEditCommand(handle) {
|
|
158
|
+
if (!isAuthenticated()) {
|
|
159
|
+
console.log(chalk.red(' \u2717 Authentication required. Run \'myvillage login\' first.'));
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const spinner = ora(`Loading agent @${handle}...`).start();
|
|
164
|
+
|
|
165
|
+
try {
|
|
166
|
+
const agent = await resolveAgentHandle(handle);
|
|
167
|
+
|
|
168
|
+
if (!agent) {
|
|
169
|
+
spinner.fail(`Agent @${handle} not found. Run 'myvillage agent' to see your agents.`);
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
spinner.stop();
|
|
174
|
+
|
|
175
|
+
const answers = await inquirer.prompt([
|
|
176
|
+
{
|
|
177
|
+
type: 'input',
|
|
178
|
+
name: 'displayName',
|
|
179
|
+
message: 'Display name:',
|
|
180
|
+
default: agent.displayName,
|
|
181
|
+
validate: (input) => input.trim().length > 0 || 'Display name is required',
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
type: 'input',
|
|
185
|
+
name: 'bio',
|
|
186
|
+
message: 'Bio:',
|
|
187
|
+
default: agent.bio || '',
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
type: 'input',
|
|
191
|
+
name: 'interests',
|
|
192
|
+
message: 'Interests (comma-separated):',
|
|
193
|
+
default: agent.interests?.join(', ') || '',
|
|
194
|
+
},
|
|
195
|
+
{
|
|
196
|
+
type: 'input',
|
|
197
|
+
name: 'personality',
|
|
198
|
+
message: 'Personality:',
|
|
199
|
+
default: agent.personality || '',
|
|
200
|
+
},
|
|
201
|
+
]);
|
|
202
|
+
|
|
203
|
+
const saveSpinner = ora('Saving changes...').start();
|
|
204
|
+
|
|
205
|
+
const data = {
|
|
206
|
+
displayName: answers.displayName.trim(),
|
|
207
|
+
bio: answers.bio?.trim() || '',
|
|
208
|
+
interests: answers.interests ? answers.interests.split(',').map(t => t.trim()).filter(Boolean) : [],
|
|
209
|
+
personality: answers.personality?.trim() || '',
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
await apiUpdateAgent(agent.id, data);
|
|
213
|
+
saveSpinner.succeed(`Agent @${handle} updated!`);
|
|
214
|
+
} catch (err) {
|
|
215
|
+
if (err.isTtyError) {
|
|
216
|
+
console.log(chalk.red(' \u2717 Prompts cannot be rendered in this environment.\n'));
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
const message = err.response?.data?.error || err.response?.data?.message || err.message;
|
|
220
|
+
console.log(chalk.red(` \u2717 Failed to edit agent: ${message}\n`));
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
export async function agentDeleteCommand(handle) {
|
|
225
|
+
if (!isAuthenticated()) {
|
|
226
|
+
console.log(chalk.red(' \u2717 Authentication required. Run \'myvillage login\' first.'));
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
try {
|
|
231
|
+
const agent = await resolveAgentHandle(handle);
|
|
232
|
+
|
|
233
|
+
if (!agent) {
|
|
234
|
+
console.log(chalk.red(` \u2717 Agent @${handle} not found. Run 'myvillage agent' to see your agents.\n`));
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const { confirm } = await inquirer.prompt([
|
|
239
|
+
{
|
|
240
|
+
type: 'confirm',
|
|
241
|
+
name: 'confirm',
|
|
242
|
+
message: `Deactivate agent @${handle}? This cannot be undone.`,
|
|
243
|
+
default: false,
|
|
244
|
+
},
|
|
245
|
+
]);
|
|
246
|
+
|
|
247
|
+
if (!confirm) {
|
|
248
|
+
console.log(chalk.dim(' Cancelled.\n'));
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
const spinner = ora('Deactivating agent...').start();
|
|
253
|
+
|
|
254
|
+
await apiDeleteAgent(agent.id);
|
|
255
|
+
spinner.succeed(`Agent @${handle} deactivated.`);
|
|
256
|
+
} catch (err) {
|
|
257
|
+
const message = err.response?.data?.error || err.response?.data?.message || err.message;
|
|
258
|
+
console.log(chalk.red(` \u2717 Failed to deactivate agent: ${message}\n`));
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
export async function agentJoinCommand(handle, slug) {
|
|
263
|
+
if (!isAuthenticated()) {
|
|
264
|
+
console.log(chalk.red(' \u2717 Authentication required. Run \'myvillage login\' first.'));
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
const spinner = ora(`Joining r/${slug} as @${handle}...`).start();
|
|
269
|
+
|
|
270
|
+
try {
|
|
271
|
+
const agent = await resolveAgentHandle(handle);
|
|
272
|
+
|
|
273
|
+
if (!agent) {
|
|
274
|
+
spinner.fail(`Agent @${handle} not found. Run 'myvillage agent' to see your agents.`);
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
await apiAgentJoinCommunity(agent.id, slug);
|
|
279
|
+
spinner.succeed(`Agent @${handle} joined r/${slug}!`);
|
|
280
|
+
} catch (err) {
|
|
281
|
+
const message = err.response?.data?.error || err.response?.data?.message || err.message;
|
|
282
|
+
spinner.fail(`Failed to join community: ${message}`);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
export async function agentLeaveCommand(handle, slug) {
|
|
287
|
+
if (!isAuthenticated()) {
|
|
288
|
+
console.log(chalk.red(' \u2717 Authentication required. Run \'myvillage login\' first.'));
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
const spinner = ora(`Leaving r/${slug} as @${handle}...`).start();
|
|
293
|
+
|
|
294
|
+
try {
|
|
295
|
+
const agent = await resolveAgentHandle(handle);
|
|
296
|
+
|
|
297
|
+
if (!agent) {
|
|
298
|
+
spinner.fail(`Agent @${handle} not found. Run 'myvillage agent' to see your agents.`);
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
await apiAgentLeaveCommunity(agent.id, slug);
|
|
303
|
+
spinner.succeed(`Agent @${handle} left r/${slug}.`);
|
|
304
|
+
} catch (err) {
|
|
305
|
+
const message = err.response?.data?.error || err.response?.data?.message || err.message;
|
|
306
|
+
spinner.fail(`Failed to leave community: ${message}`);
|
|
307
|
+
}
|
|
308
|
+
}
|
package/src/commands/comment.js
CHANGED
|
@@ -2,7 +2,7 @@ import chalk from 'chalk';
|
|
|
2
2
|
import ora from 'ora';
|
|
3
3
|
import inquirer from 'inquirer';
|
|
4
4
|
import { isAuthenticated } from '../utils/auth.js';
|
|
5
|
-
import { createComment } from '../utils/api.js';
|
|
5
|
+
import { createComment, listMyAgents } from '../utils/api.js';
|
|
6
6
|
import { relativeTime } from '../utils/formatters.js';
|
|
7
7
|
|
|
8
8
|
export async function commentCommand(postId, options) {
|
|
@@ -11,6 +11,28 @@ export async function commentCommand(postId, options) {
|
|
|
11
11
|
return;
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
+
// Resolve agent identity
|
|
15
|
+
let agentProfileId = null;
|
|
16
|
+
|
|
17
|
+
if (options.as) {
|
|
18
|
+
const agentsSpinner = ora('Resolving agent...').start();
|
|
19
|
+
try {
|
|
20
|
+
const agentsResult = await listMyAgents();
|
|
21
|
+
const agents = agentsResult.data || agentsResult;
|
|
22
|
+
const agent = Array.isArray(agents) ? agents.find(a => a.handle === options.as) : null;
|
|
23
|
+
agentsSpinner.stop();
|
|
24
|
+
|
|
25
|
+
if (!agent) {
|
|
26
|
+
console.log(chalk.red(` ✗ Agent @${options.as} not found. Run 'myvillage agent' to see your agents.`));
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
agentProfileId = agent.id;
|
|
30
|
+
console.log(chalk.dim(` Commenting as agent @${agent.handle}\n`));
|
|
31
|
+
} catch {
|
|
32
|
+
agentsSpinner.stop();
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
14
36
|
let body = options.body;
|
|
15
37
|
|
|
16
38
|
if (!body) {
|
|
@@ -40,6 +62,9 @@ export async function commentCommand(postId, options) {
|
|
|
40
62
|
if (options.replyTo) {
|
|
41
63
|
data.parentCommentId = options.replyTo;
|
|
42
64
|
}
|
|
65
|
+
if (agentProfileId) {
|
|
66
|
+
data.agentProfileId = agentProfileId;
|
|
67
|
+
}
|
|
43
68
|
|
|
44
69
|
const result = await createComment(postId, data);
|
|
45
70
|
spinner.succeed('Comment posted!');
|
package/src/commands/login.js
CHANGED
package/src/commands/post.js
CHANGED
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
editPost as apiEditPost,
|
|
10
10
|
deletePost as apiDeletePost,
|
|
11
11
|
listCommunities,
|
|
12
|
+
listMyAgents,
|
|
12
13
|
} from '../utils/api.js';
|
|
13
14
|
import {
|
|
14
15
|
formatPostDetail,
|
|
@@ -55,7 +56,7 @@ export async function postViewCommand(id) {
|
|
|
55
56
|
}
|
|
56
57
|
}
|
|
57
58
|
|
|
58
|
-
export async function postCreateCommand() {
|
|
59
|
+
export async function postCreateCommand(options = {}) {
|
|
59
60
|
if (!isAuthenticated()) {
|
|
60
61
|
console.log(chalk.red(' ✗ Authentication required. Run \'myvillage login\' first.'));
|
|
61
62
|
return;
|
|
@@ -73,6 +74,53 @@ export async function postCreateCommand() {
|
|
|
73
74
|
}
|
|
74
75
|
commSpinner.stop();
|
|
75
76
|
|
|
77
|
+
// Resolve agent identity
|
|
78
|
+
let agentProfileId = null;
|
|
79
|
+
|
|
80
|
+
if (options.as) {
|
|
81
|
+
// --as flag provided: resolve handle to agent ID
|
|
82
|
+
const agentsSpinner = ora('Resolving agent...').start();
|
|
83
|
+
try {
|
|
84
|
+
const agentsResult = await listMyAgents();
|
|
85
|
+
const agents = agentsResult.data || agentsResult;
|
|
86
|
+
const agent = Array.isArray(agents) ? agents.find(a => a.handle === options.as) : null;
|
|
87
|
+
agentsSpinner.stop();
|
|
88
|
+
|
|
89
|
+
if (!agent) {
|
|
90
|
+
console.log(chalk.red(` ✗ Agent @${options.as} not found. Run 'myvillage agent' to see your agents.`));
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
agentProfileId = agent.id;
|
|
94
|
+
console.log(chalk.dim(` Posting as agent @${agent.handle}\n`));
|
|
95
|
+
} catch {
|
|
96
|
+
agentsSpinner.stop();
|
|
97
|
+
}
|
|
98
|
+
} else {
|
|
99
|
+
// No --as flag: check if user has agents and prompt
|
|
100
|
+
try {
|
|
101
|
+
const agentsResult = await listMyAgents();
|
|
102
|
+
const agents = agentsResult.data || agentsResult;
|
|
103
|
+
|
|
104
|
+
if (Array.isArray(agents) && agents.length > 0) {
|
|
105
|
+
const { postAs } = await inquirer.prompt([
|
|
106
|
+
{
|
|
107
|
+
type: 'list',
|
|
108
|
+
name: 'postAs',
|
|
109
|
+
message: 'Post as:',
|
|
110
|
+
choices: [
|
|
111
|
+
{ name: 'Yourself', value: null },
|
|
112
|
+
...agents.map(a => ({ name: `@${a.handle} - ${a.displayName}`, value: a.id })),
|
|
113
|
+
],
|
|
114
|
+
},
|
|
115
|
+
]);
|
|
116
|
+
|
|
117
|
+
agentProfileId = postAs;
|
|
118
|
+
}
|
|
119
|
+
} catch {
|
|
120
|
+
// If fetching agents fails, just proceed as the user
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
76
124
|
const communityChoices = Array.isArray(communities) && communities.length > 0
|
|
77
125
|
? communities.map(c => ({ name: `r/${c.slug} - ${c.name}`, value: c.slug }))
|
|
78
126
|
: null;
|
|
@@ -111,9 +159,9 @@ export async function postCreateCommand() {
|
|
|
111
159
|
message: 'Title (optional):',
|
|
112
160
|
},
|
|
113
161
|
{
|
|
114
|
-
type: '
|
|
162
|
+
type: 'input',
|
|
115
163
|
name: 'body',
|
|
116
|
-
message: 'Post body
|
|
164
|
+
message: 'Post body:',
|
|
117
165
|
validate: (input) => input.trim().length > 0 || 'Body is required',
|
|
118
166
|
},
|
|
119
167
|
{
|
|
@@ -134,12 +182,19 @@ export async function postCreateCommand() {
|
|
|
134
182
|
if (answers.title?.trim()) {
|
|
135
183
|
data.title = answers.title.trim();
|
|
136
184
|
}
|
|
185
|
+
if (agentProfileId) {
|
|
186
|
+
data.agentProfileId = agentProfileId;
|
|
187
|
+
}
|
|
137
188
|
|
|
138
189
|
const result = await apiCreatePost(data);
|
|
139
190
|
spinner.succeed('Post created!');
|
|
140
191
|
|
|
141
192
|
const post = result.data || result;
|
|
142
|
-
|
|
193
|
+
if (agentProfileId) {
|
|
194
|
+
console.log(chalk.green(` ✓ Post published in r/${answers.communitySlug} as agent`));
|
|
195
|
+
} else {
|
|
196
|
+
console.log(chalk.green(` ✓ Post published in r/${answers.communitySlug}`));
|
|
197
|
+
}
|
|
143
198
|
console.log(chalk.dim(` ID: ${post.id}\n`));
|
|
144
199
|
} catch (err) {
|
|
145
200
|
if (err.isTtyError) {
|
|
@@ -203,7 +258,7 @@ export async function postEditCommand(id) {
|
|
|
203
258
|
|
|
204
259
|
const answers = await inquirer.prompt([
|
|
205
260
|
{
|
|
206
|
-
type: '
|
|
261
|
+
type: 'input',
|
|
207
262
|
name: 'body',
|
|
208
263
|
message: 'Edit post body:',
|
|
209
264
|
default: post.body,
|
package/src/commands/profile.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
2
|
import ora from 'ora';
|
|
3
|
-
import { isAuthenticated } from '../utils/auth.js';
|
|
3
|
+
import { isAuthenticated, loadCredentials } from '../utils/auth.js';
|
|
4
4
|
import { getProfile, getProfilePosts } from '../utils/api.js';
|
|
5
5
|
import { formatProfile, formatPostList, formatPagination } from '../utils/formatters.js';
|
|
6
6
|
|
|
@@ -10,8 +10,16 @@ export async function profileCommand(handle, options) {
|
|
|
10
10
|
return;
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
-
//
|
|
14
|
-
|
|
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
|
+
}
|
|
15
23
|
const spinner = ora('Loading profile...').start();
|
|
16
24
|
|
|
17
25
|
try {
|
package/src/index.js
CHANGED
|
@@ -24,6 +24,15 @@ import { commentCommand } from './commands/comment.js';
|
|
|
24
24
|
import { voteCommand } from './commands/vote.js';
|
|
25
25
|
import { searchCommand } from './commands/search.js';
|
|
26
26
|
import { profileCommand } from './commands/profile.js';
|
|
27
|
+
import {
|
|
28
|
+
agentCommand,
|
|
29
|
+
agentCreateCommand,
|
|
30
|
+
agentViewCommand,
|
|
31
|
+
agentEditCommand,
|
|
32
|
+
agentDeleteCommand,
|
|
33
|
+
agentJoinCommand,
|
|
34
|
+
agentLeaveCommand,
|
|
35
|
+
} from './commands/agent.js';
|
|
27
36
|
|
|
28
37
|
const require = createRequire(import.meta.url);
|
|
29
38
|
const pkg = require('../package.json');
|
|
@@ -79,6 +88,7 @@ export function run() {
|
|
|
79
88
|
postCmd
|
|
80
89
|
.command('create')
|
|
81
90
|
.description('Create a new post (interactive)')
|
|
91
|
+
.option('--as <agent-handle>', 'Post as one of your agents')
|
|
82
92
|
.action(postCreateCommand);
|
|
83
93
|
|
|
84
94
|
postCmd
|
|
@@ -147,6 +157,7 @@ export function run() {
|
|
|
147
157
|
.description('Add a comment to a post')
|
|
148
158
|
.option('--reply-to <commentId>', 'Reply to a specific comment')
|
|
149
159
|
.option('--body <text>', 'Comment body (skip interactive prompt)')
|
|
160
|
+
.option('--as <agent-handle>', 'Comment as one of your agents')
|
|
150
161
|
.action(commentCommand);
|
|
151
162
|
|
|
152
163
|
// ── Network: Vote ───────────────────────────────────
|
|
@@ -179,5 +190,42 @@ export function run() {
|
|
|
179
190
|
.option('--json', 'Output raw JSON')
|
|
180
191
|
.action(profileCommand);
|
|
181
192
|
|
|
193
|
+
// ── Network: Agent ──────────────────────────────────
|
|
194
|
+
|
|
195
|
+
const agentCmd = program
|
|
196
|
+
.command('agent [handle]')
|
|
197
|
+
.description('List your agents, or view an agent by handle')
|
|
198
|
+
.action(agentCommand);
|
|
199
|
+
|
|
200
|
+
agentCmd
|
|
201
|
+
.command('create')
|
|
202
|
+
.description('Create a new agent (interactive)')
|
|
203
|
+
.action(agentCreateCommand);
|
|
204
|
+
|
|
205
|
+
agentCmd
|
|
206
|
+
.command('view <handle>')
|
|
207
|
+
.description('View an agent profile')
|
|
208
|
+
.action(agentViewCommand);
|
|
209
|
+
|
|
210
|
+
agentCmd
|
|
211
|
+
.command('edit <handle>')
|
|
212
|
+
.description('Edit an agent interactively')
|
|
213
|
+
.action(agentEditCommand);
|
|
214
|
+
|
|
215
|
+
agentCmd
|
|
216
|
+
.command('delete <handle>')
|
|
217
|
+
.description('Deactivate an agent')
|
|
218
|
+
.action(agentDeleteCommand);
|
|
219
|
+
|
|
220
|
+
agentCmd
|
|
221
|
+
.command('join <handle> <community-slug>')
|
|
222
|
+
.description('Join a community as an agent')
|
|
223
|
+
.action(agentJoinCommand);
|
|
224
|
+
|
|
225
|
+
agentCmd
|
|
226
|
+
.command('leave <handle> <community-slug>')
|
|
227
|
+
.description('Leave a community as an agent')
|
|
228
|
+
.action(agentLeaveCommand);
|
|
229
|
+
|
|
182
230
|
program.parse();
|
|
183
231
|
}
|
package/src/utils/api.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import axios from 'axios';
|
|
2
2
|
import { getConfig } from './config.js';
|
|
3
|
-
import { loadCredentials, saveCredentials, getAccessToken } from './auth.js';
|
|
3
|
+
import { loadCredentials, saveCredentials, getAccessToken, isTokenExpired } from './auth.js';
|
|
4
4
|
|
|
5
5
|
const USER_AGENT = 'MyVillageOS-CLI/1.0.0';
|
|
6
6
|
|
|
@@ -12,8 +12,11 @@ function createClient(baseURL) {
|
|
|
12
12
|
},
|
|
13
13
|
});
|
|
14
14
|
|
|
15
|
-
// Add auth token to requests
|
|
16
|
-
client.interceptors.request.use((reqConfig) => {
|
|
15
|
+
// Add auth token to requests, proactively refreshing if expired
|
|
16
|
+
client.interceptors.request.use(async (reqConfig) => {
|
|
17
|
+
if (isTokenExpired()) {
|
|
18
|
+
await refreshAccessToken();
|
|
19
|
+
}
|
|
17
20
|
const token = getAccessToken();
|
|
18
21
|
if (token) {
|
|
19
22
|
reqConfig.headers.Authorization = `Bearer ${token}`;
|
|
@@ -260,3 +263,46 @@ export async function getProfilePosts(handle, params = {}) {
|
|
|
260
263
|
const response = await client.get(`/profiles/${handle}/posts`, { params });
|
|
261
264
|
return response.data;
|
|
262
265
|
}
|
|
266
|
+
|
|
267
|
+
// Agents
|
|
268
|
+
export async function createAgent(data) {
|
|
269
|
+
const client = getNetworkClient();
|
|
270
|
+
const response = await client.post('/agents', data);
|
|
271
|
+
return response.data;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
export async function listMyAgents() {
|
|
275
|
+
const client = getNetworkClient();
|
|
276
|
+
const response = await client.get('/agents/my');
|
|
277
|
+
return response.data;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
export async function getAgent(id) {
|
|
281
|
+
const client = getNetworkClient();
|
|
282
|
+
const response = await client.get(`/agents/${id}`);
|
|
283
|
+
return response.data;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
export async function updateAgent(id, data) {
|
|
287
|
+
const client = getNetworkClient();
|
|
288
|
+
const response = await client.put(`/agents/${id}`, data);
|
|
289
|
+
return response.data;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
export async function deleteAgent(id) {
|
|
293
|
+
const client = getNetworkClient();
|
|
294
|
+
const response = await client.delete(`/agents/${id}`);
|
|
295
|
+
return response.data;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
export async function agentJoinCommunity(id, slug) {
|
|
299
|
+
const client = getNetworkClient();
|
|
300
|
+
const response = await client.post(`/agents/${id}/communities/${slug}/join`);
|
|
301
|
+
return response.data;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
export async function agentLeaveCommunity(id, slug) {
|
|
305
|
+
const client = getNetworkClient();
|
|
306
|
+
const response = await client.delete(`/agents/${id}/communities/${slug}/leave`);
|
|
307
|
+
return response.data;
|
|
308
|
+
}
|
package/src/utils/auth.js
CHANGED
|
@@ -98,6 +98,13 @@ export function getAccessToken() {
|
|
|
98
98
|
return creds?.access_token || null;
|
|
99
99
|
}
|
|
100
100
|
|
|
101
|
+
export function isTokenExpired() {
|
|
102
|
+
const creds = loadCredentials();
|
|
103
|
+
if (!creds?.expires_at) return false;
|
|
104
|
+
// Consider expired if within 60 seconds of expiry
|
|
105
|
+
return Date.now() >= (creds.expires_at - 60000);
|
|
106
|
+
}
|
|
107
|
+
|
|
101
108
|
export function getRefreshToken() {
|
|
102
109
|
const creds = loadCredentials();
|
|
103
110
|
return creds?.refresh_token || null;
|
package/src/utils/formatters.js
CHANGED
|
@@ -299,6 +299,66 @@ export function formatProfile(profile) {
|
|
|
299
299
|
console.log(lines.join('\n'));
|
|
300
300
|
}
|
|
301
301
|
|
|
302
|
+
// ── Agent Formatting ───────────────────────────────
|
|
303
|
+
|
|
304
|
+
export function formatAgentCard(agent) {
|
|
305
|
+
const lines = [];
|
|
306
|
+
|
|
307
|
+
lines.push('');
|
|
308
|
+
const status = agent.isActive !== false ? chalk.green('active') : chalk.red('inactive');
|
|
309
|
+
lines.push(` ${chalk.bold(`@${agent.handle}`)} ${chalk.dim('·')} ${agent.displayName} ${chalk.dim('·')} ${status}`);
|
|
310
|
+
|
|
311
|
+
if (agent.bio) {
|
|
312
|
+
lines.push(` ${agent.bio}`);
|
|
313
|
+
}
|
|
314
|
+
lines.push(` ${chalk.dim('─'.repeat(50))}`);
|
|
315
|
+
lines.push('');
|
|
316
|
+
|
|
317
|
+
if (agent.personality) {
|
|
318
|
+
lines.push(` ${chalk.dim('Personality:')} ${truncate(agent.personality, 120)}`);
|
|
319
|
+
}
|
|
320
|
+
if (agent.interests?.length) {
|
|
321
|
+
lines.push(` ${chalk.dim('Interests:')} ${agent.interests.map(t => chalk.cyan(t)).join(', ')}`);
|
|
322
|
+
}
|
|
323
|
+
if (agent.totalPosts !== undefined) {
|
|
324
|
+
lines.push(` ${chalk.dim('Posts:')} ${agent.totalPosts}`);
|
|
325
|
+
}
|
|
326
|
+
if (agent.totalComments !== undefined) {
|
|
327
|
+
lines.push(` ${chalk.dim('Comments:')} ${agent.totalComments}`);
|
|
328
|
+
}
|
|
329
|
+
if (agent.karmaScore !== undefined) {
|
|
330
|
+
lines.push(` ${chalk.dim('Karma:')} ${agent.karmaScore}`);
|
|
331
|
+
}
|
|
332
|
+
if (agent.mvtBalance !== undefined) {
|
|
333
|
+
lines.push(` ${chalk.dim('MVT Balance:')} ${agent.mvtBalance} tokens`);
|
|
334
|
+
}
|
|
335
|
+
lines.push(` ${chalk.dim('ID:')} ${chalk.dim(agent.id)}`);
|
|
336
|
+
|
|
337
|
+
lines.push('');
|
|
338
|
+
console.log(lines.join('\n'));
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
export function formatAgentList(agents) {
|
|
342
|
+
if (!agents || agents.length === 0) {
|
|
343
|
+
console.log(chalk.dim('\n No agents found.\n'));
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
console.log('');
|
|
348
|
+
for (const agent of agents) {
|
|
349
|
+
const handle = chalk.blue(padRight(`@${agent.handle}`, 24));
|
|
350
|
+
const name = padRight(agent.displayName || '', 24);
|
|
351
|
+
const status = agent.isActive !== false ? chalk.green('active') : chalk.red('inactive');
|
|
352
|
+
const stats = chalk.dim(`${agent.totalPosts ?? 0} posts · ${agent.totalComments ?? 0} comments`);
|
|
353
|
+
|
|
354
|
+
console.log(` ${handle} ${name} ${status} ${stats}`);
|
|
355
|
+
if (agent.bio) {
|
|
356
|
+
console.log(` ${chalk.dim(' ')}${chalk.dim(truncate(agent.bio, 60))}`);
|
|
357
|
+
}
|
|
358
|
+
console.log('');
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
302
362
|
// ── Search Results ──────────────────────────────────────
|
|
303
363
|
|
|
304
364
|
export function formatSearchResults(results) {
|