@myvillage/cli 1.18.0 → 1.22.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 +12 -2
- package/src/commands/api-key.js +172 -0
- package/src/index.js +35 -0
- package/src/utils/api.js +26 -0
package/package.json
CHANGED
|
@@ -152,8 +152,18 @@ export async function agentLoop(agentName, { signal }) {
|
|
|
152
152
|
mentionsFound = contextResult.mentionsCount;
|
|
153
153
|
|
|
154
154
|
if (activeTask) {
|
|
155
|
-
|
|
156
|
-
context
|
|
155
|
+
// A claimed task takes over the iteration. The default monitoring
|
|
156
|
+
// context becomes secondary background; the instruction is the
|
|
157
|
+
// primary user prompt and the system prompt switches to a
|
|
158
|
+
// task-execution framing so the LLM doesn't drift back into
|
|
159
|
+
// "report on the feed" mode.
|
|
160
|
+
const instructionText = activeTask.instruction
|
|
161
|
+
|| (activeTask.input ? JSON.stringify(activeTask.input, null, 2) : '');
|
|
162
|
+
|
|
163
|
+
systemPrompt = `${systemPrompt}\n\n## ACTIVE TASK MODE\nA client has assigned you a task. Your job this iteration is to execute the instruction below using your available tools. The feed context is provided only for situational awareness — do not let "no new feed activity" prevent you from carrying out the task.`;
|
|
164
|
+
|
|
165
|
+
context = `## TASK (id=${activeTask.id}, type=${activeTask.taskType})\n${instructionText}\n\n---\n\n## FEED CONTEXT (for awareness only)\n${context}`;
|
|
166
|
+
|
|
157
167
|
logActivity(agentDir, { type: 'task_claimed', taskId: activeTask.id, taskType: activeTask.taskType });
|
|
158
168
|
}
|
|
159
169
|
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import inquirer from 'inquirer';
|
|
3
|
+
import { villageSpinner, brand } from '../utils/brand.js';
|
|
4
|
+
import { isAuthenticated } from '../utils/auth.js';
|
|
5
|
+
import {
|
|
6
|
+
createApiKey,
|
|
7
|
+
listApiKeys,
|
|
8
|
+
revokeApiKey,
|
|
9
|
+
listAllowedScopes,
|
|
10
|
+
} from '../utils/api.js';
|
|
11
|
+
|
|
12
|
+
// ── create ──────────────────────────────────────────────
|
|
13
|
+
|
|
14
|
+
export async function apiKeyCreateCommand(options = {}) {
|
|
15
|
+
if (!isAuthenticated()) {
|
|
16
|
+
console.log(chalk.red(' ✗ Authentication required. Run \'myvillage login\' first.'));
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (!options.name) {
|
|
21
|
+
console.log(chalk.red(' ✗ --name is required (e.g. --name "claude-desktop")\n'));
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
let scopes;
|
|
26
|
+
if (options.scopes) {
|
|
27
|
+
scopes = options.scopes.split(',').map(s => s.trim()).filter(Boolean);
|
|
28
|
+
} else {
|
|
29
|
+
// Interactive picker — fetch what the caller is allowed to mint
|
|
30
|
+
const spinner = villageSpinner('Loading available scopes...').start();
|
|
31
|
+
let allowed;
|
|
32
|
+
try {
|
|
33
|
+
const result = await listAllowedScopes();
|
|
34
|
+
allowed = result.allowedScopes || {};
|
|
35
|
+
spinner.stop();
|
|
36
|
+
} catch (err) {
|
|
37
|
+
spinner.fail(`Failed to load scopes: ${err.response?.data?.error || err.message}`);
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
const scopeKeys = Object.keys(allowed);
|
|
41
|
+
if (scopeKeys.length === 0) {
|
|
42
|
+
console.log(chalk.yellow(' ⚠ Your account has no API scopes available.\n'));
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
const { picked } = await inquirer.prompt([{
|
|
46
|
+
type: 'checkbox',
|
|
47
|
+
name: 'picked',
|
|
48
|
+
message: 'Pick the scopes this key should carry (Space to toggle, Enter to confirm):',
|
|
49
|
+
pageSize: Math.min(scopeKeys.length, 15),
|
|
50
|
+
choices: scopeKeys.map(s => ({
|
|
51
|
+
name: `${s} ${chalk.dim('— ' + allowed[s])}`,
|
|
52
|
+
value: s,
|
|
53
|
+
checked: s === 'agents:tasks:read' || s === 'agents:tasks:write',
|
|
54
|
+
})),
|
|
55
|
+
validate: (input) => input.length > 0 || 'Pick at least one scope.',
|
|
56
|
+
}]);
|
|
57
|
+
scopes = picked;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const body = { name: options.name, scopes };
|
|
61
|
+
if (options.expiresInDays) {
|
|
62
|
+
body.expiresInDays = Number(options.expiresInDays);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const spinner = villageSpinner('Creating API key...').start();
|
|
66
|
+
try {
|
|
67
|
+
const result = await createApiKey(body);
|
|
68
|
+
spinner.succeed('API key created.');
|
|
69
|
+
|
|
70
|
+
console.log(brand.teal('\n Key (shown ONCE — copy it now):\n'));
|
|
71
|
+
console.log(' ' + chalk.bold(result.key));
|
|
72
|
+
console.log('');
|
|
73
|
+
console.log(brand.teal(` Id: ${result.apiKey.id}`));
|
|
74
|
+
console.log(brand.teal(` Prefix: ${result.apiKey.prefix}`));
|
|
75
|
+
console.log(brand.teal(` Scopes: ${result.apiKey.scopes.join(', ')}`));
|
|
76
|
+
if (result.apiKey.expiresAt) {
|
|
77
|
+
console.log(brand.teal(` Expires: ${result.apiKey.expiresAt}`));
|
|
78
|
+
} else {
|
|
79
|
+
console.log(brand.teal(' Expires: never (revoke manually when no longer needed)'));
|
|
80
|
+
}
|
|
81
|
+
console.log(chalk.yellow('\n ⚠ Treat this key like a password. Anyone with it can act as you'));
|
|
82
|
+
console.log(chalk.yellow(' within the scopes you selected. Store it in an env var, not in git.\n'));
|
|
83
|
+
console.log(brand.teal(' Use in REST calls:'));
|
|
84
|
+
console.log(' ' + chalk.dim(`curl -H "Authorization: Bearer ${result.key.slice(0, 12)}..." https://portal.myvillageproject.ai/api/...\n`));
|
|
85
|
+
console.log(brand.teal(' Use in Claude Desktop / Cursor MCP config:'));
|
|
86
|
+
console.log(' ' + chalk.dim(`"headers": { "Authorization": "Bearer ${result.key.slice(0, 12)}..." }\n`));
|
|
87
|
+
} catch (err) {
|
|
88
|
+
const msg = err.response?.data?.error || err.message;
|
|
89
|
+
spinner.fail(`Failed to create API key: ${msg}`);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// ── list ────────────────────────────────────────────────
|
|
94
|
+
|
|
95
|
+
export async function apiKeyListCommand() {
|
|
96
|
+
if (!isAuthenticated()) {
|
|
97
|
+
console.log(chalk.red(' ✗ Authentication required. Run \'myvillage login\' first.'));
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
try {
|
|
102
|
+
const result = await listApiKeys();
|
|
103
|
+
const keys = result.apiKeys || [];
|
|
104
|
+
if (keys.length === 0) {
|
|
105
|
+
console.log(brand.teal(' No API keys yet. Create one with: myvillage api-key create --name "..."\n'));
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
console.log(brand.teal(`\n ${keys.length} API key(s):\n`));
|
|
109
|
+
for (const k of keys) {
|
|
110
|
+
const status = k.isActive ? brand.green('active') : chalk.dim('revoked');
|
|
111
|
+
console.log(` ${chalk.bold(k.name)} ${status}`);
|
|
112
|
+
console.log(` id: ${k.id}`);
|
|
113
|
+
console.log(` prefix: ${k.prefix}`);
|
|
114
|
+
console.log(` scopes: ${(k.scopes || []).join(', ')}`);
|
|
115
|
+
if (k.lastUsedAt) console.log(` used: ${k.lastUsedAt}`);
|
|
116
|
+
if (k.expiresAt) console.log(` expires: ${k.expiresAt}`);
|
|
117
|
+
console.log('');
|
|
118
|
+
}
|
|
119
|
+
} catch (err) {
|
|
120
|
+
const msg = err.response?.data?.error || err.message;
|
|
121
|
+
console.log(chalk.red(` ✗ Failed to list API keys: ${msg}\n`));
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// ── revoke ──────────────────────────────────────────────
|
|
126
|
+
|
|
127
|
+
export async function apiKeyRevokeCommand(id) {
|
|
128
|
+
if (!isAuthenticated()) {
|
|
129
|
+
console.log(chalk.red(' ✗ Authentication required. Run \'myvillage login\' first.'));
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
if (!id) {
|
|
133
|
+
console.log(chalk.red(' ✗ Usage: myvillage api-key revoke <id>\n'));
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
try {
|
|
138
|
+
await revokeApiKey(id);
|
|
139
|
+
console.log(brand.green(` ✓ API key ${id} revoked. Any further requests using it will fail.\n`));
|
|
140
|
+
} catch (err) {
|
|
141
|
+
const msg = err.response?.data?.error || err.message;
|
|
142
|
+
console.log(chalk.red(` ✗ Failed to revoke API key: ${msg}\n`));
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// ── show-scopes ─────────────────────────────────────────
|
|
147
|
+
|
|
148
|
+
export async function apiKeyShowScopesCommand() {
|
|
149
|
+
if (!isAuthenticated()) {
|
|
150
|
+
console.log(chalk.red(' ✗ Authentication required. Run \'myvillage login\' first.'));
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
try {
|
|
155
|
+
const result = await listAllowedScopes();
|
|
156
|
+
const allowed = result.allowedScopes || {};
|
|
157
|
+
const keys = Object.keys(allowed);
|
|
158
|
+
if (keys.length === 0) {
|
|
159
|
+
console.log(brand.teal(' No API scopes available for your account.\n'));
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
console.log(brand.teal(`\n ${keys.length} scope(s) available to you:\n`));
|
|
163
|
+
for (const k of keys) {
|
|
164
|
+
console.log(` ${chalk.bold(k)}`);
|
|
165
|
+
console.log(` ${chalk.dim(allowed[k])}`);
|
|
166
|
+
}
|
|
167
|
+
console.log('');
|
|
168
|
+
} catch (err) {
|
|
169
|
+
const msg = err.response?.data?.error || err.message;
|
|
170
|
+
console.log(chalk.red(` ✗ Failed to load allowed scopes: ${msg}\n`));
|
|
171
|
+
}
|
|
172
|
+
}
|
package/src/index.js
CHANGED
|
@@ -9,6 +9,12 @@ import {
|
|
|
9
9
|
mediaDraftStatusCommand,
|
|
10
10
|
} from './commands/media.js';
|
|
11
11
|
import { logoutCommand } from './commands/logout.js';
|
|
12
|
+
import {
|
|
13
|
+
apiKeyCreateCommand,
|
|
14
|
+
apiKeyListCommand,
|
|
15
|
+
apiKeyRevokeCommand,
|
|
16
|
+
apiKeyShowScopesCommand,
|
|
17
|
+
} from './commands/api-key.js';
|
|
12
18
|
import { createGameCommand } from './commands/create-game.js';
|
|
13
19
|
import { createCommand } from './commands/create-app.js';
|
|
14
20
|
import { deployCommand } from './commands/deploy.js';
|
|
@@ -141,6 +147,35 @@ export function run() {
|
|
|
141
147
|
.description('Clear stored credentials')
|
|
142
148
|
.action(logoutCommand);
|
|
143
149
|
|
|
150
|
+
// ── API Keys (developer self-service) ──────────────────
|
|
151
|
+
|
|
152
|
+
const apiKeyCmd = program
|
|
153
|
+
.command('api-key')
|
|
154
|
+
.description('Manage your API keys (used for REST and MCP authentication)');
|
|
155
|
+
|
|
156
|
+
apiKeyCmd
|
|
157
|
+
.command('create')
|
|
158
|
+
.description('Create a new API key. Shown ONCE — copy it immediately.')
|
|
159
|
+
.requiredOption('--name <name>', 'Friendly name for this key (e.g. "claude-desktop")')
|
|
160
|
+
.option('--scopes <list>', 'Comma-separated scopes (omit for interactive picker)')
|
|
161
|
+
.option('--expires-in-days <n>', 'Number of days until expiry (default: never)')
|
|
162
|
+
.action(apiKeyCreateCommand);
|
|
163
|
+
|
|
164
|
+
apiKeyCmd
|
|
165
|
+
.command('list')
|
|
166
|
+
.description('List your API keys (names, prefixes, scopes — never the secret)')
|
|
167
|
+
.action(apiKeyListCommand);
|
|
168
|
+
|
|
169
|
+
apiKeyCmd
|
|
170
|
+
.command('revoke <id>')
|
|
171
|
+
.description('Revoke an API key by id so further requests using it are rejected')
|
|
172
|
+
.action(apiKeyRevokeCommand);
|
|
173
|
+
|
|
174
|
+
apiKeyCmd
|
|
175
|
+
.command('show-scopes')
|
|
176
|
+
.description('Print the API scopes your account is allowed to mint into a key')
|
|
177
|
+
.action(apiKeyShowScopesCommand);
|
|
178
|
+
|
|
144
179
|
program
|
|
145
180
|
.command('create-game')
|
|
146
181
|
.description('Create a new game project with interactive wizard')
|
package/src/utils/api.js
CHANGED
|
@@ -701,6 +701,32 @@ export async function retryFailedAgentTasks(villageAgentId, errorPattern) {
|
|
|
701
701
|
return response.data;
|
|
702
702
|
}
|
|
703
703
|
|
|
704
|
+
// ── API Key management ─────────────────────────────────
|
|
705
|
+
|
|
706
|
+
export async function createApiKey(data) {
|
|
707
|
+
const client = getPlatformClient();
|
|
708
|
+
const response = await client.post('/api-keys', data);
|
|
709
|
+
return response.data;
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
export async function listApiKeys() {
|
|
713
|
+
const client = getPlatformClient();
|
|
714
|
+
const response = await client.get('/api-keys');
|
|
715
|
+
return response.data;
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
export async function revokeApiKey(id) {
|
|
719
|
+
const client = getPlatformClient();
|
|
720
|
+
const response = await client.delete(`/api-keys/${encodeURIComponent(id)}`);
|
|
721
|
+
return response.data;
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
export async function listAllowedScopes() {
|
|
725
|
+
const client = getPlatformClient();
|
|
726
|
+
const response = await client.get('/api-keys/allowed-scopes');
|
|
727
|
+
return response.data;
|
|
728
|
+
}
|
|
729
|
+
|
|
704
730
|
// ── Wisdom (VillageBooks repurposed as agent skill packs) ──────────
|
|
705
731
|
|
|
706
732
|
export async function listVillageBooks(params = {}) {
|