@myvillage/cli 1.10.2 → 1.18.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.
@@ -12,6 +12,20 @@ import {
12
12
  agentJoinCommunity as apiAgentJoinCommunity,
13
13
  listCommunities,
14
14
  getAgentActivity,
15
+ listMyUnlinkedAgentProfiles,
16
+ createVillageAgent as apiCreateVillageAgent,
17
+ listAgentTasks,
18
+ assignAgentTask,
19
+ claimAgentTask,
20
+ completeAgentTask,
21
+ upsertAgentMemory,
22
+ listAgentMemory,
23
+ getAgentMemoryEntry,
24
+ deleteAgentMemoryEntry,
25
+ listKnowledgeFiltered,
26
+ shareKnowledgeAsAgent,
27
+ retryAgentTask,
28
+ retryFailedAgentTasks,
15
29
  } from '../utils/api.js';
16
30
  import {
17
31
  getAgentDir,
@@ -39,6 +53,36 @@ export async function agentCreateLocalCommand() {
39
53
  }
40
54
 
41
55
  try {
56
+ // Optional: let the developer reuse an existing AgentProfile (network identity)
57
+ // they already own and haven't yet linked to a VillageAgent.
58
+ let preselectedProfile = null;
59
+ try {
60
+ const result = await listMyUnlinkedAgentProfiles();
61
+ const profiles = Array.isArray(result?.data) ? result.data : [];
62
+ if (profiles.length > 0) {
63
+ const { useExisting } = await inquirer.prompt([{
64
+ type: 'confirm',
65
+ name: 'useExisting',
66
+ message: `You have ${profiles.length} unlinked network identity(ies). Reuse one for this agent?`,
67
+ default: false,
68
+ }]);
69
+ if (useExisting) {
70
+ const { profileId } = await inquirer.prompt([{
71
+ type: 'list',
72
+ name: 'profileId',
73
+ message: 'Pick a network identity:',
74
+ choices: profiles.map(p => ({
75
+ name: `@${p.handle} — ${p.displayName}`,
76
+ value: p.id,
77
+ })),
78
+ }]);
79
+ preselectedProfile = profiles.find(p => p.id === profileId) || null;
80
+ }
81
+ }
82
+ } catch {
83
+ // listMyUnlinkedAgentProfiles can fail if the user has no villager — that's fine, just skip
84
+ }
85
+
42
86
  const answers = await inquirer.prompt([
43
87
  {
44
88
  type: 'input',
@@ -151,6 +195,16 @@ export async function agentCreateLocalCommand() {
151
195
  model: answers.model,
152
196
  });
153
197
 
198
+ // If the developer picked an existing AgentProfile, pre-fill man.agent_id
199
+ // so `start` skips the lazy-create step and links the VillageAgent shim to it instead.
200
+ if (preselectedProfile?.id) {
201
+ const cfg = readAgentConfig(answers.name);
202
+ cfg.man = cfg.man || {};
203
+ cfg.man.agent_id = preselectedProfile.id;
204
+ cfg.man.handle = preselectedProfile.handle;
205
+ writeAgentConfig(answers.name, cfg);
206
+ }
207
+
154
208
  spinner.succeed('Agent scaffolded!');
155
209
 
156
210
  console.log(brand.green(` \u2713 Agent created at ~/.myvillage/agents/${answers.name}/\n`));
@@ -231,7 +285,7 @@ export async function agentStartCommand(name) {
231
285
  }
232
286
  }
233
287
 
234
- // Register on MAN if first start
288
+ // Register on MAN if first start (skipped when `create` preselected an existing AgentProfile)
235
289
  if (!agentConfig.man?.agent_id) {
236
290
  const regSpinner = villageSpinner('Registering agent on the MAN network...').start();
237
291
  try {
@@ -251,6 +305,32 @@ export async function agentStartCommand(name) {
251
305
 
252
306
  regSpinner.succeed(`Registered as @${agent.handle || agentConfig.name} on MAN.`);
253
307
 
308
+ // Also create the VillageAgent shim so this agent has a task queue
309
+ // and shared autonomous-engagement controls. This is what `loop.js`
310
+ // polls against. Failure is non-fatal — the agent can still run locally.
311
+ try {
312
+ const shimResult = await apiCreateVillageAgent({
313
+ name: agentConfig.display_name || agentConfig.name,
314
+ title: 'Developer Agent',
315
+ description: agentConfig.description || '',
316
+ avatar: agent.avatarUrl || '',
317
+ archetypes: [],
318
+ greeting: '',
319
+ starterPrompts: [],
320
+ associatedVillages: [],
321
+ specialInstructions: null,
322
+ agentProfileId: agent.id,
323
+ });
324
+ const shim = shimResult.agent || shimResult;
325
+ if (shim?.id) {
326
+ agentConfig.man.village_agent_id = shim.id;
327
+ writeAgentConfig(name, agentConfig);
328
+ }
329
+ } catch (shimErr) {
330
+ const shimMsg = shimErr.response?.data?.error || shimErr.message;
331
+ console.log(brand.teal(` Note: task queue not available (${shimMsg}). Agent will run in feed-monitoring mode.`));
332
+ }
333
+
254
334
  // Auto-join default communities so the agent can post
255
335
  try {
256
336
  const commResult = await listCommunities({ pageSize: 50 });
@@ -284,6 +364,34 @@ export async function agentStartCommand(name) {
284
364
  }
285
365
  }
286
366
 
367
+ // If we have an AgentProfile but no VillageAgent shim yet (e.g., the developer
368
+ // preselected an existing AgentProfile via `create`), create it now.
369
+ if (agentConfig.man?.agent_id && !agentConfig.man?.village_agent_id) {
370
+ try {
371
+ const shimResult = await apiCreateVillageAgent({
372
+ name: agentConfig.display_name || agentConfig.name,
373
+ title: 'Developer Agent',
374
+ description: agentConfig.description || '',
375
+ avatar: '',
376
+ archetypes: [],
377
+ greeting: '',
378
+ starterPrompts: [],
379
+ associatedVillages: [],
380
+ specialInstructions: null,
381
+ agentProfileId: agentConfig.man.agent_id,
382
+ });
383
+ const shim = shimResult.agent || shimResult;
384
+ if (shim?.id) {
385
+ agentConfig.man.village_agent_id = shim.id;
386
+ writeAgentConfig(name, agentConfig);
387
+ console.log(brand.teal(` Task queue ready: ${shim.id}`));
388
+ }
389
+ } catch (shimErr) {
390
+ const shimMsg = shimErr.response?.data?.error || shimErr.message;
391
+ console.log(brand.teal(` Note: task queue not available (${shimMsg}).`));
392
+ }
393
+ }
394
+
287
395
  // Migrate tools.yaml: replace man-feed with myvillage MCP server
288
396
  const toolsConfig = readToolsYaml(name);
289
397
  if (toolsConfig.servers?.['man-feed'] && !toolsConfig.servers?.['myvillage']) {
@@ -300,6 +408,9 @@ export async function agentStartCommand(name) {
300
408
  if (agentConfig.man?.agent_id) {
301
409
  process.env.MYVILLAGE_AGENT_ID = agentConfig.man.agent_id;
302
410
  }
411
+ if (agentConfig.man?.village_agent_id) {
412
+ process.env.MYVILLAGE_VILLAGE_AGENT_ID = agentConfig.man.village_agent_id;
413
+ }
303
414
 
304
415
  // Fork the daemon process
305
416
  const spinner = villageSpinner(`Starting agent "${name}"...`).start();
@@ -670,3 +781,286 @@ export async function agentDeleteLocalCommand(name) {
670
781
  console.log(chalk.red(` \u2717 Failed to delete agent: ${err.message}\n`));
671
782
  }
672
783
  }
784
+
785
+ // \u2500\u2500 Tasks \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
786
+
787
+ function resolveVillageAgentId(name) {
788
+ if (!agentExists(name)) {
789
+ console.log(chalk.red(` \u2717 Agent "${name}" not found.\n`));
790
+ return null;
791
+ }
792
+ const config = readAgentConfig(name);
793
+ const id = config?.man?.village_agent_id;
794
+ if (!id) {
795
+ console.log(chalk.yellow(` Agent "${name}" has no task queue yet \u2014 start it once so the VillageAgent shim is created.\n`));
796
+ return null;
797
+ }
798
+ return id;
799
+ }
800
+
801
+ export async function agentTaskListCommand(name, options = {}) {
802
+ if (!isAuthenticated()) {
803
+ console.log(chalk.red(' \u2717 Authentication required. Run \'myvillage login\' first.'));
804
+ return;
805
+ }
806
+ const villageAgentId = resolveVillageAgentId(name);
807
+ if (!villageAgentId) return;
808
+
809
+ try {
810
+ const result = await listAgentTasks(villageAgentId, {
811
+ status: options.status,
812
+ limit: options.limit ? Number(options.limit) : undefined,
813
+ });
814
+ const tasks = result.tasks || [];
815
+ if (tasks.length === 0) {
816
+ console.log(brand.teal(` No tasks${options.status ? ` with status ${options.status}` : ''}.\n`));
817
+ return;
818
+ }
819
+ console.log(brand.teal(` ${tasks.length} task(s):\n`));
820
+ for (const t of tasks) {
821
+ console.log(` ${brand.gold(t.id)} ${chalk.bold(t.taskType)} ${t.status} priority=${t.priority}`);
822
+ if (t.instruction) console.log(` ${chalk.dim(t.instruction)}`);
823
+ }
824
+ console.log('');
825
+ } catch (err) {
826
+ const msg = err.response?.data?.error || err.message;
827
+ console.log(chalk.red(` \u2717 Failed to list tasks: ${msg}\n`));
828
+ }
829
+ }
830
+
831
+ export async function agentTaskAssignCommand(name, options = {}) {
832
+ if (!isAuthenticated()) {
833
+ console.log(chalk.red(' \u2717 Authentication required. Run \'myvillage login\' first.'));
834
+ return;
835
+ }
836
+ const villageAgentId = resolveVillageAgentId(name);
837
+ if (!villageAgentId) return;
838
+
839
+ if (!options.type) {
840
+ console.log(chalk.red(' \u2717 --type is required (e.g., CLIENT_TASK, GENERATE_POST, SHARE_KNOWLEDGE)\n'));
841
+ return;
842
+ }
843
+
844
+ let inputObj = {};
845
+ if (options.input) {
846
+ try { inputObj = JSON.parse(options.input); }
847
+ catch { console.log(chalk.red(' \u2717 --input must be valid JSON\n')); return; }
848
+ }
849
+
850
+ try {
851
+ const result = await assignAgentTask(villageAgentId, {
852
+ taskType: options.type,
853
+ instruction: options.instruction,
854
+ input: inputObj,
855
+ priority: options.priority ? Number(options.priority) : 5,
856
+ });
857
+ const task = result.task || result;
858
+ console.log(brand.green(` \u2713 Task ${task.id} assigned (${task.taskType}, status=${task.status}).\n`));
859
+ } catch (err) {
860
+ const msg = err.response?.data?.error || err.message;
861
+ console.log(chalk.red(` \u2717 Failed to assign task: ${msg}\n`));
862
+ }
863
+ }
864
+
865
+ // Exported for use by the runtime loop, not registered as a CLI command directly.
866
+ export async function pollAndClaimNextTask(villageAgentId) {
867
+ const result = await listAgentTasks(villageAgentId, { status: 'PENDING', limit: 5 });
868
+ const pending = result.tasks || [];
869
+ if (pending.length === 0) return null;
870
+ for (const task of pending) {
871
+ try {
872
+ const claim = await claimAgentTask(villageAgentId, task.id);
873
+ return claim.data || claim;
874
+ } catch {
875
+ // Race lost \u2014 try the next task
876
+ }
877
+ }
878
+ return null;
879
+ }
880
+
881
+ export async function reportTaskOutcome(villageAgentId, taskId, outcome) {
882
+ return completeAgentTask(villageAgentId, taskId, outcome);
883
+ }
884
+
885
+ export async function agentTaskRetryCommand(name, taskId) {
886
+ if (!isAuthenticated()) {
887
+ console.log(chalk.red(' ✗ Authentication required. Run \'myvillage login\' first.'));
888
+ return;
889
+ }
890
+ const villageAgentId = resolveVillageAgentId(name);
891
+ if (!villageAgentId) return;
892
+ if (!taskId) {
893
+ console.log(chalk.red(' ✗ Usage: myvillage agent task-retry <name> <taskId>\n'));
894
+ return;
895
+ }
896
+
897
+ try {
898
+ await retryAgentTask(villageAgentId, taskId);
899
+ console.log(brand.green(` ✓ Task ${taskId} reset to PENDING. Agent will re-claim on next poll.\n`));
900
+ } catch (err) {
901
+ const msg = err.response?.data?.error || err.message;
902
+ console.log(chalk.red(` ✗ Retry failed: ${msg}\n`));
903
+ }
904
+ }
905
+
906
+ export async function agentTaskRetryFailedCommand(name, options = {}) {
907
+ if (!isAuthenticated()) {
908
+ console.log(chalk.red(' ✗ Authentication required. Run \'myvillage login\' first.'));
909
+ return;
910
+ }
911
+ const villageAgentId = resolveVillageAgentId(name);
912
+ if (!villageAgentId) return;
913
+
914
+ try {
915
+ const result = await retryFailedAgentTasks(villageAgentId, options.filter);
916
+ const filterNote = options.filter ? chalk.dim(` (filter: ${options.filter})`) : '';
917
+ console.log(brand.green(` ✓ ${result.retried} task(s) reset to PENDING${filterNote}.\n`));
918
+ } catch (err) {
919
+ const msg = err.response?.data?.error || err.message;
920
+ console.log(chalk.red(` ✗ Bulk retry failed: ${msg}\n`));
921
+ }
922
+ }
923
+
924
+ // ── Memory (short-term KV) and Recall (long-term searchable) ────
925
+
926
+ function resolveAgentProfileId(name) {
927
+ if (!agentExists(name)) {
928
+ console.log(chalk.red(` ✗ Agent "${name}" not found.\n`));
929
+ return null;
930
+ }
931
+ const config = readAgentConfig(name);
932
+ const id = config?.man?.agent_id;
933
+ if (!id) {
934
+ console.log(chalk.yellow(` Agent "${name}" hasn't registered on the network yet — start it once so an AgentProfile is created.\n`));
935
+ return null;
936
+ }
937
+ return id;
938
+ }
939
+
940
+ export async function agentMemoryCommand(name, action, ...args) {
941
+ if (!isAuthenticated()) {
942
+ console.log(chalk.red(' ✗ Authentication required. Run \'myvillage login\' first.'));
943
+ return;
944
+ }
945
+ const agentProfileId = resolveAgentProfileId(name);
946
+ if (!agentProfileId) return;
947
+
948
+ try {
949
+ if (action === 'list') {
950
+ const result = await listAgentMemory(agentProfileId);
951
+ const items = result.data || result.memories || result;
952
+ if (!Array.isArray(items) || items.length === 0) {
953
+ console.log(brand.teal(' No memory entries.\n'));
954
+ return;
955
+ }
956
+ console.log(brand.teal(` ${items.length} memory entr${items.length === 1 ? 'y' : 'ies'}:\n`));
957
+ for (const m of items) {
958
+ console.log(` ${chalk.bold(m.key)}${m.category ? chalk.dim(` [${m.category}]`) : ''}`);
959
+ const v = typeof m.value === 'string' ? m.value : JSON.stringify(m.value);
960
+ console.log(` ${chalk.dim(v.slice(0, 200))}`);
961
+ }
962
+ console.log('');
963
+ } else if (action === 'get') {
964
+ const key = args[0];
965
+ if (!key) {
966
+ console.log(chalk.red(' ✗ Usage: myvillage agent memory <name> get <key>\n'));
967
+ return;
968
+ }
969
+ const result = await getAgentMemoryEntry(agentProfileId, key);
970
+ console.log(JSON.stringify(result.data || result, null, 2));
971
+ } else if (action === 'set') {
972
+ const [key, ...rest] = args;
973
+ const value = rest.join(' ');
974
+ if (!key || !value) {
975
+ console.log(chalk.red(' ✗ Usage: myvillage agent memory <name> set <key> <value>\n'));
976
+ return;
977
+ }
978
+ // Try JSON first so { "foo": 1 } stays structured; fall back to string.
979
+ let parsed;
980
+ try { parsed = JSON.parse(value); } catch { parsed = value; }
981
+ await upsertAgentMemory(agentProfileId, { key, value: parsed });
982
+ console.log(brand.green(` ✓ ${key} set.\n`));
983
+ } else if (action === 'delete') {
984
+ const key = args[0];
985
+ if (!key) {
986
+ console.log(chalk.red(' ✗ Usage: myvillage agent memory <name> delete <key>\n'));
987
+ return;
988
+ }
989
+ await deleteAgentMemoryEntry(agentProfileId, key);
990
+ console.log(brand.green(` ✓ ${key} deleted.\n`));
991
+ } else {
992
+ console.log(chalk.red(` ✗ Unknown memory action: ${action}. Use list|get|set|delete.\n`));
993
+ }
994
+ } catch (err) {
995
+ const msg = err.response?.data?.error || err.message;
996
+ console.log(chalk.red(` ✗ Memory ${action} failed: ${msg}\n`));
997
+ }
998
+ }
999
+
1000
+ export async function agentRecallCommand(name, query, options = {}) {
1001
+ if (!isAuthenticated()) {
1002
+ console.log(chalk.red(' ✗ Authentication required. Run \'myvillage login\' first.'));
1003
+ return;
1004
+ }
1005
+ const agentProfileId = resolveAgentProfileId(name);
1006
+ if (!agentProfileId) return;
1007
+ if (!query) {
1008
+ console.log(chalk.red(' ✗ Usage: myvillage agent recall <name> <query>\n'));
1009
+ return;
1010
+ }
1011
+
1012
+ try {
1013
+ const result = await listKnowledgeFiltered({
1014
+ createdByAgentId: agentProfileId,
1015
+ search: query,
1016
+ limit: options.limit ? Number(options.limit) : 10,
1017
+ });
1018
+ const items = result.data || result.knowledge || result.knowledgeSubmissions || [];
1019
+ if (!Array.isArray(items) || items.length === 0) {
1020
+ console.log(brand.teal(` No memories matched "${query}".\n`));
1021
+ return;
1022
+ }
1023
+ console.log(brand.teal(` ${items.length} matching memor${items.length === 1 ? 'y' : 'ies'}:\n`));
1024
+ for (const k of items) {
1025
+ const text = k.originalText || k.text || '';
1026
+ console.log(` ${brand.gold(k.id)} ${chalk.dim(new Date(k.createdAt).toLocaleString())}`);
1027
+ console.log(` ${text.slice(0, 280)}${text.length > 280 ? '…' : ''}`);
1028
+ }
1029
+ console.log('');
1030
+ } catch (err) {
1031
+ const msg = err.response?.data?.error || err.message;
1032
+ console.log(chalk.red(` ✗ Recall failed: ${msg}\n`));
1033
+ }
1034
+ }
1035
+
1036
+ export async function agentRememberCommand(name, text, options = {}) {
1037
+ if (!isAuthenticated()) {
1038
+ console.log(chalk.red(' ✗ Authentication required. Run \'myvillage login\' first.'));
1039
+ return;
1040
+ }
1041
+ const agentProfileId = resolveAgentProfileId(name);
1042
+ if (!agentProfileId) return;
1043
+ if (!text) {
1044
+ console.log(chalk.red(' ✗ Usage: myvillage agent remember <name> "<text to remember>"\n'));
1045
+ return;
1046
+ }
1047
+
1048
+ const themes = options.themes
1049
+ ? options.themes.split(',').map(t => t.trim()).filter(Boolean)
1050
+ : [];
1051
+
1052
+ try {
1053
+ const result = await shareKnowledgeAsAgent({
1054
+ original_text: text,
1055
+ summary: options.summary || null,
1056
+ themes,
1057
+ sharing_option: options.sharing || 'PRIVATE',
1058
+ agent_profile_id: agentProfileId,
1059
+ });
1060
+ const k = result.knowledge || result.data || result;
1061
+ console.log(brand.green(` ✓ Remembered as ${k.id || 'knowledge entry'}.\n`));
1062
+ } catch (err) {
1063
+ const msg = err.response?.data?.error || err.message;
1064
+ console.log(chalk.red(` ✗ Remember failed: ${msg}\n`));
1065
+ }
1066
+ }
@@ -7,7 +7,7 @@ import inquirer from 'inquirer';
7
7
  import { isAuthenticated } from '../utils/auth.js';
8
8
  import { createGameCommand } from './create-game.js';
9
9
  import { createAgenticAppProject } from '../utils/agentic-templates.js';
10
- import { registerOAuthClient } from '../utils/api.js';
10
+ import { registerOAuthClient, registerClientAgent, listMyAgents } from '../utils/api.js';
11
11
 
12
12
  export async function createCommand() {
13
13
  // Check authentication
@@ -160,6 +160,65 @@ async function applicationFlow() {
160
160
  }
161
161
  }
162
162
 
163
+ // Client agent automation (optional)
164
+ let agentConfig = null;
165
+ if (hasOAuth && oauthCredentials) {
166
+ const { enableAgent } = await inquirer.prompt([{
167
+ type: 'confirm',
168
+ name: 'enableAgent',
169
+ message: 'Enable platform agent automation?',
170
+ default: false,
171
+ }]);
172
+
173
+ if (enableAgent) {
174
+ try {
175
+ const agentResult = await listMyAgents();
176
+ const agents = agentResult.data || agentResult;
177
+
178
+ if (Array.isArray(agents) && agents.length > 0) {
179
+ const { selectedAgent, workflowTypes } = await inquirer.prompt([
180
+ {
181
+ type: 'list',
182
+ name: 'selectedAgent',
183
+ message: 'Which agent should automate this app?',
184
+ choices: agents.map(a => ({ name: `@${a.handle} — ${a.displayName || a.handle}`, value: a })),
185
+ },
186
+ {
187
+ type: 'checkbox',
188
+ name: 'workflowTypes',
189
+ message: 'Select workflow types:',
190
+ choices: [
191
+ { name: 'Submission Processor', value: 'SUBMISSION_PROCESSOR', checked: true },
192
+ { name: 'Digest Generator', value: 'DIGEST_GENERATOR' },
193
+ { name: 'Member Matcher', value: 'MEMBER_MATCHER' },
194
+ { name: 'Stale Data Detector', value: 'STALE_DATA_DETECTOR' },
195
+ ],
196
+ validate: input => input.length > 0 ? true : 'Select at least one workflow type.',
197
+ },
198
+ ]);
199
+
200
+ const agentSpinner = villageSpinner('Registering client agent...').start();
201
+ const regResult = await registerClientAgent({
202
+ villageAgentId: selectedAgent.villageAgent?.id || selectedAgent.villageAgentId || selectedAgent.id,
203
+ clientId: slug,
204
+ clientName: basics.name,
205
+ baseUrl: 'http://localhost:3000',
206
+ workflowType: workflowTypes[0],
207
+ });
208
+ agentConfig = { apiKey: regResult.apiKey, clientId: slug };
209
+ agentSpinner.succeed('Client agent registered!');
210
+ console.log(chalk.yellow.bold(' ⚠ Save your API key — it won\'t be shown again!'));
211
+ console.log(brand.teal(` API Key: ${brand.gold(regResult.apiKey)}`));
212
+ } else {
213
+ console.log(brand.teal(' No agents found. Create one first with: myvillage agent create'));
214
+ }
215
+ } catch (err) {
216
+ console.log(chalk.yellow(` ⚠ Failed to register client agent: ${err.response?.data?.error || err.message}`));
217
+ console.log(chalk.dim(' You can register later with: myvillage agent register-client'));
218
+ }
219
+ }
220
+ }
221
+
163
222
  // Scaffold project (always Next.js)
164
223
  const spinner = villageSpinner('Creating application...').start();
165
224
  try {
@@ -172,6 +231,7 @@ async function applicationFlow() {
172
231
  includeRestApi,
173
232
  mcpToolGroups,
174
233
  oauthCredentials,
234
+ agentConfig,
175
235
  });
176
236
 
177
237
  spinner.text = 'Installing dependencies...';