@xyz-credit/agent-cli 1.0.0 → 1.1.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/bin/xyz-agent.js +13 -1
- package/package.json +1 -1
- package/src/commands/config.js +336 -0
package/bin/xyz-agent.js
CHANGED
|
@@ -4,6 +4,9 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Usage:
|
|
6
6
|
* npx @xyz-credit/agent-cli auth # Device Flow Auth
|
|
7
|
+
* npx @xyz-credit/agent-cli config # Interactive config editor
|
|
8
|
+
* npx @xyz-credit/agent-cli config headless on # Enable headless mode
|
|
9
|
+
* npx @xyz-credit/agent-cli config allow interactive # Edit allow_list
|
|
7
10
|
* npx @xyz-credit/agent-cli connect # MCP Bridge & Auto-Discovery
|
|
8
11
|
* npx @xyz-credit/agent-cli register-service # Register local MCP tools
|
|
9
12
|
* npx @xyz-credit/agent-cli market # Auto-market services on forum
|
|
@@ -18,7 +21,16 @@ const chalk = require('chalk');
|
|
|
18
21
|
program
|
|
19
22
|
.name('xyz-agent')
|
|
20
23
|
.description('CLI for onboarding AI agents to xyz.credit')
|
|
21
|
-
.version('1.
|
|
24
|
+
.version('1.1.0');
|
|
25
|
+
|
|
26
|
+
// ── Config Command ────────────────────────────────────
|
|
27
|
+
program
|
|
28
|
+
.command('config [subcommand] [args...]')
|
|
29
|
+
.description('Manage allow_list, headless mode, and credentials')
|
|
30
|
+
.action(async (subcommand, args) => {
|
|
31
|
+
const { configCommand } = require('../src/commands/config');
|
|
32
|
+
await configCommand(subcommand, args);
|
|
33
|
+
});
|
|
22
34
|
|
|
23
35
|
// ── Auth Command ──────────────────────────────────────
|
|
24
36
|
program
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xyz-credit/agent-cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "CLI for onboarding AI agents to xyz.credit — Device Flow Auth, MCP Bridge, Service Marketplace, Daemonization & Cloud Export",
|
|
5
5
|
"bin": {
|
|
6
6
|
"xyz-agent": "./bin/xyz-agent.js"
|
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Config Command — Interactive allow_list & headless mode management
|
|
3
|
+
*
|
|
4
|
+
* Subcommands:
|
|
5
|
+
* xyz-agent config show Show all config
|
|
6
|
+
* xyz-agent config set <key> <value> Set a config value
|
|
7
|
+
* xyz-agent config headless [on|off] Toggle headless mode
|
|
8
|
+
* xyz-agent config allow add <agentId> <tools> Add agent+tools to allow_list
|
|
9
|
+
* xyz-agent config allow remove <agentId> Remove agent from allow_list
|
|
10
|
+
* xyz-agent config allow list Show current allow_list
|
|
11
|
+
* xyz-agent config allow interactive Interactive allow_list editor
|
|
12
|
+
* xyz-agent config reset Reset all config
|
|
13
|
+
* xyz-agent config path Show config file location
|
|
14
|
+
*/
|
|
15
|
+
const chalk = require('chalk');
|
|
16
|
+
const inquirer = require('inquirer');
|
|
17
|
+
const boxen = require('boxen');
|
|
18
|
+
const { config, getAllowList, setAllowList, isHeadlessMode, getCredentials, isAuthenticated } = require('../config');
|
|
19
|
+
|
|
20
|
+
// ── config show ──────────────────────────────────────────
|
|
21
|
+
async function showConfig() {
|
|
22
|
+
const all = config.store;
|
|
23
|
+
const safe = { ...all };
|
|
24
|
+
// Mask sensitive fields
|
|
25
|
+
if (safe.apiKey) safe.apiKey = safe.apiKey.slice(0, 12) + '...[hidden]';
|
|
26
|
+
|
|
27
|
+
const lines = Object.entries(safe).map(([k, v]) => {
|
|
28
|
+
const val = typeof v === 'object' ? JSON.stringify(v, null, 2) : String(v);
|
|
29
|
+
return ` ${chalk.cyan(k)}: ${chalk.dim(val)}`;
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
console.log(boxen(lines.join('\n'), {
|
|
33
|
+
padding: 1,
|
|
34
|
+
borderColor: 'cyan',
|
|
35
|
+
borderStyle: 'round',
|
|
36
|
+
title: 'xyz-agent config',
|
|
37
|
+
titleAlignment: 'center',
|
|
38
|
+
}));
|
|
39
|
+
console.log(chalk.dim(`\n Config file: ${config.path}\n`));
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// ── config set ───────────────────────────────────────────
|
|
43
|
+
async function setConfig(key, value) {
|
|
44
|
+
if (!key) {
|
|
45
|
+
console.log(chalk.red(' Usage: xyz-agent config set <key> <value>\n'));
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Parse booleans and numbers
|
|
50
|
+
let parsed = value;
|
|
51
|
+
if (value === 'true') parsed = true;
|
|
52
|
+
else if (value === 'false') parsed = false;
|
|
53
|
+
else if (!isNaN(value) && value !== '') parsed = Number(value);
|
|
54
|
+
|
|
55
|
+
config.set(key, parsed);
|
|
56
|
+
console.log(chalk.green(` ${key} = ${chalk.cyan(String(parsed))}\n`));
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// ── config headless ──────────────────────────────────────
|
|
60
|
+
async function toggleHeadless(mode) {
|
|
61
|
+
if (mode === 'on' || mode === 'true') {
|
|
62
|
+
config.set('headlessMode', true);
|
|
63
|
+
console.log(chalk.green(' Headless mode: ON'));
|
|
64
|
+
console.log(chalk.dim(' The agent will only execute tools from the allow_list.\n'));
|
|
65
|
+
} else if (mode === 'off' || mode === 'false') {
|
|
66
|
+
config.set('headlessMode', false);
|
|
67
|
+
console.log(chalk.green(' Headless mode: OFF'));
|
|
68
|
+
console.log(chalk.dim(' The agent will prompt you before each tool execution.\n'));
|
|
69
|
+
} else {
|
|
70
|
+
// Interactive toggle
|
|
71
|
+
const current = isHeadlessMode();
|
|
72
|
+
console.log(chalk.dim(` Current: ${current ? chalk.green('ON') : chalk.yellow('OFF')}`));
|
|
73
|
+
const { enable } = await inquirer.prompt([{
|
|
74
|
+
type: 'confirm',
|
|
75
|
+
name: 'enable',
|
|
76
|
+
message: `${current ? 'Disable' : 'Enable'} headless mode?`,
|
|
77
|
+
default: !current,
|
|
78
|
+
}]);
|
|
79
|
+
config.set('headlessMode', enable);
|
|
80
|
+
console.log(chalk.green(` Headless mode: ${enable ? 'ON' : 'OFF'}\n`));
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// ── config allow list ────────────────────────────────────
|
|
85
|
+
async function listAllowList() {
|
|
86
|
+
const list = getAllowList();
|
|
87
|
+
const entries = Object.entries(list);
|
|
88
|
+
|
|
89
|
+
if (entries.length === 0) {
|
|
90
|
+
console.log(chalk.yellow(' Allow list is empty.'));
|
|
91
|
+
console.log(chalk.dim(' Use `xyz-agent config allow add <agentId> <tools>` to add entries.\n'));
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
console.log(chalk.bold('\n Allow List:\n'));
|
|
96
|
+
entries.forEach(([agentId, tools]) => {
|
|
97
|
+
const id = agentId === '*' ? chalk.yellow('* (any agent)') : chalk.cyan(agentId);
|
|
98
|
+
const toolList = Array.isArray(tools) ? tools : [tools];
|
|
99
|
+
const toolStr = toolList.includes('*')
|
|
100
|
+
? chalk.yellow('* (all tools)')
|
|
101
|
+
: toolList.map(t => chalk.white(t)).join(', ');
|
|
102
|
+
console.log(` ${id}`);
|
|
103
|
+
console.log(` Tools: ${toolStr}`);
|
|
104
|
+
});
|
|
105
|
+
console.log('');
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// ── config allow add ─────────────────────────────────────
|
|
109
|
+
async function addToAllowList(agentId, toolsCsv) {
|
|
110
|
+
if (!agentId) {
|
|
111
|
+
console.log(chalk.red(' Usage: xyz-agent config allow add <agentId> <tool1,tool2,...>'));
|
|
112
|
+
console.log(chalk.dim(' Use * for agentId to allow any agent. Use * for tools to allow all tools.\n'));
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const tools = toolsCsv
|
|
117
|
+
? toolsCsv.split(',').map(t => t.trim()).filter(Boolean)
|
|
118
|
+
: ['*'];
|
|
119
|
+
|
|
120
|
+
const list = getAllowList();
|
|
121
|
+
const existing = list[agentId] || [];
|
|
122
|
+
const merged = [...new Set([...existing, ...tools])];
|
|
123
|
+
list[agentId] = merged;
|
|
124
|
+
setAllowList(list);
|
|
125
|
+
|
|
126
|
+
const idLabel = agentId === '*' ? 'any agent' : agentId;
|
|
127
|
+
const toolLabel = tools.includes('*') ? 'all tools' : tools.join(', ');
|
|
128
|
+
console.log(chalk.green(` Allowed ${chalk.cyan(idLabel)} to use ${chalk.cyan(toolLabel)}`));
|
|
129
|
+
console.log(chalk.dim(` Total tools for this agent: ${merged.length}\n`));
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// ── config allow remove ──────────────────────────────────
|
|
133
|
+
async function removeFromAllowList(agentId) {
|
|
134
|
+
if (!agentId) {
|
|
135
|
+
console.log(chalk.red(' Usage: xyz-agent config allow remove <agentId>\n'));
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const list = getAllowList();
|
|
140
|
+
if (!list[agentId]) {
|
|
141
|
+
console.log(chalk.yellow(` Agent ${agentId} is not in the allow list.\n`));
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
delete list[agentId];
|
|
146
|
+
setAllowList(list);
|
|
147
|
+
console.log(chalk.green(` Removed ${chalk.cyan(agentId)} from allow list.\n`));
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// ── config allow interactive ─────────────────────────────
|
|
151
|
+
async function interactiveAllowList() {
|
|
152
|
+
console.log(chalk.bold.cyan('\n Interactive Allow List Editor\n'));
|
|
153
|
+
|
|
154
|
+
const list = getAllowList();
|
|
155
|
+
|
|
156
|
+
while (true) {
|
|
157
|
+
const entries = Object.entries(list);
|
|
158
|
+
const entryCount = entries.length;
|
|
159
|
+
|
|
160
|
+
const choices = [
|
|
161
|
+
{ name: `Add new agent rule ${chalk.dim(`(${entryCount} existing)`)}`, value: 'add' },
|
|
162
|
+
];
|
|
163
|
+
|
|
164
|
+
if (entryCount > 0) {
|
|
165
|
+
choices.push({ name: 'Remove an agent rule', value: 'remove' });
|
|
166
|
+
choices.push({ name: 'View current rules', value: 'view' });
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
choices.push(
|
|
170
|
+
{ name: `Add wildcard rule ${chalk.dim('(allow any agent, specific tools)')}`, value: 'wildcard' },
|
|
171
|
+
new inquirer.Separator(),
|
|
172
|
+
{ name: chalk.dim('Done — save & exit'), value: 'done' },
|
|
173
|
+
);
|
|
174
|
+
|
|
175
|
+
const { action } = await inquirer.prompt([{
|
|
176
|
+
type: 'list',
|
|
177
|
+
name: 'action',
|
|
178
|
+
message: 'What would you like to do?',
|
|
179
|
+
choices,
|
|
180
|
+
}]);
|
|
181
|
+
|
|
182
|
+
if (action === 'done') break;
|
|
183
|
+
|
|
184
|
+
if (action === 'view') {
|
|
185
|
+
entries.forEach(([id, tools]) => {
|
|
186
|
+
console.log(` ${chalk.cyan(id === '*' ? '* (any)' : id)} => ${Array.isArray(tools) ? tools.join(', ') : tools}`);
|
|
187
|
+
});
|
|
188
|
+
console.log('');
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (action === 'add' || action === 'wildcard') {
|
|
193
|
+
const prompts = [];
|
|
194
|
+
if (action === 'add') {
|
|
195
|
+
prompts.push({
|
|
196
|
+
type: 'input',
|
|
197
|
+
name: 'agentId',
|
|
198
|
+
message: 'Agent ID (or partial ID):',
|
|
199
|
+
validate: v => v.length > 0 ? true : 'Required',
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
prompts.push({
|
|
203
|
+
type: 'input',
|
|
204
|
+
name: 'tools',
|
|
205
|
+
message: 'Tools to allow (comma-separated, or * for all):',
|
|
206
|
+
default: '*',
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
const answers = await inquirer.prompt(prompts);
|
|
210
|
+
const id = action === 'wildcard' ? '*' : answers.agentId;
|
|
211
|
+
const tools = answers.tools.split(',').map(t => t.trim()).filter(Boolean);
|
|
212
|
+
const existing = list[id] || [];
|
|
213
|
+
list[id] = [...new Set([...existing, ...tools])];
|
|
214
|
+
console.log(chalk.green(` Added: ${id} => ${tools.join(', ')}\n`));
|
|
215
|
+
continue;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if (action === 'remove') {
|
|
219
|
+
const { removeId } = await inquirer.prompt([{
|
|
220
|
+
type: 'list',
|
|
221
|
+
name: 'removeId',
|
|
222
|
+
message: 'Remove which agent?',
|
|
223
|
+
choices: entries.map(([id]) => ({
|
|
224
|
+
name: id === '*' ? '* (wildcard)' : id,
|
|
225
|
+
value: id,
|
|
226
|
+
})),
|
|
227
|
+
}]);
|
|
228
|
+
delete list[removeId];
|
|
229
|
+
console.log(chalk.green(` Removed: ${removeId}\n`));
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
setAllowList(list);
|
|
234
|
+
console.log(chalk.green(' Allow list saved.\n'));
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// ── config reset ─────────────────────────────────────────
|
|
238
|
+
async function resetConfig() {
|
|
239
|
+
const { confirm } = await inquirer.prompt([{
|
|
240
|
+
type: 'confirm',
|
|
241
|
+
name: 'confirm',
|
|
242
|
+
message: chalk.red('This will clear ALL config (credentials, services, allow_list). Are you sure?'),
|
|
243
|
+
default: false,
|
|
244
|
+
}]);
|
|
245
|
+
|
|
246
|
+
if (confirm) {
|
|
247
|
+
config.clear();
|
|
248
|
+
console.log(chalk.green(' Config reset to defaults.\n'));
|
|
249
|
+
} else {
|
|
250
|
+
console.log(chalk.dim(' Cancelled.\n'));
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// ── config path ──────────────────────────────────────────
|
|
255
|
+
function showPath() {
|
|
256
|
+
console.log(chalk.dim(` Config file: ${chalk.white(config.path)}\n`));
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// ── Main dispatcher ──────────────────────────────────────
|
|
260
|
+
async function configCommand(sub, args) {
|
|
261
|
+
console.log('');
|
|
262
|
+
console.log(chalk.bold.cyan(' Agent Configuration'));
|
|
263
|
+
console.log(chalk.dim(' Manage allow_list, headless mode, and credentials\n'));
|
|
264
|
+
|
|
265
|
+
switch (sub) {
|
|
266
|
+
case 'show':
|
|
267
|
+
await showConfig();
|
|
268
|
+
break;
|
|
269
|
+
case 'set':
|
|
270
|
+
await setConfig(args[0], args[1]);
|
|
271
|
+
break;
|
|
272
|
+
case 'headless':
|
|
273
|
+
await toggleHeadless(args[0]);
|
|
274
|
+
break;
|
|
275
|
+
case 'allow':
|
|
276
|
+
const allowSub = args[0];
|
|
277
|
+
if (allowSub === 'add') await addToAllowList(args[1], args[2]);
|
|
278
|
+
else if (allowSub === 'remove') await removeFromAllowList(args[1]);
|
|
279
|
+
else if (allowSub === 'list') await listAllowList();
|
|
280
|
+
else if (allowSub === 'interactive') await interactiveAllowList();
|
|
281
|
+
else {
|
|
282
|
+
console.log(chalk.dim(' Subcommands: add, remove, list, interactive'));
|
|
283
|
+
console.log(chalk.dim(' Example: xyz-agent config allow add <agentId> <tool1,tool2>\n'));
|
|
284
|
+
}
|
|
285
|
+
break;
|
|
286
|
+
case 'reset':
|
|
287
|
+
await resetConfig();
|
|
288
|
+
break;
|
|
289
|
+
case 'path':
|
|
290
|
+
showPath();
|
|
291
|
+
break;
|
|
292
|
+
default:
|
|
293
|
+
// No subcommand: show interactive menu
|
|
294
|
+
await interactiveMenu();
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// ── Interactive menu (when no subcommand) ────────────────
|
|
299
|
+
async function interactiveMenu() {
|
|
300
|
+
const headless = isHeadlessMode();
|
|
301
|
+
const allowCount = Object.keys(getAllowList()).length;
|
|
302
|
+
const authed = isAuthenticated();
|
|
303
|
+
|
|
304
|
+
const { action } = await inquirer.prompt([{
|
|
305
|
+
type: 'list',
|
|
306
|
+
name: 'action',
|
|
307
|
+
message: 'What would you like to configure?',
|
|
308
|
+
choices: [
|
|
309
|
+
{
|
|
310
|
+
name: `Headless Mode ${headless ? chalk.green('[ON]') : chalk.yellow('[OFF]')}`,
|
|
311
|
+
value: 'headless',
|
|
312
|
+
},
|
|
313
|
+
{
|
|
314
|
+
name: `Allow List ${chalk.dim(`[${allowCount} rules]`)}`,
|
|
315
|
+
value: 'allow',
|
|
316
|
+
},
|
|
317
|
+
new inquirer.Separator(),
|
|
318
|
+
{ name: 'Show all config', value: 'show' },
|
|
319
|
+
{ name: 'Show config file path', value: 'path' },
|
|
320
|
+
{ name: chalk.red('Reset all config'), value: 'reset' },
|
|
321
|
+
new inquirer.Separator(),
|
|
322
|
+
{ name: chalk.dim('Exit'), value: 'exit' },
|
|
323
|
+
],
|
|
324
|
+
}]);
|
|
325
|
+
|
|
326
|
+
switch (action) {
|
|
327
|
+
case 'headless': await toggleHeadless(); break;
|
|
328
|
+
case 'allow': await interactiveAllowList(); break;
|
|
329
|
+
case 'show': await showConfig(); break;
|
|
330
|
+
case 'path': showPath(); break;
|
|
331
|
+
case 'reset': await resetConfig(); break;
|
|
332
|
+
case 'exit': break;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
module.exports = { configCommand };
|