@myvillage/cli 1.10.2 → 1.17.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/agent-runtime/loop.js +81 -3
- package/src/commands/agent-client.js +435 -0
- package/src/commands/agent-grant.js +131 -0
- package/src/commands/agent-local.js +354 -1
- package/src/commands/create-app.js +61 -1
- package/src/commands/media.js +185 -187
- package/src/commands/wisdom.js +185 -0
- package/src/index.js +199 -0
- package/src/utils/agentic-templates.js +10 -2
- package/src/utils/api.js +157 -0
- package/src/utils/formatters.js +72 -0
- package/src/utils/wisdom.js +102 -0
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { isAuthenticated } from '../utils/auth.js';
|
|
3
|
+
import { readAgentConfig } from '../utils/local-agent.js';
|
|
4
|
+
import { getPlatformClient } from '../utils/api.js';
|
|
5
|
+
|
|
6
|
+
const VALID_PROVIDERS = ['google', 'microsoft', 'zoom'];
|
|
7
|
+
|
|
8
|
+
function ensureAuthed() {
|
|
9
|
+
if (!isAuthenticated()) {
|
|
10
|
+
console.log(chalk.red(" ✗ Authentication required. Run 'myvillage login' first."));
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
return true;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function resolveAgentId(name) {
|
|
17
|
+
const config = readAgentConfig(name);
|
|
18
|
+
if (!config) {
|
|
19
|
+
console.log(chalk.red(` ✗ Local agent "${name}" not found`));
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
const agentId = config?.man?.agent_id;
|
|
23
|
+
if (!agentId) {
|
|
24
|
+
console.log(
|
|
25
|
+
chalk.red(
|
|
26
|
+
` ✗ Agent "${name}" has not been registered on the platform yet. ` +
|
|
27
|
+
`Run 'myvillage agent start ${name}' once to register it.`,
|
|
28
|
+
),
|
|
29
|
+
);
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
return agentId;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function normalizeProvider(provider) {
|
|
36
|
+
const lc = (provider || '').toLowerCase();
|
|
37
|
+
if (!VALID_PROVIDERS.includes(lc)) {
|
|
38
|
+
console.log(
|
|
39
|
+
chalk.red(` ✗ Invalid provider "${provider}". Must be one of: ${VALID_PROVIDERS.join(', ')}`),
|
|
40
|
+
);
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
return lc.toUpperCase();
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function printApiError(err, fallback) {
|
|
47
|
+
const status = err?.response?.status;
|
|
48
|
+
const data = err?.response?.data;
|
|
49
|
+
const code = data?.code ? ` (${data.code})` : '';
|
|
50
|
+
const message = data?.error || err?.message || fallback;
|
|
51
|
+
console.log(chalk.red(` ✗ ${message}${code}${status ? ` [HTTP ${status}]` : ''}`));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// ── List grants ────────────────────────────────────────────
|
|
55
|
+
|
|
56
|
+
export async function agentGrantsCommand(name) {
|
|
57
|
+
if (!ensureAuthed()) return;
|
|
58
|
+
const agentId = resolveAgentId(name);
|
|
59
|
+
if (!agentId) return;
|
|
60
|
+
|
|
61
|
+
const client = getPlatformClient();
|
|
62
|
+
try {
|
|
63
|
+
const response = await client.get(`/agents/${agentId}/credential-grants`);
|
|
64
|
+
const grants = response.data?.data ?? [];
|
|
65
|
+
if (grants.length === 0) {
|
|
66
|
+
console.log(chalk.dim(` No active credential grants for agent "${name}".`));
|
|
67
|
+
console.log(chalk.dim(` Grant one with: myvillage agent grant ${name} <google|microsoft|zoom>`));
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
console.log(chalk.bold(`Active credential grants for "${name}" (${agentId}):\n`));
|
|
71
|
+
for (const g of grants) {
|
|
72
|
+
const granted = new Date(g.grantedAt).toISOString().slice(0, 10);
|
|
73
|
+
console.log(` - ${chalk.cyan(g.provider)} granted ${granted}`);
|
|
74
|
+
}
|
|
75
|
+
} catch (err) {
|
|
76
|
+
printApiError(err, 'Failed to list grants');
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// ── Grant a provider ───────────────────────────────────────
|
|
81
|
+
|
|
82
|
+
export async function agentGrantCommand(name, provider) {
|
|
83
|
+
if (!ensureAuthed()) return;
|
|
84
|
+
const agentId = resolveAgentId(name);
|
|
85
|
+
if (!agentId) return;
|
|
86
|
+
const upperProvider = normalizeProvider(provider);
|
|
87
|
+
if (!upperProvider) return;
|
|
88
|
+
|
|
89
|
+
const client = getPlatformClient();
|
|
90
|
+
try {
|
|
91
|
+
await client.post(`/agents/${agentId}/credential-grants`, { provider: upperProvider });
|
|
92
|
+
console.log(
|
|
93
|
+
chalk.green(` ✓ Granted ${upperProvider} to agent "${name}".`) +
|
|
94
|
+
chalk.dim(`\n The agent can now use ${upperProvider}-backed tools (e.g. gmail_send).`),
|
|
95
|
+
);
|
|
96
|
+
} catch (err) {
|
|
97
|
+
if (err?.response?.status === 404 && err?.response?.data?.code === 'CREDENTIAL_NOT_CONNECTED') {
|
|
98
|
+
console.log(
|
|
99
|
+
chalk.yellow(
|
|
100
|
+
` ⚠ You haven't connected ${upperProvider} yet. ` +
|
|
101
|
+
`Connect it from the mobile app or web portal, then re-run this command.`,
|
|
102
|
+
),
|
|
103
|
+
);
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
printApiError(err, 'Failed to grant provider');
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// ── Revoke a provider ──────────────────────────────────────
|
|
111
|
+
|
|
112
|
+
export async function agentRevokeCommand(name, provider) {
|
|
113
|
+
if (!ensureAuthed()) return;
|
|
114
|
+
const agentId = resolveAgentId(name);
|
|
115
|
+
if (!agentId) return;
|
|
116
|
+
const upperProvider = normalizeProvider(provider);
|
|
117
|
+
if (!upperProvider) return;
|
|
118
|
+
|
|
119
|
+
const client = getPlatformClient();
|
|
120
|
+
try {
|
|
121
|
+
await client.delete(`/agents/${agentId}/credential-grants/${upperProvider}`);
|
|
122
|
+
console.log(
|
|
123
|
+
chalk.green(` ✓ Revoked ${upperProvider} from agent "${name}".`) +
|
|
124
|
+
chalk.dim(
|
|
125
|
+
`\n Tools using ${upperProvider} will fail within ~60 seconds (cache TTL).`,
|
|
126
|
+
),
|
|
127
|
+
);
|
|
128
|
+
} catch (err) {
|
|
129
|
+
printApiError(err, 'Failed to revoke provider');
|
|
130
|
+
}
|
|
131
|
+
}
|
|
@@ -12,6 +12,18 @@ 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,
|
|
15
27
|
} from '../utils/api.js';
|
|
16
28
|
import {
|
|
17
29
|
getAgentDir,
|
|
@@ -39,6 +51,36 @@ export async function agentCreateLocalCommand() {
|
|
|
39
51
|
}
|
|
40
52
|
|
|
41
53
|
try {
|
|
54
|
+
// Optional: let the developer reuse an existing AgentProfile (network identity)
|
|
55
|
+
// they already own and haven't yet linked to a VillageAgent.
|
|
56
|
+
let preselectedProfile = null;
|
|
57
|
+
try {
|
|
58
|
+
const result = await listMyUnlinkedAgentProfiles();
|
|
59
|
+
const profiles = Array.isArray(result?.data) ? result.data : [];
|
|
60
|
+
if (profiles.length > 0) {
|
|
61
|
+
const { useExisting } = await inquirer.prompt([{
|
|
62
|
+
type: 'confirm',
|
|
63
|
+
name: 'useExisting',
|
|
64
|
+
message: `You have ${profiles.length} unlinked network identity(ies). Reuse one for this agent?`,
|
|
65
|
+
default: false,
|
|
66
|
+
}]);
|
|
67
|
+
if (useExisting) {
|
|
68
|
+
const { profileId } = await inquirer.prompt([{
|
|
69
|
+
type: 'list',
|
|
70
|
+
name: 'profileId',
|
|
71
|
+
message: 'Pick a network identity:',
|
|
72
|
+
choices: profiles.map(p => ({
|
|
73
|
+
name: `@${p.handle} — ${p.displayName}`,
|
|
74
|
+
value: p.id,
|
|
75
|
+
})),
|
|
76
|
+
}]);
|
|
77
|
+
preselectedProfile = profiles.find(p => p.id === profileId) || null;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
} catch {
|
|
81
|
+
// listMyUnlinkedAgentProfiles can fail if the user has no villager — that's fine, just skip
|
|
82
|
+
}
|
|
83
|
+
|
|
42
84
|
const answers = await inquirer.prompt([
|
|
43
85
|
{
|
|
44
86
|
type: 'input',
|
|
@@ -151,6 +193,16 @@ export async function agentCreateLocalCommand() {
|
|
|
151
193
|
model: answers.model,
|
|
152
194
|
});
|
|
153
195
|
|
|
196
|
+
// If the developer picked an existing AgentProfile, pre-fill man.agent_id
|
|
197
|
+
// so `start` skips the lazy-create step and links the VillageAgent shim to it instead.
|
|
198
|
+
if (preselectedProfile?.id) {
|
|
199
|
+
const cfg = readAgentConfig(answers.name);
|
|
200
|
+
cfg.man = cfg.man || {};
|
|
201
|
+
cfg.man.agent_id = preselectedProfile.id;
|
|
202
|
+
cfg.man.handle = preselectedProfile.handle;
|
|
203
|
+
writeAgentConfig(answers.name, cfg);
|
|
204
|
+
}
|
|
205
|
+
|
|
154
206
|
spinner.succeed('Agent scaffolded!');
|
|
155
207
|
|
|
156
208
|
console.log(brand.green(` \u2713 Agent created at ~/.myvillage/agents/${answers.name}/\n`));
|
|
@@ -231,7 +283,7 @@ export async function agentStartCommand(name) {
|
|
|
231
283
|
}
|
|
232
284
|
}
|
|
233
285
|
|
|
234
|
-
// Register on MAN if first start
|
|
286
|
+
// Register on MAN if first start (skipped when `create` preselected an existing AgentProfile)
|
|
235
287
|
if (!agentConfig.man?.agent_id) {
|
|
236
288
|
const regSpinner = villageSpinner('Registering agent on the MAN network...').start();
|
|
237
289
|
try {
|
|
@@ -251,6 +303,32 @@ export async function agentStartCommand(name) {
|
|
|
251
303
|
|
|
252
304
|
regSpinner.succeed(`Registered as @${agent.handle || agentConfig.name} on MAN.`);
|
|
253
305
|
|
|
306
|
+
// Also create the VillageAgent shim so this agent has a task queue
|
|
307
|
+
// and shared autonomous-engagement controls. This is what `loop.js`
|
|
308
|
+
// polls against. Failure is non-fatal — the agent can still run locally.
|
|
309
|
+
try {
|
|
310
|
+
const shimResult = await apiCreateVillageAgent({
|
|
311
|
+
name: agentConfig.display_name || agentConfig.name,
|
|
312
|
+
title: 'Developer Agent',
|
|
313
|
+
description: agentConfig.description || '',
|
|
314
|
+
avatar: agent.avatarUrl || '',
|
|
315
|
+
archetypes: [],
|
|
316
|
+
greeting: '',
|
|
317
|
+
starterPrompts: [],
|
|
318
|
+
associatedVillages: [],
|
|
319
|
+
specialInstructions: null,
|
|
320
|
+
agentProfileId: agent.id,
|
|
321
|
+
});
|
|
322
|
+
const shim = shimResult.agent || shimResult;
|
|
323
|
+
if (shim?.id) {
|
|
324
|
+
agentConfig.man.village_agent_id = shim.id;
|
|
325
|
+
writeAgentConfig(name, agentConfig);
|
|
326
|
+
}
|
|
327
|
+
} catch (shimErr) {
|
|
328
|
+
const shimMsg = shimErr.response?.data?.error || shimErr.message;
|
|
329
|
+
console.log(brand.teal(` Note: task queue not available (${shimMsg}). Agent will run in feed-monitoring mode.`));
|
|
330
|
+
}
|
|
331
|
+
|
|
254
332
|
// Auto-join default communities so the agent can post
|
|
255
333
|
try {
|
|
256
334
|
const commResult = await listCommunities({ pageSize: 50 });
|
|
@@ -284,6 +362,34 @@ export async function agentStartCommand(name) {
|
|
|
284
362
|
}
|
|
285
363
|
}
|
|
286
364
|
|
|
365
|
+
// If we have an AgentProfile but no VillageAgent shim yet (e.g., the developer
|
|
366
|
+
// preselected an existing AgentProfile via `create`), create it now.
|
|
367
|
+
if (agentConfig.man?.agent_id && !agentConfig.man?.village_agent_id) {
|
|
368
|
+
try {
|
|
369
|
+
const shimResult = await apiCreateVillageAgent({
|
|
370
|
+
name: agentConfig.display_name || agentConfig.name,
|
|
371
|
+
title: 'Developer Agent',
|
|
372
|
+
description: agentConfig.description || '',
|
|
373
|
+
avatar: '',
|
|
374
|
+
archetypes: [],
|
|
375
|
+
greeting: '',
|
|
376
|
+
starterPrompts: [],
|
|
377
|
+
associatedVillages: [],
|
|
378
|
+
specialInstructions: null,
|
|
379
|
+
agentProfileId: agentConfig.man.agent_id,
|
|
380
|
+
});
|
|
381
|
+
const shim = shimResult.agent || shimResult;
|
|
382
|
+
if (shim?.id) {
|
|
383
|
+
agentConfig.man.village_agent_id = shim.id;
|
|
384
|
+
writeAgentConfig(name, agentConfig);
|
|
385
|
+
console.log(brand.teal(` Task queue ready: ${shim.id}`));
|
|
386
|
+
}
|
|
387
|
+
} catch (shimErr) {
|
|
388
|
+
const shimMsg = shimErr.response?.data?.error || shimErr.message;
|
|
389
|
+
console.log(brand.teal(` Note: task queue not available (${shimMsg}).`));
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
287
393
|
// Migrate tools.yaml: replace man-feed with myvillage MCP server
|
|
288
394
|
const toolsConfig = readToolsYaml(name);
|
|
289
395
|
if (toolsConfig.servers?.['man-feed'] && !toolsConfig.servers?.['myvillage']) {
|
|
@@ -300,6 +406,9 @@ export async function agentStartCommand(name) {
|
|
|
300
406
|
if (agentConfig.man?.agent_id) {
|
|
301
407
|
process.env.MYVILLAGE_AGENT_ID = agentConfig.man.agent_id;
|
|
302
408
|
}
|
|
409
|
+
if (agentConfig.man?.village_agent_id) {
|
|
410
|
+
process.env.MYVILLAGE_VILLAGE_AGENT_ID = agentConfig.man.village_agent_id;
|
|
411
|
+
}
|
|
303
412
|
|
|
304
413
|
// Fork the daemon process
|
|
305
414
|
const spinner = villageSpinner(`Starting agent "${name}"...`).start();
|
|
@@ -670,3 +779,247 @@ export async function agentDeleteLocalCommand(name) {
|
|
|
670
779
|
console.log(chalk.red(` \u2717 Failed to delete agent: ${err.message}\n`));
|
|
671
780
|
}
|
|
672
781
|
}
|
|
782
|
+
|
|
783
|
+
// \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
|
|
784
|
+
|
|
785
|
+
function resolveVillageAgentId(name) {
|
|
786
|
+
if (!agentExists(name)) {
|
|
787
|
+
console.log(chalk.red(` \u2717 Agent "${name}" not found.\n`));
|
|
788
|
+
return null;
|
|
789
|
+
}
|
|
790
|
+
const config = readAgentConfig(name);
|
|
791
|
+
const id = config?.man?.village_agent_id;
|
|
792
|
+
if (!id) {
|
|
793
|
+
console.log(chalk.yellow(` Agent "${name}" has no task queue yet \u2014 start it once so the VillageAgent shim is created.\n`));
|
|
794
|
+
return null;
|
|
795
|
+
}
|
|
796
|
+
return id;
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
export async function agentTaskListCommand(name, options = {}) {
|
|
800
|
+
if (!isAuthenticated()) {
|
|
801
|
+
console.log(chalk.red(' \u2717 Authentication required. Run \'myvillage login\' first.'));
|
|
802
|
+
return;
|
|
803
|
+
}
|
|
804
|
+
const villageAgentId = resolveVillageAgentId(name);
|
|
805
|
+
if (!villageAgentId) return;
|
|
806
|
+
|
|
807
|
+
try {
|
|
808
|
+
const result = await listAgentTasks(villageAgentId, {
|
|
809
|
+
status: options.status,
|
|
810
|
+
limit: options.limit ? Number(options.limit) : undefined,
|
|
811
|
+
});
|
|
812
|
+
const tasks = result.tasks || [];
|
|
813
|
+
if (tasks.length === 0) {
|
|
814
|
+
console.log(brand.teal(` No tasks${options.status ? ` with status ${options.status}` : ''}.\n`));
|
|
815
|
+
return;
|
|
816
|
+
}
|
|
817
|
+
console.log(brand.teal(` ${tasks.length} task(s):\n`));
|
|
818
|
+
for (const t of tasks) {
|
|
819
|
+
console.log(` ${brand.gold(t.id)} ${chalk.bold(t.taskType)} ${t.status} priority=${t.priority}`);
|
|
820
|
+
if (t.instruction) console.log(` ${chalk.dim(t.instruction)}`);
|
|
821
|
+
}
|
|
822
|
+
console.log('');
|
|
823
|
+
} catch (err) {
|
|
824
|
+
const msg = err.response?.data?.error || err.message;
|
|
825
|
+
console.log(chalk.red(` \u2717 Failed to list tasks: ${msg}\n`));
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
export async function agentTaskAssignCommand(name, options = {}) {
|
|
830
|
+
if (!isAuthenticated()) {
|
|
831
|
+
console.log(chalk.red(' \u2717 Authentication required. Run \'myvillage login\' first.'));
|
|
832
|
+
return;
|
|
833
|
+
}
|
|
834
|
+
const villageAgentId = resolveVillageAgentId(name);
|
|
835
|
+
if (!villageAgentId) return;
|
|
836
|
+
|
|
837
|
+
if (!options.type) {
|
|
838
|
+
console.log(chalk.red(' \u2717 --type is required (e.g., CLIENT_TASK, GENERATE_POST, SHARE_KNOWLEDGE)\n'));
|
|
839
|
+
return;
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
let inputObj = {};
|
|
843
|
+
if (options.input) {
|
|
844
|
+
try { inputObj = JSON.parse(options.input); }
|
|
845
|
+
catch { console.log(chalk.red(' \u2717 --input must be valid JSON\n')); return; }
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
try {
|
|
849
|
+
const result = await assignAgentTask(villageAgentId, {
|
|
850
|
+
taskType: options.type,
|
|
851
|
+
instruction: options.instruction,
|
|
852
|
+
input: inputObj,
|
|
853
|
+
priority: options.priority ? Number(options.priority) : 5,
|
|
854
|
+
});
|
|
855
|
+
const task = result.task || result;
|
|
856
|
+
console.log(brand.green(` \u2713 Task ${task.id} assigned (${task.taskType}, status=${task.status}).\n`));
|
|
857
|
+
} catch (err) {
|
|
858
|
+
const msg = err.response?.data?.error || err.message;
|
|
859
|
+
console.log(chalk.red(` \u2717 Failed to assign task: ${msg}\n`));
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
// Exported for use by the runtime loop, not registered as a CLI command directly.
|
|
864
|
+
export async function pollAndClaimNextTask(villageAgentId) {
|
|
865
|
+
const result = await listAgentTasks(villageAgentId, { status: 'PENDING', limit: 5 });
|
|
866
|
+
const pending = result.tasks || [];
|
|
867
|
+
if (pending.length === 0) return null;
|
|
868
|
+
for (const task of pending) {
|
|
869
|
+
try {
|
|
870
|
+
const claim = await claimAgentTask(villageAgentId, task.id);
|
|
871
|
+
return claim.data || claim;
|
|
872
|
+
} catch {
|
|
873
|
+
// Race lost \u2014 try the next task
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
return null;
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
export async function reportTaskOutcome(villageAgentId, taskId, outcome) {
|
|
880
|
+
return completeAgentTask(villageAgentId, taskId, outcome);
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
// ── Memory (short-term KV) and Recall (long-term searchable) ────
|
|
884
|
+
|
|
885
|
+
function resolveAgentProfileId(name) {
|
|
886
|
+
if (!agentExists(name)) {
|
|
887
|
+
console.log(chalk.red(` ✗ Agent "${name}" not found.\n`));
|
|
888
|
+
return null;
|
|
889
|
+
}
|
|
890
|
+
const config = readAgentConfig(name);
|
|
891
|
+
const id = config?.man?.agent_id;
|
|
892
|
+
if (!id) {
|
|
893
|
+
console.log(chalk.yellow(` Agent "${name}" hasn't registered on the network yet — start it once so an AgentProfile is created.\n`));
|
|
894
|
+
return null;
|
|
895
|
+
}
|
|
896
|
+
return id;
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
export async function agentMemoryCommand(name, action, ...args) {
|
|
900
|
+
if (!isAuthenticated()) {
|
|
901
|
+
console.log(chalk.red(' ✗ Authentication required. Run \'myvillage login\' first.'));
|
|
902
|
+
return;
|
|
903
|
+
}
|
|
904
|
+
const agentProfileId = resolveAgentProfileId(name);
|
|
905
|
+
if (!agentProfileId) return;
|
|
906
|
+
|
|
907
|
+
try {
|
|
908
|
+
if (action === 'list') {
|
|
909
|
+
const result = await listAgentMemory(agentProfileId);
|
|
910
|
+
const items = result.data || result.memories || result;
|
|
911
|
+
if (!Array.isArray(items) || items.length === 0) {
|
|
912
|
+
console.log(brand.teal(' No memory entries.\n'));
|
|
913
|
+
return;
|
|
914
|
+
}
|
|
915
|
+
console.log(brand.teal(` ${items.length} memory entr${items.length === 1 ? 'y' : 'ies'}:\n`));
|
|
916
|
+
for (const m of items) {
|
|
917
|
+
console.log(` ${chalk.bold(m.key)}${m.category ? chalk.dim(` [${m.category}]`) : ''}`);
|
|
918
|
+
const v = typeof m.value === 'string' ? m.value : JSON.stringify(m.value);
|
|
919
|
+
console.log(` ${chalk.dim(v.slice(0, 200))}`);
|
|
920
|
+
}
|
|
921
|
+
console.log('');
|
|
922
|
+
} else if (action === 'get') {
|
|
923
|
+
const key = args[0];
|
|
924
|
+
if (!key) {
|
|
925
|
+
console.log(chalk.red(' ✗ Usage: myvillage agent memory <name> get <key>\n'));
|
|
926
|
+
return;
|
|
927
|
+
}
|
|
928
|
+
const result = await getAgentMemoryEntry(agentProfileId, key);
|
|
929
|
+
console.log(JSON.stringify(result.data || result, null, 2));
|
|
930
|
+
} else if (action === 'set') {
|
|
931
|
+
const [key, ...rest] = args;
|
|
932
|
+
const value = rest.join(' ');
|
|
933
|
+
if (!key || !value) {
|
|
934
|
+
console.log(chalk.red(' ✗ Usage: myvillage agent memory <name> set <key> <value>\n'));
|
|
935
|
+
return;
|
|
936
|
+
}
|
|
937
|
+
// Try JSON first so { "foo": 1 } stays structured; fall back to string.
|
|
938
|
+
let parsed;
|
|
939
|
+
try { parsed = JSON.parse(value); } catch { parsed = value; }
|
|
940
|
+
await upsertAgentMemory(agentProfileId, { key, value: parsed });
|
|
941
|
+
console.log(brand.green(` ✓ ${key} set.\n`));
|
|
942
|
+
} else if (action === 'delete') {
|
|
943
|
+
const key = args[0];
|
|
944
|
+
if (!key) {
|
|
945
|
+
console.log(chalk.red(' ✗ Usage: myvillage agent memory <name> delete <key>\n'));
|
|
946
|
+
return;
|
|
947
|
+
}
|
|
948
|
+
await deleteAgentMemoryEntry(agentProfileId, key);
|
|
949
|
+
console.log(brand.green(` ✓ ${key} deleted.\n`));
|
|
950
|
+
} else {
|
|
951
|
+
console.log(chalk.red(` ✗ Unknown memory action: ${action}. Use list|get|set|delete.\n`));
|
|
952
|
+
}
|
|
953
|
+
} catch (err) {
|
|
954
|
+
const msg = err.response?.data?.error || err.message;
|
|
955
|
+
console.log(chalk.red(` ✗ Memory ${action} failed: ${msg}\n`));
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
export async function agentRecallCommand(name, query, options = {}) {
|
|
960
|
+
if (!isAuthenticated()) {
|
|
961
|
+
console.log(chalk.red(' ✗ Authentication required. Run \'myvillage login\' first.'));
|
|
962
|
+
return;
|
|
963
|
+
}
|
|
964
|
+
const agentProfileId = resolveAgentProfileId(name);
|
|
965
|
+
if (!agentProfileId) return;
|
|
966
|
+
if (!query) {
|
|
967
|
+
console.log(chalk.red(' ✗ Usage: myvillage agent recall <name> <query>\n'));
|
|
968
|
+
return;
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
try {
|
|
972
|
+
const result = await listKnowledgeFiltered({
|
|
973
|
+
createdByAgentId: agentProfileId,
|
|
974
|
+
search: query,
|
|
975
|
+
limit: options.limit ? Number(options.limit) : 10,
|
|
976
|
+
});
|
|
977
|
+
const items = result.data || result.knowledge || result.knowledgeSubmissions || [];
|
|
978
|
+
if (!Array.isArray(items) || items.length === 0) {
|
|
979
|
+
console.log(brand.teal(` No memories matched "${query}".\n`));
|
|
980
|
+
return;
|
|
981
|
+
}
|
|
982
|
+
console.log(brand.teal(` ${items.length} matching memor${items.length === 1 ? 'y' : 'ies'}:\n`));
|
|
983
|
+
for (const k of items) {
|
|
984
|
+
const text = k.originalText || k.text || '';
|
|
985
|
+
console.log(` ${brand.gold(k.id)} ${chalk.dim(new Date(k.createdAt).toLocaleString())}`);
|
|
986
|
+
console.log(` ${text.slice(0, 280)}${text.length > 280 ? '…' : ''}`);
|
|
987
|
+
}
|
|
988
|
+
console.log('');
|
|
989
|
+
} catch (err) {
|
|
990
|
+
const msg = err.response?.data?.error || err.message;
|
|
991
|
+
console.log(chalk.red(` ✗ Recall failed: ${msg}\n`));
|
|
992
|
+
}
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
export async function agentRememberCommand(name, text, options = {}) {
|
|
996
|
+
if (!isAuthenticated()) {
|
|
997
|
+
console.log(chalk.red(' ✗ Authentication required. Run \'myvillage login\' first.'));
|
|
998
|
+
return;
|
|
999
|
+
}
|
|
1000
|
+
const agentProfileId = resolveAgentProfileId(name);
|
|
1001
|
+
if (!agentProfileId) return;
|
|
1002
|
+
if (!text) {
|
|
1003
|
+
console.log(chalk.red(' ✗ Usage: myvillage agent remember <name> "<text to remember>"\n'));
|
|
1004
|
+
return;
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
const themes = options.themes
|
|
1008
|
+
? options.themes.split(',').map(t => t.trim()).filter(Boolean)
|
|
1009
|
+
: [];
|
|
1010
|
+
|
|
1011
|
+
try {
|
|
1012
|
+
const result = await shareKnowledgeAsAgent({
|
|
1013
|
+
original_text: text,
|
|
1014
|
+
summary: options.summary || null,
|
|
1015
|
+
themes,
|
|
1016
|
+
sharing_option: options.sharing || 'PRIVATE',
|
|
1017
|
+
agent_profile_id: agentProfileId,
|
|
1018
|
+
});
|
|
1019
|
+
const k = result.knowledge || result.data || result;
|
|
1020
|
+
console.log(brand.green(` ✓ Remembered as ${k.id || 'knowledge entry'}.\n`));
|
|
1021
|
+
} catch (err) {
|
|
1022
|
+
const msg = err.response?.data?.error || err.message;
|
|
1023
|
+
console.log(chalk.red(` ✗ Remember failed: ${msg}\n`));
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
@@ -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...';
|