@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.
@@ -0,0 +1,185 @@
1
+ import chalk from 'chalk';
2
+ import { readFileSync, existsSync } from 'fs';
3
+ import { join, basename } from 'path';
4
+ import { isAuthenticated } from '../utils/auth.js';
5
+ import { brand, villageSpinner } from '../utils/brand.js';
6
+ import { agentExists } from '../utils/local-agent.js';
7
+ import {
8
+ listVillageBooks,
9
+ exportVillageBook,
10
+ importVillageBook,
11
+ } from '../utils/api.js';
12
+ import {
13
+ parseWisdom,
14
+ serializeWisdom,
15
+ slugify,
16
+ getAgentWisdomDir,
17
+ ensureWisdomDir,
18
+ readAgentWisdom,
19
+ writeWisdomFile,
20
+ } from '../utils/wisdom.js';
21
+
22
+ function requireAuth() {
23
+ if (!isAuthenticated()) {
24
+ console.log(chalk.red(' ✗ Authentication required. Run \'myvillage login\' first.'));
25
+ return false;
26
+ }
27
+ return true;
28
+ }
29
+
30
+ function resolveAgentName(name, action) {
31
+ if (!name) {
32
+ console.log(chalk.red(` ✗ Specify an agent: myvillage wisdom ${action} --agent <name>\n`));
33
+ return null;
34
+ }
35
+ if (!agentExists(name)) {
36
+ console.log(chalk.red(` ✗ Agent "${name}" not found.\n`));
37
+ return null;
38
+ }
39
+ return name;
40
+ }
41
+
42
+ // ── list ────────────────────────────────────────────────
43
+
44
+ export async function wisdomListCommand(options = {}) {
45
+ if (options.remote) {
46
+ if (!requireAuth()) return;
47
+ try {
48
+ const result = await listVillageBooks({ bookTypeId: 'wisdom' });
49
+ // The API can return either an array directly or wrapped in { data }
50
+ const books = Array.isArray(result) ? result : result?.data || [];
51
+ const wisdom = books.filter(b => b.bookType?.name === 'wisdom' || !b.bookType);
52
+ if (wisdom.length === 0) {
53
+ console.log(brand.teal(' No community wisdom files published yet.\n'));
54
+ return;
55
+ }
56
+ console.log(brand.teal(` ${wisdom.length} wisdom file(s) on the network:\n`));
57
+ for (const w of wisdom) {
58
+ console.log(` ${brand.gold(w.id)} ${chalk.bold(w.name)}`);
59
+ if (w.description) console.log(` ${chalk.dim(w.description)}`);
60
+ }
61
+ console.log('');
62
+ } catch (err) {
63
+ const msg = err.response?.data?.error || err.message;
64
+ console.log(chalk.red(` ✗ Failed to list remote wisdom: ${msg}\n`));
65
+ }
66
+ return;
67
+ }
68
+
69
+ // Local mode: list every wisdom file across every agent on disk
70
+ if (!options.agent) {
71
+ console.log(chalk.yellow(' Hint: pass --agent <name> to list one agent\'s wisdom, or --remote to list community-published wisdom.\n'));
72
+ return;
73
+ }
74
+ if (!resolveAgentName(options.agent, 'list')) return;
75
+ const files = readAgentWisdom(options.agent);
76
+ if (files.length === 0) {
77
+ console.log(brand.teal(` Agent "${options.agent}" has no wisdom files yet.\n`));
78
+ return;
79
+ }
80
+ console.log(brand.teal(` ${files.length} local wisdom file(s) for "${options.agent}":\n`));
81
+ for (const w of files) {
82
+ const linked = w.villageBookId ? brand.gold(` (synced ${w.villageBookId})`) : chalk.dim(' (local-only)');
83
+ console.log(` ${chalk.bold(w.name)}${linked}`);
84
+ if (w.description) console.log(` ${chalk.dim(w.description)}`);
85
+ console.log(` ${chalk.dim(w.path)}`);
86
+ }
87
+ console.log('');
88
+ }
89
+
90
+ // ── pull ────────────────────────────────────────────────
91
+
92
+ export async function wisdomPullCommand(id, options = {}) {
93
+ if (!requireAuth()) return;
94
+ if (!id) {
95
+ console.log(chalk.red(' ✗ Specify a village book id: myvillage wisdom pull <id> --into <agent>\n'));
96
+ return;
97
+ }
98
+ if (!options.into) {
99
+ console.log(chalk.red(' ✗ Specify a target agent: myvillage wisdom pull <id> --into <agent>\n'));
100
+ return;
101
+ }
102
+ if (!resolveAgentName(options.into, 'pull')) return;
103
+
104
+ const spinner = villageSpinner('Fetching wisdom from the network...').start();
105
+ try {
106
+ const text = await exportVillageBook(id);
107
+ const { frontmatter } = parseWisdom(text);
108
+ const slug = slugify(frontmatter.name || id);
109
+ const dir = ensureWisdomDir(options.into);
110
+ const filePath = join(dir, `${slug}.wisdom`);
111
+ writeWisdomFile(filePath, parseWisdom(text)); // re-serialize for consistent formatting
112
+ spinner.succeed(`Wisdom "${frontmatter.name || id}" saved to ${filePath}`);
113
+ } catch (err) {
114
+ const msg = err.response?.data?.error || err.message;
115
+ spinner.fail(`Failed to pull wisdom: ${msg}`);
116
+ }
117
+ }
118
+
119
+ // ── push ────────────────────────────────────────────────
120
+
121
+ export async function wisdomPushCommand(filePath) {
122
+ if (!requireAuth()) return;
123
+ if (!filePath) {
124
+ console.log(chalk.red(' ✗ Specify a .wisdom file: myvillage wisdom push <file>\n'));
125
+ return;
126
+ }
127
+ if (!existsSync(filePath)) {
128
+ console.log(chalk.red(` ✗ File not found: ${filePath}\n`));
129
+ return;
130
+ }
131
+
132
+ const content = readFileSync(filePath, 'utf-8');
133
+ const spinner = villageSpinner('Publishing wisdom to the network...').start();
134
+ try {
135
+ const result = await importVillageBook({ content });
136
+ const book = result?.data || result;
137
+ if (book?.id) {
138
+ // Save the assigned id back into the local file so future pushes update
139
+ // the same row instead of creating duplicates.
140
+ const parsed = parseWisdom(content);
141
+ parsed.frontmatter.villageBookId = book.id;
142
+ writeWisdomFile(filePath, parsed);
143
+ spinner.succeed(`Published as ${book.id}`);
144
+ } else {
145
+ spinner.succeed('Published.');
146
+ }
147
+ } catch (err) {
148
+ const msg = err.response?.data?.error || err.message;
149
+ spinner.fail(`Failed to push wisdom: ${msg}`);
150
+ }
151
+ }
152
+
153
+ // ── new ─────────────────────────────────────────────────
154
+
155
+ export async function wisdomNewCommand(name, options = {}) {
156
+ if (!name) {
157
+ console.log(chalk.red(' ✗ Specify a name: myvillage wisdom new <name> --agent <agent>\n'));
158
+ return;
159
+ }
160
+ if (!options.agent) {
161
+ console.log(chalk.red(' ✗ Specify a target agent: myvillage wisdom new <name> --agent <agent>\n'));
162
+ return;
163
+ }
164
+ if (!resolveAgentName(options.agent, 'new')) return;
165
+
166
+ const slug = slugify(name);
167
+ const dir = ensureWisdomDir(options.agent);
168
+ const filePath = join(dir, `${slug}.wisdom`);
169
+ if (existsSync(filePath)) {
170
+ console.log(chalk.yellow(` ${filePath} already exists; edit it instead.\n`));
171
+ return;
172
+ }
173
+
174
+ const template = serializeWisdom({
175
+ frontmatter: {
176
+ name: slug,
177
+ description: options.description || 'Describe what this skill does and when to apply it.',
178
+ trigger: options.trigger || 'Describe the condition that should activate this skill.',
179
+ },
180
+ body: '<!-- The body of this wisdom file. Write it as a short skill: tone, structure, examples. The agent loads it into its system prompt every iteration. -->\n',
181
+ });
182
+ writeWisdomFile(filePath, parseWisdom(template));
183
+ console.log(brand.green(` ✓ Wisdom scaffolded at ${filePath}`));
184
+ console.log(brand.teal(` Edit the body, then publish with: myvillage wisdom push ${filePath}\n`));
185
+ }
package/src/index.js CHANGED
@@ -2,6 +2,12 @@ import { Command } from 'commander';
2
2
  import { createRequire } from 'module';
3
3
  import updateNotifier from 'update-notifier';
4
4
  import { loginCommand } from './commands/login.js';
5
+ import {
6
+ mediaDraftCreateCommand,
7
+ mediaDraftEditCommand,
8
+ mediaDraftListCommand,
9
+ mediaDraftStatusCommand,
10
+ } from './commands/media.js';
5
11
  import { logoutCommand } from './commands/logout.js';
6
12
  import { createGameCommand } from './commands/create-game.js';
7
13
  import { createCommand } from './commands/create-app.js';
@@ -45,7 +51,27 @@ import {
45
51
  agentLogsCommand,
46
52
  agentAddToolCommand,
47
53
  agentRemoveToolCommand,
54
+ agentTaskListCommand,
55
+ agentTaskAssignCommand,
56
+ agentTaskRetryCommand,
57
+ agentTaskRetryFailedCommand,
58
+ agentMemoryCommand,
59
+ agentRecallCommand,
60
+ agentRememberCommand,
48
61
  } from './commands/agent-local.js';
62
+ import {
63
+ agentGrantCommand,
64
+ agentRevokeCommand,
65
+ agentGrantsCommand,
66
+ } from './commands/agent-grant.js';
67
+ import {
68
+ agentRegisterClientCommand,
69
+ agentListClientsCommand,
70
+ agentViewClientCommand,
71
+ agentEditClientCommand,
72
+ agentDeactivateClientCommand,
73
+ agentRotateClientKeyCommand,
74
+ } from './commands/agent-client.js';
49
75
  import {
50
76
  bizreqsNewCommand,
51
77
  bizreqsSpecCommand,
@@ -69,6 +95,12 @@ import { checkinCommand } from './commands/checkin.js';
69
95
  import { discoverCommand } from './commands/discover.js';
70
96
  import { logCommand } from './commands/log.js';
71
97
  import { storyCommand } from './commands/story.js';
98
+ import {
99
+ wisdomListCommand,
100
+ wisdomPullCommand,
101
+ wisdomPushCommand,
102
+ wisdomNewCommand,
103
+ } from './commands/wisdom.js';
72
104
  import {
73
105
  soulprintInitCommand,
74
106
  soulprintIngestCommand,
@@ -408,6 +440,109 @@ export function run() {
408
440
  .description('Remove an MCP server tool from a local agent')
409
441
  .action(agentRemoveToolCommand);
410
442
 
443
+ // Task queue commands — assign and inspect work for a developer's agent
444
+ agentCmd
445
+ .command('task-list <name>')
446
+ .description('List tasks queued for a local agent')
447
+ .option('--status <status>', 'Filter by status (PENDING|IN_PROGRESS|COMPLETED|FAILED|CANCELLED)')
448
+ .option('--limit <n>', 'Max number of tasks to show', '20')
449
+ .action(agentTaskListCommand);
450
+
451
+ agentCmd
452
+ .command('task-assign <name>')
453
+ .description('Assign a task to a local agent (it will pick it up on next poll)')
454
+ .option('--type <type>', 'Task type (e.g., CLIENT_TASK, GENERATE_POST, SHARE_KNOWLEDGE)')
455
+ .option('--instruction <text>', 'Free-text instruction (required for CLIENT_TASK)')
456
+ .option('--input <json>', 'JSON-encoded structured input payload')
457
+ .option('--priority <n>', 'Priority 1-10 (lower runs first)', '5')
458
+ .action(agentTaskAssignCommand);
459
+
460
+ agentCmd
461
+ .command('task-retry <name> <taskId>')
462
+ .description('Reset a FAILED or CANCELLED task back to PENDING so the agent retries it')
463
+ .action(agentTaskRetryCommand);
464
+
465
+ agentCmd
466
+ .command('task-retry-failed <name>')
467
+ .description('Bulk-reset every FAILED task for an agent back to PENDING')
468
+ .option('--filter <text>', 'Only retry tasks whose errorMessage contains this substring')
469
+ .action(agentTaskRetryFailedCommand);
470
+
471
+ // Agent memory (short-term KV state) and recall (long-term searchable memory)
472
+ agentCmd
473
+ .command('memory <name> <action> [args...]')
474
+ .description('Read/write an agent\'s short-term memory: list | get <key> | set <key> <value> | delete <key>')
475
+ .action((name, action, args = []) => agentMemoryCommand(name, action, ...args));
476
+
477
+ agentCmd
478
+ .command('recall <name> <query>')
479
+ .description('Search this agent\'s long-term memory (Knowledge submissions it authored)')
480
+ .option('--limit <n>', 'Max results to show', '10')
481
+ .action(agentRecallCommand);
482
+
483
+ agentCmd
484
+ .command('remember <name> <text>')
485
+ .description('Save a memory to this agent\'s long-term store (Knowledge submission with source=AI_AGENT)')
486
+ .option('--summary <text>', 'Short summary of the memory')
487
+ .option('--themes <list>', 'Comma-separated tags')
488
+ .option('--sharing <option>', 'PRIVATE | VILLAGE_ONLY | PUBLIC', 'PRIVATE')
489
+ .action(agentRememberCommand);
490
+
491
+ // Per-agent OAuth credential grants
492
+ agentCmd
493
+ .command('grants <name>')
494
+ .description('List active OAuth credential grants for a local agent')
495
+ .action(agentGrantsCommand);
496
+
497
+ agentCmd
498
+ .command('grant <name> <provider>')
499
+ .description('Grant the agent access to a connected OAuth provider (google|microsoft|zoom)')
500
+ .action(agentGrantCommand);
501
+
502
+ agentCmd
503
+ .command('revoke <name> <provider>')
504
+ .description('Revoke an OAuth provider grant from the agent')
505
+ .action(agentRevokeCommand);
506
+
507
+ // Client agent registration commands
508
+ agentCmd
509
+ .command('register-client')
510
+ .description('Register a client application for agent automation')
511
+ .option('--agent <handle>', 'Agent handle')
512
+ .option('--client-id <id>', 'Client identifier (lowercase)')
513
+ .option('--name <name>', 'Client display name')
514
+ .option('--url <url>', 'Client base URL')
515
+ .option('--workflow <type>', 'Workflow type (e.g., submission_processor)')
516
+ .option('--schedule <cron>', 'Cron schedule expression')
517
+ .option('--timezone <tz>', 'Timezone (default: America/Chicago)')
518
+ .option('--env-file <path>', 'Write API key to this .env file')
519
+ .action(agentRegisterClientCommand);
520
+
521
+ agentCmd
522
+ .command('list-clients')
523
+ .description('List all registered client agent configs')
524
+ .action(agentListClientsCommand);
525
+
526
+ agentCmd
527
+ .command('view-client <configId>')
528
+ .description('View a client agent config')
529
+ .action(agentViewClientCommand);
530
+
531
+ agentCmd
532
+ .command('edit-client <configId>')
533
+ .description('Edit a client agent config')
534
+ .action(agentEditClientCommand);
535
+
536
+ agentCmd
537
+ .command('deactivate-client <configId>')
538
+ .description('Deactivate a client agent config')
539
+ .action(agentDeactivateClientCommand);
540
+
541
+ agentCmd
542
+ .command('rotate-client-key <configId>')
543
+ .description('Rotate the API key for a client agent config')
544
+ .action(agentRotateClientKeyCommand);
545
+
411
546
  // ── Village Content Pipeline ───────────────────────────
412
547
 
413
548
  program
@@ -432,6 +567,49 @@ export function run() {
432
567
  .description('Share a story or wisdom with the network')
433
568
  .action(storyCommand);
434
569
 
570
+ // ── Media: Soulprint Reels (mobile-app posts) ──────────
571
+
572
+ const mediaCmd = program
573
+ .command('media')
574
+ .description('Manage Soulprint Reels (internal social posts shown in the mobile app)');
575
+
576
+ const mediaDraftCmd = mediaCmd
577
+ .command('draft')
578
+ .description('Reel draft commands');
579
+
580
+ mediaDraftCmd
581
+ .command('create')
582
+ .description('Create a new reel draft (video, photo, carousel, audio, text)')
583
+ .option('-f, --file <path>', 'Attach a media file (repeatable)', (v, prev) => prev ? [...prev, v] : [v])
584
+ .option('-a, --asset-type <type>', 'Asset type when --file is given (IMAGE, VIDEO, THUMBNAIL, DOCUMENT)')
585
+ .option('--submit', 'Auto-submit the draft for review after creation')
586
+ .action(mediaDraftCreateCommand);
587
+
588
+ mediaDraftCmd
589
+ .command('edit <id>')
590
+ .description('Edit a DRAFT or REJECTED reel draft')
591
+ .action(mediaDraftEditCommand);
592
+
593
+ mediaDraftCmd
594
+ .command('list')
595
+ .description('List reel drafts')
596
+ .option('--status <status>', 'Filter by status (DRAFT, SUBMITTED, IN_REVIEW, APPROVED, REJECTED, PUBLISHED)')
597
+ .option('--content-type <type>', 'Filter by content type (VIDEO, PHOTO, CAROUSEL, AUDIO, TEXT)')
598
+ .option('--platform <platform>', '[Deprecated] Legacy platform alias — maps forward to content type')
599
+ .option('--visibility <visibility>', 'Filter by visibility (PUBLIC, COMMUNITY, PRIVATE)')
600
+ .option('--community-id <id>', 'Filter by community')
601
+ .option('--search <query>', 'Search captions / titles / quotes')
602
+ .option('-n, --limit <number>', 'Number of drafts', '20')
603
+ .option('--offset <number>', 'Pagination offset', '0')
604
+ .option('--sort <sort>', 'Sort: newest, oldest, publish_date', 'newest')
605
+ .option('--json', 'Output raw JSON')
606
+ .action(mediaDraftListCommand);
607
+
608
+ mediaDraftCmd
609
+ .command('status <id>')
610
+ .description('Show status of a reel draft')
611
+ .action(mediaDraftStatusCommand);
612
+
435
613
  // ── BizReqs: Business Requirements Pipeline ───────────
436
614
 
437
615
  const bizreqsCmd = program
@@ -483,6 +661,40 @@ export function run() {
483
661
 
484
662
  // ── SoulPrint Studio: Model Training Pipeline ───────────
485
663
 
664
+ // \u2500\u2500 Wisdom: agent skill packs (Books of Wisdom) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
665
+
666
+ const wisdomCmd = program
667
+ .command('wisdom')
668
+ .description('Manage .wisdom files \u2014 agent skill packs backed by the network\'s Books of Wisdom');
669
+
670
+ wisdomCmd
671
+ .command('list')
672
+ .description('List wisdom files (use --remote for community-published, --agent for local)')
673
+ .option('--remote', 'List community-published wisdom on the network')
674
+ .option('--agent <name>', 'List a local agent\'s wisdom files')
675
+ .action(wisdomListCommand);
676
+
677
+ wisdomCmd
678
+ .command('pull <id>')
679
+ .description('Fetch a community wisdom file and save it into a local agent')
680
+ .requiredOption('--into <agent>', 'Local agent to save the wisdom file into')
681
+ .action(wisdomPullCommand);
682
+
683
+ wisdomCmd
684
+ .command('push <file>')
685
+ .description('Publish a .wisdom file to the network so other agents can use it')
686
+ .action(wisdomPushCommand);
687
+
688
+ wisdomCmd
689
+ .command('new <name>')
690
+ .description('Scaffold a new local .wisdom file for an agent')
691
+ .requiredOption('--agent <name>', 'Local agent to add the wisdom file to')
692
+ .option('--description <text>', 'Short description shown in the agent\'s skill list')
693
+ .option('--trigger <text>', 'Condition that should activate this skill')
694
+ .action(wisdomNewCommand);
695
+
696
+ // \u2500\u2500 SoulPrint Studio (legacy): training pipeline \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
697
+
486
698
  const soulprintCmd = program
487
699
  .command('soulprint')
488
700
  .description('SoulPrint Studio \u2014 datasets, training, and model publishing');
@@ -138,6 +138,14 @@ ${description}
138
138
  - Speaks casually but clearly
139
139
  - Concise in responses
140
140
 
141
+ ## Handling tasks
142
+ When you receive a TASK in your context, follow these rules:
143
+
144
+ - **Use the values from the task input verbatim.** If the task input is JSON like \`{"communitySlug":"general"}\`, call the tool with \`communitySlug: "general"\` exactly. Do not substitute, translate, or invent slugs, IDs, or names.
145
+ - **If a required value is missing, do NOT guess.** Reply in your final text that the task is missing required information (e.g. "Task is missing a communitySlug — cannot proceed"). Don't pick a community at random.
146
+ - **If a tool call fails, do NOT claim success.** Report what failed and why in your final text. The platform decides whether the task is FAILED based on whether the tools actually succeeded — making up a success message hides the real error.
147
+ - **Use the platform's communities you already belong to.** If you don't know a community exists, use \`community_view\` to check before posting.
148
+
141
149
  ## Boundaries
142
150
  - Never share personal files or private data to the feed
143
151
  - Ask before posting anything longer than 2 sentences
@@ -40,6 +40,7 @@ export function createAgenticAppProject(targetDir, options) {
40
40
  includeRestApi = true,
41
41
  mcpToolGroups = [],
42
42
  oauthCredentials = null,
43
+ agentConfig = null,
43
44
  } = options;
44
45
 
45
46
  const slug = name.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/(^-|-$)/g, '');
@@ -88,7 +89,7 @@ export function createAgenticAppProject(targetDir, options) {
88
89
  writeFileSync(join(targetDir, 'package.json'), generatePackageJson(slug, description, includeMcp));
89
90
  writeFileSync(join(targetDir, 'next.config.mjs'), generateNextConfig());
90
91
  writeFileSync(join(targetDir, '.gitignore'), generateGitignore());
91
- writeFileSync(join(targetDir, '.env.local'), generateEnv(oauthCredentials, hasOAuth, includeMcp));
92
+ writeFileSync(join(targetDir, '.env.local'), generateEnv(oauthCredentials, hasOAuth, includeMcp, agentConfig));
92
93
  writeFileSync(join(targetDir, '.env.example'), generateEnvExample(hasOAuth, includeMcp));
93
94
  writeFileSync(join(targetDir, 'README.md'), generateReadme(name, description, hasOAuth, includeMcp, includeRestApi, features));
94
95
  writeFileSync(join(targetDir, 'jsconfig.json'), generateJsConfig());
@@ -214,7 +215,7 @@ out/
214
215
  `;
215
216
  }
216
217
 
217
- function generateEnv(oauthCredentials, hasOAuth, includeMcp) {
218
+ function generateEnv(oauthCredentials, hasOAuth, includeMcp, agentConfig = null) {
218
219
  const lines = [];
219
220
 
220
221
  if (hasOAuth) {
@@ -233,6 +234,11 @@ function generateEnv(oauthCredentials, hasOAuth, includeMcp) {
233
234
  lines.push('MYVILLAGEOS_MCP_URL=https://mcp.myvillageproject.ai');
234
235
  }
235
236
 
237
+ if (agentConfig?.apiKey) {
238
+ lines.push(`MYVILLAGE_AGENT_API_KEY=${agentConfig.apiKey}`);
239
+ lines.push(`MYVILLAGE_AGENT_CLIENT_ID=${agentConfig.clientId}`);
240
+ }
241
+
236
242
  lines.push('ANTHROPIC_API_KEY=');
237
243
 
238
244
  return lines.join('\n') + '\n';
@@ -254,6 +260,8 @@ function generateEnvExample(hasOAuth, includeMcp) {
254
260
  lines.push('MYVILLAGEOS_MCP_URL=https://mcp.myvillageproject.ai');
255
261
  }
256
262
 
263
+ lines.push('MYVILLAGE_AGENT_API_KEY=');
264
+ lines.push('MYVILLAGE_AGENT_CLIENT_ID=');
257
265
  lines.push('ANTHROPIC_API_KEY=');
258
266
 
259
267
  return lines.join('\n') + '\n';
package/src/utils/api.js CHANGED
@@ -418,6 +418,44 @@ export async function listPostsByFilters(params = {}) {
418
418
  return response.data;
419
419
  }
420
420
 
421
+ // ── Client Agent Configs API (/api/client-agents/configs) ──
422
+
423
+ export async function registerClientAgent(data) {
424
+ const client = getPlatformClient();
425
+ const response = await client.post('/client-agents/configs', data);
426
+ return response.data;
427
+ }
428
+
429
+ export async function listClientAgentConfigs() {
430
+ const client = getPlatformClient();
431
+ const response = await client.get('/client-agents/configs');
432
+ return response.data;
433
+ }
434
+
435
+ export async function getClientAgentConfig(configId) {
436
+ const client = getPlatformClient();
437
+ const response = await client.get(`/client-agents/configs/${encodeURIComponent(configId)}`);
438
+ return response.data;
439
+ }
440
+
441
+ export async function updateClientAgentConfig(configId, data) {
442
+ const client = getPlatformClient();
443
+ const response = await client.patch(`/client-agents/configs/${encodeURIComponent(configId)}`, data);
444
+ return response.data;
445
+ }
446
+
447
+ export async function deactivateClientAgent(configId) {
448
+ const client = getPlatformClient();
449
+ const response = await client.delete(`/client-agents/configs/${encodeURIComponent(configId)}`);
450
+ return response.data;
451
+ }
452
+
453
+ export async function rotateClientAgentKey(configId) {
454
+ const client = getPlatformClient();
455
+ const response = await client.post(`/client-agents/configs/${encodeURIComponent(configId)}/rotate-key`);
456
+ return response.data;
457
+ }
458
+
421
459
  // ── BizReqs API Client (/api/bizreqs) ───────────────────
422
460
 
423
461
  export function getBizReqsClient() {
@@ -564,3 +602,144 @@ export async function submitGameForReview(gameId) {
564
602
  const response = await client.put(`/games/${encodeURIComponent(gameId)}`, { status: 'SUBMITTED' });
565
603
  return response.data;
566
604
  }
605
+
606
+ // ── Village Agents (developer agents) ───────────────────
607
+
608
+ // AgentProfiles owned by the caller that aren't yet linked to a VillageAgent.
609
+ // Used by the CLI "attach existing network identity" picker.
610
+ export async function listMyUnlinkedAgentProfiles() {
611
+ const client = getNetworkClient();
612
+ const response = await client.get('/agents/my', { params: { unlinked: 'true' } });
613
+ return response.data;
614
+ }
615
+
616
+ // Create a VillageAgent. Optionally either:
617
+ // - link to an existing AgentProfile via `agentProfileId`
618
+ // - auto-create a fresh AgentProfile via `createNetworkIdentity: true`
619
+ export async function createVillageAgent(data) {
620
+ const client = getPlatformClient();
621
+ const response = await client.post('/village-agents', data);
622
+ return response.data;
623
+ }
624
+
625
+ export async function listVillageAgents() {
626
+ const client = getPlatformClient();
627
+ const response = await client.get('/village-agents');
628
+ return response.data;
629
+ }
630
+
631
+ export async function getVillageAgent(id) {
632
+ const client = getPlatformClient();
633
+ const response = await client.get(`/village-agents/${encodeURIComponent(id)}`);
634
+ return response.data;
635
+ }
636
+
637
+ export async function updateVillageAgent(id, data) {
638
+ const client = getPlatformClient();
639
+ const response = await client.patch(`/village-agents/${encodeURIComponent(id)}`, data);
640
+ return response.data;
641
+ }
642
+
643
+ // ── Agent Task Queue ────────────────────────────────────
644
+
645
+ export async function listAgentTasks(villageAgentId, params = {}) {
646
+ const client = getPlatformClient();
647
+ const response = await client.get(
648
+ `/village-agents/${encodeURIComponent(villageAgentId)}/tasks`,
649
+ { params },
650
+ );
651
+ return response.data;
652
+ }
653
+
654
+ export async function assignAgentTask(villageAgentId, data) {
655
+ const client = getPlatformClient();
656
+ const response = await client.post(
657
+ `/village-agents/${encodeURIComponent(villageAgentId)}/tasks`,
658
+ data,
659
+ );
660
+ return response.data;
661
+ }
662
+
663
+ export async function claimAgentTask(villageAgentId, taskId) {
664
+ const client = getPlatformClient();
665
+ const response = await client.post(
666
+ `/village-agents/${encodeURIComponent(villageAgentId)}/tasks/${encodeURIComponent(taskId)}/claim`,
667
+ );
668
+ return response.data;
669
+ }
670
+
671
+ // completeAgentTask is also used for "fail" — pass `errorMessage` to mark FAILED,
672
+ // otherwise the task is marked COMPLETED with the given `output`.
673
+ export async function completeAgentTask(villageAgentId, taskId, data = {}) {
674
+ const client = getPlatformClient();
675
+ const response = await client.post(
676
+ `/village-agents/${encodeURIComponent(villageAgentId)}/tasks/${encodeURIComponent(taskId)}/complete`,
677
+ data,
678
+ );
679
+ return response.data;
680
+ }
681
+
682
+ // Retry a single FAILED or CANCELLED task: resets it to PENDING so the
683
+ // agent daemon re-claims it on the next polling iteration.
684
+ export async function retryAgentTask(villageAgentId, taskId) {
685
+ const client = getPlatformClient();
686
+ const response = await client.post(
687
+ `/village-agents/${encodeURIComponent(villageAgentId)}/tasks/${encodeURIComponent(taskId)}/retry`,
688
+ );
689
+ return response.data;
690
+ }
691
+
692
+ // Bulk-retry every FAILED task for an agent. Optional `errorPattern`
693
+ // filters by errorMessage substring (case-insensitive).
694
+ export async function retryFailedAgentTasks(villageAgentId, errorPattern) {
695
+ const client = getPlatformClient();
696
+ const body = errorPattern ? { errorPattern } : {};
697
+ const response = await client.post(
698
+ `/village-agents/${encodeURIComponent(villageAgentId)}/tasks/retry-failed`,
699
+ body,
700
+ );
701
+ return response.data;
702
+ }
703
+
704
+ // ── Wisdom (VillageBooks repurposed as agent skill packs) ──────────
705
+
706
+ export async function listVillageBooks(params = {}) {
707
+ const client = getPlatformClient();
708
+ const response = await client.get('/village-books', { params });
709
+ return response.data;
710
+ }
711
+
712
+ // Returns the raw .wisdom file text (YAML frontmatter + markdown body).
713
+ export async function exportVillageBook(id) {
714
+ const client = getPlatformClient();
715
+ const response = await client.get(
716
+ `/village-books/${encodeURIComponent(id)}/export`,
717
+ { responseType: 'text', transformResponse: (data) => data },
718
+ );
719
+ return response.data;
720
+ }
721
+
722
+ export async function importVillageBook(payload) {
723
+ const client = getPlatformClient();
724
+ const response = await client.post('/village-books/import', payload);
725
+ return response.data;
726
+ }
727
+
728
+ // ── Knowledge (long-term agent memory) ──────────────────
729
+
730
+ // Filter / search knowledge submissions. Pass `createdByAgentId` to narrow
731
+ // to a specific agent's own memories.
732
+ export async function listKnowledgeFiltered(params = {}) {
733
+ const client = getPlatformClient();
734
+ const response = await client.get('/knowledge/filtered', { params });
735
+ return response.data;
736
+ }
737
+
738
+ // Write a memory by submitting a Knowledge entry attributed to the agent.
739
+ // Backed by /api/agent-tools/share-knowledge — the agent_profile_id override
740
+ // makes the entry appear in this specific agent's recall results.
741
+ export async function shareKnowledgeAsAgent(data) {
742
+ const client = getPlatformClient();
743
+ const response = await client.post('/agent-tools/share-knowledge', data);
744
+ return response.data;
745
+ }