@kikimoradev/kcloud 0.7.6 → 0.8.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/README.md +7 -1
- package/dist/main.js +1405 -450
- package/package.json +2 -1
package/dist/main.js
CHANGED
|
@@ -10,6 +10,9 @@ import { z } from "zod";
|
|
|
10
10
|
import { spawn } from "node:child_process";
|
|
11
11
|
import { setTimeout as setTimeout$1 } from "node:timers/promises";
|
|
12
12
|
import createClient from "openapi-fetch";
|
|
13
|
+
import { finished } from "node:stream/promises";
|
|
14
|
+
import { StdioServerTransport, serveStdio } from "@modelcontextprotocol/server/stdio";
|
|
15
|
+
import { McpServer } from "@modelcontextprotocol/server";
|
|
13
16
|
//#region ../../packages/cli-kit/src/errors.ts
|
|
14
17
|
const exitCodes = {
|
|
15
18
|
ok: 0,
|
|
@@ -2442,11 +2445,12 @@ const metricsRangeSchema = z.enum([
|
|
|
2442
2445
|
"7d",
|
|
2443
2446
|
"90d"
|
|
2444
2447
|
]);
|
|
2445
|
-
const
|
|
2448
|
+
const agentMetricsRanges = [
|
|
2446
2449
|
"1h",
|
|
2447
2450
|
"24h",
|
|
2448
2451
|
"7d"
|
|
2449
|
-
]
|
|
2452
|
+
];
|
|
2453
|
+
const agentMetricsRangeSchema = z.enum(agentMetricsRanges);
|
|
2450
2454
|
const metricsResolutionSchema = z.enum(["minute", "hour"]);
|
|
2451
2455
|
const instantSchema = z.iso.datetime();
|
|
2452
2456
|
const byteCountSchema = z.int().nonnegative();
|
|
@@ -3316,13 +3320,36 @@ const agentPlanOf = (agent) => ({
|
|
|
3316
3320
|
});
|
|
3317
3321
|
//#endregion
|
|
3318
3322
|
//#region src/commands/agents/config.ts
|
|
3319
|
-
const
|
|
3320
|
-
id,
|
|
3321
|
-
|
|
3322
|
-
|
|
3323
|
-
|
|
3324
|
-
|
|
3325
|
-
|
|
3323
|
+
const agentsOf = async (api, project) => {
|
|
3324
|
+
const projects = project === void 0 ? (await projectsOf(api)).map(({ id, slug }) => ({
|
|
3325
|
+
id,
|
|
3326
|
+
slug
|
|
3327
|
+
})) : [{
|
|
3328
|
+
id: await resolveProjectId(api, project),
|
|
3329
|
+
slug: project
|
|
3330
|
+
}];
|
|
3331
|
+
const listed = [];
|
|
3332
|
+
for (const { id, slug } of projects) {
|
|
3333
|
+
const { items } = dataOf("list the agents of the project", await api.GET("/projects/{id}/agents", { params: { path: { id } } }), agentHints);
|
|
3334
|
+
for (const agent of items) listed.push({
|
|
3335
|
+
project: slug,
|
|
3336
|
+
...agent
|
|
3337
|
+
});
|
|
3338
|
+
}
|
|
3339
|
+
return listed;
|
|
3340
|
+
};
|
|
3341
|
+
const changedAgent = async (api, agentId, document) => {
|
|
3342
|
+
const current = dataOf("describe the agent", await api.GET("/agents/{id}", { params: { path: { id: agentId } } }), agentHints);
|
|
3343
|
+
const change = typeof document === "object" && document !== null && "version" in document ? document : {
|
|
3344
|
+
...document,
|
|
3345
|
+
version: current.version
|
|
3346
|
+
};
|
|
3347
|
+
validated(updateAgentBodySchema, change, "a change to an agent");
|
|
3348
|
+
return dataOf("change the agent", await api.PATCH("/agents/{id}", {
|
|
3349
|
+
params: { path: { id: agentId } },
|
|
3350
|
+
body: change
|
|
3351
|
+
}), agentHints);
|
|
3352
|
+
};
|
|
3326
3353
|
const configCommands = (adaptersOf) => [
|
|
3327
3354
|
defineCommand({
|
|
3328
3355
|
name: "list",
|
|
@@ -3336,15 +3363,7 @@ const configCommands = (adaptersOf) => [
|
|
|
3336
3363
|
},
|
|
3337
3364
|
run: async (invocation) => {
|
|
3338
3365
|
const run = await runOf(adaptersOf, invocation);
|
|
3339
|
-
const
|
|
3340
|
-
const listed = [];
|
|
3341
|
-
for (const project of projects) {
|
|
3342
|
-
const { items } = dataOf("list the agents of the project", await run.api.GET("/projects/{id}/agents", { params: { path: { id: project.id } } }), agentHints);
|
|
3343
|
-
for (const agent of items) listed.push({
|
|
3344
|
-
project: project.slug,
|
|
3345
|
-
...agent
|
|
3346
|
-
});
|
|
3347
|
-
}
|
|
3366
|
+
const listed = await agentsOf(run.api, invocation.values.project);
|
|
3348
3367
|
return printed(run, listed, (items) => items.length === 0 ? "There is no agent here yet; kcloud agents create makes one." : renderTable([
|
|
3349
3368
|
{ header: "PROJECT" },
|
|
3350
3369
|
{ header: "AGENT" },
|
|
@@ -3409,18 +3428,7 @@ const configCommands = (adaptersOf) => [
|
|
|
3409
3428
|
const agentId = await agentFrom(run, invocation.positionals[0], invocation.values.project);
|
|
3410
3429
|
const file = invocation.values.file;
|
|
3411
3430
|
if (file === void 0) throw new UsageError("agents set needs --file <path>.", "The file holds the sections to change; kcloud agents show <agent> --json shows the current ones.");
|
|
3412
|
-
const
|
|
3413
|
-
const document = await documentOf(run, file);
|
|
3414
|
-
const change = typeof document === "object" && document !== null && "version" in document ? document : {
|
|
3415
|
-
...document,
|
|
3416
|
-
version: current.version
|
|
3417
|
-
};
|
|
3418
|
-
validated(updateAgentBodySchema, change, "a change to an agent");
|
|
3419
|
-
const body = change;
|
|
3420
|
-
const agent = dataOf("change the agent", await run.api.PATCH("/agents/{id}", {
|
|
3421
|
-
params: { path: { id: agentId } },
|
|
3422
|
-
body
|
|
3423
|
-
}), agentHints);
|
|
3431
|
+
const agent = await changedAgent(run.api, agentId, await documentOf(run, file));
|
|
3424
3432
|
return printed(run, agent, (changed) => changed.restartRequired ? `Changed ${changed.name}; it needs kcloud agents update to take the change.` : `Changed ${changed.name}.`);
|
|
3425
3433
|
}
|
|
3426
3434
|
}),
|
|
@@ -3778,16 +3786,39 @@ const pageOptions = {
|
|
|
3778
3786
|
default: false
|
|
3779
3787
|
}
|
|
3780
3788
|
};
|
|
3781
|
-
const
|
|
3789
|
+
const allUsage = ["--all", "follow the cursor to the end instead of one page"];
|
|
3790
|
+
const pageUsage = [["--limit <n>", `how many rows (default: 50)`], allUsage];
|
|
3782
3791
|
const dayOf = (date) => date.toISOString().slice(0, 10);
|
|
3792
|
+
const costWindowOf = (days, now) => ({
|
|
3793
|
+
from: dayOf(/* @__PURE__ */ new Date(now.getTime() - (days - 1) * 24 * 60 * 6e4)),
|
|
3794
|
+
to: dayOf(now)
|
|
3795
|
+
});
|
|
3783
3796
|
const windowFrom = (days, now) => {
|
|
3784
3797
|
const span = days === void 0 ? 7 : Number(days);
|
|
3785
3798
|
if (days !== void 0 && (!/^[0-9]+$/u.test(days) || span < 1 || span > 365)) throw new UsageError(`--days ${days} is not a whole number of days from 1 to 365.`);
|
|
3799
|
+
return costWindowOf(span, now);
|
|
3800
|
+
};
|
|
3801
|
+
const transcriptHints = {
|
|
3802
|
+
...agentHints,
|
|
3803
|
+
"session.not_found": "kcloud agents sessions <agent> shows the sessions and their ids.",
|
|
3804
|
+
"transcript.unreadable": "The transcript of that session cannot be read."
|
|
3805
|
+
};
|
|
3806
|
+
const transcriptOf = async (api, agentId, sessionId, all) => collect(async (cursor) => {
|
|
3807
|
+
const answer = dataOf("read the transcript", await api.GET("/agents/{id}/sessions/{sessionId}/transcript", { params: {
|
|
3808
|
+
path: {
|
|
3809
|
+
id: agentId,
|
|
3810
|
+
sessionId
|
|
3811
|
+
},
|
|
3812
|
+
...cursor === null ? {} : { query: { cursor } }
|
|
3813
|
+
} }), transcriptHints);
|
|
3786
3814
|
return {
|
|
3787
|
-
|
|
3788
|
-
|
|
3815
|
+
items: answer.entries,
|
|
3816
|
+
nextCursor: answer.nextCursor
|
|
3789
3817
|
};
|
|
3790
|
-
}
|
|
3818
|
+
}, {
|
|
3819
|
+
limit: 200,
|
|
3820
|
+
all
|
|
3821
|
+
});
|
|
3791
3822
|
const observabilityCommands = (adaptersOf) => [
|
|
3792
3823
|
defineCommand({
|
|
3793
3824
|
name: "sessions",
|
|
@@ -3848,10 +3879,10 @@ const observabilityCommands = (adaptersOf) => [
|
|
|
3848
3879
|
name: "transcript",
|
|
3849
3880
|
summary: "Print the transcript of one session of an agent.",
|
|
3850
3881
|
usage: "agents transcript <agent> <session> [--project <name>] [--all]",
|
|
3851
|
-
optionsUsage: [...agentUsage,
|
|
3882
|
+
optionsUsage: [...agentUsage, allUsage],
|
|
3852
3883
|
options: {
|
|
3853
3884
|
...agentOption,
|
|
3854
|
-
|
|
3885
|
+
all: pageOptions.all
|
|
3855
3886
|
},
|
|
3856
3887
|
positionals: {
|
|
3857
3888
|
min: 2,
|
|
@@ -3860,27 +3891,7 @@ const observabilityCommands = (adaptersOf) => [
|
|
|
3860
3891
|
run: async (invocation) => {
|
|
3861
3892
|
const run = await runOf(adaptersOf, invocation);
|
|
3862
3893
|
const agentId = await agentFrom(run, invocation.positionals[0], invocation.values.project);
|
|
3863
|
-
const
|
|
3864
|
-
const page = await collect(async (cursor, limit) => {
|
|
3865
|
-
const answer = dataOf("read the transcript", await run.api.GET("/agents/{id}/sessions/{sessionId}/transcript", { params: {
|
|
3866
|
-
path: {
|
|
3867
|
-
id: agentId,
|
|
3868
|
-
sessionId
|
|
3869
|
-
},
|
|
3870
|
-
query: {
|
|
3871
|
-
limit,
|
|
3872
|
-
...cursor === null ? {} : { cursor }
|
|
3873
|
-
}
|
|
3874
|
-
} }), {
|
|
3875
|
-
...agentHints,
|
|
3876
|
-
"session.not_found": "kcloud agents sessions <agent> shows the sessions and their ids.",
|
|
3877
|
-
"transcript.unreadable": "The transcript of that session cannot be read."
|
|
3878
|
-
});
|
|
3879
|
-
return {
|
|
3880
|
-
items: answer.entries,
|
|
3881
|
-
nextCursor: answer.nextCursor
|
|
3882
|
-
};
|
|
3883
|
-
}, windowOf(invocation.values.limit, invocation.values.all, 200));
|
|
3894
|
+
const page = await transcriptOf(run.api, agentId, invocation.positionals[1] ?? "", invocation.values.all);
|
|
3884
3895
|
return printed(run, page.items, (entries) => entries.length === 0 ? "The session left no transcript." : entries.map((entry) => JSON.stringify(entry)).join("\n"));
|
|
3885
3896
|
}
|
|
3886
3897
|
}),
|
|
@@ -4120,10 +4131,18 @@ const readBody = async (response, request, onId) => {
|
|
|
4120
4131
|
reader.cancel();
|
|
4121
4132
|
}, request.idleMs ?? 1e3);
|
|
4122
4133
|
};
|
|
4134
|
+
const stop = () => {
|
|
4135
|
+
reader.cancel().catch(() => void 0);
|
|
4136
|
+
};
|
|
4137
|
+
request.signal?.addEventListener("abort", stop, { once: true });
|
|
4138
|
+
if (request.signal?.aborted === true) stop();
|
|
4123
4139
|
armIdle();
|
|
4124
4140
|
try {
|
|
4125
4141
|
for (;;) {
|
|
4126
|
-
const chunk = await reader.read()
|
|
4142
|
+
const chunk = await reader.read().catch((error) => {
|
|
4143
|
+
if (request.signal?.aborted === true) return { done: true };
|
|
4144
|
+
throw error;
|
|
4145
|
+
});
|
|
4127
4146
|
if (chunk.done) break;
|
|
4128
4147
|
buffer += decoder.decode(chunk.value, { stream: true });
|
|
4129
4148
|
let boundary = buffer.indexOf("\n\n");
|
|
@@ -4147,6 +4166,7 @@ const readBody = async (response, request, onId) => {
|
|
|
4147
4166
|
}
|
|
4148
4167
|
} finally {
|
|
4149
4168
|
if (timer.current !== null) clearTimeout(timer.current);
|
|
4169
|
+
request.signal?.removeEventListener("abort", stop);
|
|
4150
4170
|
}
|
|
4151
4171
|
return {
|
|
4152
4172
|
closed,
|
|
@@ -4166,7 +4186,9 @@ const streamFrames = async (request) => {
|
|
|
4166
4186
|
const url = `${request.origin}/api/v1${request.path}${query.size === 0 ? "" : `?${query.toString()}`}`;
|
|
4167
4187
|
const resume = { lastEventId: null };
|
|
4168
4188
|
let backoff = 0;
|
|
4189
|
+
const aborted = () => request.signal?.aborted === true;
|
|
4169
4190
|
for (;;) {
|
|
4191
|
+
if (aborted()) return;
|
|
4170
4192
|
let response;
|
|
4171
4193
|
try {
|
|
4172
4194
|
response = await fetching(url, {
|
|
@@ -4175,9 +4197,11 @@ const streamFrames = async (request) => {
|
|
|
4175
4197
|
authorization: `Bearer ${request.token}`,
|
|
4176
4198
|
...request.resumable === true && resume.lastEventId !== null ? { "last-event-id": resume.lastEventId } : {}
|
|
4177
4199
|
},
|
|
4178
|
-
redirect: "error"
|
|
4200
|
+
redirect: "error",
|
|
4201
|
+
...request.signal === void 0 ? {} : { signal: request.signal }
|
|
4179
4202
|
});
|
|
4180
4203
|
} catch (error) {
|
|
4204
|
+
if (aborted()) return;
|
|
4181
4205
|
throw unreachable(request.origin, error);
|
|
4182
4206
|
}
|
|
4183
4207
|
if (!response.ok) {
|
|
@@ -4194,7 +4218,7 @@ const streamFrames = async (request) => {
|
|
|
4194
4218
|
resume.lastEventId = id;
|
|
4195
4219
|
});
|
|
4196
4220
|
if (closed !== null && fatalReasons.has(closed)) throw new CliError(`The instance closed the stream: ${closed}.`, closed === "session.expired" ? "The token is no longer valid; run kcloud login again." : "You are no longer a member of that project.");
|
|
4197
|
-
if (!request.follow) return;
|
|
4221
|
+
if (!request.follow || aborted()) return;
|
|
4198
4222
|
request.onNotice?.(closed === null ? "The stream ended; opening it again." : `The instance closed the stream: ${closed}. Opening it again.`);
|
|
4199
4223
|
backoff = nextBackoff(backoff);
|
|
4200
4224
|
await request.wait(jittered(backoff));
|
|
@@ -4288,8 +4312,7 @@ const streamCommands = (adaptersOf) => [{
|
|
|
4288
4312
|
},
|
|
4289
4313
|
onFrame: (frame) => {
|
|
4290
4314
|
if (frame.event === "agent.state") return;
|
|
4291
|
-
|
|
4292
|
-
else context.terminal.line(lineOf(frame));
|
|
4315
|
+
context.terminal.line(settings.json ? JSON.stringify(frame.data) : lineOf(frame));
|
|
4293
4316
|
}
|
|
4294
4317
|
});
|
|
4295
4318
|
return exitCodes.ok;
|
|
@@ -4505,7 +4528,7 @@ const secretUsage = [
|
|
|
4505
4528
|
//#region src/commands/credentials.ts
|
|
4506
4529
|
const projectOption$2 = { project: { type: "string" } };
|
|
4507
4530
|
const projectUsage$2 = [["--project <name>", "the project the credential belongs to, by slug, name or id"]];
|
|
4508
|
-
const
|
|
4531
|
+
const credentialHints = {
|
|
4509
4532
|
"project.not_found": "kcloud projects list shows the projects you are a member of.",
|
|
4510
4533
|
"credential.not_found": "kcloud credentials list --project <name> shows them.",
|
|
4511
4534
|
"credential.label_taken": "Another credential of the project already has that label.",
|
|
@@ -4515,7 +4538,7 @@ const projectFrom$2 = async (run, given) => {
|
|
|
4515
4538
|
if (given === void 0) throw new UsageError("This command needs a project.", "Pass --project <name>; kcloud projects list shows them.");
|
|
4516
4539
|
return resolveProjectId(run.api, given);
|
|
4517
4540
|
};
|
|
4518
|
-
const credentialsOf = async (api, projectId) => dataOf("list the credentials of the project", await api.GET("/projects/{id}/credentials", { params: { path: { id: projectId } } }),
|
|
4541
|
+
const credentialsOf = async (api, projectId) => dataOf("list the credentials of the project", await api.GET("/projects/{id}/credentials", { params: { path: { id: projectId } } }), credentialHints).items;
|
|
4519
4542
|
const resolveCredentialId = async (api, projectId, reference) => {
|
|
4520
4543
|
const credentials = await credentialsOf(api, projectId);
|
|
4521
4544
|
const found = credentials.filter((credential) => credential.id === reference || credential.label === reference);
|
|
@@ -4601,7 +4624,7 @@ const createCredentialsCommand = (adaptersOf) => defineGroup({
|
|
|
4601
4624
|
label,
|
|
4602
4625
|
secret
|
|
4603
4626
|
}
|
|
4604
|
-
}),
|
|
4627
|
+
}), credentialHints);
|
|
4605
4628
|
return printed(run, credential, (stored) => `Stored ${stored.label} (${stored.fingerprint}) as ${stored.id}.`);
|
|
4606
4629
|
}
|
|
4607
4630
|
}),
|
|
@@ -4631,7 +4654,7 @@ const createCredentialsCommand = (adaptersOf) => defineGroup({
|
|
|
4631
4654
|
credentialId
|
|
4632
4655
|
} },
|
|
4633
4656
|
body: { label }
|
|
4634
|
-
}),
|
|
4657
|
+
}), credentialHints);
|
|
4635
4658
|
return printed(run, credential, (renamed) => `The credential is ${renamed.label} now.`);
|
|
4636
4659
|
}
|
|
4637
4660
|
}),
|
|
@@ -4676,7 +4699,7 @@ const createCredentialsCommand = (adaptersOf) => defineGroup({
|
|
|
4676
4699
|
secret,
|
|
4677
4700
|
restartAgents: values["restart-agents"]
|
|
4678
4701
|
}
|
|
4679
|
-
}),
|
|
4702
|
+
}), credentialHints);
|
|
4680
4703
|
return printed(run, credential, (rotated) => `Rotated ${rotated.label} to ${rotated.fingerprint}${values["restart-agents"] ? "; its agents restart" : ""}.`);
|
|
4681
4704
|
}
|
|
4682
4705
|
}),
|
|
@@ -4697,7 +4720,7 @@ const createCredentialsCommand = (adaptersOf) => defineGroup({
|
|
|
4697
4720
|
doneWith("verify the credential", await run.api.POST("/projects/{id}/credentials/{credentialId}/verify", { params: { path: {
|
|
4698
4721
|
id: projectId,
|
|
4699
4722
|
credentialId
|
|
4700
|
-
} } }),
|
|
4723
|
+
} } }), credentialHints);
|
|
4701
4724
|
return said(run, `The instance is verifying ${credentialId}; kcloud credentials list shows the answer.`);
|
|
4702
4725
|
}
|
|
4703
4726
|
}),
|
|
@@ -4724,14 +4747,430 @@ const createCredentialsCommand = (adaptersOf) => defineGroup({
|
|
|
4724
4747
|
doneWith("delete the credential", await run.api.DELETE("/projects/{id}/credentials/{credentialId}", { params: { path: {
|
|
4725
4748
|
id: projectId,
|
|
4726
4749
|
credentialId
|
|
4727
|
-
} } }),
|
|
4750
|
+
} } }), credentialHints);
|
|
4728
4751
|
return said(run, `Deleted the credential ${reference}.`);
|
|
4729
4752
|
}
|
|
4730
4753
|
})
|
|
4731
4754
|
]
|
|
4732
4755
|
});
|
|
4733
4756
|
//#endregion
|
|
4757
|
+
//#region src/mcp/tool.ts
|
|
4758
|
+
const defineTool = (definition) => ({
|
|
4759
|
+
...definition,
|
|
4760
|
+
idempotent: definition.idempotent ?? definition.safety === "read",
|
|
4761
|
+
run: (input, run) => definition.run(definition.input.parse(input), run)
|
|
4762
|
+
});
|
|
4763
|
+
const annotationsOf = (tool) => ({
|
|
4764
|
+
title: tool.title,
|
|
4765
|
+
readOnlyHint: tool.safety === "read",
|
|
4766
|
+
destructiveHint: tool.safety === "destructive",
|
|
4767
|
+
idempotentHint: tool.idempotent,
|
|
4768
|
+
openWorldHint: false
|
|
4769
|
+
});
|
|
4770
|
+
const textOf = (text, isError) => ({
|
|
4771
|
+
content: [{
|
|
4772
|
+
type: "text",
|
|
4773
|
+
text
|
|
4774
|
+
}],
|
|
4775
|
+
...isError ? { isError } : {}
|
|
4776
|
+
});
|
|
4777
|
+
const apiErrorOf = (error) => {
|
|
4778
|
+
if (error instanceof ApiError) return error;
|
|
4779
|
+
return error instanceof Error ? apiErrorOf(error.cause) : null;
|
|
4780
|
+
};
|
|
4781
|
+
const refusalOf = (error) => {
|
|
4782
|
+
const details = apiErrorOf(error)?.details ?? {};
|
|
4783
|
+
return [
|
|
4784
|
+
error.message,
|
|
4785
|
+
...error.hint === null ? [] : [error.hint],
|
|
4786
|
+
...Object.keys(details).length === 0 ? [] : [`Details: ${JSON.stringify(details)}`]
|
|
4787
|
+
].join("\n");
|
|
4788
|
+
};
|
|
4789
|
+
const resultOf = async (answer) => {
|
|
4790
|
+
try {
|
|
4791
|
+
return textOf(JSON.stringify(await answer()), false);
|
|
4792
|
+
} catch (error) {
|
|
4793
|
+
if (error instanceof z.ZodError) return textOf(z.prettifyError(error), true);
|
|
4794
|
+
if (error instanceof CliError) return textOf(refusalOf(error), true);
|
|
4795
|
+
throw error;
|
|
4796
|
+
}
|
|
4797
|
+
};
|
|
4798
|
+
//#endregion
|
|
4799
|
+
//#region src/commands/tokens.ts
|
|
4800
|
+
const lifetimeOf = (given) => {
|
|
4801
|
+
if (given === void 0) return null;
|
|
4802
|
+
const days = Number(given);
|
|
4803
|
+
if (!/^[0-9]+$/u.test(given) || days < 1 || days > 365) throw new UsageError(`--expires-in ${given} is not a whole number of days from 1 to 365.`);
|
|
4804
|
+
return days;
|
|
4805
|
+
};
|
|
4806
|
+
const tokenHints = { "token.not_found": "kcloud tokens list shows the tokens and their ids." };
|
|
4807
|
+
const createTokensCommand = (adaptersOf) => defineGroup({
|
|
4808
|
+
name: "tokens",
|
|
4809
|
+
summary: "The access tokens of your account on this instance.",
|
|
4810
|
+
usage: "tokens <command>",
|
|
4811
|
+
subcommands: [
|
|
4812
|
+
defineCommand({
|
|
4813
|
+
name: "list",
|
|
4814
|
+
summary: "List the access tokens of your account.",
|
|
4815
|
+
usage: "tokens list",
|
|
4816
|
+
options: {},
|
|
4817
|
+
positionals: {
|
|
4818
|
+
min: 0,
|
|
4819
|
+
max: 0
|
|
4820
|
+
},
|
|
4821
|
+
run: async ({ settings, context }) => {
|
|
4822
|
+
const adapters = adaptersOf(context);
|
|
4823
|
+
const session = await openSession({
|
|
4824
|
+
settings,
|
|
4825
|
+
context,
|
|
4826
|
+
api: adapters.api
|
|
4827
|
+
});
|
|
4828
|
+
const { items } = dataOf("list the access tokens", await session.api.GET("/auth/me/tokens"));
|
|
4829
|
+
if (settings.json) {
|
|
4830
|
+
context.terminal.json(items);
|
|
4831
|
+
return exitCodes.ok;
|
|
4832
|
+
}
|
|
4833
|
+
context.terminal.line(items.length === 0 ? "Your account holds no access token on this instance." : renderTable([
|
|
4834
|
+
{ header: "ID" },
|
|
4835
|
+
{ header: "NAME" },
|
|
4836
|
+
{ header: "LAST USED" },
|
|
4837
|
+
{ header: "EXPIRES" }
|
|
4838
|
+
], items.map((token) => [
|
|
4839
|
+
token.id,
|
|
4840
|
+
token.name,
|
|
4841
|
+
token.lastUsedAt ?? "never",
|
|
4842
|
+
token.expiresAt ?? "never"
|
|
4843
|
+
])));
|
|
4844
|
+
return exitCodes.ok;
|
|
4845
|
+
}
|
|
4846
|
+
}),
|
|
4847
|
+
defineCommand({
|
|
4848
|
+
name: "create",
|
|
4849
|
+
summary: "Create an access token and print its secret once.",
|
|
4850
|
+
usage: "tokens create <name> [--expires-in <days>]",
|
|
4851
|
+
optionsUsage: [["--expires-in <days>", `how long the token lives, from 1 to 365 days (default: forever)`]],
|
|
4852
|
+
options: { "expires-in": { type: "string" } },
|
|
4853
|
+
positionals: {
|
|
4854
|
+
min: 1,
|
|
4855
|
+
max: 1
|
|
4856
|
+
},
|
|
4857
|
+
run: async ({ values, positionals, settings, context }) => {
|
|
4858
|
+
const adapters = adaptersOf(context);
|
|
4859
|
+
const expiresInDays = lifetimeOf(values["expires-in"]);
|
|
4860
|
+
const session = await openSession({
|
|
4861
|
+
settings,
|
|
4862
|
+
context,
|
|
4863
|
+
api: adapters.api
|
|
4864
|
+
});
|
|
4865
|
+
const created = dataOf("create an access token", await session.api.POST("/auth/me/tokens", { body: {
|
|
4866
|
+
name: positionals[0] ?? "",
|
|
4867
|
+
expiresInDays
|
|
4868
|
+
} }));
|
|
4869
|
+
if (settings.json) {
|
|
4870
|
+
context.terminal.json(created);
|
|
4871
|
+
return exitCodes.ok;
|
|
4872
|
+
}
|
|
4873
|
+
context.terminal.line(created.secret);
|
|
4874
|
+
context.terminal.error(`The token ${created.accessToken.name} is shown only here; the instance keeps its hash.`);
|
|
4875
|
+
return exitCodes.ok;
|
|
4876
|
+
}
|
|
4877
|
+
}),
|
|
4878
|
+
defineCommand({
|
|
4879
|
+
name: "revoke",
|
|
4880
|
+
summary: "Revoke one access token of your account.",
|
|
4881
|
+
usage: "tokens revoke <id>",
|
|
4882
|
+
options: {},
|
|
4883
|
+
positionals: {
|
|
4884
|
+
min: 1,
|
|
4885
|
+
max: 1
|
|
4886
|
+
},
|
|
4887
|
+
run: async ({ positionals, settings, context }) => {
|
|
4888
|
+
const adapters = adaptersOf(context);
|
|
4889
|
+
const id = positionals[0] ?? "";
|
|
4890
|
+
const session = await openSession({
|
|
4891
|
+
settings,
|
|
4892
|
+
context,
|
|
4893
|
+
api: adapters.api
|
|
4894
|
+
});
|
|
4895
|
+
await confirmed({
|
|
4896
|
+
prompt: adapters.prompter(settings),
|
|
4897
|
+
settings,
|
|
4898
|
+
question: `Revoke the token ${id}? Everything using it stops working.`
|
|
4899
|
+
});
|
|
4900
|
+
doneWith(`revoke the token ${id}`, await session.api.DELETE("/auth/me/tokens/{id}", { params: { path: { id } } }), tokenHints);
|
|
4901
|
+
context.terminal.line(`Revoked the token ${id}.`);
|
|
4902
|
+
return exitCodes.ok;
|
|
4903
|
+
}
|
|
4904
|
+
})
|
|
4905
|
+
]
|
|
4906
|
+
});
|
|
4907
|
+
//#endregion
|
|
4908
|
+
//#region src/mcp/tools/account.ts
|
|
4909
|
+
const accountTools = [
|
|
4910
|
+
defineTool({
|
|
4911
|
+
name: "whoami",
|
|
4912
|
+
title: "Who am I",
|
|
4913
|
+
description: "Say which instance and which account this server acts as, and whether the version of kcloud matches the version of the instance.",
|
|
4914
|
+
operations: ["getMe", "getPlatformVersion"],
|
|
4915
|
+
safety: "read",
|
|
4916
|
+
input: z.object({}),
|
|
4917
|
+
run: async (_, run) => {
|
|
4918
|
+
const me = dataOf("say who the token belongs to", await run.api.GET("/auth/me"));
|
|
4919
|
+
const platform = dataOf("say which version it runs", await run.api.GET("/version"));
|
|
4920
|
+
return {
|
|
4921
|
+
origin: run.origin,
|
|
4922
|
+
profile: run.profileName,
|
|
4923
|
+
user: me,
|
|
4924
|
+
kcloud: run.kcloudVersion,
|
|
4925
|
+
instance: platform.version,
|
|
4926
|
+
matched: platform.version === run.kcloudVersion
|
|
4927
|
+
};
|
|
4928
|
+
}
|
|
4929
|
+
}),
|
|
4930
|
+
defineTool({
|
|
4931
|
+
name: "tokens_list",
|
|
4932
|
+
title: "List access tokens",
|
|
4933
|
+
description: "List the access tokens of your account, without their secrets.",
|
|
4934
|
+
operations: ["listAccessTokens"],
|
|
4935
|
+
safety: "read",
|
|
4936
|
+
input: z.object({}),
|
|
4937
|
+
run: async (_, run) => dataOf("list the access tokens", await run.api.GET("/auth/me/tokens")).items
|
|
4938
|
+
}),
|
|
4939
|
+
defineTool({
|
|
4940
|
+
name: "token_revoke",
|
|
4941
|
+
title: "Revoke an access token",
|
|
4942
|
+
description: "Revoke one access token of your account. Everything that uses it stops working, this server too when it is the token of this server.",
|
|
4943
|
+
operations: ["revokeAccessToken"],
|
|
4944
|
+
safety: "destructive",
|
|
4945
|
+
input: z.object({ id: idSchema.describe("The id of the token, from tokens_list.") }),
|
|
4946
|
+
run: async ({ id }, run) => {
|
|
4947
|
+
doneWith(`revoke the token ${id}`, await run.api.DELETE("/auth/me/tokens/{id}", { params: { path: { id } } }), tokenHints);
|
|
4948
|
+
return { revoked: id };
|
|
4949
|
+
}
|
|
4950
|
+
}),
|
|
4951
|
+
defineTool({
|
|
4952
|
+
name: "locale_set",
|
|
4953
|
+
title: "Set the language of the panel",
|
|
4954
|
+
description: "Set the language the panel and its messages use for your account.",
|
|
4955
|
+
operations: ["changeOwnLocale"],
|
|
4956
|
+
safety: "write",
|
|
4957
|
+
idempotent: true,
|
|
4958
|
+
input: z.object({ locale: localeSchema }),
|
|
4959
|
+
run: async ({ locale }, run) => dataOf("set the language", await run.api.PATCH("/auth/me/locale", { body: { locale } }))
|
|
4960
|
+
})
|
|
4961
|
+
];
|
|
4962
|
+
//#endregion
|
|
4963
|
+
//#region src/mcp/fields.ts
|
|
4964
|
+
const projectField = z.string().trim().min(1).describe("The project, by its slug, its name or its id.");
|
|
4965
|
+
const agentFields = {
|
|
4966
|
+
agent: z.string().trim().min(1).describe("The agent, as <project>/<agent>, as its slug or name together with project, or by its id."),
|
|
4967
|
+
project: z.string().trim().min(1).optional().describe("The project of the agent, when agent names the agent alone.")
|
|
4968
|
+
};
|
|
4969
|
+
const pageFields = {
|
|
4970
|
+
limit: z.int().min(1).max(200).optional().describe(`How many items one page holds, up to ${String(200)}.`),
|
|
4971
|
+
all: z.boolean().default(false).describe("Follow the cursor to the end instead of returning one page.")
|
|
4972
|
+
};
|
|
4973
|
+
const windowOfPage = ({ limit, all }, fallback = 50) => ({
|
|
4974
|
+
limit: limit ?? fallback,
|
|
4975
|
+
all
|
|
4976
|
+
});
|
|
4977
|
+
const projectIdOf = (run, project) => resolveProjectId(run.api, project);
|
|
4978
|
+
const agentIdOf = (run, { agent, project }) => resolveAgentId(run.api, agent, project);
|
|
4979
|
+
//#endregion
|
|
4980
|
+
//#region src/mcp/tools/agents.ts
|
|
4981
|
+
const actionOn = async (run, agentId, action, force) => dataOf(`${action} the agent`, await run.api.POST("/agents/{id}/actions/{action}", {
|
|
4982
|
+
params: { path: {
|
|
4983
|
+
id: agentId,
|
|
4984
|
+
action
|
|
4985
|
+
} },
|
|
4986
|
+
body: force ? { force: true } : {}
|
|
4987
|
+
}), agentHints);
|
|
4988
|
+
const agentTools = [
|
|
4989
|
+
defineTool({
|
|
4990
|
+
name: "agents_list",
|
|
4991
|
+
title: "List agents",
|
|
4992
|
+
description: "List the agents of one project, or of every project you can see.",
|
|
4993
|
+
operations: ["listAgents"],
|
|
4994
|
+
safety: "read",
|
|
4995
|
+
input: z.object({ project: projectField.optional().describe("One project instead of all of them.") }),
|
|
4996
|
+
run: async ({ project }, run) => agentsOf(run.api, project)
|
|
4997
|
+
}),
|
|
4998
|
+
defineTool({
|
|
4999
|
+
name: "agent_show",
|
|
5000
|
+
title: "Show an agent",
|
|
5001
|
+
description: "Show one agent with its state, runtime, repositories and the actions it takes now. asPlan returns what agent_create takes instead, to copy an agent.",
|
|
5002
|
+
operations: ["getAgent"],
|
|
5003
|
+
safety: "read",
|
|
5004
|
+
input: z.object({
|
|
5005
|
+
...agentFields,
|
|
5006
|
+
asPlan: z.boolean().default(false)
|
|
5007
|
+
}),
|
|
5008
|
+
run: async ({ asPlan, ...reference }, run) => {
|
|
5009
|
+
const agent = dataOf("describe the agent", await run.api.GET("/agents/{id}", { params: { path: { id: await agentIdOf(run, reference) } } }), agentHints);
|
|
5010
|
+
return asPlan ? agentPlanOf(agent) : agent;
|
|
5011
|
+
}
|
|
5012
|
+
}),
|
|
5013
|
+
defineTool({
|
|
5014
|
+
name: "agent_create",
|
|
5015
|
+
title: "Create an agent",
|
|
5016
|
+
description: "Create an agent in a project. The credentials, repositories and tools are named by the ids that credentials_list, repositories_list and tools_list return; agent_show with asPlan shows a complete example. The agent stays stopped until agent_action starts it.",
|
|
5017
|
+
operations: ["createAgent"],
|
|
5018
|
+
safety: "write",
|
|
5019
|
+
input: z.object({
|
|
5020
|
+
project: projectField,
|
|
5021
|
+
agent: createAgentBodySchema
|
|
5022
|
+
}),
|
|
5023
|
+
run: async ({ project, agent }, run) => dataOf("create the agent", await run.api.POST("/projects/{id}/agents", {
|
|
5024
|
+
params: { path: { id: await projectIdOf(run, project) } },
|
|
5025
|
+
body: agent
|
|
5026
|
+
}), agentHints)
|
|
5027
|
+
}),
|
|
5028
|
+
defineTool({
|
|
5029
|
+
name: "agent_update",
|
|
5030
|
+
title: "Change an agent",
|
|
5031
|
+
description: "Change sections of an agent. Without version the change applies to the version read just before it. When restartRequired comes back true, agent_action update applies the change.",
|
|
5032
|
+
operations: ["updateAgent"],
|
|
5033
|
+
safety: "write",
|
|
5034
|
+
input: z.object({
|
|
5035
|
+
...agentFields,
|
|
5036
|
+
version: z.int().nonnegative().optional().describe("The version the change is based on, to refuse a change made meanwhile."),
|
|
5037
|
+
sections: updateAgentSectionsSchema
|
|
5038
|
+
}),
|
|
5039
|
+
run: async ({ version, sections, ...reference }, run) => changedAgent(run.api, await agentIdOf(run, reference), {
|
|
5040
|
+
sections,
|
|
5041
|
+
...version === void 0 ? {} : { version }
|
|
5042
|
+
})
|
|
5043
|
+
}),
|
|
5044
|
+
defineTool({
|
|
5045
|
+
name: "agent_delete",
|
|
5046
|
+
title: "Delete an agent",
|
|
5047
|
+
description: "Delete an agent with its container, its workspace and its history, after its current session, or at once with now.",
|
|
5048
|
+
operations: ["deleteAgent"],
|
|
5049
|
+
safety: "destructive",
|
|
5050
|
+
input: z.object({
|
|
5051
|
+
...agentFields,
|
|
5052
|
+
now: z.boolean().default(false)
|
|
5053
|
+
}),
|
|
5054
|
+
run: async ({ now, ...reference }, run) => {
|
|
5055
|
+
const agentId = await agentIdOf(run, reference);
|
|
5056
|
+
doneWith("delete the agent", await run.api.DELETE("/agents/{id}", { params: {
|
|
5057
|
+
path: { id: agentId },
|
|
5058
|
+
...now ? { query: { force: "true" } } : {}
|
|
5059
|
+
} }), agentHints);
|
|
5060
|
+
return {
|
|
5061
|
+
deleted: agentId,
|
|
5062
|
+
now
|
|
5063
|
+
};
|
|
5064
|
+
}
|
|
5065
|
+
}),
|
|
5066
|
+
defineTool({
|
|
5067
|
+
name: "agent_action",
|
|
5068
|
+
title: "Start, pause, resume, restart or update an agent",
|
|
5069
|
+
description: "start lets the agent work, pause holds it between sessions, resume lets a paused agent work again, restart restarts its container, update replaces the container to apply pending changes. The agent returns with the actions it takes next.",
|
|
5070
|
+
operations: ["runAgentAction"],
|
|
5071
|
+
safety: "write",
|
|
5072
|
+
input: z.object({
|
|
5073
|
+
...agentFields,
|
|
5074
|
+
action: z.enum([
|
|
5075
|
+
"start",
|
|
5076
|
+
"pause",
|
|
5077
|
+
"resume",
|
|
5078
|
+
"restart",
|
|
5079
|
+
"update"
|
|
5080
|
+
])
|
|
5081
|
+
}),
|
|
5082
|
+
run: async ({ action, ...reference }, run) => actionOn(run, await agentIdOf(run, reference), action, false)
|
|
5083
|
+
}),
|
|
5084
|
+
defineTool({
|
|
5085
|
+
name: "agent_interrupt",
|
|
5086
|
+
title: "Stop, reset or cancel an agent",
|
|
5087
|
+
description: "stop stops the agent after its current session, or at once with now; reset throws its workspace away and provisions it again; cancel cancels what it is doing.",
|
|
5088
|
+
operations: ["runAgentAction"],
|
|
5089
|
+
safety: "destructive",
|
|
5090
|
+
input: z.object({
|
|
5091
|
+
...agentFields,
|
|
5092
|
+
action: z.enum([
|
|
5093
|
+
"stop",
|
|
5094
|
+
"reset",
|
|
5095
|
+
"cancel"
|
|
5096
|
+
]),
|
|
5097
|
+
now: z.boolean().default(false).describe("For stop: stop the container at once.")
|
|
5098
|
+
}),
|
|
5099
|
+
run: async ({ action, now, ...reference }, run) => actionOn(run, await agentIdOf(run, reference), action, action === "stop" && now)
|
|
5100
|
+
})
|
|
5101
|
+
];
|
|
5102
|
+
//#endregion
|
|
5103
|
+
//#region src/mcp/tools/credentials.ts
|
|
5104
|
+
const credentialField$1 = z.string().trim().min(1).describe("The credential, by its label or its id.");
|
|
5105
|
+
const credentialTools = [
|
|
5106
|
+
defineTool({
|
|
5107
|
+
name: "credentials_list",
|
|
5108
|
+
title: "List credentials",
|
|
5109
|
+
description: "List the Claude and GitHub credentials of a project, with their fingerprints and verification, never their secrets.",
|
|
5110
|
+
operations: ["listCredentials"],
|
|
5111
|
+
safety: "read",
|
|
5112
|
+
input: z.object({ project: projectField }),
|
|
5113
|
+
run: async ({ project }, run) => credentialsOf(run.api, await projectIdOf(run, project))
|
|
5114
|
+
}),
|
|
5115
|
+
defineTool({
|
|
5116
|
+
name: "credential_update",
|
|
5117
|
+
title: "Rename or verify a credential",
|
|
5118
|
+
description: "rename gives a credential a new label; verify asks the instance to check it against its service, and credentials_list shows the answer.",
|
|
5119
|
+
operations: ["renameCredential", "verifyCredential"],
|
|
5120
|
+
safety: "write",
|
|
5121
|
+
input: z.object({
|
|
5122
|
+
project: projectField,
|
|
5123
|
+
credential: credentialField$1,
|
|
5124
|
+
action: z.enum(["rename", "verify"]),
|
|
5125
|
+
label: z.string().trim().min(1).optional().describe("The new label, for rename.")
|
|
5126
|
+
}),
|
|
5127
|
+
run: async ({ project, credential, action, label }, run) => {
|
|
5128
|
+
const projectId = await projectIdOf(run, project);
|
|
5129
|
+
const credentialId = await resolveCredentialId(run.api, projectId, credential);
|
|
5130
|
+
if (action === "verify") {
|
|
5131
|
+
doneWith("verify the credential", await run.api.POST("/projects/{id}/credentials/{credentialId}/verify", { params: { path: {
|
|
5132
|
+
id: projectId,
|
|
5133
|
+
credentialId
|
|
5134
|
+
} } }), credentialHints);
|
|
5135
|
+
return {
|
|
5136
|
+
id: credentialId,
|
|
5137
|
+
verifying: true
|
|
5138
|
+
};
|
|
5139
|
+
}
|
|
5140
|
+
if (label === void 0) throw new UsageError("rename needs a label.");
|
|
5141
|
+
return dataOf("rename the credential", await run.api.PATCH("/projects/{id}/credentials/{credentialId}", {
|
|
5142
|
+
params: { path: {
|
|
5143
|
+
id: projectId,
|
|
5144
|
+
credentialId
|
|
5145
|
+
} },
|
|
5146
|
+
body: { label }
|
|
5147
|
+
}), credentialHints);
|
|
5148
|
+
}
|
|
5149
|
+
}),
|
|
5150
|
+
defineTool({
|
|
5151
|
+
name: "credential_delete",
|
|
5152
|
+
title: "Delete a credential",
|
|
5153
|
+
description: "Delete a credential of a project that no agent and no repository uses.",
|
|
5154
|
+
operations: ["deleteCredential"],
|
|
5155
|
+
safety: "destructive",
|
|
5156
|
+
input: z.object({
|
|
5157
|
+
project: projectField,
|
|
5158
|
+
credential: credentialField$1
|
|
5159
|
+
}),
|
|
5160
|
+
run: async ({ project, credential }, run) => {
|
|
5161
|
+
const projectId = await projectIdOf(run, project);
|
|
5162
|
+
const credentialId = await resolveCredentialId(run.api, projectId, credential);
|
|
5163
|
+
doneWith("delete the credential", await run.api.DELETE("/projects/{id}/credentials/{credentialId}", { params: { path: {
|
|
5164
|
+
id: projectId,
|
|
5165
|
+
credentialId
|
|
5166
|
+
} } }), credentialHints);
|
|
5167
|
+
return { deleted: credentialId };
|
|
5168
|
+
}
|
|
5169
|
+
})
|
|
5170
|
+
];
|
|
5171
|
+
//#endregion
|
|
4734
5172
|
//#region src/commands/notifications.ts
|
|
5173
|
+
const notificationHints = { "notification.not_found": "kcloud notifications list shows them." };
|
|
4735
5174
|
const createNotificationsCommand = (adaptersOf) => defineGroup({
|
|
4736
5175
|
name: "notifications",
|
|
4737
5176
|
summary: "What the instance wants you to look at.",
|
|
@@ -4795,7 +5234,7 @@ const createNotificationsCommand = (adaptersOf) => defineGroup({
|
|
|
4795
5234
|
run: async (invocation) => {
|
|
4796
5235
|
const run = await runOf(adaptersOf, invocation);
|
|
4797
5236
|
const id = invocation.positionals[0] ?? "";
|
|
4798
|
-
const counted = dataOf("mark the notification as read", await run.api.POST("/notifications/{id}/read", { params: { path: { id } } }),
|
|
5237
|
+
const counted = dataOf("mark the notification as read", await run.api.POST("/notifications/{id}/read", { params: { path: { id } } }), notificationHints);
|
|
4799
5238
|
return printed(run, counted, (shown) => `Read it; ${shown.unreadCount} are left unread.`);
|
|
4800
5239
|
}
|
|
4801
5240
|
}),
|
|
@@ -4846,8 +5285,7 @@ const createNotificationsCommand = (adaptersOf) => defineGroup({
|
|
|
4846
5285
|
},
|
|
4847
5286
|
onFrame: (frame) => {
|
|
4848
5287
|
if (frame.event !== "notification") return;
|
|
4849
|
-
|
|
4850
|
-
else context.terminal.line(JSON.stringify(frame.data));
|
|
5288
|
+
context.terminal.line(JSON.stringify(frame.data));
|
|
4851
5289
|
}
|
|
4852
5290
|
});
|
|
4853
5291
|
return exitCodes.ok;
|
|
@@ -4855,97 +5293,522 @@ const createNotificationsCommand = (adaptersOf) => defineGroup({
|
|
|
4855
5293
|
})
|
|
4856
5294
|
]
|
|
4857
5295
|
});
|
|
5296
|
+
const notificationTools = [
|
|
5297
|
+
defineTool({
|
|
5298
|
+
name: "notifications_list",
|
|
5299
|
+
title: "List notifications",
|
|
5300
|
+
description: "List your notifications, newest first.",
|
|
5301
|
+
operations: ["listNotifications"],
|
|
5302
|
+
safety: "read",
|
|
5303
|
+
input: z.object({
|
|
5304
|
+
...pageFields,
|
|
5305
|
+
unread: z.boolean().default(false)
|
|
5306
|
+
}),
|
|
5307
|
+
run: async ({ unread, limit, all }, run) => collect(async (cursor, size) => dataOf("list the notifications", await run.api.GET("/notifications", { params: { query: {
|
|
5308
|
+
limit: size,
|
|
5309
|
+
...cursor === null ? {} : { cursor },
|
|
5310
|
+
...unread ? { unread: "true" } : {}
|
|
5311
|
+
} } })), windowOfPage({
|
|
5312
|
+
limit,
|
|
5313
|
+
all
|
|
5314
|
+
}))
|
|
5315
|
+
}),
|
|
5316
|
+
defineTool({
|
|
5317
|
+
name: "notifications_mark_read",
|
|
5318
|
+
title: "Mark notifications as read",
|
|
5319
|
+
description: "Mark one notification as read with id, or every notification with all.",
|
|
5320
|
+
operations: ["markNotificationRead", "markAllNotificationsRead"],
|
|
5321
|
+
safety: "write",
|
|
5322
|
+
idempotent: true,
|
|
5323
|
+
input: z.object({
|
|
5324
|
+
id: idSchema.optional().describe("The notification, from notifications_list."),
|
|
5325
|
+
all: z.boolean().default(false)
|
|
5326
|
+
}),
|
|
5327
|
+
run: async ({ id, all }, run) => {
|
|
5328
|
+
if (all === (id !== void 0)) throw new UsageError("Pass either id or all.");
|
|
5329
|
+
return id === void 0 ? dataOf("mark every notification as read", await run.api.POST("/notifications/read-all")) : dataOf("mark the notification as read", await run.api.POST("/notifications/{id}/read", { params: { path: { id } } }), notificationHints);
|
|
5330
|
+
}
|
|
5331
|
+
}),
|
|
5332
|
+
defineTool({
|
|
5333
|
+
name: "notifications_wait",
|
|
5334
|
+
title: "Wait for notifications",
|
|
5335
|
+
description: "Wait until the instance raises a notification, or until the seconds run out, and return what it raised meanwhile. notifications_list shows the older ones.",
|
|
5336
|
+
operations: ["streamNotifications"],
|
|
5337
|
+
safety: "read",
|
|
5338
|
+
input: z.object({ seconds: z.int().min(1).max(50).default(30) }),
|
|
5339
|
+
run: async ({ seconds }, run) => {
|
|
5340
|
+
const raised = [];
|
|
5341
|
+
const settled = new AbortController();
|
|
5342
|
+
const settling = { timer: null };
|
|
5343
|
+
await streamFrames({
|
|
5344
|
+
origin: run.origin,
|
|
5345
|
+
token: await run.token(),
|
|
5346
|
+
path: "/notifications/stream",
|
|
5347
|
+
follow: true,
|
|
5348
|
+
action: "wait for notifications",
|
|
5349
|
+
wait: run.wait,
|
|
5350
|
+
signal: AbortSignal.any([AbortSignal.timeout(seconds * 1e3), settled.signal]),
|
|
5351
|
+
onFrame: (frame) => {
|
|
5352
|
+
if (frame.event !== "notification") return;
|
|
5353
|
+
settling.timer ??= setTimeout(() => {
|
|
5354
|
+
settled.abort();
|
|
5355
|
+
}, defaultIdleMs);
|
|
5356
|
+
raised.push(frame.data);
|
|
5357
|
+
}
|
|
5358
|
+
});
|
|
5359
|
+
if (settling.timer !== null) clearTimeout(settling.timer);
|
|
5360
|
+
return raised;
|
|
5361
|
+
}
|
|
5362
|
+
})
|
|
5363
|
+
];
|
|
4858
5364
|
//#endregion
|
|
4859
|
-
//#region src/
|
|
4860
|
-
const
|
|
4861
|
-
|
|
4862
|
-
|
|
4863
|
-
|
|
4864
|
-
|
|
4865
|
-
|
|
4866
|
-
|
|
4867
|
-
|
|
4868
|
-
|
|
5365
|
+
//#region src/mcp/tools/observability.ts
|
|
5366
|
+
const snapshotMs = 1e4;
|
|
5367
|
+
const sources = {
|
|
5368
|
+
live: {
|
|
5369
|
+
path: "/agents/{id}/live",
|
|
5370
|
+
resumable: false
|
|
5371
|
+
},
|
|
5372
|
+
stored: {
|
|
5373
|
+
path: "/agents/{id}/events",
|
|
5374
|
+
resumable: true
|
|
4869
5375
|
}
|
|
4870
5376
|
};
|
|
4871
|
-
const
|
|
4872
|
-
|
|
4873
|
-
|
|
4874
|
-
|
|
4875
|
-
|
|
4876
|
-
|
|
4877
|
-
|
|
4878
|
-
|
|
4879
|
-
|
|
4880
|
-
|
|
4881
|
-
|
|
4882
|
-
|
|
4883
|
-
|
|
4884
|
-
|
|
4885
|
-
|
|
4886
|
-
|
|
4887
|
-
|
|
4888
|
-
|
|
4889
|
-
|
|
4890
|
-
|
|
4891
|
-
|
|
4892
|
-
|
|
4893
|
-
|
|
4894
|
-
|
|
4895
|
-
|
|
4896
|
-
|
|
4897
|
-
|
|
4898
|
-
|
|
4899
|
-
|
|
4900
|
-
|
|
4901
|
-
|
|
4902
|
-
|
|
4903
|
-
|
|
4904
|
-
|
|
5377
|
+
const observabilityTools = [
|
|
5378
|
+
defineTool({
|
|
5379
|
+
name: "agent_sessions",
|
|
5380
|
+
title: "List the sessions of an agent",
|
|
5381
|
+
description: "List the Claude sessions of an agent, newest first, with their role, model, outcome and cost.",
|
|
5382
|
+
operations: ["listAgentSessions"],
|
|
5383
|
+
safety: "read",
|
|
5384
|
+
input: z.object({
|
|
5385
|
+
...agentFields,
|
|
5386
|
+
...pageFields,
|
|
5387
|
+
role: z.enum(agentRoles).optional(),
|
|
5388
|
+
task: z.string().trim().min(1).optional().describe("Only the sessions of this task id.")
|
|
5389
|
+
}),
|
|
5390
|
+
run: async ({ role, task, limit, all, ...reference }, run) => {
|
|
5391
|
+
const agentId = await agentIdOf(run, reference);
|
|
5392
|
+
return collect(async (cursor, size) => dataOf("list the sessions of the agent", await run.api.GET("/agents/{id}/sessions", { params: {
|
|
5393
|
+
path: { id: agentId },
|
|
5394
|
+
query: {
|
|
5395
|
+
limit: size,
|
|
5396
|
+
...cursor === null ? {} : { cursor },
|
|
5397
|
+
...role === void 0 ? {} : { role },
|
|
5398
|
+
...task === void 0 ? {} : { taskId: task }
|
|
5399
|
+
}
|
|
5400
|
+
} }), agentHints), windowOfPage({
|
|
5401
|
+
limit,
|
|
5402
|
+
all
|
|
5403
|
+
}));
|
|
5404
|
+
}
|
|
5405
|
+
}),
|
|
5406
|
+
defineTool({
|
|
5407
|
+
name: "agent_transcript",
|
|
5408
|
+
title: "Read a session transcript",
|
|
5409
|
+
description: "Read the transcript of one session of an agent, one page of entries as the instance cuts it, or every entry with all.",
|
|
5410
|
+
operations: ["getAgentSessionTranscript"],
|
|
5411
|
+
safety: "read",
|
|
5412
|
+
input: z.object({
|
|
5413
|
+
...agentFields,
|
|
5414
|
+
session: z.string().trim().min(1).describe("The session id, from agent_sessions."),
|
|
5415
|
+
all: pageFields.all
|
|
5416
|
+
}),
|
|
5417
|
+
run: async ({ session, all, ...reference }, run) => transcriptOf(run.api, await agentIdOf(run, reference), session, all)
|
|
5418
|
+
}),
|
|
5419
|
+
defineTool({
|
|
5420
|
+
name: "agent_costs",
|
|
5421
|
+
title: "Show what an agent spent",
|
|
5422
|
+
description: "Show what an agent spent over the last days, by day with totals, or by task with byTask.",
|
|
5423
|
+
operations: ["getAgentCosts", "listAgentTaskCosts"],
|
|
5424
|
+
safety: "read",
|
|
5425
|
+
input: z.object({
|
|
5426
|
+
...agentFields,
|
|
5427
|
+
...pageFields,
|
|
5428
|
+
days: z.int().min(1).max(365).default(7),
|
|
5429
|
+
byTask: z.boolean().default(false)
|
|
5430
|
+
}),
|
|
5431
|
+
run: async ({ days, byTask, limit, all, ...reference }, run) => {
|
|
5432
|
+
const agentId = await agentIdOf(run, reference);
|
|
5433
|
+
const { from, to } = costWindowOf(days, run.now());
|
|
5434
|
+
if (!byTask) return dataOf("show what the agent spent", await run.api.GET("/agents/{id}/costs", { params: {
|
|
5435
|
+
path: { id: agentId },
|
|
5436
|
+
query: {
|
|
5437
|
+
from,
|
|
5438
|
+
to
|
|
5439
|
+
}
|
|
5440
|
+
} }), agentHints);
|
|
5441
|
+
return collect(async (cursor, size) => dataOf("list what the tasks cost", await run.api.GET("/agents/{id}/costs/tasks", { params: {
|
|
5442
|
+
path: { id: agentId },
|
|
5443
|
+
query: {
|
|
5444
|
+
from,
|
|
5445
|
+
to,
|
|
5446
|
+
limit: size,
|
|
5447
|
+
...cursor === null ? {} : { cursor }
|
|
5448
|
+
}
|
|
5449
|
+
} }), agentHints), windowOfPage({
|
|
5450
|
+
limit,
|
|
5451
|
+
all
|
|
5452
|
+
}));
|
|
5453
|
+
}
|
|
5454
|
+
}),
|
|
5455
|
+
defineTool({
|
|
5456
|
+
name: "agent_metrics",
|
|
5457
|
+
title: "Show what the container of an agent used",
|
|
5458
|
+
description: "Show the CPU and memory the container of an agent used over a range.",
|
|
5459
|
+
operations: ["getAgentMetrics"],
|
|
5460
|
+
safety: "read",
|
|
5461
|
+
input: z.object({
|
|
5462
|
+
...agentFields,
|
|
5463
|
+
range: z.enum(agentMetricsRanges).default("1h")
|
|
5464
|
+
}),
|
|
5465
|
+
run: async ({ range, ...reference }, run) => dataOf("show what the agent used", await run.api.GET("/agents/{id}/metrics", { params: {
|
|
5466
|
+
path: { id: await agentIdOf(run, reference) },
|
|
5467
|
+
query: { range }
|
|
5468
|
+
} }), agentHints)
|
|
5469
|
+
}),
|
|
5470
|
+
defineTool({
|
|
5471
|
+
name: "agent_memory_search",
|
|
5472
|
+
title: "Search the memory of an agent",
|
|
5473
|
+
description: "Search what an agent remembers of the tasks it finished.",
|
|
5474
|
+
operations: ["searchAgentMemory"],
|
|
5475
|
+
safety: "read",
|
|
5476
|
+
input: z.object({
|
|
5477
|
+
...agentFields,
|
|
5478
|
+
query: z.string().max(200).optional(),
|
|
5479
|
+
limit: z.int().min(1).max(100).default(20)
|
|
5480
|
+
}),
|
|
5481
|
+
run: async ({ query, limit, ...reference }, run) => dataOf("search what the agent remembers", await run.api.GET("/agents/{id}/memory", { params: {
|
|
5482
|
+
path: { id: await agentIdOf(run, reference) },
|
|
5483
|
+
query: {
|
|
5484
|
+
limit,
|
|
5485
|
+
...query === void 0 ? {} : { q: query }
|
|
5486
|
+
}
|
|
5487
|
+
} }), agentHints)
|
|
5488
|
+
}),
|
|
5489
|
+
defineTool({
|
|
5490
|
+
name: "agent_events",
|
|
5491
|
+
title: "Read the events of an agent",
|
|
5492
|
+
description: "Read what an agent wrote: live is what it is writing now, stored is its stored history. The answer holds the events up to now and lastEventId; pass that as since to read only newer events next time.",
|
|
5493
|
+
operations: ["streamAgentLive", "streamAgentEvents"],
|
|
5494
|
+
safety: "read",
|
|
5495
|
+
input: z.object({
|
|
5496
|
+
...agentFields,
|
|
5497
|
+
source: z.enum(["live", "stored"]).default("stored"),
|
|
5498
|
+
since: z.string().trim().min(1).max(64).optional().describe("Start after this event id."),
|
|
5499
|
+
limit: z.int().min(1).max(500).optional().describe("How many events to replay.")
|
|
5500
|
+
}),
|
|
5501
|
+
run: async ({ source, since, limit, ...reference }, run) => {
|
|
5502
|
+
const agentId = await agentIdOf(run, reference);
|
|
5503
|
+
const { path, resumable } = sources[source];
|
|
5504
|
+
const events = [];
|
|
5505
|
+
let lastEventId = since ?? null;
|
|
5506
|
+
await streamFrames({
|
|
5507
|
+
origin: run.origin,
|
|
5508
|
+
token: await run.token(),
|
|
5509
|
+
path: path.replace("{id}", encodeURIComponent(agentId)),
|
|
5510
|
+
query: {
|
|
5511
|
+
...since === void 0 ? {} : { since },
|
|
5512
|
+
...limit === void 0 ? {} : { limit: String(limit) }
|
|
5513
|
+
},
|
|
5514
|
+
follow: false,
|
|
5515
|
+
resumable,
|
|
5516
|
+
idleMs: defaultIdleMs,
|
|
5517
|
+
action: `read the ${source} events of the agent`,
|
|
5518
|
+
wait: run.wait,
|
|
5519
|
+
signal: AbortSignal.timeout(snapshotMs),
|
|
5520
|
+
onFrame: (frame) => {
|
|
5521
|
+
if (frame.id !== null) lastEventId = frame.id;
|
|
5522
|
+
if (frame.event !== "agent.state") events.push(frame.data);
|
|
5523
|
+
}
|
|
5524
|
+
});
|
|
5525
|
+
return {
|
|
5526
|
+
events,
|
|
5527
|
+
lastEventId
|
|
5528
|
+
};
|
|
5529
|
+
}
|
|
5530
|
+
})
|
|
5531
|
+
];
|
|
5532
|
+
//#endregion
|
|
5533
|
+
//#region src/commands/tools.ts
|
|
5534
|
+
const projectOption$1 = { project: { type: "string" } };
|
|
5535
|
+
const projectUsage$1 = [["--project <name>", "the project the tool belongs to, by slug, name or id"]];
|
|
5536
|
+
const toolHints = {
|
|
5537
|
+
"project.not_found": "kcloud projects list shows the projects you are a member of.",
|
|
5538
|
+
"tool.not_found": "kcloud tools list --project <name> shows them.",
|
|
5539
|
+
"tool.name_taken": "Another tool of the project has that name.",
|
|
5540
|
+
"tool.slug_taken": "Another tool of the project has that slug.",
|
|
5541
|
+
"tool.in_use": "Take it off its agents first.",
|
|
5542
|
+
"tool.incomplete": "Every secret of the tool needs a value.",
|
|
5543
|
+
"concurrency.conflict": "Someone changed the tool meanwhile; read it again and retry."
|
|
5544
|
+
};
|
|
5545
|
+
const projectFrom$1 = async (run, given) => {
|
|
5546
|
+
if (given === void 0) throw new UsageError("This command needs a project.", "Pass --project <name>; kcloud projects list shows them.");
|
|
5547
|
+
return resolveProjectId(run.api, given);
|
|
5548
|
+
};
|
|
5549
|
+
const toolsOf = async (api, projectId) => dataOf("list the tools of the project", await api.GET("/projects/{id}/tools", { params: { path: { id: projectId } } }), toolHints).items;
|
|
5550
|
+
const resolveTool = async (api, projectId, reference) => {
|
|
5551
|
+
const tools = await toolsOf(api, projectId);
|
|
5552
|
+
const found = tools.filter((tool) => tool.id === reference || tool.slug === reference || tool.name === reference);
|
|
5553
|
+
const [first] = found;
|
|
5554
|
+
if (first === void 0) throw new CliError(`The project has no tool ${reference}.`, tools.length === 0 ? "kcloud tools create --file <path> makes one." : `Its tools are ${tools.map(({ slug }) => slug).join(", ")}.`);
|
|
5555
|
+
if (found.length > 1) throw new CliError(`${reference} names ${found.length} tools of the project.`, "Name it by its slug or its id instead.");
|
|
5556
|
+
return first;
|
|
5557
|
+
};
|
|
5558
|
+
const changedTool = async (api, projectId, tool, document) => {
|
|
5559
|
+
const change = typeof document === "object" && document !== null && "version" in document ? document : {
|
|
5560
|
+
...document,
|
|
5561
|
+
version: tool.version
|
|
5562
|
+
};
|
|
5563
|
+
validated(updateToolBodySchema, change, "a change to a tool");
|
|
5564
|
+
return dataOf("change the tool", await api.PATCH("/projects/{id}/tools/{toolId}", {
|
|
5565
|
+
params: { path: {
|
|
5566
|
+
id: projectId,
|
|
5567
|
+
toolId: tool.id
|
|
5568
|
+
} },
|
|
5569
|
+
body: change
|
|
5570
|
+
}), toolHints);
|
|
5571
|
+
};
|
|
5572
|
+
const createToolsCommand = (adaptersOf) => defineGroup({
|
|
5573
|
+
name: "tools",
|
|
5574
|
+
summary: "The HTTP APIs and MCP servers a project offers its agents.",
|
|
5575
|
+
usage: "tools <command>",
|
|
5576
|
+
subcommands: [
|
|
5577
|
+
defineCommand({
|
|
5578
|
+
name: "list",
|
|
5579
|
+
summary: "List the tools of a project.",
|
|
5580
|
+
usage: "tools list --project <name>",
|
|
5581
|
+
optionsUsage: projectUsage$1,
|
|
5582
|
+
options: projectOption$1,
|
|
5583
|
+
positionals: {
|
|
5584
|
+
min: 0,
|
|
5585
|
+
max: 0
|
|
5586
|
+
},
|
|
5587
|
+
run: async (invocation) => {
|
|
5588
|
+
const run = await runOf(adaptersOf, invocation);
|
|
5589
|
+
const projectId = await projectFrom$1(run, invocation.values.project);
|
|
5590
|
+
const items = await toolsOf(run.api, projectId);
|
|
5591
|
+
return printed(run, items, (tools) => tools.length === 0 ? "The project offers no tool yet." : renderTable([
|
|
5592
|
+
{ header: "SLUG" },
|
|
5593
|
+
{ header: "NAME" },
|
|
5594
|
+
{ header: "KIND" },
|
|
5595
|
+
{ header: "SECRETS" },
|
|
5596
|
+
{
|
|
5597
|
+
header: "AGENTS",
|
|
5598
|
+
align: "right"
|
|
5599
|
+
},
|
|
5600
|
+
{ header: "ID" }
|
|
5601
|
+
], tools.map((tool) => [
|
|
5602
|
+
tool.slug,
|
|
5603
|
+
tool.name,
|
|
5604
|
+
tool.kind,
|
|
5605
|
+
tool.complete ? "complete" : "incomplete",
|
|
5606
|
+
String(tool.agentsUsing),
|
|
5607
|
+
tool.id
|
|
5608
|
+
])));
|
|
4905
5609
|
}
|
|
4906
5610
|
}),
|
|
4907
5611
|
defineCommand({
|
|
4908
|
-
name: "
|
|
4909
|
-
summary: "
|
|
4910
|
-
usage: "
|
|
4911
|
-
|
|
5612
|
+
name: "show",
|
|
5613
|
+
summary: "Show one tool with its configuration.",
|
|
5614
|
+
usage: "tools show <tool> --project <name>",
|
|
5615
|
+
optionsUsage: projectUsage$1,
|
|
5616
|
+
options: projectOption$1,
|
|
4912
5617
|
positionals: {
|
|
4913
5618
|
min: 1,
|
|
4914
5619
|
max: 1
|
|
4915
5620
|
},
|
|
4916
|
-
run: async (
|
|
4917
|
-
const
|
|
4918
|
-
const
|
|
4919
|
-
|
|
4920
|
-
|
|
4921
|
-
|
|
4922
|
-
|
|
4923
|
-
|
|
4924
|
-
|
|
4925
|
-
|
|
5621
|
+
run: async (invocation) => {
|
|
5622
|
+
const run = await runOf(adaptersOf, invocation);
|
|
5623
|
+
const projectId = await projectFrom$1(run, invocation.values.project);
|
|
5624
|
+
const tool = await resolveTool(run.api, projectId, invocation.positionals[0] ?? "");
|
|
5625
|
+
return printed(run, tool, (shown) => [
|
|
5626
|
+
renderTable([{ header: "FIELD" }, { header: "VALUE" }], [
|
|
5627
|
+
["name", shown.name],
|
|
5628
|
+
["slug", shown.slug],
|
|
5629
|
+
["kind", shown.kind],
|
|
5630
|
+
["version", String(shown.version)],
|
|
5631
|
+
["complete", shown.complete ? "yes" : "no"],
|
|
5632
|
+
["agents", String(shown.agentsUsing)],
|
|
5633
|
+
["id", shown.id]
|
|
5634
|
+
]),
|
|
5635
|
+
"",
|
|
5636
|
+
JSON.stringify(shown.config, null, 2)
|
|
5637
|
+
].join("\n"));
|
|
4926
5638
|
}
|
|
4927
5639
|
}),
|
|
4928
5640
|
defineCommand({
|
|
4929
|
-
name: "
|
|
4930
|
-
summary: "
|
|
4931
|
-
usage: "
|
|
4932
|
-
|
|
5641
|
+
name: "create",
|
|
5642
|
+
summary: "Create a tool from a JSON description of it.",
|
|
5643
|
+
usage: "tools create --project <name> --file <path>",
|
|
5644
|
+
optionsUsage: [...projectUsage$1, ...fileUsage("the tool")],
|
|
5645
|
+
options: {
|
|
5646
|
+
...projectOption$1,
|
|
5647
|
+
...fileOptions
|
|
5648
|
+
},
|
|
5649
|
+
positionals: {
|
|
5650
|
+
min: 0,
|
|
5651
|
+
max: 0
|
|
5652
|
+
},
|
|
5653
|
+
run: async (invocation) => {
|
|
5654
|
+
const run = await runOf(adaptersOf, invocation);
|
|
5655
|
+
const projectId = await projectFrom$1(run, invocation.values.project);
|
|
5656
|
+
const file = invocation.values.file;
|
|
5657
|
+
if (file === void 0) throw new UsageError("tools create needs --file <path>.", "kcloud tools show <tool> --json prints the shape of a tool.");
|
|
5658
|
+
const document = await documentOf(run, file);
|
|
5659
|
+
validated(createToolBodySchema, document, "a tool");
|
|
5660
|
+
const body = document;
|
|
5661
|
+
const tool = dataOf("create the tool", await run.api.POST("/projects/{id}/tools", {
|
|
5662
|
+
params: { path: { id: projectId } },
|
|
5663
|
+
body
|
|
5664
|
+
}), toolHints);
|
|
5665
|
+
return printed(run, tool, (created) => `Created ${created.name} (${created.slug}) as ${created.id}.`);
|
|
5666
|
+
}
|
|
5667
|
+
}),
|
|
5668
|
+
defineCommand({
|
|
5669
|
+
name: "update",
|
|
5670
|
+
summary: "Change a tool from a JSON description of the change.",
|
|
5671
|
+
usage: "tools update <tool> --project <name> --file <path>",
|
|
5672
|
+
optionsUsage: [...projectUsage$1, ...fileUsage("the change")],
|
|
5673
|
+
options: {
|
|
5674
|
+
...projectOption$1,
|
|
5675
|
+
...fileOptions
|
|
5676
|
+
},
|
|
4933
5677
|
positionals: {
|
|
4934
5678
|
min: 1,
|
|
4935
5679
|
max: 1
|
|
4936
5680
|
},
|
|
4937
|
-
run: async (
|
|
4938
|
-
const
|
|
4939
|
-
const
|
|
4940
|
-
|
|
4941
|
-
|
|
4942
|
-
|
|
4943
|
-
|
|
5681
|
+
run: async (invocation) => {
|
|
5682
|
+
const run = await runOf(adaptersOf, invocation);
|
|
5683
|
+
const projectId = await projectFrom$1(run, invocation.values.project);
|
|
5684
|
+
const tool = await resolveTool(run.api, projectId, invocation.positionals[0] ?? "");
|
|
5685
|
+
const file = invocation.values.file;
|
|
5686
|
+
if (file === void 0) throw new UsageError("tools update needs --file <path>.", "The file holds the sections to change, and the version it is based on.");
|
|
5687
|
+
const updated = await changedTool(run.api, projectId, tool, await documentOf(run, file));
|
|
5688
|
+
return printed(run, updated, (shown) => `Changed ${shown.name}.`);
|
|
5689
|
+
}
|
|
5690
|
+
}),
|
|
5691
|
+
defineCommand({
|
|
5692
|
+
name: "delete",
|
|
5693
|
+
summary: "Delete a tool no agent uses.",
|
|
5694
|
+
usage: "tools delete <tool> --project <name>",
|
|
5695
|
+
optionsUsage: projectUsage$1,
|
|
5696
|
+
options: projectOption$1,
|
|
5697
|
+
positionals: {
|
|
5698
|
+
min: 1,
|
|
5699
|
+
max: 1
|
|
5700
|
+
},
|
|
5701
|
+
run: async (invocation) => {
|
|
5702
|
+
const run = await runOf(adaptersOf, invocation);
|
|
5703
|
+
const projectId = await projectFrom$1(run, invocation.values.project);
|
|
5704
|
+
const reference = invocation.positionals[0] ?? "";
|
|
5705
|
+
const tool = await resolveTool(run.api, projectId, reference);
|
|
5706
|
+
await confirmed({
|
|
5707
|
+
prompt: run.prompt,
|
|
5708
|
+
settings: run.settings,
|
|
5709
|
+
question: `Delete the tool ${tool.name}?`
|
|
5710
|
+
});
|
|
5711
|
+
doneWith("delete the tool", await run.api.DELETE("/projects/{id}/tools/{toolId}", { params: { path: {
|
|
5712
|
+
id: projectId,
|
|
5713
|
+
toolId: tool.id
|
|
5714
|
+
} } }), toolHints);
|
|
5715
|
+
return said(run, `Deleted the tool ${tool.name}.`);
|
|
4944
5716
|
}
|
|
4945
5717
|
})
|
|
4946
5718
|
]
|
|
4947
5719
|
});
|
|
4948
5720
|
//#endregion
|
|
5721
|
+
//#region src/mcp/tools/project-tools.ts
|
|
5722
|
+
const toolField = z.string().trim().min(1).describe("The tool, by its slug, its name or its id.");
|
|
5723
|
+
const withoutSecrets = "Secrets never pass through this server: a tool made here holds none and stays incomplete until someone sets them in the panel or with kcloud tools update --file.";
|
|
5724
|
+
const projectToolTools = [
|
|
5725
|
+
defineTool({
|
|
5726
|
+
name: "tools_list",
|
|
5727
|
+
title: "List tools",
|
|
5728
|
+
description: "List the HTTP APIs and MCP servers a project can give its agents, or show one of them with tool.",
|
|
5729
|
+
operations: ["listTools"],
|
|
5730
|
+
safety: "read",
|
|
5731
|
+
input: z.object({
|
|
5732
|
+
project: projectField,
|
|
5733
|
+
tool: toolField.optional()
|
|
5734
|
+
}),
|
|
5735
|
+
run: async ({ project, tool }, run) => {
|
|
5736
|
+
const projectId = await projectIdOf(run, project);
|
|
5737
|
+
return tool === void 0 ? toolsOf(run.api, projectId) : resolveTool(run.api, projectId, tool);
|
|
5738
|
+
}
|
|
5739
|
+
}),
|
|
5740
|
+
defineTool({
|
|
5741
|
+
name: "tool_create",
|
|
5742
|
+
title: "Create a tool",
|
|
5743
|
+
description: `Create an HTTP API or an MCP server tool in a project. ${withoutSecrets}`,
|
|
5744
|
+
operations: ["createTool"],
|
|
5745
|
+
safety: "write",
|
|
5746
|
+
input: z.object({
|
|
5747
|
+
project: projectField,
|
|
5748
|
+
tool: z.strictObject({
|
|
5749
|
+
kind: toolKindSchema,
|
|
5750
|
+
name: toolNameSchema,
|
|
5751
|
+
slug: toolSlugSchema.optional(),
|
|
5752
|
+
description: toolDescriptionSchema,
|
|
5753
|
+
config: toolConfigSchema
|
|
5754
|
+
})
|
|
5755
|
+
}),
|
|
5756
|
+
run: async ({ project, tool }, run) => {
|
|
5757
|
+
const projectId = await projectIdOf(run, project);
|
|
5758
|
+
const body = {
|
|
5759
|
+
...tool,
|
|
5760
|
+
secrets: {}
|
|
5761
|
+
};
|
|
5762
|
+
validated(createToolBodySchema, body, "a tool");
|
|
5763
|
+
return dataOf("create the tool", await run.api.POST("/projects/{id}/tools", {
|
|
5764
|
+
params: { path: { id: projectId } },
|
|
5765
|
+
body
|
|
5766
|
+
}), toolHints);
|
|
5767
|
+
}
|
|
5768
|
+
}),
|
|
5769
|
+
defineTool({
|
|
5770
|
+
name: "tool_update",
|
|
5771
|
+
title: "Change a tool",
|
|
5772
|
+
description: `Change sections of a tool. Without version the change applies to the version read just before it. ${withoutSecrets}`,
|
|
5773
|
+
operations: ["updateTool"],
|
|
5774
|
+
safety: "write",
|
|
5775
|
+
input: z.object({
|
|
5776
|
+
project: projectField,
|
|
5777
|
+
tool: toolField,
|
|
5778
|
+
version: z.int().nonnegative().optional().describe("The version the change is based on, to refuse a change made meanwhile."),
|
|
5779
|
+
sections: updateToolSectionsSchema.omit({ secrets: true }).strict()
|
|
5780
|
+
}),
|
|
5781
|
+
run: async ({ project, tool, version, sections }, run) => {
|
|
5782
|
+
const projectId = await projectIdOf(run, project);
|
|
5783
|
+
const current = await resolveTool(run.api, projectId, tool);
|
|
5784
|
+
return changedTool(run.api, projectId, current, {
|
|
5785
|
+
sections,
|
|
5786
|
+
...version === void 0 ? {} : { version }
|
|
5787
|
+
});
|
|
5788
|
+
}
|
|
5789
|
+
}),
|
|
5790
|
+
defineTool({
|
|
5791
|
+
name: "tool_delete",
|
|
5792
|
+
title: "Delete a tool",
|
|
5793
|
+
description: "Delete a tool no agent uses.",
|
|
5794
|
+
operations: ["deleteTool"],
|
|
5795
|
+
safety: "destructive",
|
|
5796
|
+
input: z.object({
|
|
5797
|
+
project: projectField,
|
|
5798
|
+
tool: toolField
|
|
5799
|
+
}),
|
|
5800
|
+
run: async ({ project, tool }, run) => {
|
|
5801
|
+
const projectId = await projectIdOf(run, project);
|
|
5802
|
+
const found = await resolveTool(run.api, projectId, tool);
|
|
5803
|
+
doneWith("delete the tool", await run.api.DELETE("/projects/{id}/tools/{toolId}", { params: { path: {
|
|
5804
|
+
id: projectId,
|
|
5805
|
+
toolId: found.id
|
|
5806
|
+
} } }), toolHints);
|
|
5807
|
+
return { deleted: found.id };
|
|
5808
|
+
}
|
|
5809
|
+
})
|
|
5810
|
+
];
|
|
5811
|
+
//#endregion
|
|
4949
5812
|
//#region src/commands/projects.ts
|
|
4950
5813
|
const projectHint = { "project.not_found": "kcloud projects list shows the projects you are a member of." };
|
|
4951
5814
|
const createProjectsCommand = (adaptersOf) => defineGroup({
|
|
@@ -5036,42 +5899,48 @@ const createProjectsCommand = (adaptersOf) => defineGroup({
|
|
|
5036
5899
|
]
|
|
5037
5900
|
});
|
|
5038
5901
|
//#endregion
|
|
5039
|
-
//#region src/
|
|
5040
|
-
const
|
|
5041
|
-
name: "
|
|
5042
|
-
|
|
5043
|
-
|
|
5044
|
-
|
|
5045
|
-
|
|
5046
|
-
|
|
5047
|
-
|
|
5048
|
-
|
|
5049
|
-
|
|
5050
|
-
|
|
5051
|
-
|
|
5052
|
-
|
|
5053
|
-
|
|
5054
|
-
|
|
5055
|
-
|
|
5056
|
-
|
|
5057
|
-
|
|
5058
|
-
|
|
5059
|
-
|
|
5060
|
-
|
|
5061
|
-
|
|
5062
|
-
|
|
5063
|
-
|
|
5064
|
-
|
|
5065
|
-
|
|
5066
|
-
|
|
5067
|
-
|
|
5068
|
-
|
|
5069
|
-
|
|
5902
|
+
//#region src/mcp/tools/projects.ts
|
|
5903
|
+
const projectTools = [defineTool({
|
|
5904
|
+
name: "projects_list",
|
|
5905
|
+
title: "List projects",
|
|
5906
|
+
description: "List the projects you are a member of.",
|
|
5907
|
+
operations: ["listProjects"],
|
|
5908
|
+
safety: "read",
|
|
5909
|
+
input: z.object({}),
|
|
5910
|
+
run: async (_, run) => dataOf("list the projects", await run.api.GET("/projects")).items
|
|
5911
|
+
}), defineTool({
|
|
5912
|
+
name: "project_show",
|
|
5913
|
+
title: "Show a project",
|
|
5914
|
+
description: "Show one project together with its members.",
|
|
5915
|
+
operations: ["getProject", "listProjectMembers"],
|
|
5916
|
+
safety: "read",
|
|
5917
|
+
input: z.object({ project: projectField }),
|
|
5918
|
+
run: async ({ project }, run) => {
|
|
5919
|
+
const id = await projectIdOf(run, project);
|
|
5920
|
+
const shown = dataOf("describe the project", await run.api.GET("/projects/{id}", { params: { path: { id } } }), projectHint);
|
|
5921
|
+
const { items } = dataOf("list the members of the project", await run.api.GET("/projects/{id}/members", { params: { path: { id } } }), projectHint);
|
|
5922
|
+
return {
|
|
5923
|
+
...shown,
|
|
5924
|
+
members: items
|
|
5925
|
+
};
|
|
5926
|
+
}
|
|
5927
|
+
})];
|
|
5928
|
+
//#endregion
|
|
5929
|
+
//#region src/mcp/tools/releases.ts
|
|
5930
|
+
const releaseTools = [defineTool({
|
|
5931
|
+
name: "releases_list",
|
|
5932
|
+
title: "List releases",
|
|
5933
|
+
description: "List the Kikimora releases an agent can run, with the Claude Code and Playwright MCP versions each one ships.",
|
|
5934
|
+
operations: ["listReleases"],
|
|
5935
|
+
safety: "read",
|
|
5936
|
+
input: z.object({}),
|
|
5937
|
+
run: async (_, run) => dataOf("list the releases", await run.api.GET("/releases")).items
|
|
5938
|
+
})];
|
|
5070
5939
|
//#endregion
|
|
5071
5940
|
//#region src/commands/repositories.ts
|
|
5072
|
-
const projectOption
|
|
5073
|
-
const projectUsage
|
|
5074
|
-
const
|
|
5941
|
+
const projectOption = { project: { type: "string" } };
|
|
5942
|
+
const projectUsage = [["--project <name>", "the project the repository belongs to, by slug, name or id"]];
|
|
5943
|
+
const repositoryHints = {
|
|
5075
5944
|
"project.not_found": "kcloud projects list shows the projects you are a member of.",
|
|
5076
5945
|
"repository.not_found": "kcloud repositories list --project <name> shows them.",
|
|
5077
5946
|
"repository.already_registered": "The project already has that repository.",
|
|
@@ -5081,11 +5950,11 @@ const hints$1 = {
|
|
|
5081
5950
|
"credential.rejected": "GitHub refused that credential; kcloud credentials rotate <credential> --project <name> replaces it.",
|
|
5082
5951
|
"credential.not_found": "kcloud credentials list --project <name> shows them."
|
|
5083
5952
|
};
|
|
5084
|
-
const projectFrom
|
|
5953
|
+
const projectFrom = async (run, given) => {
|
|
5085
5954
|
if (given === void 0) throw new UsageError("This command needs a project.", "Pass --project <name>; kcloud projects list shows them.");
|
|
5086
5955
|
return resolveProjectId(run.api, given);
|
|
5087
5956
|
};
|
|
5088
|
-
const repositoriesOf = async (api, projectId) => dataOf("list the repositories of the project", await api.GET("/projects/{id}/repositories", { params: { path: { id: projectId } } }),
|
|
5957
|
+
const repositoriesOf = async (api, projectId) => dataOf("list the repositories of the project", await api.GET("/projects/{id}/repositories", { params: { path: { id: projectId } } }), repositoryHints).items;
|
|
5089
5958
|
const resolveRepositoryId = async (api, projectId, reference) => {
|
|
5090
5959
|
const repositories = await repositoriesOf(api, projectId);
|
|
5091
5960
|
const found = repositories.filter((repository) => repository.id === reference || repository.name === reference || `${repository.owner}/${repository.name}` === reference);
|
|
@@ -5108,15 +5977,15 @@ const createRepositoriesCommand = (adaptersOf) => defineGroup({
|
|
|
5108
5977
|
name: "list",
|
|
5109
5978
|
summary: "List the repositories registered in a project.",
|
|
5110
5979
|
usage: "repositories list --project <name>",
|
|
5111
|
-
optionsUsage: projectUsage
|
|
5112
|
-
options: projectOption
|
|
5980
|
+
optionsUsage: projectUsage,
|
|
5981
|
+
options: projectOption,
|
|
5113
5982
|
positionals: {
|
|
5114
5983
|
min: 0,
|
|
5115
5984
|
max: 0
|
|
5116
5985
|
},
|
|
5117
5986
|
run: async (invocation) => {
|
|
5118
5987
|
const run = await runOf(adaptersOf, invocation);
|
|
5119
|
-
const projectId = await projectFrom
|
|
5988
|
+
const projectId = await projectFrom(run, invocation.values.project);
|
|
5120
5989
|
const items = await repositoriesOf(run.api, projectId);
|
|
5121
5990
|
return printed(run, items, (repositories) => repositories.length === 0 ? "The project has no repository yet." : renderTable([
|
|
5122
5991
|
{ header: "REPOSITORY" },
|
|
@@ -5139,9 +6008,9 @@ const createRepositoriesCommand = (adaptersOf) => defineGroup({
|
|
|
5139
6008
|
name: "available",
|
|
5140
6009
|
summary: "List the GitHub repositories one credential can see.",
|
|
5141
6010
|
usage: "repositories available --project <name> --credential <label>",
|
|
5142
|
-
optionsUsage: [...projectUsage
|
|
6011
|
+
optionsUsage: [...projectUsage, ["--credential <label>", "the github_pat credential to look through"]],
|
|
5143
6012
|
options: {
|
|
5144
|
-
...projectOption
|
|
6013
|
+
...projectOption,
|
|
5145
6014
|
credential: { type: "string" }
|
|
5146
6015
|
},
|
|
5147
6016
|
positionals: {
|
|
@@ -5150,14 +6019,14 @@ const createRepositoriesCommand = (adaptersOf) => defineGroup({
|
|
|
5150
6019
|
},
|
|
5151
6020
|
run: async (invocation) => {
|
|
5152
6021
|
const run = await runOf(adaptersOf, invocation);
|
|
5153
|
-
const projectId = await projectFrom
|
|
6022
|
+
const projectId = await projectFrom(run, invocation.values.project);
|
|
5154
6023
|
const credential = invocation.values.credential;
|
|
5155
6024
|
if (credential === void 0) throw new UsageError("repositories available needs --credential <label>.", "kcloud credentials list --project <name> shows them.");
|
|
5156
6025
|
const credentialId = await resolveCredentialId(run.api, projectId, credential);
|
|
5157
6026
|
const listed = dataOf("list the repositories of the credential", await run.api.GET("/projects/{id}/repositories/github", { params: {
|
|
5158
6027
|
path: { id: projectId },
|
|
5159
6028
|
query: { credentialId }
|
|
5160
|
-
} }),
|
|
6029
|
+
} }), repositoryHints);
|
|
5161
6030
|
return printed(run, listed, (page) => [page.items.length === 0 ? "The credential sees no repository." : renderTable([
|
|
5162
6031
|
{ header: "REPOSITORY" },
|
|
5163
6032
|
{ header: "BRANCH" },
|
|
@@ -5173,9 +6042,9 @@ const createRepositoriesCommand = (adaptersOf) => defineGroup({
|
|
|
5173
6042
|
name: "add",
|
|
5174
6043
|
summary: "Register a GitHub repository in a project.",
|
|
5175
6044
|
usage: "repositories add <owner/name> --project <name> --credential <label>",
|
|
5176
|
-
optionsUsage: [...projectUsage
|
|
6045
|
+
optionsUsage: [...projectUsage, ["--credential <label>", "the github_pat credential that can see it"]],
|
|
5177
6046
|
options: {
|
|
5178
|
-
...projectOption
|
|
6047
|
+
...projectOption,
|
|
5179
6048
|
credential: { type: "string" }
|
|
5180
6049
|
},
|
|
5181
6050
|
positionals: {
|
|
@@ -5184,7 +6053,7 @@ const createRepositoriesCommand = (adaptersOf) => defineGroup({
|
|
|
5184
6053
|
},
|
|
5185
6054
|
run: async (invocation) => {
|
|
5186
6055
|
const run = await runOf(adaptersOf, invocation);
|
|
5187
|
-
const projectId = await projectFrom
|
|
6056
|
+
const projectId = await projectFrom(run, invocation.values.project);
|
|
5188
6057
|
const [owner, name] = ownerAndName(invocation.positionals[0] ?? "");
|
|
5189
6058
|
const credential = invocation.values.credential;
|
|
5190
6059
|
if (credential === void 0) throw new UsageError("repositories add needs --credential <label>.", "kcloud credentials list --project <name> shows them.");
|
|
@@ -5196,7 +6065,7 @@ const createRepositoriesCommand = (adaptersOf) => defineGroup({
|
|
|
5196
6065
|
name,
|
|
5197
6066
|
credentialId
|
|
5198
6067
|
}
|
|
5199
|
-
}),
|
|
6068
|
+
}), repositoryHints);
|
|
5200
6069
|
return printed(run, repository, (added) => `Registered ${added.owner}/${added.name} on ${added.defaultBranch} as ${added.id}.`);
|
|
5201
6070
|
}
|
|
5202
6071
|
}),
|
|
@@ -5204,9 +6073,9 @@ const createRepositoriesCommand = (adaptersOf) => defineGroup({
|
|
|
5204
6073
|
name: "set-credential",
|
|
5205
6074
|
summary: "Point a repository at another credential.",
|
|
5206
6075
|
usage: "repositories set-credential <owner/name> --project <name> --credential <label>",
|
|
5207
|
-
optionsUsage: [...projectUsage
|
|
6076
|
+
optionsUsage: [...projectUsage, ["--credential <label>", "the github_pat credential to use from now on"]],
|
|
5208
6077
|
options: {
|
|
5209
|
-
...projectOption
|
|
6078
|
+
...projectOption,
|
|
5210
6079
|
credential: { type: "string" }
|
|
5211
6080
|
},
|
|
5212
6081
|
positionals: {
|
|
@@ -5215,7 +6084,7 @@ const createRepositoriesCommand = (adaptersOf) => defineGroup({
|
|
|
5215
6084
|
},
|
|
5216
6085
|
run: async (invocation) => {
|
|
5217
6086
|
const run = await runOf(adaptersOf, invocation);
|
|
5218
|
-
const projectId = await projectFrom
|
|
6087
|
+
const projectId = await projectFrom(run, invocation.values.project);
|
|
5219
6088
|
const repositoryId = await resolveRepositoryId(run.api, projectId, invocation.positionals[0] ?? "");
|
|
5220
6089
|
const credential = invocation.values.credential;
|
|
5221
6090
|
if (credential === void 0) throw new UsageError("repositories set-credential needs --credential <label>.");
|
|
@@ -5226,7 +6095,7 @@ const createRepositoriesCommand = (adaptersOf) => defineGroup({
|
|
|
5226
6095
|
repositoryId
|
|
5227
6096
|
} },
|
|
5228
6097
|
body: { credentialId }
|
|
5229
|
-
}),
|
|
6098
|
+
}), repositoryHints);
|
|
5230
6099
|
return printed(run, repository, (changed) => `${changed.owner}/${changed.name} uses ${credential} now.`);
|
|
5231
6100
|
}
|
|
5232
6101
|
}),
|
|
@@ -5234,20 +6103,20 @@ const createRepositoriesCommand = (adaptersOf) => defineGroup({
|
|
|
5234
6103
|
name: "refresh",
|
|
5235
6104
|
summary: "Check a repository against GitHub again.",
|
|
5236
6105
|
usage: "repositories refresh <owner/name> --project <name>",
|
|
5237
|
-
optionsUsage: projectUsage
|
|
5238
|
-
options: projectOption
|
|
6106
|
+
optionsUsage: projectUsage,
|
|
6107
|
+
options: projectOption,
|
|
5239
6108
|
positionals: {
|
|
5240
6109
|
min: 1,
|
|
5241
6110
|
max: 1
|
|
5242
6111
|
},
|
|
5243
6112
|
run: async (invocation) => {
|
|
5244
6113
|
const run = await runOf(adaptersOf, invocation);
|
|
5245
|
-
const projectId = await projectFrom
|
|
6114
|
+
const projectId = await projectFrom(run, invocation.values.project);
|
|
5246
6115
|
const repositoryId = await resolveRepositoryId(run.api, projectId, invocation.positionals[0] ?? "");
|
|
5247
6116
|
const repository = dataOf("check the repository", await run.api.POST("/projects/{id}/repositories/{repositoryId}/refresh", { params: { path: {
|
|
5248
6117
|
id: projectId,
|
|
5249
6118
|
repositoryId
|
|
5250
|
-
} } }),
|
|
6119
|
+
} } }), repositoryHints);
|
|
5251
6120
|
return printed(run, repository, (checked) => `${checked.owner}/${checked.name} is ${checked.status} on ${checked.defaultBranch}.`);
|
|
5252
6121
|
}
|
|
5253
6122
|
}),
|
|
@@ -5255,15 +6124,15 @@ const createRepositoriesCommand = (adaptersOf) => defineGroup({
|
|
|
5255
6124
|
name: "delete",
|
|
5256
6125
|
summary: "Take a repository out of a project.",
|
|
5257
6126
|
usage: "repositories delete <owner/name> --project <name>",
|
|
5258
|
-
optionsUsage: projectUsage
|
|
5259
|
-
options: projectOption
|
|
6127
|
+
optionsUsage: projectUsage,
|
|
6128
|
+
options: projectOption,
|
|
5260
6129
|
positionals: {
|
|
5261
6130
|
min: 1,
|
|
5262
6131
|
max: 1
|
|
5263
6132
|
},
|
|
5264
6133
|
run: async (invocation) => {
|
|
5265
6134
|
const run = await runOf(adaptersOf, invocation);
|
|
5266
|
-
const projectId = await projectFrom
|
|
6135
|
+
const projectId = await projectFrom(run, invocation.values.project);
|
|
5267
6136
|
const reference = invocation.positionals[0] ?? "";
|
|
5268
6137
|
const repositoryId = await resolveRepositoryId(run.api, projectId, reference);
|
|
5269
6138
|
await confirmed({
|
|
@@ -5274,307 +6143,392 @@ const createRepositoriesCommand = (adaptersOf) => defineGroup({
|
|
|
5274
6143
|
doneWith("delete the repository", await run.api.DELETE("/projects/{id}/repositories/{repositoryId}", { params: { path: {
|
|
5275
6144
|
id: projectId,
|
|
5276
6145
|
repositoryId
|
|
5277
|
-
} } }),
|
|
6146
|
+
} } }), repositoryHints);
|
|
5278
6147
|
return said(run, `Took ${reference} out of the project.`);
|
|
5279
6148
|
}
|
|
5280
6149
|
})
|
|
5281
6150
|
]
|
|
5282
6151
|
});
|
|
5283
6152
|
//#endregion
|
|
5284
|
-
//#region src/
|
|
5285
|
-
const
|
|
5286
|
-
const
|
|
5287
|
-
const
|
|
5288
|
-
|
|
5289
|
-
|
|
5290
|
-
|
|
5291
|
-
|
|
5292
|
-
|
|
5293
|
-
|
|
5294
|
-
|
|
5295
|
-
|
|
5296
|
-
|
|
5297
|
-
|
|
5298
|
-
|
|
5299
|
-
}
|
|
5300
|
-
const
|
|
5301
|
-
|
|
5302
|
-
|
|
5303
|
-
|
|
5304
|
-
|
|
5305
|
-
|
|
5306
|
-
|
|
5307
|
-
|
|
6153
|
+
//#region src/mcp/tools/repositories.ts
|
|
6154
|
+
const repositoryField = z.string().trim().min(1).describe("The repository, as owner/name, by its name or by its id.");
|
|
6155
|
+
const credentialField = z.string().trim().min(1).describe("A github_pat credential of the project, by its label or its id.");
|
|
6156
|
+
const repositoryTools = [
|
|
6157
|
+
defineTool({
|
|
6158
|
+
name: "repositories_list",
|
|
6159
|
+
title: "List repositories",
|
|
6160
|
+
description: "registered lists the GitHub repositories of a project; github lists what one github_pat credential can see on GitHub, to pick one for repository_add.",
|
|
6161
|
+
operations: ["listRepositories", "listGitHubRepositories"],
|
|
6162
|
+
safety: "read",
|
|
6163
|
+
input: z.object({
|
|
6164
|
+
project: projectField,
|
|
6165
|
+
source: z.enum(["registered", "github"]).default("registered"),
|
|
6166
|
+
credential: credentialField.optional().describe("The credential to look through, for github.")
|
|
6167
|
+
}),
|
|
6168
|
+
run: async ({ project, source, credential }, run) => {
|
|
6169
|
+
const projectId = await projectIdOf(run, project);
|
|
6170
|
+
if (source === "registered") return repositoriesOf(run.api, projectId);
|
|
6171
|
+
if (credential === void 0) throw new UsageError("github needs a credential.");
|
|
6172
|
+
const credentialId = await resolveCredentialId(run.api, projectId, credential);
|
|
6173
|
+
return dataOf("list the repositories of the credential", await run.api.GET("/projects/{id}/repositories/github", { params: {
|
|
6174
|
+
path: { id: projectId },
|
|
6175
|
+
query: { credentialId }
|
|
6176
|
+
} }), repositoryHints);
|
|
6177
|
+
}
|
|
6178
|
+
}),
|
|
6179
|
+
defineTool({
|
|
6180
|
+
name: "repository_add",
|
|
6181
|
+
title: "Register a repository",
|
|
6182
|
+
description: "Register a GitHub repository in a project, so agents can clone it.",
|
|
6183
|
+
operations: ["registerRepository"],
|
|
6184
|
+
safety: "write",
|
|
6185
|
+
input: z.object({
|
|
6186
|
+
project: projectField,
|
|
6187
|
+
repository: z.string().trim().min(1).describe("The GitHub repository, as owner/name."),
|
|
6188
|
+
credential: credentialField
|
|
6189
|
+
}),
|
|
6190
|
+
run: async ({ project, repository, credential }, run) => {
|
|
6191
|
+
const projectId = await projectIdOf(run, project);
|
|
6192
|
+
const [owner, name] = ownerAndName(repository);
|
|
6193
|
+
const credentialId = await resolveCredentialId(run.api, projectId, credential);
|
|
6194
|
+
return dataOf("register the repository", await run.api.POST("/projects/{id}/repositories", {
|
|
6195
|
+
params: { path: { id: projectId } },
|
|
6196
|
+
body: {
|
|
6197
|
+
owner,
|
|
6198
|
+
name,
|
|
6199
|
+
credentialId
|
|
6200
|
+
}
|
|
6201
|
+
}), repositoryHints);
|
|
6202
|
+
}
|
|
6203
|
+
}),
|
|
6204
|
+
defineTool({
|
|
6205
|
+
name: "repository_update",
|
|
6206
|
+
title: "Change or refresh a repository",
|
|
6207
|
+
description: "set_credential points a repository at another github_pat credential; refresh checks it on GitHub again.",
|
|
6208
|
+
operations: ["updateRepository", "refreshRepository"],
|
|
6209
|
+
safety: "write",
|
|
6210
|
+
input: z.object({
|
|
6211
|
+
project: projectField,
|
|
6212
|
+
repository: repositoryField,
|
|
6213
|
+
action: z.enum(["set_credential", "refresh"]),
|
|
6214
|
+
credential: credentialField.optional().describe("The new credential, for set_credential.")
|
|
6215
|
+
}),
|
|
6216
|
+
run: async ({ project, repository, action, credential }, run) => {
|
|
6217
|
+
const projectId = await projectIdOf(run, project);
|
|
6218
|
+
const repositoryId = await resolveRepositoryId(run.api, projectId, repository);
|
|
6219
|
+
if (action === "refresh") return dataOf("check the repository", await run.api.POST("/projects/{id}/repositories/{repositoryId}/refresh", { params: { path: {
|
|
6220
|
+
id: projectId,
|
|
6221
|
+
repositoryId
|
|
6222
|
+
} } }), repositoryHints);
|
|
6223
|
+
if (credential === void 0) throw new UsageError("set_credential needs a credential.");
|
|
6224
|
+
const credentialId = await resolveCredentialId(run.api, projectId, credential);
|
|
6225
|
+
return dataOf("change the credential of the repository", await run.api.PATCH("/projects/{id}/repositories/{repositoryId}", {
|
|
6226
|
+
params: { path: {
|
|
6227
|
+
id: projectId,
|
|
6228
|
+
repositoryId
|
|
6229
|
+
} },
|
|
6230
|
+
body: { credentialId }
|
|
6231
|
+
}), repositoryHints);
|
|
6232
|
+
}
|
|
6233
|
+
}),
|
|
6234
|
+
defineTool({
|
|
6235
|
+
name: "repository_delete",
|
|
6236
|
+
title: "Remove a repository",
|
|
6237
|
+
description: "Take a repository no agent uses out of a project.",
|
|
6238
|
+
operations: ["deleteRepository"],
|
|
6239
|
+
safety: "destructive",
|
|
6240
|
+
input: z.object({
|
|
6241
|
+
project: projectField,
|
|
6242
|
+
repository: repositoryField
|
|
6243
|
+
}),
|
|
6244
|
+
run: async ({ project, repository }, run) => {
|
|
6245
|
+
const projectId = await projectIdOf(run, project);
|
|
6246
|
+
const repositoryId = await resolveRepositoryId(run.api, projectId, repository);
|
|
6247
|
+
doneWith("delete the repository", await run.api.DELETE("/projects/{id}/repositories/{repositoryId}", { params: { path: {
|
|
6248
|
+
id: projectId,
|
|
6249
|
+
repositoryId
|
|
6250
|
+
} } }), repositoryHints);
|
|
6251
|
+
return { deleted: repositoryId };
|
|
6252
|
+
}
|
|
6253
|
+
})
|
|
6254
|
+
];
|
|
6255
|
+
//#endregion
|
|
6256
|
+
//#region src/mcp/tools/tasks.ts
|
|
6257
|
+
const taskFields = {
|
|
6258
|
+
...agentFields,
|
|
6259
|
+
task: z.string().trim().min(1).describe("The id of the task, from agent_tasks_list.")
|
|
5308
6260
|
};
|
|
5309
|
-
const
|
|
5310
|
-
|
|
5311
|
-
|
|
5312
|
-
|
|
5313
|
-
|
|
5314
|
-
|
|
5315
|
-
|
|
5316
|
-
|
|
5317
|
-
|
|
5318
|
-
|
|
5319
|
-
options: projectOption,
|
|
5320
|
-
positionals: {
|
|
5321
|
-
min: 0,
|
|
5322
|
-
max: 0
|
|
5323
|
-
},
|
|
5324
|
-
run: async (invocation) => {
|
|
5325
|
-
const run = await runOf(adaptersOf, invocation);
|
|
5326
|
-
const projectId = await projectFrom(run, invocation.values.project);
|
|
5327
|
-
const items = await toolsOf(run.api, projectId);
|
|
5328
|
-
return printed(run, items, (tools) => tools.length === 0 ? "The project offers no tool yet." : renderTable([
|
|
5329
|
-
{ header: "SLUG" },
|
|
5330
|
-
{ header: "NAME" },
|
|
5331
|
-
{ header: "KIND" },
|
|
5332
|
-
{ header: "SECRETS" },
|
|
5333
|
-
{
|
|
5334
|
-
header: "AGENTS",
|
|
5335
|
-
align: "right"
|
|
5336
|
-
},
|
|
5337
|
-
{ header: "ID" }
|
|
5338
|
-
], tools.map((tool) => [
|
|
5339
|
-
tool.slug,
|
|
5340
|
-
tool.name,
|
|
5341
|
-
tool.kind,
|
|
5342
|
-
tool.complete ? "complete" : "incomplete",
|
|
5343
|
-
String(tool.agentsUsing),
|
|
5344
|
-
tool.id
|
|
5345
|
-
])));
|
|
5346
|
-
}
|
|
6261
|
+
const taskTools = [
|
|
6262
|
+
defineTool({
|
|
6263
|
+
name: "agent_tasks_list",
|
|
6264
|
+
title: "List the tasks of an agent",
|
|
6265
|
+
description: "List the task queue of an agent, with the status, attempts and cost of each task.",
|
|
6266
|
+
operations: ["listAgentTasks"],
|
|
6267
|
+
safety: "read",
|
|
6268
|
+
input: z.object({
|
|
6269
|
+
...agentFields,
|
|
6270
|
+
status: z.enum(taskStatuses).optional()
|
|
5347
6271
|
}),
|
|
5348
|
-
|
|
5349
|
-
|
|
5350
|
-
|
|
5351
|
-
|
|
5352
|
-
|
|
5353
|
-
|
|
5354
|
-
|
|
5355
|
-
|
|
5356
|
-
|
|
5357
|
-
|
|
5358
|
-
|
|
5359
|
-
|
|
5360
|
-
|
|
5361
|
-
|
|
5362
|
-
|
|
5363
|
-
renderTable([{ header: "FIELD" }, { header: "VALUE" }], [
|
|
5364
|
-
["name", shown.name],
|
|
5365
|
-
["slug", shown.slug],
|
|
5366
|
-
["kind", shown.kind],
|
|
5367
|
-
["version", String(shown.version)],
|
|
5368
|
-
["complete", shown.complete ? "yes" : "no"],
|
|
5369
|
-
["agents", String(shown.agentsUsing)],
|
|
5370
|
-
["id", shown.id]
|
|
5371
|
-
]),
|
|
5372
|
-
"",
|
|
5373
|
-
JSON.stringify(shown.config, null, 2)
|
|
5374
|
-
].join("\n"));
|
|
5375
|
-
}
|
|
6272
|
+
run: async ({ status, ...reference }, run) => dataOf("list the tasks of the agent", await run.api.GET("/agents/{id}/tasks", { params: {
|
|
6273
|
+
path: { id: await agentIdOf(run, reference) },
|
|
6274
|
+
...status === void 0 ? {} : { query: { status } }
|
|
6275
|
+
} }), taskHints)
|
|
6276
|
+
}),
|
|
6277
|
+
defineTool({
|
|
6278
|
+
name: "agent_task_add",
|
|
6279
|
+
title: "Queue a task",
|
|
6280
|
+
description: "Put a task in the queue of an agent.",
|
|
6281
|
+
operations: ["addAgentTask"],
|
|
6282
|
+
safety: "write",
|
|
6283
|
+
input: z.object({
|
|
6284
|
+
...agentFields,
|
|
6285
|
+
title: z.string().trim().min(1).optional().describe("A short name for the task."),
|
|
6286
|
+
description: z.string().trim().min(1).describe("What the agent should do.")
|
|
5376
6287
|
}),
|
|
5377
|
-
|
|
5378
|
-
|
|
5379
|
-
|
|
5380
|
-
|
|
5381
|
-
|
|
5382
|
-
options: {
|
|
5383
|
-
...projectOption,
|
|
5384
|
-
...fileOptions
|
|
5385
|
-
},
|
|
5386
|
-
positionals: {
|
|
5387
|
-
min: 0,
|
|
5388
|
-
max: 0
|
|
5389
|
-
},
|
|
5390
|
-
run: async (invocation) => {
|
|
5391
|
-
const run = await runOf(adaptersOf, invocation);
|
|
5392
|
-
const projectId = await projectFrom(run, invocation.values.project);
|
|
5393
|
-
const file = invocation.values.file;
|
|
5394
|
-
if (file === void 0) throw new UsageError("tools create needs --file <path>.", "kcloud tools show <tool> --json prints the shape of a tool.");
|
|
5395
|
-
const document = await documentOf(run, file);
|
|
5396
|
-
validated(createToolBodySchema, document, "a tool");
|
|
5397
|
-
const body = document;
|
|
5398
|
-
const tool = dataOf("create the tool", await run.api.POST("/projects/{id}/tools", {
|
|
5399
|
-
params: { path: { id: projectId } },
|
|
5400
|
-
body
|
|
5401
|
-
}), hints);
|
|
5402
|
-
return printed(run, tool, (created) => `Created ${created.name} (${created.slug}) as ${created.id}.`);
|
|
6288
|
+
run: async ({ title, description, ...reference }, run) => dataOf("add the task", await run.api.POST("/agents/{id}/tasks", {
|
|
6289
|
+
params: { path: { id: await agentIdOf(run, reference) } },
|
|
6290
|
+
body: {
|
|
6291
|
+
description,
|
|
6292
|
+
...title === void 0 ? {} : { title }
|
|
5403
6293
|
}
|
|
5404
|
-
}),
|
|
5405
|
-
|
|
5406
|
-
|
|
5407
|
-
|
|
5408
|
-
|
|
5409
|
-
|
|
5410
|
-
|
|
5411
|
-
|
|
5412
|
-
|
|
5413
|
-
|
|
5414
|
-
|
|
5415
|
-
|
|
5416
|
-
|
|
5417
|
-
},
|
|
5418
|
-
|
|
5419
|
-
|
|
5420
|
-
|
|
5421
|
-
|
|
5422
|
-
|
|
5423
|
-
|
|
5424
|
-
|
|
5425
|
-
|
|
5426
|
-
|
|
5427
|
-
|
|
6294
|
+
}), taskHints)
|
|
6295
|
+
}),
|
|
6296
|
+
defineTool({
|
|
6297
|
+
name: "agent_task_retry",
|
|
6298
|
+
title: "Retry a task",
|
|
6299
|
+
description: "Queue a failed task of an agent again.",
|
|
6300
|
+
operations: ["retryAgentTask"],
|
|
6301
|
+
safety: "write",
|
|
6302
|
+
input: z.object(taskFields),
|
|
6303
|
+
run: async ({ task, ...reference }, run) => {
|
|
6304
|
+
doneWith("retry the task", await run.api.POST("/agents/{id}/tasks/{taskId}/retry", { params: { path: {
|
|
6305
|
+
id: await agentIdOf(run, reference),
|
|
6306
|
+
taskId: task
|
|
6307
|
+
} } }), taskHints);
|
|
6308
|
+
return { queued: task };
|
|
6309
|
+
}
|
|
6310
|
+
}),
|
|
6311
|
+
defineTool({
|
|
6312
|
+
name: "agent_task_cancel",
|
|
6313
|
+
title: "Cancel a task",
|
|
6314
|
+
description: "Cancel a task of an agent that has not started.",
|
|
6315
|
+
operations: ["cancelAgentTask"],
|
|
6316
|
+
safety: "destructive",
|
|
6317
|
+
input: z.object(taskFields),
|
|
6318
|
+
run: async ({ task, ...reference }, run) => {
|
|
6319
|
+
doneWith("cancel the task", await run.api.POST("/agents/{id}/tasks/{taskId}/cancel", { params: { path: {
|
|
6320
|
+
id: await agentIdOf(run, reference),
|
|
6321
|
+
taskId: task
|
|
6322
|
+
} } }), taskHints);
|
|
6323
|
+
return { cancelled: task };
|
|
6324
|
+
}
|
|
6325
|
+
})
|
|
6326
|
+
];
|
|
6327
|
+
//#endregion
|
|
6328
|
+
//#region src/mcp/tools/index.ts
|
|
6329
|
+
const kcloudTools = [
|
|
6330
|
+
...accountTools,
|
|
6331
|
+
...projectTools,
|
|
6332
|
+
...credentialTools,
|
|
6333
|
+
...repositoryTools,
|
|
6334
|
+
...projectToolTools,
|
|
6335
|
+
...agentTools,
|
|
6336
|
+
...taskTools,
|
|
6337
|
+
...observabilityTools,
|
|
6338
|
+
...notificationTools,
|
|
6339
|
+
...releaseTools
|
|
6340
|
+
];
|
|
6341
|
+
//#endregion
|
|
6342
|
+
//#region src/mcp/server.ts
|
|
6343
|
+
const instructions = [
|
|
6344
|
+
"These tools act on one Kikimora Cloud instance as the account kcloud is signed in with; whoami names both.",
|
|
6345
|
+
"Name a project by its slug, its name or its id. Name an agent as <project>/<agent>, as its slug or name together with project, or by its id.",
|
|
6346
|
+
"A refusal quotes the error code of the instance. Its hint may name a kcloud command, such as kcloud agents list; the tool for the same resource and verb, agents_list, does that work here.",
|
|
6347
|
+
"Secrets never pass through these tools. For them the person at the terminal runs kcloud credentials add, kcloud credentials rotate, kcloud tokens create, kcloud auth password, or sets the secrets of a tool in the panel."
|
|
6348
|
+
].join("\n");
|
|
6349
|
+
const createKcloudServer = ({ version, open }) => {
|
|
6350
|
+
const server = new McpServer({
|
|
6351
|
+
name: "kcloud",
|
|
6352
|
+
title: "Kikimora Cloud",
|
|
6353
|
+
version
|
|
6354
|
+
}, { instructions });
|
|
6355
|
+
for (const tool of kcloudTools) server.registerTool(tool.name, {
|
|
6356
|
+
title: tool.title,
|
|
6357
|
+
description: tool.description,
|
|
6358
|
+
inputSchema: tool.input,
|
|
6359
|
+
annotations: annotationsOf(tool)
|
|
6360
|
+
}, (input) => resultOf(async () => tool.run(input, await open())));
|
|
6361
|
+
return server;
|
|
6362
|
+
};
|
|
6363
|
+
//#endregion
|
|
6364
|
+
//#region src/commands/mcp.ts
|
|
6365
|
+
const createMcpCommand = (adaptersOf, cliVersion) => defineCommand({
|
|
6366
|
+
name: "mcp",
|
|
6367
|
+
summary: "Serve the work of kcloud to an MCP client over stdio.",
|
|
6368
|
+
usage: "mcp [--profile <name>] [--url <origin>]",
|
|
6369
|
+
optionsUsage: [["", "an MCP client starts it, as in: claude mcp add kcloud -- kcloud mcp"], ["", "each tool call uses the profile or KIKIMORA_CLOUD_TOKEN of that moment"]],
|
|
6370
|
+
options: {},
|
|
6371
|
+
positionals: {
|
|
6372
|
+
min: 0,
|
|
6373
|
+
max: 0
|
|
6374
|
+
},
|
|
6375
|
+
run: async ({ settings, context }) => {
|
|
6376
|
+
const adapters = adaptersOf(context);
|
|
6377
|
+
const { stdin, stdout } = context.io;
|
|
6378
|
+
const connection = serveStdio(() => createKcloudServer({
|
|
6379
|
+
version: cliVersion,
|
|
6380
|
+
open: async () => {
|
|
6381
|
+
const session = await openSession({
|
|
6382
|
+
settings,
|
|
6383
|
+
context,
|
|
6384
|
+
api: adapters.api
|
|
6385
|
+
});
|
|
6386
|
+
return {
|
|
6387
|
+
api: session.api,
|
|
6388
|
+
origin: session.origin,
|
|
6389
|
+
profileName: session.profileName,
|
|
6390
|
+
kcloudVersion: cliVersion,
|
|
6391
|
+
token: () => tokenOf({
|
|
6392
|
+
settings,
|
|
6393
|
+
context
|
|
6394
|
+
}),
|
|
6395
|
+
wait: adapters.wait,
|
|
6396
|
+
now: adapters.now
|
|
5428
6397
|
};
|
|
5429
|
-
validated(updateToolBodySchema, change, "a change to a tool");
|
|
5430
|
-
const body = change;
|
|
5431
|
-
const updated = dataOf("change the tool", await run.api.PATCH("/projects/{id}/tools/{toolId}", {
|
|
5432
|
-
params: { path: {
|
|
5433
|
-
id: projectId,
|
|
5434
|
-
toolId: tool.id
|
|
5435
|
-
} },
|
|
5436
|
-
body
|
|
5437
|
-
}), hints);
|
|
5438
|
-
return printed(run, updated, (shown) => `Changed ${shown.name}.`);
|
|
5439
6398
|
}
|
|
5440
|
-
}),
|
|
5441
|
-
|
|
5442
|
-
|
|
5443
|
-
|
|
5444
|
-
usage: "tools delete <tool> --project <name>",
|
|
5445
|
-
optionsUsage: projectUsage,
|
|
5446
|
-
options: projectOption,
|
|
5447
|
-
positionals: {
|
|
5448
|
-
min: 1,
|
|
5449
|
-
max: 1
|
|
5450
|
-
},
|
|
5451
|
-
run: async (invocation) => {
|
|
5452
|
-
const run = await runOf(adaptersOf, invocation);
|
|
5453
|
-
const projectId = await projectFrom(run, invocation.values.project);
|
|
5454
|
-
const reference = invocation.positionals[0] ?? "";
|
|
5455
|
-
const tool = await resolveTool(run.api, projectId, reference);
|
|
5456
|
-
await confirmed({
|
|
5457
|
-
prompt: run.prompt,
|
|
5458
|
-
settings: run.settings,
|
|
5459
|
-
question: `Delete the tool ${tool.name}?`
|
|
5460
|
-
});
|
|
5461
|
-
doneWith("delete the tool", await run.api.DELETE("/projects/{id}/tools/{toolId}", { params: { path: {
|
|
5462
|
-
id: projectId,
|
|
5463
|
-
toolId: tool.id
|
|
5464
|
-
} } }), hints);
|
|
5465
|
-
return said(run, `Deleted the tool ${tool.name}.`);
|
|
6399
|
+
}), {
|
|
6400
|
+
transport: new StdioServerTransport(stdin, stdout),
|
|
6401
|
+
onerror: (error) => {
|
|
6402
|
+
context.terminal.error(context.terminal.errorStyles.dim(error.message));
|
|
5466
6403
|
}
|
|
5467
|
-
})
|
|
5468
|
-
|
|
6404
|
+
});
|
|
6405
|
+
await finished(stdin, { writable: false }).catch(() => void 0);
|
|
6406
|
+
await connection.close();
|
|
6407
|
+
return exitCodes.ok;
|
|
6408
|
+
}
|
|
5469
6409
|
});
|
|
5470
6410
|
//#endregion
|
|
5471
|
-
//#region src/commands/
|
|
5472
|
-
const
|
|
5473
|
-
|
|
5474
|
-
|
|
5475
|
-
|
|
5476
|
-
|
|
6411
|
+
//#region src/commands/profiles.ts
|
|
6412
|
+
const rowsOf = (store) => Object.entries(store.profiles).map(([name, profile]) => [
|
|
6413
|
+
name === store.current ? `${name} *` : name,
|
|
6414
|
+
profile.origin,
|
|
6415
|
+
profile.email ?? "unknown"
|
|
6416
|
+
]);
|
|
6417
|
+
const namedProfile = (store, name) => {
|
|
6418
|
+
if (store.profiles[name] === void 0) {
|
|
6419
|
+
const known = Object.keys(store.profiles);
|
|
6420
|
+
throw new CliError(`There is no profile ${name} here.`, known.length === 0 ? "Run kcloud login --url <origin> to make one." : `kcloud profiles list shows ${known.join(", ")}.`);
|
|
6421
|
+
}
|
|
5477
6422
|
};
|
|
5478
|
-
const
|
|
5479
|
-
name: "
|
|
5480
|
-
summary: "The
|
|
5481
|
-
usage: "
|
|
6423
|
+
const createProfilesCommand = () => defineGroup({
|
|
6424
|
+
name: "profiles",
|
|
6425
|
+
summary: "The instances this machine is signed in to.",
|
|
6426
|
+
usage: "profiles <command>",
|
|
5482
6427
|
subcommands: [
|
|
5483
6428
|
defineCommand({
|
|
5484
6429
|
name: "list",
|
|
5485
|
-
summary: "List the
|
|
5486
|
-
usage: "
|
|
6430
|
+
summary: "List the saved profiles, marking the current one.",
|
|
6431
|
+
usage: "profiles list",
|
|
5487
6432
|
options: {},
|
|
5488
6433
|
positionals: {
|
|
5489
6434
|
min: 0,
|
|
5490
6435
|
max: 0
|
|
5491
6436
|
},
|
|
5492
6437
|
run: async ({ settings, context }) => {
|
|
5493
|
-
const
|
|
5494
|
-
const session = await openSession({
|
|
5495
|
-
settings,
|
|
5496
|
-
context,
|
|
5497
|
-
api: adapters.api
|
|
5498
|
-
});
|
|
5499
|
-
const { items } = dataOf("list the access tokens", await session.api.GET("/auth/me/tokens"));
|
|
6438
|
+
const store = await readProfileStore(context.homeDirectory);
|
|
5500
6439
|
if (settings.json) {
|
|
5501
|
-
context.terminal.json(
|
|
6440
|
+
context.terminal.json({
|
|
6441
|
+
current: store.current,
|
|
6442
|
+
profiles: Object.entries(store.profiles).map(([name, profile]) => ({
|
|
6443
|
+
name,
|
|
6444
|
+
origin: profile.origin,
|
|
6445
|
+
email: profile.email
|
|
6446
|
+
}))
|
|
6447
|
+
});
|
|
5502
6448
|
return exitCodes.ok;
|
|
5503
6449
|
}
|
|
5504
|
-
|
|
5505
|
-
|
|
5506
|
-
{ header: "
|
|
5507
|
-
{ header: "
|
|
5508
|
-
{ header: "
|
|
5509
|
-
],
|
|
5510
|
-
token.id,
|
|
5511
|
-
token.name,
|
|
5512
|
-
token.lastUsedAt ?? "never",
|
|
5513
|
-
token.expiresAt ?? "never"
|
|
5514
|
-
])));
|
|
6450
|
+
const rows = rowsOf(store);
|
|
6451
|
+
context.terminal.line(rows.length === 0 ? "No instance is signed in here; kcloud login --url <origin> signs in." : renderTable([
|
|
6452
|
+
{ header: "PROFILE" },
|
|
6453
|
+
{ header: "INSTANCE" },
|
|
6454
|
+
{ header: "ACCOUNT" }
|
|
6455
|
+
], rows));
|
|
5515
6456
|
return exitCodes.ok;
|
|
5516
6457
|
}
|
|
5517
6458
|
}),
|
|
5518
6459
|
defineCommand({
|
|
5519
|
-
name: "
|
|
5520
|
-
summary: "
|
|
5521
|
-
usage: "
|
|
5522
|
-
|
|
5523
|
-
options: { "expires-in": { type: "string" } },
|
|
6460
|
+
name: "use",
|
|
6461
|
+
summary: "Make one of the saved profiles the current one.",
|
|
6462
|
+
usage: "profiles use <name>",
|
|
6463
|
+
options: {},
|
|
5524
6464
|
positionals: {
|
|
5525
6465
|
min: 1,
|
|
5526
6466
|
max: 1
|
|
5527
6467
|
},
|
|
5528
|
-
run: async ({
|
|
5529
|
-
const
|
|
5530
|
-
const
|
|
5531
|
-
|
|
5532
|
-
|
|
5533
|
-
|
|
5534
|
-
|
|
6468
|
+
run: async ({ positionals, context }) => {
|
|
6469
|
+
const [name = ""] = positionals;
|
|
6470
|
+
const store = await readProfileStore(context.homeDirectory);
|
|
6471
|
+
namedProfile(store, name);
|
|
6472
|
+
await writeProfileStore(context.homeDirectory, {
|
|
6473
|
+
...store,
|
|
6474
|
+
current: name
|
|
5535
6475
|
});
|
|
5536
|
-
|
|
5537
|
-
name: positionals[0] ?? "",
|
|
5538
|
-
expiresInDays
|
|
5539
|
-
} }));
|
|
5540
|
-
if (settings.json) {
|
|
5541
|
-
context.terminal.json(created);
|
|
5542
|
-
return exitCodes.ok;
|
|
5543
|
-
}
|
|
5544
|
-
context.terminal.line(created.secret);
|
|
5545
|
-
context.terminal.error(`The token ${created.accessToken.name} is shown only here; the instance keeps its hash.`);
|
|
6476
|
+
context.terminal.line(`${name} is the current profile.`);
|
|
5546
6477
|
return exitCodes.ok;
|
|
5547
6478
|
}
|
|
5548
6479
|
}),
|
|
5549
6480
|
defineCommand({
|
|
5550
|
-
name: "
|
|
5551
|
-
summary: "
|
|
5552
|
-
usage: "
|
|
6481
|
+
name: "remove",
|
|
6482
|
+
summary: "Forget a saved profile without revoking its token.",
|
|
6483
|
+
usage: "profiles remove <name>",
|
|
5553
6484
|
options: {},
|
|
5554
6485
|
positionals: {
|
|
5555
6486
|
min: 1,
|
|
5556
6487
|
max: 1
|
|
5557
6488
|
},
|
|
5558
|
-
run: async ({ positionals,
|
|
5559
|
-
const
|
|
5560
|
-
const
|
|
5561
|
-
|
|
5562
|
-
|
|
5563
|
-
|
|
5564
|
-
api: adapters.api
|
|
5565
|
-
});
|
|
5566
|
-
await confirmed({
|
|
5567
|
-
prompt: adapters.prompter(settings),
|
|
5568
|
-
settings,
|
|
5569
|
-
question: `Revoke the token ${id}? Everything using it stops working.`
|
|
5570
|
-
});
|
|
5571
|
-
doneWith(`revoke the token ${id}`, await session.api.DELETE("/auth/me/tokens/{id}", { params: { path: { id } } }), { "token.not_found": "kcloud tokens list shows the tokens and their ids." });
|
|
5572
|
-
context.terminal.line(`Revoked the token ${id}.`);
|
|
6489
|
+
run: async ({ positionals, context }) => {
|
|
6490
|
+
const [name = ""] = positionals;
|
|
6491
|
+
const store = await readProfileStore(context.homeDirectory);
|
|
6492
|
+
namedProfile(store, name);
|
|
6493
|
+
await writeProfileStore(context.homeDirectory, withoutProfile(store, name));
|
|
6494
|
+
context.terminal.line(`Forgot ${name}; its token stays usable until it is revoked.`);
|
|
5573
6495
|
return exitCodes.ok;
|
|
5574
6496
|
}
|
|
5575
6497
|
})
|
|
5576
6498
|
]
|
|
5577
6499
|
});
|
|
6500
|
+
//#endregion
|
|
6501
|
+
//#region src/commands/releases.ts
|
|
6502
|
+
const createReleasesCommand = (adaptersOf) => defineGroup({
|
|
6503
|
+
name: "releases",
|
|
6504
|
+
summary: "The Kikimora releases an agent can run.",
|
|
6505
|
+
usage: "releases <command>",
|
|
6506
|
+
subcommands: [defineCommand({
|
|
6507
|
+
name: "list",
|
|
6508
|
+
summary: "List the releases you can give an agent.",
|
|
6509
|
+
usage: "releases list",
|
|
6510
|
+
options: {},
|
|
6511
|
+
positionals: {
|
|
6512
|
+
min: 0,
|
|
6513
|
+
max: 0
|
|
6514
|
+
},
|
|
6515
|
+
run: async (invocation) => {
|
|
6516
|
+
const run = await runOf(adaptersOf, invocation);
|
|
6517
|
+
const releases = dataOf("list the releases", await run.api.GET("/releases"));
|
|
6518
|
+
return printed(run, releases, (listed) => listed.items.length === 0 ? "The catalogue holds no release an agent can run." : renderTable([
|
|
6519
|
+
{ header: "VERSION" },
|
|
6520
|
+
{ header: "CLAUDE CODE" },
|
|
6521
|
+
{ header: "PLAYWRIGHT MCP" },
|
|
6522
|
+
{ header: "PUBLISHED" }
|
|
6523
|
+
], listed.items.map((release) => [
|
|
6524
|
+
release.version,
|
|
6525
|
+
release.claudeCodeVersion ?? "-",
|
|
6526
|
+
release.playwrightMcpVersion ?? "-",
|
|
6527
|
+
release.imageCreatedAt ?? "-"
|
|
6528
|
+
])));
|
|
6529
|
+
}
|
|
6530
|
+
})]
|
|
6531
|
+
});
|
|
5578
6532
|
const cliVersion = createRequire(import.meta.url)("../package.json").version;
|
|
5579
6533
|
//#endregion
|
|
5580
6534
|
//#region src/commands.ts
|
|
@@ -5592,7 +6546,8 @@ const kcloudCommands = () => [
|
|
|
5592
6546
|
createToolsCommand(createAdapters),
|
|
5593
6547
|
createAgentsCommand(createAdapters),
|
|
5594
6548
|
createNotificationsCommand(createAdapters),
|
|
5595
|
-
createReleasesCommand(createAdapters)
|
|
6549
|
+
createReleasesCommand(createAdapters),
|
|
6550
|
+
createMcpCommand(createAdapters, cliVersion)
|
|
5596
6551
|
];
|
|
5597
6552
|
//#endregion
|
|
5598
6553
|
//#region src/main.ts
|