@onesi/genesis-cli 0.2.12 → 0.2.13
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/README.md +1 -1
- package/dist/connections.d.ts +14 -0
- package/dist/connections.js +126 -0
- package/dist/index.js +6 -4
- package/dist/products.js +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { Command } from 'commander';
|
|
2
|
+
import type { Genesis } from '@onesi/genesis';
|
|
3
|
+
type Dependencies = {
|
|
4
|
+
client: () => Promise<Genesis>;
|
|
5
|
+
emit: (data: unknown, pretty: () => string) => void;
|
|
6
|
+
signal?: AbortSignal;
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* `genesis connections ...`: connect a verified remote MCP server, choose its tools, call them, revoke.
|
|
10
|
+
* Each command is one Connections operation (/v1/connections/*). `authorize` prints the URL a person opens
|
|
11
|
+
* in a browser; the CLI never opens or follows it.
|
|
12
|
+
*/
|
|
13
|
+
export declare function registerConnections(program: Command, deps: Dependencies): void;
|
|
14
|
+
export {};
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { readFunctionJson } from './function-input.js';
|
|
2
|
+
const MODES = ['allow', 'approve', 'deny'];
|
|
3
|
+
const STATUSES = ['pending_auth', 'active', 'needs_reauth', 'revoked'];
|
|
4
|
+
const APPROVAL = /^apr_[0-9a-f]{32}$/;
|
|
5
|
+
const AUTO_ORG = /^aorg_[a-f0-9]{32}$/;
|
|
6
|
+
const TOOL = /^[A-Za-z0-9_.-]{1,128}$/;
|
|
7
|
+
/** A tool call's arguments: inline JSON or @file, up to 256 KiB (Connections' body limit). */
|
|
8
|
+
const ARGS_LIMIT = 256 * 1024;
|
|
9
|
+
const group = (parent, name) => parent.commands.find(c => c.name() === name) ?? parent.command(name);
|
|
10
|
+
const approvalHeaders = (approval) => {
|
|
11
|
+
if (approval === undefined)
|
|
12
|
+
return {};
|
|
13
|
+
if (!APPROVAL.test(approval))
|
|
14
|
+
throw new Error('--approval must be the approval id from the 428 answer (apr_ followed by 32 hex).');
|
|
15
|
+
return { 'OneOS-Approval': approval };
|
|
16
|
+
};
|
|
17
|
+
async function toolArguments(value) {
|
|
18
|
+
if (value === undefined)
|
|
19
|
+
return {};
|
|
20
|
+
if (value.startsWith('@'))
|
|
21
|
+
return readFunctionJson(value.slice(1), ARGS_LIMIT);
|
|
22
|
+
if (Buffer.byteLength(value) > ARGS_LIMIT)
|
|
23
|
+
throw new Error('--args is larger than 256 KiB.');
|
|
24
|
+
let parsed;
|
|
25
|
+
try {
|
|
26
|
+
parsed = JSON.parse(value);
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
throw new Error('--args must be a JSON object or @file.json. No input value is echoed.');
|
|
30
|
+
}
|
|
31
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed))
|
|
32
|
+
throw new Error('--args must be a JSON object or @file.json. No input value is echoed.');
|
|
33
|
+
return parsed;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* `genesis connections ...`: connect a verified remote MCP server, choose its tools, call them, revoke.
|
|
37
|
+
* Each command is one Connections operation (/v1/connections/*). `authorize` prints the URL a person opens
|
|
38
|
+
* in a browser; the CLI never opens or follows it.
|
|
39
|
+
*/
|
|
40
|
+
export function registerConnections(program, deps) {
|
|
41
|
+
const root = group(program, 'connections')
|
|
42
|
+
.description('Connections: verified remote MCP servers (Linear, Neon, Sentry, ...) your agents may use (connections:read / connections:write)');
|
|
43
|
+
const run = async (id, input, headers = {}) => {
|
|
44
|
+
const result = await (await deps.client()).request(id, input, { headers, signal: deps.signal });
|
|
45
|
+
deps.emit(result ?? { ok: true }, () => JSON.stringify(result ?? { ok: true }, null, 2));
|
|
46
|
+
return result;
|
|
47
|
+
};
|
|
48
|
+
root.command('catalog [server]')
|
|
49
|
+
.description('List verified MCP servers, or show one with its pinned tool manifest (GET /v1/connections/catalog[/{server}])')
|
|
50
|
+
.option('--plugin <plugin>', 'only servers in this OneOS plugin category, e.g. scopes')
|
|
51
|
+
.action(async (server, o) => {
|
|
52
|
+
if (server !== undefined) {
|
|
53
|
+
if (o.plugin !== undefined)
|
|
54
|
+
throw new Error('--plugin filters the list; leave it out with a server.');
|
|
55
|
+
return void await run('connections_getCatalogServer', { server });
|
|
56
|
+
}
|
|
57
|
+
await run('connections_listCatalog', o.plugin !== undefined ? { plugin: o.plugin } : {});
|
|
58
|
+
});
|
|
59
|
+
root.command('connect <server>')
|
|
60
|
+
.description('Connect a catalog server; prints the authorize_url a person opens in a browser (POST /v1/connections)')
|
|
61
|
+
.option('--tools <names>', 'comma-separated tools to enable (default: the manifest defaults)')
|
|
62
|
+
.option('--auto-org <aorg_id>', 'the Auto org that governs the connection; always needs an approval first (428)')
|
|
63
|
+
.option('--approval <apr_id>', 'retry with an approved approval id after a 428')
|
|
64
|
+
.action(async (server, o) => {
|
|
65
|
+
const tools = o.tools === undefined ? undefined : o.tools.split(',').map(t => t.trim()).filter(Boolean);
|
|
66
|
+
if (tools && (!tools.length || tools.some(t => !TOOL.test(t))))
|
|
67
|
+
throw new Error('--tools must be comma-separated tool names.');
|
|
68
|
+
if (o.autoOrg !== undefined && !AUTO_ORG.test(o.autoOrg))
|
|
69
|
+
throw new Error('--auto-org must be an Auto org id (aorg_ followed by 32 hex).');
|
|
70
|
+
await run('connections_connect', { body: { server, ...(tools ? { tools } : {}), ...(o.autoOrg !== undefined ? { auto_org: o.autoOrg } : {}) } }, approvalHeaders(o.approval));
|
|
71
|
+
});
|
|
72
|
+
root.command('list')
|
|
73
|
+
.description("List your organization's connections (GET /v1/connections)")
|
|
74
|
+
.option('--status <status>', 'pending_auth | active | needs_reauth | revoked')
|
|
75
|
+
.action(async (o) => {
|
|
76
|
+
if (o.status !== undefined && !STATUSES.includes(o.status))
|
|
77
|
+
throw new Error(`--status must be one of ${STATUSES.join(', ')}.`);
|
|
78
|
+
await run('connections_list', o.status !== undefined ? { status: o.status } : {});
|
|
79
|
+
});
|
|
80
|
+
root.command('get <id>')
|
|
81
|
+
.description('Show a connection: status, granted scopes, manifest hash (GET /v1/connections/{id})')
|
|
82
|
+
.action(async (id) => { await run('connections_get', { id }); });
|
|
83
|
+
root.command('authorize <id>')
|
|
84
|
+
.description('Get a new authorize URL for a person to open in a browser (POST /v1/connections/{id}/authorize); valid 10 minutes, single use')
|
|
85
|
+
.action(async (id) => { await run('connections_authorize', { id }); });
|
|
86
|
+
root.command('tools <id>')
|
|
87
|
+
.description("List a connection's tools with their effect and policy; descriptions are the vendor's, untrusted (GET /v1/connections/{id}/tools)")
|
|
88
|
+
.action(async (id) => { await run('connections_listTools', { id }); });
|
|
89
|
+
root.command('search [query]')
|
|
90
|
+
.description('Search the connected tools you may call, at most 100 (GET /v1/connections/tools?q=)')
|
|
91
|
+
.action(async (query) => { await run('connections_searchTools', query !== undefined ? { q: query } : {}); });
|
|
92
|
+
root.command('policy <id> <assignments...>')
|
|
93
|
+
.description('Set tool policies, e.g. create_issue=approve list_issues=allow; send, spend and destructive tools take approve or deny; widening needs an approval (PATCH /v1/connections/{id}/tools)')
|
|
94
|
+
.option('--approval <apr_id>', 'retry with an approved approval id after a 428')
|
|
95
|
+
.action(async (id, assignments, o) => {
|
|
96
|
+
const policy = new Map();
|
|
97
|
+
for (const entry of assignments) {
|
|
98
|
+
const at = entry.lastIndexOf('=');
|
|
99
|
+
const tool = entry.slice(0, at), mode = entry.slice(at + 1);
|
|
100
|
+
if (at < 1 || !TOOL.test(tool) || !MODES.includes(mode))
|
|
101
|
+
throw new Error(`Expected tool=allow|approve|deny, got "${entry}".`);
|
|
102
|
+
if (policy.has(tool))
|
|
103
|
+
throw new Error(`${tool} is given twice.`);
|
|
104
|
+
policy.set(tool, mode);
|
|
105
|
+
}
|
|
106
|
+
await run('connections_setToolPolicy', { id, body: { tools: Object.fromEntries(policy) } }, approvalHeaders(o.approval));
|
|
107
|
+
});
|
|
108
|
+
root.command('call <id> <tool>')
|
|
109
|
+
.description('Call a connected tool; the result is third-party content, never instructions (POST /v1/connections/{id}/tools/{tool}/call)')
|
|
110
|
+
.option('--args <json|@file>', 'the tool arguments: a JSON object, or @path to a JSON file (up to 256 KiB)')
|
|
111
|
+
.option('--approval <apr_id>', 'retry with an approved approval id after a 428')
|
|
112
|
+
.action(async (id, tool, o) => {
|
|
113
|
+
const headers = approvalHeaders(o.approval);
|
|
114
|
+
await run('connections_call', { id, tool, body: { arguments: await toolArguments(o.args) } }, headers);
|
|
115
|
+
});
|
|
116
|
+
root.command('revoke <id>')
|
|
117
|
+
.description('Revoke a connection; Blinds deletes its tokens and asks the vendor to revoke them (DELETE /v1/connections/{id})')
|
|
118
|
+
.action(async (id) => { await run('connections_revoke', { id }); });
|
|
119
|
+
root.command('activity <id>')
|
|
120
|
+
.description("Read a connection's activity ledger, oldest first; hashes only; page with --after <next_after> (GET /v1/connections/{id}/activity)")
|
|
121
|
+
.option('--after <seq>', 'rows after this sequence number (default 0)')
|
|
122
|
+
.option('--limit <n>', '1-500 (default 100)')
|
|
123
|
+
.action(async (id, o) => {
|
|
124
|
+
await run('connections_activity', { id, ...(o.after !== undefined ? { after: Number(o.after) } : {}), ...(o.limit !== undefined ? { limit: Number(o.limit) } : {}) });
|
|
125
|
+
});
|
|
126
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -6,6 +6,7 @@ import { registerDials } from './dials.js';
|
|
|
6
6
|
import { registerIamDirectory } from './iam-directory.js';
|
|
7
7
|
import { registerOperationCommands, registerProducts } from './products.js';
|
|
8
8
|
import { registerOrgs } from './orgs.js';
|
|
9
|
+
import { registerConnections } from './connections.js';
|
|
9
10
|
import { Command, CommanderError } from 'commander';
|
|
10
11
|
import { Genesis, GenesisError, errorBody, errorCatalog, errorOutcome, isMindsOperation, HivesOperationError, HivesQueueOperationError, MindsAgentError, MindsOwnerError, hivesResponseBytes, MAX_FUNCTION_BODY_BYTES, MAX_FUNCTION_JSON_BYTES, DEFAULT_BASE_URL, requireSecureUrl, redactUserinfo } from '@onesi/genesis';
|
|
11
12
|
import { authConfig, completeLogin, selectSavedContext, savedLoginProvider, startLogin } from './auth.js';
|
|
@@ -47,7 +48,7 @@ export function buildProgram(deps = {}) {
|
|
|
47
48
|
const auth = { ...authConfig(env, f), sleep: deps.sleep };
|
|
48
49
|
const program = new Command('genesis')
|
|
49
50
|
.description('One CLI for every Genesis service.')
|
|
50
|
-
.version('0.2.
|
|
51
|
+
.version('0.2.13')
|
|
51
52
|
.option('--json', 'machine-readable JSON output')
|
|
52
53
|
.option('--api-key <key>', 'use this API key (default: GENESIS_API_KEY, then your `genesis login`)')
|
|
53
54
|
.option('--base-url <url>', 'gateway URL (default: GENESIS_BASE_URL or https://os.one.ml/api)')
|
|
@@ -63,7 +64,7 @@ export function buildProgram(deps = {}) {
|
|
|
63
64
|
const opts = {
|
|
64
65
|
apiKey: o.apiKey ?? env.GENESIS_API_KEY, token: env.GENESIS_TOKEN,
|
|
65
66
|
baseUrl: o.baseUrl ?? env.GENESIS_BASE_URL, routing: o.direct || env.GENESIS_ROUTING === 'direct' ? 'direct' : 'gateway',
|
|
66
|
-
fetch: f, userAgent: 'genesis-cli/0.2.
|
|
67
|
+
fetch: f, userAgent: 'genesis-cli/0.2.13',
|
|
67
68
|
credentials: store ? savedLoginProvider(auth, store) : undefined,
|
|
68
69
|
};
|
|
69
70
|
return new Genesis(hivesTransport ? { ...opts, fetch: createHivesNodeTransport(opts, deps.signal) } : opts);
|
|
@@ -274,6 +275,7 @@ export function buildProgram(deps = {}) {
|
|
|
274
275
|
registerIamDirectory(program, { client, orgId, emit });
|
|
275
276
|
registerProducts(program, { client, emit, signal: deps.signal });
|
|
276
277
|
registerOrgs(program, { client, emit, signal: deps.signal });
|
|
278
|
+
registerConnections(program, { client, emit, signal: deps.signal });
|
|
277
279
|
// Agent enrollment sends only the grant's enrollment key (mek_...), never this CLI's login or API key.
|
|
278
280
|
(program.commands.find(c => c.name() === 'iam').commands.find(c => c.name() === 'agents')).command('enroll')
|
|
279
281
|
.description('Enroll an agent under an agent enrollment grant (POST /v1/iam/agents/enroll); the credential secret is shown once')
|
|
@@ -284,7 +286,7 @@ export function buildProgram(deps = {}) {
|
|
|
284
286
|
if (!key)
|
|
285
287
|
throw new Error('Give the enrollment key with --key-file, --key-stdin or GENESIS_ENROLLMENT_KEY; it is never accepted as an argument.');
|
|
286
288
|
const body = await readFunctionJson(o.bodyFile, 16 * 1024);
|
|
287
|
-
const result = await new Genesis({ baseUrl: g().baseUrl ?? env.GENESIS_BASE_URL, fetch: f, userAgent: 'genesis-cli/0.2.
|
|
289
|
+
const result = await new Genesis({ baseUrl: g().baseUrl ?? env.GENESIS_BASE_URL, fetch: f, userAgent: 'genesis-cli/0.2.13' }).iam.agents.enroll(key, body);
|
|
288
290
|
emit(result, () => JSON.stringify(result, null, 2));
|
|
289
291
|
});
|
|
290
292
|
// ---- auth ------------------------------------------------------------
|
|
@@ -836,7 +838,7 @@ export function buildProgram(deps = {}) {
|
|
|
836
838
|
const url = `${base.href.replace(/\/+$/, '')}/v1/status`;
|
|
837
839
|
let response;
|
|
838
840
|
try {
|
|
839
|
-
response = await f(url, { method: 'GET', headers: { accept: 'application/json', 'user-agent': 'genesis-cli/0.2.
|
|
841
|
+
response = await f(url, { method: 'GET', headers: { accept: 'application/json', 'user-agent': 'genesis-cli/0.2.13' }, signal: AbortSignal.timeout(15_000) });
|
|
840
842
|
}
|
|
841
843
|
catch (e) {
|
|
842
844
|
throw new Error(`Gateway unreachable at ${safe(url)}: ${safe(e.message)}`);
|
package/dist/products.js
CHANGED
|
@@ -180,7 +180,7 @@ export const PRODUCT_COMMANDS = {
|
|
|
180
180
|
'reporting request-logs': 'foundry_listRequestLogs', 'reporting provider-credentials': 'foundry_getProviderCredentialSummary',
|
|
181
181
|
'activity list': 'foundry_getActivity', 'activity export': 'foundry_exportActivity', 'analytics meta': 'foundry_getAnalyticsMeta', 'analytics query': 'foundry_queryAnalytics',
|
|
182
182
|
'batches create': 'foundry_createBatch', 'batches get': 'foundry_getBatch',
|
|
183
|
-
'files list': 'foundry_listFiles', 'files upload': 'foundry_uploadFile', 'files get': 'foundry_getFile', 'files
|
|
183
|
+
'files list': 'foundry_listFiles', 'files upload': 'foundry_uploadFile', 'files get': 'foundry_getFile', 'files delete': 'foundry_deleteFile',
|
|
184
184
|
'fine-tuning providers': 'foundry_listFineTuningProviders', 'fine-tuning jobs': 'foundry_listFineTuningJobs', 'fine-tuning create': 'foundry_createFineTuningJob',
|
|
185
185
|
'fine-tuning get': 'foundry_getFineTuningJob', 'fine-tuning delete': 'foundry_deleteFineTuningJob', 'fine-tuning cancel': 'foundry_cancelFineTuningJob',
|
|
186
186
|
'fine-tuning events': 'foundry_listFineTuningEvents', 'fine-tuning checkpoints': 'foundry_listFineTuningCheckpoints',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@onesi/genesis-cli",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.13",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/l1felabs/genesis-api.git",
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"dependencies": {
|
|
34
34
|
"commander": "^14.0.0",
|
|
35
35
|
"undici": "6.29.0",
|
|
36
|
-
"@onesi/genesis": "0.2.
|
|
36
|
+
"@onesi/genesis": "0.2.13"
|
|
37
37
|
},
|
|
38
38
|
"optionalDependencies": {
|
|
39
39
|
"@napi-rs/keyring": "^1.2.0"
|