@kikimoradev/kcloud 0.14.0 → 0.16.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/dist/main.js +1129 -76
- package/package.json +1 -1
package/dist/main.js
CHANGED
|
@@ -13,6 +13,24 @@ import createClient from "openapi-fetch";
|
|
|
13
13
|
import { finished } from "node:stream/promises";
|
|
14
14
|
import { StdioServerTransport, serveStdio } from "@modelcontextprotocol/server/stdio";
|
|
15
15
|
import { McpServer } from "@modelcontextprotocol/server";
|
|
16
|
+
//#region ../../packages/cli-kit/src/bytes.ts
|
|
17
|
+
const units = [
|
|
18
|
+
"B",
|
|
19
|
+
"KiB",
|
|
20
|
+
"MiB",
|
|
21
|
+
"GiB",
|
|
22
|
+
"TiB"
|
|
23
|
+
];
|
|
24
|
+
const bytesOf = (bytes) => {
|
|
25
|
+
let value = bytes;
|
|
26
|
+
let unit = 0;
|
|
27
|
+
while (value >= 1024 && unit < units.length - 1) {
|
|
28
|
+
value /= 1024;
|
|
29
|
+
unit += 1;
|
|
30
|
+
}
|
|
31
|
+
return unit === 0 ? `${bytes} B` : `${value.toFixed(1)} ${units[unit] ?? ""}`;
|
|
32
|
+
};
|
|
33
|
+
//#endregion
|
|
16
34
|
//#region ../../packages/cli-kit/src/errors.ts
|
|
17
35
|
const exitCodes = {
|
|
18
36
|
ok: 0,
|
|
@@ -91,12 +109,13 @@ const commandFactoryOf = (spec) => (definition) => {
|
|
|
91
109
|
}
|
|
92
110
|
};
|
|
93
111
|
};
|
|
94
|
-
const defineGroup$1 = ({ name, summary, usage, subcommands }) => ({
|
|
112
|
+
const defineGroup$1 = ({ name, summary, usage, subcommands, fallback }) => ({
|
|
95
113
|
name,
|
|
96
114
|
summary,
|
|
97
115
|
usage,
|
|
98
116
|
optionsUsage: [],
|
|
99
117
|
subcommands,
|
|
118
|
+
...fallback === void 0 ? {} : { fallback },
|
|
100
119
|
execute: () => {
|
|
101
120
|
throw new Error(`${name} is a group of commands and runs none of its own`);
|
|
102
121
|
}
|
|
@@ -334,23 +353,27 @@ const report = (error, terminal, environment, program, debugVariable) => {
|
|
|
334
353
|
};
|
|
335
354
|
const dispatcherOf = (spec) => {
|
|
336
355
|
const rendering = spec;
|
|
337
|
-
const
|
|
356
|
+
const executed = (command, args, help, context) => {
|
|
357
|
+
if (!help) return command.execute(args, context);
|
|
358
|
+
context.terminal.line(commandUsageOf(rendering, command));
|
|
359
|
+
return Promise.resolve(exitCodes.ok);
|
|
360
|
+
};
|
|
361
|
+
const walk = async (commands, args, trail, context, fallback) => {
|
|
338
362
|
const located = locateCommand(args, spec.globalOptions);
|
|
363
|
+
const command = located.name === null ? void 0 : commands.find(({ name }) => name === located.name);
|
|
364
|
+
const listsHelp = located.name === null && located.help;
|
|
365
|
+
if (command === void 0 && fallback !== void 0 && !listsHelp) return executed(fallback, args, located.help, context);
|
|
366
|
+
const listed = fallback === void 0 ? commands : [fallback, ...commands];
|
|
339
367
|
if (located.name === null) {
|
|
340
368
|
if (located.help) {
|
|
341
|
-
context.terminal.line(commandsUsageOf(rendering,
|
|
369
|
+
context.terminal.line(commandsUsageOf(rendering, listed, trail));
|
|
342
370
|
return exitCodes.ok;
|
|
343
371
|
}
|
|
344
|
-
throw new UsageError(trail.length === 0 ? "Name a command." : `${pathOf(trail)}needs a subcommand.`, commandsUsageOf(rendering,
|
|
345
|
-
}
|
|
346
|
-
const command = commands.find(({ name }) => name === located.name);
|
|
347
|
-
if (command === void 0) throw new UsageError(`There is no command ${pathOf(trail)}${located.name}.`, commandsUsageOf(rendering, commands, trail));
|
|
348
|
-
if (command.subcommands.length > 0) return walk(command.subcommands, located.args, [...trail, command.name], context);
|
|
349
|
-
if (located.help) {
|
|
350
|
-
context.terminal.line(commandUsageOf(rendering, command));
|
|
351
|
-
return exitCodes.ok;
|
|
372
|
+
throw new UsageError(trail.length === 0 ? "Name a command." : `${pathOf(trail)}needs a subcommand.`, commandsUsageOf(rendering, listed, trail));
|
|
352
373
|
}
|
|
353
|
-
|
|
374
|
+
if (command === void 0) throw new UsageError(`There is no command ${pathOf(trail)}${located.name}.`, commandsUsageOf(rendering, listed, trail));
|
|
375
|
+
if (command.subcommands.length > 0) return walk(command.subcommands, located.args, [...trail, command.name], context, command.fallback);
|
|
376
|
+
return executed(command, located.args, located.help, context);
|
|
354
377
|
};
|
|
355
378
|
return async ({ argv, commands, context }) => {
|
|
356
379
|
try {
|
|
@@ -972,6 +995,7 @@ const errorCodes = Object.keys({
|
|
|
972
995
|
"agent.tool_duplicate": "rule_violation",
|
|
973
996
|
"agent.context_too_long": "rule_violation",
|
|
974
997
|
"agent.docker_unavailable": "rule_violation",
|
|
998
|
+
"agent.node_unsuitable": "rule_violation",
|
|
975
999
|
"agent.storage_exhausted": "rule_violation",
|
|
976
1000
|
"agent.invalid_transition": "rule_violation",
|
|
977
1001
|
"agent.busy": "conflict",
|
|
@@ -988,6 +1012,17 @@ const errorCodes = Object.keys({
|
|
|
988
1012
|
"runtime.monitor_draining": "conflict",
|
|
989
1013
|
"runtime.monitor_limited": "conflict",
|
|
990
1014
|
"runtime.monitor_check_unsupported": "rule_violation",
|
|
1015
|
+
"node.not_found": "not_found",
|
|
1016
|
+
"node.name_taken": "conflict",
|
|
1017
|
+
"node.offline": "rule_violation",
|
|
1018
|
+
"node.not_registered": "rule_violation",
|
|
1019
|
+
"node.registration_invalid": "unauthenticated",
|
|
1020
|
+
"node.registration_used": "conflict",
|
|
1021
|
+
"node.in_use": "conflict",
|
|
1022
|
+
"node.host_unknown": "rule_violation",
|
|
1023
|
+
"node.split_exceeds_host": "rule_violation",
|
|
1024
|
+
"project.last_admin": "conflict",
|
|
1025
|
+
"agent.node_unassigned": "rule_violation",
|
|
991
1026
|
"stream.too_many": "rate_limited",
|
|
992
1027
|
"infrastructure.not_configured": "rule_violation",
|
|
993
1028
|
"infrastructure.provider_error": "internal",
|
|
@@ -1235,6 +1270,7 @@ const agentSessionSchema = z.object({
|
|
|
1235
1270
|
endedAt: z.iso.datetime().nullable(),
|
|
1236
1271
|
ok: z.boolean().nullable(),
|
|
1237
1272
|
killedReason: z.string().nullable(),
|
|
1273
|
+
failureReason: z.string().nullable(),
|
|
1238
1274
|
error: z.string().nullable(),
|
|
1239
1275
|
costUsd: z.number().nullable(),
|
|
1240
1276
|
durationMs: z.int().nullable(),
|
|
@@ -1329,7 +1365,8 @@ z.object({
|
|
|
1329
1365
|
unavailableReason: z.enum([
|
|
1330
1366
|
"not_found",
|
|
1331
1367
|
"tampered",
|
|
1332
|
-
"too_large"
|
|
1368
|
+
"too_large",
|
|
1369
|
+
"node_offline"
|
|
1333
1370
|
]).nullable(),
|
|
1334
1371
|
truncated: z.boolean()
|
|
1335
1372
|
}).meta({ id: "TranscriptPage" });
|
|
@@ -1392,6 +1429,233 @@ pageSchema(z.object({
|
|
|
1392
1429
|
finishedAt: z.iso.datetime().nullable()
|
|
1393
1430
|
}).meta({ id: "TaskCost" })).meta({ id: "TaskCostPage" });
|
|
1394
1431
|
costQuerySchema.extend({ cursor: cursorSchema.optional() });
|
|
1432
|
+
z.enum(["monitor", "executor"]);
|
|
1433
|
+
const sessionSlotCountSchema = z.int().min(1).max(64);
|
|
1434
|
+
const sessionQueueEntrySchema = z.object({
|
|
1435
|
+
agentId: idSchema,
|
|
1436
|
+
agentName: z.string().nullable(),
|
|
1437
|
+
projectId: idSchema.nullable(),
|
|
1438
|
+
projectName: z.string().nullable(),
|
|
1439
|
+
position: z.int().positive(),
|
|
1440
|
+
since: z.iso.datetime(),
|
|
1441
|
+
taskId: z.string().nullable()
|
|
1442
|
+
}).meta({ id: "SessionQueueEntry" });
|
|
1443
|
+
const sessionPoolSchema = z.object({
|
|
1444
|
+
slots: sessionSlotCountSchema.nullable(),
|
|
1445
|
+
effective: z.int().nonnegative(),
|
|
1446
|
+
inUse: z.int().nonnegative(),
|
|
1447
|
+
waiting: z.array(sessionQueueEntrySchema)
|
|
1448
|
+
}).meta({ id: "SessionPool" });
|
|
1449
|
+
z.object({
|
|
1450
|
+
version: z.int().nonnegative(),
|
|
1451
|
+
ready: z.boolean(),
|
|
1452
|
+
monitor: sessionPoolSchema,
|
|
1453
|
+
executor: sessionPoolSchema
|
|
1454
|
+
}).meta({ id: "SessionSlots" });
|
|
1455
|
+
z.object({
|
|
1456
|
+
version: z.int().nonnegative(),
|
|
1457
|
+
monitor: sessionSlotCountSchema.nullable(),
|
|
1458
|
+
executor: sessionSlotCountSchema.nullable()
|
|
1459
|
+
}).meta({ id: "UpdateSessionSlotsBody" });
|
|
1460
|
+
const agentAdmissionStateSchema = z.discriminatedUnion("state", [z.object({
|
|
1461
|
+
state: z.literal("waiting"),
|
|
1462
|
+
since: z.iso.datetime(),
|
|
1463
|
+
position: z.int().positive()
|
|
1464
|
+
}), z.object({ state: z.literal("holding") })]).meta({ id: "AgentAdmissionState" });
|
|
1465
|
+
const agentAdmissionSchema = z.object({
|
|
1466
|
+
monitor: agentAdmissionStateSchema.nullable(),
|
|
1467
|
+
executor: agentAdmissionStateSchema.nullable()
|
|
1468
|
+
}).meta({ id: "AgentAdmission" });
|
|
1469
|
+
const nodeStateSchema = z.enum([
|
|
1470
|
+
"pending",
|
|
1471
|
+
"online",
|
|
1472
|
+
"offline"
|
|
1473
|
+
]);
|
|
1474
|
+
const nodeProviderSchema = z.enum(["self_hosted"]);
|
|
1475
|
+
const nodeNameSchema = z.string().trim().regex(slugPattern);
|
|
1476
|
+
const nodeHostSchema = z.object({
|
|
1477
|
+
cpus: z.number().nonnegative(),
|
|
1478
|
+
memoryBytes: z.number().nonnegative(),
|
|
1479
|
+
dataRoot: z.string(),
|
|
1480
|
+
serverVersion: z.string()
|
|
1481
|
+
}).meta({ id: "NodeHost" });
|
|
1482
|
+
const nodeCapabilityNames = [
|
|
1483
|
+
"linux",
|
|
1484
|
+
"windows",
|
|
1485
|
+
"docker",
|
|
1486
|
+
"isolation",
|
|
1487
|
+
"gpu",
|
|
1488
|
+
"desktop"
|
|
1489
|
+
];
|
|
1490
|
+
const nodeCapabilitySchema = z.enum(nodeCapabilityNames).meta({ id: "NodeCapability" });
|
|
1491
|
+
const nodeCapabilitiesSchema = z.object({
|
|
1492
|
+
sandbox: z.boolean(),
|
|
1493
|
+
uidIsolation: z.boolean(),
|
|
1494
|
+
offered: z.array(nodeCapabilitySchema).optional()
|
|
1495
|
+
}).meta({ id: "NodeCapabilities" });
|
|
1496
|
+
const minPoolCpus = .1;
|
|
1497
|
+
const maxSplitCpus = 1024;
|
|
1498
|
+
const maxSplitMemoryMb = 16777216;
|
|
1499
|
+
const hundredths = (value) => Math.abs(Math.round(value * 100) - value * 100) < 1e-9;
|
|
1500
|
+
const splitCpusSchema = (min) => z.number().min(min).max(maxSplitCpus).refine(hundredths, { message: "expected at most two decimal places" });
|
|
1501
|
+
const splitMemorySchema = (min) => z.int().min(min).max(maxSplitMemoryMb);
|
|
1502
|
+
const nodeReserveSchema = z.object({
|
|
1503
|
+
cpus: splitCpusSchema(0),
|
|
1504
|
+
memoryMb: splitMemorySchema(0)
|
|
1505
|
+
}).meta({ id: "NodeReserve" });
|
|
1506
|
+
const nodePoolSplitSchema = z.object({
|
|
1507
|
+
cpus: splitCpusSchema(minPoolCpus),
|
|
1508
|
+
memoryMb: splitMemorySchema(256),
|
|
1509
|
+
slots: z.int().min(1).max(64)
|
|
1510
|
+
}).meta({ id: "NodePoolSplit" });
|
|
1511
|
+
const nodeSplitSchema = z.object({
|
|
1512
|
+
reserve: nodeReserveSchema,
|
|
1513
|
+
monitor: nodePoolSplitSchema,
|
|
1514
|
+
executor: nodePoolSplitSchema
|
|
1515
|
+
}).meta({ id: "NodeSplit" });
|
|
1516
|
+
const poolLimitSchema = z.object({
|
|
1517
|
+
enforced: z.boolean(),
|
|
1518
|
+
reason: z.enum([
|
|
1519
|
+
"release",
|
|
1520
|
+
"restart",
|
|
1521
|
+
"node"
|
|
1522
|
+
]).nullable()
|
|
1523
|
+
}).meta({ id: "PoolLimit" });
|
|
1524
|
+
const poolEnforcementSchema = z.object({
|
|
1525
|
+
state: z.enum([
|
|
1526
|
+
"enforced",
|
|
1527
|
+
"unsplit",
|
|
1528
|
+
"unsupported"
|
|
1529
|
+
]),
|
|
1530
|
+
reason: z.string().nullable()
|
|
1531
|
+
}).meta({ id: "PoolEnforcement" });
|
|
1532
|
+
const poolUsageSchema = z.object({
|
|
1533
|
+
memoryBytes: z.int().nonnegative(),
|
|
1534
|
+
memoryMaxBytes: z.int().nonnegative().nullable(),
|
|
1535
|
+
cpus: z.number().nonnegative().nullable(),
|
|
1536
|
+
memoryKills: z.int().nonnegative()
|
|
1537
|
+
}).meta({ id: "PoolUsage" });
|
|
1538
|
+
const nodePoolSchema = z.object({
|
|
1539
|
+
slots: z.int().nonnegative(),
|
|
1540
|
+
inUse: z.int().nonnegative(),
|
|
1541
|
+
waiting: z.array(sessionQueueEntrySchema),
|
|
1542
|
+
usage: poolUsageSchema.nullable()
|
|
1543
|
+
}).meta({ id: "NodePool" });
|
|
1544
|
+
const nodePoolsSchema = z.object({
|
|
1545
|
+
enforcement: poolEnforcementSchema,
|
|
1546
|
+
monitor: nodePoolSchema,
|
|
1547
|
+
executor: nodePoolSchema
|
|
1548
|
+
}).meta({ id: "NodePools" });
|
|
1549
|
+
const nodeSchema = z.object({
|
|
1550
|
+
id: idSchema,
|
|
1551
|
+
name: nodeNameSchema,
|
|
1552
|
+
provider: nodeProviderSchema,
|
|
1553
|
+
state: nodeStateSchema,
|
|
1554
|
+
engine: z.enum(runtimeEngineKinds).nullable(),
|
|
1555
|
+
version: z.string().nullable(),
|
|
1556
|
+
host: nodeHostSchema.nullable(),
|
|
1557
|
+
capabilities: nodeCapabilitiesSchema.nullable(),
|
|
1558
|
+
offered: z.array(nodeCapabilitySchema),
|
|
1559
|
+
split: nodeSplitSchema.nullable(),
|
|
1560
|
+
revision: z.int().nonnegative(),
|
|
1561
|
+
agentCount: z.int().nonnegative(),
|
|
1562
|
+
connectedAt: z.iso.datetime().nullable(),
|
|
1563
|
+
lastSeenAt: z.iso.datetime().nullable(),
|
|
1564
|
+
registrationExpiresAt: z.iso.datetime().nullable(),
|
|
1565
|
+
createdAt: z.iso.datetime()
|
|
1566
|
+
}).meta({ id: "Node" });
|
|
1567
|
+
const nodeAgentSchema = z.object({
|
|
1568
|
+
id: idSchema,
|
|
1569
|
+
name: z.string(),
|
|
1570
|
+
slug: z.string(),
|
|
1571
|
+
projectId: idSchema,
|
|
1572
|
+
projectName: z.string(),
|
|
1573
|
+
state: agentStateSchema,
|
|
1574
|
+
poolLimit: poolLimitSchema.nullable()
|
|
1575
|
+
}).meta({ id: "NodeAgent" });
|
|
1576
|
+
const nodeRuntimeSchema = z.object({
|
|
1577
|
+
reportedAt: z.iso.datetime(),
|
|
1578
|
+
docker: z.object({
|
|
1579
|
+
state: z.enum([
|
|
1580
|
+
"ok",
|
|
1581
|
+
"unavailable",
|
|
1582
|
+
"disabled",
|
|
1583
|
+
"adopting"
|
|
1584
|
+
]),
|
|
1585
|
+
lastError: z.string().nullable()
|
|
1586
|
+
}),
|
|
1587
|
+
storage: z.object({
|
|
1588
|
+
data: storageStateSchema,
|
|
1589
|
+
engine: storageStateSchema
|
|
1590
|
+
}),
|
|
1591
|
+
reconcile: z.object({
|
|
1592
|
+
lastTickAt: z.iso.datetime().nullable(),
|
|
1593
|
+
lastTickError: z.string().nullable(),
|
|
1594
|
+
agentsQueued: z.int().nonnegative(),
|
|
1595
|
+
agentsActive: z.int().nonnegative()
|
|
1596
|
+
}),
|
|
1597
|
+
ingest: z.object({ readers: z.int().nonnegative() }),
|
|
1598
|
+
sessions: z.object({
|
|
1599
|
+
monitor: z.object({
|
|
1600
|
+
effective: z.int().nonnegative(),
|
|
1601
|
+
inUse: z.int().nonnegative()
|
|
1602
|
+
}),
|
|
1603
|
+
executor: z.object({
|
|
1604
|
+
effective: z.int().nonnegative(),
|
|
1605
|
+
inUse: z.int().nonnegative()
|
|
1606
|
+
})
|
|
1607
|
+
}),
|
|
1608
|
+
pools: nodePoolsSchema
|
|
1609
|
+
}).meta({ id: "NodeRuntime" });
|
|
1610
|
+
nodeSchema.extend({
|
|
1611
|
+
agents: z.array(nodeAgentSchema),
|
|
1612
|
+
runtime: nodeRuntimeSchema.nullable()
|
|
1613
|
+
}).meta({ id: "NodeDetail" });
|
|
1614
|
+
z.object({ items: z.array(nodeSchema) }).meta({ id: "NodeList" });
|
|
1615
|
+
z.object({ nodeId: idSchema });
|
|
1616
|
+
z.object({
|
|
1617
|
+
organizationId: idSchema,
|
|
1618
|
+
nodeId: idSchema
|
|
1619
|
+
});
|
|
1620
|
+
z.object({
|
|
1621
|
+
revision: z.int().nonnegative(),
|
|
1622
|
+
split: nodeSplitSchema.nullable()
|
|
1623
|
+
}).meta({ id: "UpdateNodeSplitBody" });
|
|
1624
|
+
const createNodeBodySchema = z.object({ name: nodeNameSchema }).meta({ id: "CreateNodeBody" });
|
|
1625
|
+
const nodeRegistrationSchema = z.object({
|
|
1626
|
+
token: z.string(),
|
|
1627
|
+
expiresAt: z.iso.datetime()
|
|
1628
|
+
}).meta({ id: "NodeRegistration" });
|
|
1629
|
+
z.object({
|
|
1630
|
+
node: nodeSchema,
|
|
1631
|
+
registration: nodeRegistrationSchema
|
|
1632
|
+
}).meta({ id: "CreatedNode" }).meta({ id: "RenewedNodeRegistration" });
|
|
1633
|
+
z.object({
|
|
1634
|
+
token: z.string().min(1).max(256),
|
|
1635
|
+
version: z.string().min(1).max(128),
|
|
1636
|
+
engine: z.enum(runtimeEngineKinds),
|
|
1637
|
+
host: nodeHostSchema.nullable(),
|
|
1638
|
+
capabilities: nodeCapabilitiesSchema
|
|
1639
|
+
}).meta({ id: "RegisterNodeBody" });
|
|
1640
|
+
z.object({
|
|
1641
|
+
nodeId: idSchema,
|
|
1642
|
+
credential: z.string()
|
|
1643
|
+
}).meta({ id: "RegisteredNode" });
|
|
1644
|
+
const bytesPerMegabyte = 1048576;
|
|
1645
|
+
const inHundredths = (value) => Math.round(value * 100);
|
|
1646
|
+
const hostMemoryMbOf = (host) => Math.floor(host.memoryBytes / bytesPerMegabyte);
|
|
1647
|
+
const splitOverflowOf = (split, host) => {
|
|
1648
|
+
const cpus = inHundredths(split.reserve.cpus) + inHundredths(split.monitor.cpus) + inHundredths(split.executor.cpus) - inHundredths(host.cpus);
|
|
1649
|
+
const memoryMb = split.reserve.memoryMb + split.monitor.memoryMb + split.executor.memoryMb - hostMemoryMbOf(host);
|
|
1650
|
+
return {
|
|
1651
|
+
cpus: cpus / 100,
|
|
1652
|
+
memoryMb
|
|
1653
|
+
};
|
|
1654
|
+
};
|
|
1655
|
+
const splitFits = (split, host) => {
|
|
1656
|
+
const overflow = splitOverflowOf(split, host);
|
|
1657
|
+
return overflow.cpus <= 0 && overflow.memoryMb <= 0;
|
|
1658
|
+
};
|
|
1395
1659
|
//#endregion
|
|
1396
1660
|
//#region ../../packages/contracts/src/identity.ts
|
|
1397
1661
|
const emailSchema = z.email().max(254);
|
|
@@ -1575,6 +1839,11 @@ const auditActionSchema = z.enum([
|
|
|
1575
1839
|
"node.volume_resized",
|
|
1576
1840
|
"node.quiesced",
|
|
1577
1841
|
"node.resumed",
|
|
1842
|
+
"node.created",
|
|
1843
|
+
"node.registered",
|
|
1844
|
+
"node.registration_renewed",
|
|
1845
|
+
"node.deleted",
|
|
1846
|
+
"node.split_changed",
|
|
1578
1847
|
"master_key.reencrypted",
|
|
1579
1848
|
"dead_letter.retried",
|
|
1580
1849
|
"dead_letter.discarded"
|
|
@@ -1594,7 +1863,8 @@ const auditTargetTypeSchema = z.enum([
|
|
|
1594
1863
|
"backup",
|
|
1595
1864
|
"host_operation",
|
|
1596
1865
|
"volume",
|
|
1597
|
-
"queue"
|
|
1866
|
+
"queue",
|
|
1867
|
+
"node"
|
|
1598
1868
|
]);
|
|
1599
1869
|
const auditEntrySchema = z.object({
|
|
1600
1870
|
id: idSchema,
|
|
@@ -1667,43 +1937,6 @@ z.object({
|
|
|
1667
1937
|
id: idSchema,
|
|
1668
1938
|
userId: idSchema
|
|
1669
1939
|
});
|
|
1670
|
-
z.enum(["monitor", "executor"]);
|
|
1671
|
-
const sessionSlotCountSchema = z.int().min(1).max(64);
|
|
1672
|
-
const sessionQueueEntrySchema = z.object({
|
|
1673
|
-
agentId: idSchema,
|
|
1674
|
-
agentName: z.string().nullable(),
|
|
1675
|
-
projectId: idSchema.nullable(),
|
|
1676
|
-
projectName: z.string().nullable(),
|
|
1677
|
-
position: z.int().positive(),
|
|
1678
|
-
since: z.iso.datetime(),
|
|
1679
|
-
taskId: z.string().nullable()
|
|
1680
|
-
}).meta({ id: "SessionQueueEntry" });
|
|
1681
|
-
const sessionPoolSchema = z.object({
|
|
1682
|
-
slots: sessionSlotCountSchema.nullable(),
|
|
1683
|
-
effective: z.int().nonnegative(),
|
|
1684
|
-
inUse: z.int().nonnegative(),
|
|
1685
|
-
waiting: z.array(sessionQueueEntrySchema)
|
|
1686
|
-
}).meta({ id: "SessionPool" });
|
|
1687
|
-
z.object({
|
|
1688
|
-
version: z.int().nonnegative(),
|
|
1689
|
-
ready: z.boolean(),
|
|
1690
|
-
monitor: sessionPoolSchema,
|
|
1691
|
-
executor: sessionPoolSchema
|
|
1692
|
-
}).meta({ id: "SessionSlots" });
|
|
1693
|
-
z.object({
|
|
1694
|
-
version: z.int().nonnegative(),
|
|
1695
|
-
monitor: sessionSlotCountSchema.nullable(),
|
|
1696
|
-
executor: sessionSlotCountSchema.nullable()
|
|
1697
|
-
}).meta({ id: "UpdateSessionSlotsBody" });
|
|
1698
|
-
const agentAdmissionStateSchema = z.discriminatedUnion("state", [z.object({
|
|
1699
|
-
state: z.literal("waiting"),
|
|
1700
|
-
since: z.iso.datetime(),
|
|
1701
|
-
position: z.int().positive()
|
|
1702
|
-
}), z.object({ state: z.literal("holding") })]).meta({ id: "AgentAdmissionState" });
|
|
1703
|
-
const agentAdmissionSchema = z.object({
|
|
1704
|
-
monitor: agentAdmissionStateSchema.nullable(),
|
|
1705
|
-
executor: agentAdmissionStateSchema.nullable()
|
|
1706
|
-
}).meta({ id: "AgentAdmission" });
|
|
1707
1940
|
//#endregion
|
|
1708
1941
|
//#region ../../packages/contracts/src/releases.ts
|
|
1709
1942
|
const kikimoraVersionSchema = z.string().regex(versionTagPattern);
|
|
@@ -2097,7 +2330,8 @@ const agentRuntimeSchema = z.object({
|
|
|
2097
2330
|
provision: agentProvisionSchema.nullable(),
|
|
2098
2331
|
stopMode: agentStopModeSchema.nullable(),
|
|
2099
2332
|
stopDeadlineAt: z.iso.datetime().nullable(),
|
|
2100
|
-
admission: agentAdmissionSchema.nullable()
|
|
2333
|
+
admission: agentAdmissionSchema.nullable(),
|
|
2334
|
+
poolLimit: poolLimitSchema.nullable()
|
|
2101
2335
|
}).meta({ id: "AgentRuntime" });
|
|
2102
2336
|
const agentListItemSchema = z.object({
|
|
2103
2337
|
id: idSchema,
|
|
@@ -2116,6 +2350,11 @@ const agentListItemSchema = z.object({
|
|
|
2116
2350
|
availableActions: z.array(agentActionSchema),
|
|
2117
2351
|
updatedAt: z.iso.datetime()
|
|
2118
2352
|
}).meta({ id: "AgentListItem" });
|
|
2353
|
+
const agentNodeSchema = z.object({
|
|
2354
|
+
id: idSchema,
|
|
2355
|
+
name: nodeNameSchema,
|
|
2356
|
+
state: nodeStateSchema
|
|
2357
|
+
}).meta({ id: "AgentNode" });
|
|
2119
2358
|
agentListItemSchema.extend({
|
|
2120
2359
|
version: z.int().nonnegative(),
|
|
2121
2360
|
description: z.string(),
|
|
@@ -2130,6 +2369,8 @@ agentListItemSchema.extend({
|
|
|
2130
2369
|
toolAssignments: z.array(agentToolSchema),
|
|
2131
2370
|
release: releaseSummarySchema.nullable(),
|
|
2132
2371
|
runtime: agentRuntimeSchema,
|
|
2372
|
+
node: agentNodeSchema.nullable(),
|
|
2373
|
+
requiredCapabilities: z.array(nodeCapabilitySchema),
|
|
2133
2374
|
runtimeUid: z.int().positive().nullable(),
|
|
2134
2375
|
sandbox: agentSandboxSchema.nullable(),
|
|
2135
2376
|
createdAt: z.iso.datetime()
|
|
@@ -2147,11 +2388,15 @@ const distinctCredentials = {
|
|
|
2147
2388
|
const hasUniqueTools = (assignments) => new Set(assignments.map((assignment) => assignment.toolId)).size === assignments.length;
|
|
2148
2389
|
const agentRepositoryLinksSchema = z.array(agentRepositoryLinkSchema).min(1).max(32);
|
|
2149
2390
|
const agentToolAssignmentsSchema = z.array(toolAssignmentSchema).max(32).refine(hasUniqueTools, { message: "expected every tool to be assigned once" });
|
|
2391
|
+
const hasUniqueCapabilities = (names) => new Set(names).size === names.length;
|
|
2392
|
+
const requiredCapabilitiesSchema = z.array(nodeCapabilitySchema).max(nodeCapabilityNames.length).refine(hasUniqueCapabilities, { message: "expected every capability once" });
|
|
2150
2393
|
const createAgentBodySchema = z.object({
|
|
2151
2394
|
name: agentNameSchema,
|
|
2152
2395
|
slug: slugSchema.optional(),
|
|
2153
2396
|
description: agentDescriptionSchema,
|
|
2154
2397
|
kikimoraVersion: kikimoraVersionSchema,
|
|
2398
|
+
nodeId: idSchema,
|
|
2399
|
+
requiredCapabilities: requiredCapabilitiesSchema.optional(),
|
|
2155
2400
|
claudeCredentialId: idSchema,
|
|
2156
2401
|
githubCredentialId: idSchema,
|
|
2157
2402
|
repositories: agentRepositoryLinksSchema,
|
|
@@ -2423,7 +2668,7 @@ z.object({
|
|
|
2423
2668
|
pending: z.array(idSchema)
|
|
2424
2669
|
}),
|
|
2425
2670
|
capacity: capacityForecastSchema
|
|
2426
|
-
}).meta({ id: "
|
|
2671
|
+
}).meta({ id: "PlatformNode" });
|
|
2427
2672
|
z.object({ role: volumeRoleSchema });
|
|
2428
2673
|
z.object({ sizeGb: z.int().positive() }).meta({ id: "ResizeVolumeBody" });
|
|
2429
2674
|
z.object({ agents: z.int().nonnegative() }).meta({ id: "QuiesceResult" });
|
|
@@ -2529,6 +2774,7 @@ z.object({
|
|
|
2529
2774
|
version: z.string(),
|
|
2530
2775
|
apiImage: installedImageSchema,
|
|
2531
2776
|
webImage: installedImageSchema,
|
|
2777
|
+
nodeImage: installedImageSchema,
|
|
2532
2778
|
latest: z.string().nullable(),
|
|
2533
2779
|
latestError: registryUnavailableSchema.nullable(),
|
|
2534
2780
|
versions: z.array(z.string()),
|
|
@@ -2619,11 +2865,16 @@ const agentUsageSchema = z.object({
|
|
|
2619
2865
|
blockReadBytes: byteCountSchema,
|
|
2620
2866
|
blockWriteBytes: byteCountSchema
|
|
2621
2867
|
}).meta({ id: "AgentUsage" });
|
|
2868
|
+
const nodeHostSeriesSchema = z.object({
|
|
2869
|
+
nodeId: idSchema,
|
|
2870
|
+
nodeName: z.string(),
|
|
2871
|
+
points: z.array(hostPointSchema)
|
|
2872
|
+
}).meta({ id: "NodeHostSeries" });
|
|
2622
2873
|
z.object({
|
|
2623
2874
|
resolution: metricsResolutionSchema,
|
|
2624
2875
|
from: instantSchema,
|
|
2625
2876
|
to: instantSchema,
|
|
2626
|
-
|
|
2877
|
+
nodes: z.array(nodeHostSeriesSchema),
|
|
2627
2878
|
agents: z.array(agentUsageSchema)
|
|
2628
2879
|
}).meta({ id: "HostMetrics" });
|
|
2629
2880
|
z.object({
|
|
@@ -2712,7 +2963,11 @@ z.strictObject({ label: textParam }), z.strictObject({
|
|
|
2712
2963
|
}), z.strictObject({
|
|
2713
2964
|
agentName: textParam,
|
|
2714
2965
|
failureCount: countParam
|
|
2715
|
-
}), z.strictObject({ agentName: textParam }), z.strictObject({
|
|
2966
|
+
}), z.strictObject({ agentName: textParam }), z.strictObject({
|
|
2967
|
+
node: textParam,
|
|
2968
|
+
ratio: ratioParam
|
|
2969
|
+
}), z.strictObject({
|
|
2970
|
+
node: textParam,
|
|
2716
2971
|
filesystem: filesystemNameSchema,
|
|
2717
2972
|
ratio: ratioParam
|
|
2718
2973
|
}), z.strictObject({ backupKind: backupKindSchema }), z.strictObject({ hours: z.int().positive() }), z.strictObject({
|
|
@@ -3418,7 +3673,9 @@ const agentHints = {
|
|
|
3418
3673
|
"project.not_found": "kcloud projects list shows the projects you are a member of.",
|
|
3419
3674
|
"agent.invalid_transition": "kcloud agents show <agent> lists the actions it takes now.",
|
|
3420
3675
|
"agent.busy": "The agent is working on another action; try again in a moment.",
|
|
3421
|
-
"concurrency.conflict": "Someone changed the agent meanwhile; read it again and retry."
|
|
3676
|
+
"concurrency.conflict": "Someone changed the agent meanwhile; read it again and retry.",
|
|
3677
|
+
"agent.node_unsuitable": "The node lacks a capability the agent requires; kcloud nodes show <node> lists what it offers.",
|
|
3678
|
+
"node.offline": "The node of the agent is not connected; kcloud nodes show <node> tells when it was last seen."
|
|
3422
3679
|
};
|
|
3423
3680
|
const agentFrom = async (run, reference, project) => {
|
|
3424
3681
|
if (reference === void 0 || reference === "") throw new UsageError("This command needs an agent.", "Name it as <project>/<agent>, or pass --project.");
|
|
@@ -3491,12 +3748,94 @@ const confirmed = async ({ prompt, settings, question }) => {
|
|
|
3491
3748
|
})) throw new CancelledError("Nothing was changed.");
|
|
3492
3749
|
};
|
|
3493
3750
|
//#endregion
|
|
3751
|
+
//#region src/commands/nodes/pools.ts
|
|
3752
|
+
const poolLimitReasons = {
|
|
3753
|
+
release: "its Kikimora release predates pool limits",
|
|
3754
|
+
restart: "it started before the limits applied; kcloud agents restart applies them",
|
|
3755
|
+
node: "its node holds no pool to a limit"
|
|
3756
|
+
};
|
|
3757
|
+
const poolLimitText = (limit) => {
|
|
3758
|
+
if (limit === null) return "-";
|
|
3759
|
+
if (limit.enforced) return "enforced";
|
|
3760
|
+
return limit.reason === null ? "none" : `none: ${poolLimitReasons[limit.reason]}`;
|
|
3761
|
+
};
|
|
3762
|
+
const sessionFailures = {
|
|
3763
|
+
poolMemoryLimit: "killed by the pool memory limit",
|
|
3764
|
+
launcherRefused: "refused by the session starter"
|
|
3765
|
+
};
|
|
3766
|
+
const sessionFailureText = (reason) => reason === null ? "-" : sessionFailures[reason] ?? reason;
|
|
3767
|
+
const unsupportedReasons = { node_outdated: "the node runs a version that reports no pools" };
|
|
3768
|
+
const enforcementText = ({ state, reason }) => {
|
|
3769
|
+
switch (state) {
|
|
3770
|
+
case "enforced": return "The node holds each pool to the CPU and memory of its split.";
|
|
3771
|
+
case "unsplit": return "The node has no split, so no pool has a hard limit on CPU or memory.";
|
|
3772
|
+
case "unsupported": return `The node holds no pool to a limit: ${reason === null ? "it cannot" : unsupportedReasons[reason] ?? reason}.`;
|
|
3773
|
+
}
|
|
3774
|
+
};
|
|
3775
|
+
const memoryOf = (pool) => {
|
|
3776
|
+
if (pool.usage === null) return "-";
|
|
3777
|
+
const { memoryBytes, memoryMaxBytes } = pool.usage;
|
|
3778
|
+
return memoryMaxBytes === null ? bytesOf(memoryBytes) : `${bytesOf(memoryBytes)} of ${bytesOf(memoryMaxBytes)}`;
|
|
3779
|
+
};
|
|
3780
|
+
const roles = ["monitor", "executor"];
|
|
3781
|
+
const queueOf = (pools) => {
|
|
3782
|
+
const waiting = roles.flatMap((role) => pools[role].waiting.map((entry) => [
|
|
3783
|
+
role,
|
|
3784
|
+
String(entry.position),
|
|
3785
|
+
entry.projectName ?? "-",
|
|
3786
|
+
entry.agentName ?? entry.agentId,
|
|
3787
|
+
entry.since,
|
|
3788
|
+
entry.taskId ?? "-"
|
|
3789
|
+
]));
|
|
3790
|
+
return waiting.length === 0 ? "No session waits for a slot." : renderTable([
|
|
3791
|
+
{ header: "WAITS FOR" },
|
|
3792
|
+
{
|
|
3793
|
+
header: "PLACE",
|
|
3794
|
+
align: "right"
|
|
3795
|
+
},
|
|
3796
|
+
{ header: "PROJECT" },
|
|
3797
|
+
{ header: "AGENT" },
|
|
3798
|
+
{ header: "SINCE" },
|
|
3799
|
+
{ header: "TASK" }
|
|
3800
|
+
], waiting);
|
|
3801
|
+
};
|
|
3802
|
+
const poolsText = (pools) => [
|
|
3803
|
+
enforcementText(pools.enforcement),
|
|
3804
|
+
renderTable([
|
|
3805
|
+
{ header: "POOL" },
|
|
3806
|
+
{ header: "SLOTS IN USE" },
|
|
3807
|
+
{
|
|
3808
|
+
header: "CPU IN USE",
|
|
3809
|
+
align: "right"
|
|
3810
|
+
},
|
|
3811
|
+
{ header: "MEMORY IN USE" },
|
|
3812
|
+
{
|
|
3813
|
+
header: "MEMORY KILLS",
|
|
3814
|
+
align: "right"
|
|
3815
|
+
}
|
|
3816
|
+
], roles.map((role) => {
|
|
3817
|
+
const pool = pools[role];
|
|
3818
|
+
const cpus = pool.usage?.cpus ?? null;
|
|
3819
|
+
return [
|
|
3820
|
+
role,
|
|
3821
|
+
`${pool.inUse} of ${pool.slots}`,
|
|
3822
|
+
cpus === null ? "-" : cpus.toFixed(2),
|
|
3823
|
+
memoryOf(pool),
|
|
3824
|
+
pool.usage === null ? "-" : String(pool.usage.memoryKills)
|
|
3825
|
+
];
|
|
3826
|
+
})),
|
|
3827
|
+
"",
|
|
3828
|
+
queueOf(pools)
|
|
3829
|
+
].join("\n");
|
|
3830
|
+
//#endregion
|
|
3494
3831
|
//#region src/commands/agents/plan.ts
|
|
3495
3832
|
const agentPlanOf = (agent) => ({
|
|
3496
3833
|
name: agent.name,
|
|
3497
3834
|
slug: agent.slug,
|
|
3498
3835
|
description: agent.description,
|
|
3499
3836
|
kikimoraVersion: agent.kikimoraVersion,
|
|
3837
|
+
...agent.node === null ? {} : { nodeId: agent.node.id },
|
|
3838
|
+
requiredCapabilities: agent.requiredCapabilities,
|
|
3500
3839
|
claudeCredentialId: agent.claudeCredential.id,
|
|
3501
3840
|
githubCredentialId: agent.githubCredential.id,
|
|
3502
3841
|
repositories: agent.repositories.map(({ repositoryId, directory }) => ({
|
|
@@ -3667,6 +4006,7 @@ const admissionRowsOf = (agent) => {
|
|
|
3667
4006
|
const { admission } = agent.runtime;
|
|
3668
4007
|
return admission === null ? [] : [["monitor slot", slotOf(admission.monitor)], ["executor slot", slotOf(admission.executor)]];
|
|
3669
4008
|
};
|
|
4009
|
+
const poolLimitRowsOf = ({ runtime }) => runtime.poolLimit === null ? [] : [["pool limit", poolLimitText(runtime.poolLimit)]];
|
|
3670
4010
|
const detailsOf = (agent) => [
|
|
3671
4011
|
renderTable([{ header: "FIELD" }, { header: "VALUE" }], [
|
|
3672
4012
|
["name", agent.name],
|
|
@@ -3675,6 +4015,8 @@ const detailsOf = (agent) => [
|
|
|
3675
4015
|
["wanted", agent.desiredState],
|
|
3676
4016
|
["observed", agent.observedState],
|
|
3677
4017
|
["release", agent.kikimoraVersion],
|
|
4018
|
+
["node", agent.node === null ? "none" : `${agent.node.name} (${agent.node.state})`],
|
|
4019
|
+
["requires", agent.requiredCapabilities.length === 0 ? "nothing" : agent.requiredCapabilities.join(", ")],
|
|
3678
4020
|
["timezone", agent.timezone],
|
|
3679
4021
|
["machine", `${agent.machine.cpus} vCPU, ${agent.machine.memoryMb} MB`],
|
|
3680
4022
|
["docker", agent.machine.docker ? "yes" : "no"],
|
|
@@ -3682,6 +4024,7 @@ const detailsOf = (agent) => [
|
|
|
3682
4024
|
["container", agent.runtime.containerId ?? "none"],
|
|
3683
4025
|
["health", agent.runtime.health],
|
|
3684
4026
|
...admissionRowsOf(agent),
|
|
4027
|
+
...poolLimitRowsOf(agent),
|
|
3685
4028
|
["pending", agent.restartRequired ? agent.restartReasons.join(", ") : "-"],
|
|
3686
4029
|
["actions", agent.availableActions.join(", ")],
|
|
3687
4030
|
["version", String(agent.version)],
|
|
@@ -3699,6 +4042,109 @@ const detailsOf = (agent) => [
|
|
|
3699
4042
|
]))
|
|
3700
4043
|
].join("\n");
|
|
3701
4044
|
//#endregion
|
|
4045
|
+
//#region src/commands/nodes/shared.ts
|
|
4046
|
+
const nodeHints = {
|
|
4047
|
+
...organizationHints,
|
|
4048
|
+
"node.not_found": "kcloud nodes list shows the nodes of the organization.",
|
|
4049
|
+
"node.name_taken": "Another node of the organization has that name.",
|
|
4050
|
+
"node.in_use": "Delete the agents placed on it first; kcloud nodes show <node> lists them.",
|
|
4051
|
+
"node.registration_used": "The node has joined and holds its own credential; it needs no token."
|
|
4052
|
+
};
|
|
4053
|
+
const splitHints = {
|
|
4054
|
+
...nodeHints,
|
|
4055
|
+
"auth.forbidden": "Splitting a node takes an admin of its organization.",
|
|
4056
|
+
"node.host_unknown": "The node reports its CPUs and memory once its service runs; kcloud nodes show <node> shows its state.",
|
|
4057
|
+
"node.split_exceeds_host": "The split asks for more than the host has; kcloud nodes split <node> shows the host.",
|
|
4058
|
+
"concurrency.conflict": "Someone changed the node meanwhile; run the command again."
|
|
4059
|
+
};
|
|
4060
|
+
const nodesOf = async (api, organizationId) => dataOf("list the nodes", await api.GET("/organizations/{organizationId}/nodes", { params: { path: { organizationId } } }), nodeHints).items;
|
|
4061
|
+
const namesOf = (nodes) => nodes.map(({ name }) => name).join(", ");
|
|
4062
|
+
const nodeIn = (nodes, reference, organization) => {
|
|
4063
|
+
const found = nodes.find((node) => node.id === reference.toLowerCase() || node.name === reference);
|
|
4064
|
+
if (found === void 0) throw new CliError(`There is no node ${reference} in ${organization}.`, nodes.length === 0 ? "kcloud nodes create <name> creates one." : `kcloud nodes list shows ${namesOf(nodes)}.`);
|
|
4065
|
+
return found;
|
|
4066
|
+
};
|
|
4067
|
+
const resolveNode = async (scope, reference) => {
|
|
4068
|
+
const organization = await scope.organization();
|
|
4069
|
+
if (isId(reference)) return {
|
|
4070
|
+
organizationId: organization.id,
|
|
4071
|
+
nodeId: reference
|
|
4072
|
+
};
|
|
4073
|
+
const node = nodeIn(await nodesOf(scope.api, organization.id), reference, organization.slug);
|
|
4074
|
+
return {
|
|
4075
|
+
organizationId: organization.id,
|
|
4076
|
+
nodeId: node.id
|
|
4077
|
+
};
|
|
4078
|
+
};
|
|
4079
|
+
const nodeDetailOf = async (api, { organizationId, nodeId }) => dataOf("describe the node", await api.GET("/organizations/{organizationId}/nodes/{nodeId}", { params: { path: {
|
|
4080
|
+
organizationId,
|
|
4081
|
+
nodeId
|
|
4082
|
+
} } }), nodeHints);
|
|
4083
|
+
const createdNode = async (api, organizationId, name) => {
|
|
4084
|
+
const body = { name };
|
|
4085
|
+
validated(createNodeBodySchema, body, "a node");
|
|
4086
|
+
return dataOf("create the node", await api.POST("/organizations/{organizationId}/nodes", {
|
|
4087
|
+
params: { path: { organizationId } },
|
|
4088
|
+
body
|
|
4089
|
+
}), nodeHints);
|
|
4090
|
+
};
|
|
4091
|
+
const renewedRegistration = async (api, { organizationId, nodeId }) => dataOf("issue a registration token for the node", await api.POST("/organizations/{organizationId}/nodes/{nodeId}/registration", { params: { path: {
|
|
4092
|
+
organizationId,
|
|
4093
|
+
nodeId
|
|
4094
|
+
} } }), nodeHints);
|
|
4095
|
+
const deleteNode = async (api, { organizationId, nodeId }) => {
|
|
4096
|
+
doneWith("delete the node", await api.DELETE("/organizations/{organizationId}/nodes/{nodeId}", { params: { path: {
|
|
4097
|
+
organizationId,
|
|
4098
|
+
nodeId
|
|
4099
|
+
} } }), nodeHints);
|
|
4100
|
+
};
|
|
4101
|
+
const splitNode = async (api, { organizationId, nodeId }, revision, split) => dataOf(split === null ? "clear the split of the node" : "split the node", await api.PUT("/organizations/{organizationId}/nodes/{nodeId}/split", {
|
|
4102
|
+
params: { path: {
|
|
4103
|
+
organizationId,
|
|
4104
|
+
nodeId
|
|
4105
|
+
} },
|
|
4106
|
+
body: {
|
|
4107
|
+
revision,
|
|
4108
|
+
split
|
|
4109
|
+
}
|
|
4110
|
+
}), splitHints);
|
|
4111
|
+
const nodeUsage = [["", "a node is named by its name or its id"]];
|
|
4112
|
+
//#endregion
|
|
4113
|
+
//#region src/commands/projects/shared.ts
|
|
4114
|
+
const defaultProjectColor = "#7c86ff";
|
|
4115
|
+
const projectHint = {
|
|
4116
|
+
"project.not_found": "kcloud projects list shows the projects you are a member of.",
|
|
4117
|
+
"project.slug_taken": "Another project of the organization has that slug.",
|
|
4118
|
+
"project.invalid_slug": "A slug holds lowercase letters, digits and hyphens.",
|
|
4119
|
+
"project.not_empty": "Delete its agents, credentials, repositories and tools first.",
|
|
4120
|
+
"project.member_not_found": "kcloud projects members list <project> shows them.",
|
|
4121
|
+
"project.member_must_be_active": "kcloud orgs members enable <member> enables the account first."
|
|
4122
|
+
};
|
|
4123
|
+
const projectOf = async (api, id) => dataOf("describe the project", await api.GET("/projects/{id}", { params: { path: { id } } }), projectHint);
|
|
4124
|
+
//#endregion
|
|
4125
|
+
//#region src/commands/agents/placement.ts
|
|
4126
|
+
const placedNodeId = async (run, { projectId, given, ask }) => {
|
|
4127
|
+
const project = await projectOf(run.api, projectId);
|
|
4128
|
+
const organization = `the organization of ${project.slug}`;
|
|
4129
|
+
const nodes = await nodesOf(run.api, project.organizationId);
|
|
4130
|
+
if (given !== void 0) return nodeIn(nodes, given, organization).id;
|
|
4131
|
+
const [only, ...others] = nodes;
|
|
4132
|
+
if (only === void 0) throw new CliError(`The organization of ${project.slug} has no node to run the agent on.`, "kcloud nodes create <name> creates one, and its host joins with the token it prints.");
|
|
4133
|
+
if (others.length === 0) {
|
|
4134
|
+
run.context.terminal.error(`The agent runs on ${only.name}, the only node of ${organization}.`);
|
|
4135
|
+
return only.id;
|
|
4136
|
+
}
|
|
4137
|
+
if (ask) return run.prompt.select({
|
|
4138
|
+
message: "The node it runs on",
|
|
4139
|
+
choices: nodes.map((node) => ({
|
|
4140
|
+
value: node.id,
|
|
4141
|
+
label: node.name,
|
|
4142
|
+
hint: node.state
|
|
4143
|
+
}))
|
|
4144
|
+
});
|
|
4145
|
+
throw new UsageError(`agents create needs --node <name>, because ${organization} has ${nodes.length} nodes.`, `The nodes are ${nodes.map(({ name }) => name).join(", ")}.`);
|
|
4146
|
+
};
|
|
4147
|
+
//#endregion
|
|
3702
4148
|
//#region src/commands/agents/create.ts
|
|
3703
4149
|
const claudeKinds = /* @__PURE__ */ new Set(["anthropic_api_key", "claude_oauth_token"]);
|
|
3704
4150
|
const catalogueOf = async (api, projectId) => {
|
|
@@ -3800,8 +4246,21 @@ const promptText = async (run, file, question) => {
|
|
|
3800
4246
|
validate: (value) => value.trim() === "" ? "cannot be empty" : void 0
|
|
3801
4247
|
});
|
|
3802
4248
|
};
|
|
4249
|
+
const isCapability = (name) => nodeCapabilityNames.some((capability) => capability === name);
|
|
4250
|
+
const requiredCapabilitiesOf = (given) => {
|
|
4251
|
+
const names = given.flatMap((value) => value.split(",")).map((name) => name.trim()).filter((name) => name !== "");
|
|
4252
|
+
const unknown = names.filter((name) => !isCapability(name));
|
|
4253
|
+
if (unknown.length > 0) throw new UsageError(`--require ${unknown.join(", ")} ${unknown.length === 1 ? "is not a capability" : "are not capabilities"} of a node.`, `The capabilities are ${nodeCapabilityNames.join(", ")}.`);
|
|
4254
|
+
return [...new Set(names.filter(isCapability))];
|
|
4255
|
+
};
|
|
4256
|
+
const requirementOf = (required) => required === void 0 ? {} : { requiredCapabilities: [...required] };
|
|
3803
4257
|
const localTimezone = () => Intl.DateTimeFormat().resolvedOptions().timeZone || "UTC";
|
|
3804
|
-
const askedPlan = async (run, projectId, values) => {
|
|
4258
|
+
const askedPlan = async (run, projectId, values, required) => {
|
|
4259
|
+
const nodeId = await placedNodeId(run, {
|
|
4260
|
+
projectId,
|
|
4261
|
+
given: values.node,
|
|
4262
|
+
ask: !run.settings.yes
|
|
4263
|
+
});
|
|
3805
4264
|
const catalogue = await catalogueOf(run.api, projectId);
|
|
3806
4265
|
const name = values.name ?? await run.prompt.text({
|
|
3807
4266
|
message: "The name of the agent",
|
|
@@ -3819,6 +4278,8 @@ const askedPlan = async (run, projectId, values) => {
|
|
|
3819
4278
|
name,
|
|
3820
4279
|
description: "",
|
|
3821
4280
|
kikimoraVersion: release,
|
|
4281
|
+
nodeId,
|
|
4282
|
+
...requirementOf(required),
|
|
3822
4283
|
claudeCredentialId: await chosenCredential(run.prompt, catalogue, (kind) => claudeKinds.has(kind), "The Claude credential it works with"),
|
|
3823
4284
|
githubCredentialId: await chosenCredential(run.prompt, catalogue, (kind) => kind === "github_pat", "The GitHub credential it clones with"),
|
|
3824
4285
|
repositories: [...await chosenRepositories(run.prompt, catalogue)],
|
|
@@ -3837,17 +4298,32 @@ const askedPlan = async (run, projectId, values) => {
|
|
|
3837
4298
|
}
|
|
3838
4299
|
};
|
|
3839
4300
|
};
|
|
3840
|
-
const
|
|
4301
|
+
const isObject = (document) => typeof document === "object" && document !== null && !Array.isArray(document);
|
|
4302
|
+
const fileOf = async (run, projectId, { file, node }, required) => {
|
|
3841
4303
|
const document = await documentOf(run, file);
|
|
3842
|
-
|
|
3843
|
-
|
|
4304
|
+
const placed = !isObject(document) || node === void 0 && "nodeId" in document ? document : {
|
|
4305
|
+
...document,
|
|
4306
|
+
nodeId: await placedNodeId(run, {
|
|
4307
|
+
projectId,
|
|
4308
|
+
given: node,
|
|
4309
|
+
ask: false
|
|
4310
|
+
})
|
|
4311
|
+
};
|
|
4312
|
+
const plan = isObject(placed) ? {
|
|
4313
|
+
...placed,
|
|
4314
|
+
...requirementOf(required)
|
|
4315
|
+
} : placed;
|
|
4316
|
+
validated(createAgentBodySchema, plan, "an agent");
|
|
4317
|
+
return plan;
|
|
3844
4318
|
};
|
|
3845
4319
|
const createCommand = (adaptersOf) => defineCommand({
|
|
3846
4320
|
name: "create",
|
|
3847
4321
|
summary: "Create an agent from a file, from flags, or by asking step by step.",
|
|
3848
|
-
usage: "agents create --project <name> [--file <path>] [--name <name>]",
|
|
4322
|
+
usage: "agents create --project <name> [--node <node>] [--require <capability,...>] [--file <path>] [--name <name>]",
|
|
3849
4323
|
optionsUsage: [
|
|
3850
4324
|
["--project <name>", "the project that owns the agent"],
|
|
4325
|
+
["--node <node>", "the node it runs on, by name or id; needed when the organization has several"],
|
|
4326
|
+
["--require <capability,...>", `what its node has to offer: ${nodeCapabilityNames.join(", ")}`],
|
|
3851
4327
|
["--file <path>", "the whole agent as JSON, or - to read stdin"],
|
|
3852
4328
|
["--name <name>", "the name of the agent, instead of asking"],
|
|
3853
4329
|
["--release <x.y.z>", "the Kikimora release it runs, instead of asking"],
|
|
@@ -3856,11 +4332,17 @@ const createCommand = (adaptersOf) => defineCommand({
|
|
|
3856
4332
|
["--timezone <zone>", "the timezone of the agent (default: this machine's)"],
|
|
3857
4333
|
["--docker", "give the agent a Docker sidecar, when the project allows it"],
|
|
3858
4334
|
["--browser", "give the agent a browser"],
|
|
3859
|
-
["", "kcloud agents show <agent> --as-file prints what --file takes"]
|
|
4335
|
+
["", "kcloud agents show <agent> --as-file prints what --file takes"],
|
|
4336
|
+
["", "--node and --require replace the nodeId and requiredCapabilities of the file"]
|
|
3860
4337
|
],
|
|
3861
4338
|
options: {
|
|
3862
4339
|
...fileOptions,
|
|
3863
4340
|
project: { type: "string" },
|
|
4341
|
+
node: { type: "string" },
|
|
4342
|
+
require: {
|
|
4343
|
+
type: "string",
|
|
4344
|
+
multiple: true
|
|
4345
|
+
},
|
|
3864
4346
|
name: { type: "string" },
|
|
3865
4347
|
release: { type: "string" },
|
|
3866
4348
|
"monitor-prompt-file": { type: "string" },
|
|
@@ -3881,10 +4363,14 @@ const createCommand = (adaptersOf) => defineCommand({
|
|
|
3881
4363
|
},
|
|
3882
4364
|
run: async (invocation) => {
|
|
3883
4365
|
const { values } = invocation;
|
|
4366
|
+
const required = values.require === void 0 ? void 0 : requiredCapabilitiesOf(values.require);
|
|
3884
4367
|
const run = await runOf(adaptersOf, invocation);
|
|
3885
4368
|
if (values.project === void 0) throw new UsageError("agents create needs --project <name>.", "kcloud projects list shows the projects you are a member of.");
|
|
3886
4369
|
const projectId = await resolveProjectId(run, values.project);
|
|
3887
|
-
const body = values.file === void 0 ? await askedPlan(run, projectId, values) : await fileOf(run,
|
|
4370
|
+
const body = values.file === void 0 ? await askedPlan(run, projectId, values, required) : await fileOf(run, projectId, {
|
|
4371
|
+
file: values.file,
|
|
4372
|
+
node: values.node
|
|
4373
|
+
}, required);
|
|
3888
4374
|
const agent = dataOf("create the agent", await run.api.POST("/projects/{id}/agents", {
|
|
3889
4375
|
params: { path: { id: projectId } },
|
|
3890
4376
|
body
|
|
@@ -4063,6 +4549,7 @@ const observabilityCommands = (adaptersOf) => [
|
|
|
4063
4549
|
{ header: "ROLE" },
|
|
4064
4550
|
{ header: "MODEL" },
|
|
4065
4551
|
{ header: "OUTCOME" },
|
|
4552
|
+
{ header: "FAILURE" },
|
|
4066
4553
|
{
|
|
4067
4554
|
header: "COST",
|
|
4068
4555
|
align: "right"
|
|
@@ -4073,6 +4560,7 @@ const observabilityCommands = (adaptersOf) => [
|
|
|
4073
4560
|
session.agentRole ?? "-",
|
|
4074
4561
|
session.model ?? "-",
|
|
4075
4562
|
session.ok === null ? "running" : session.ok ? "ok" : "failed",
|
|
4563
|
+
sessionFailureText(session.failureReason),
|
|
4076
4564
|
session.costUsd === null ? "-" : session.costUsd.toFixed(2),
|
|
4077
4565
|
session.sessionId
|
|
4078
4566
|
])));
|
|
@@ -5204,7 +5692,7 @@ const agentTools = [
|
|
|
5204
5692
|
defineTool({
|
|
5205
5693
|
name: "agent_create",
|
|
5206
5694
|
title: "Create an agent",
|
|
5207
|
-
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.",
|
|
5695
|
+
description: "Create an agent in a project. The node it runs on, the credentials, repositories and tools are named by the ids that nodes_list, credentials_list, repositories_list and tools_list return; agent_show with asPlan shows a complete example. requiredCapabilities names what the node has to offer, from the offered list of nodes_list; the instance refuses a node that lacks one. The agent stays stopped until agent_action starts it.",
|
|
5208
5696
|
operations: ["createAgent"],
|
|
5209
5697
|
safety: "write",
|
|
5210
5698
|
input: z.object({
|
|
@@ -5369,6 +5857,391 @@ const credentialTools = [
|
|
|
5369
5857
|
})
|
|
5370
5858
|
];
|
|
5371
5859
|
//#endregion
|
|
5860
|
+
//#region src/commands/nodes/split.ts
|
|
5861
|
+
const splitFields = [
|
|
5862
|
+
"reserve.cpus",
|
|
5863
|
+
"reserve.memoryMb",
|
|
5864
|
+
"monitor.cpus",
|
|
5865
|
+
"monitor.memoryMb",
|
|
5866
|
+
"monitor.slots",
|
|
5867
|
+
"executor.cpus",
|
|
5868
|
+
"executor.memoryMb",
|
|
5869
|
+
"executor.slots"
|
|
5870
|
+
];
|
|
5871
|
+
const valuesOf = (split) => ({
|
|
5872
|
+
"reserve.cpus": split.reserve.cpus,
|
|
5873
|
+
"reserve.memoryMb": split.reserve.memoryMb,
|
|
5874
|
+
"monitor.cpus": split.monitor.cpus,
|
|
5875
|
+
"monitor.memoryMb": split.monitor.memoryMb,
|
|
5876
|
+
"monitor.slots": split.monitor.slots,
|
|
5877
|
+
"executor.cpus": split.executor.cpus,
|
|
5878
|
+
"executor.memoryMb": split.executor.memoryMb,
|
|
5879
|
+
"executor.slots": split.executor.slots
|
|
5880
|
+
});
|
|
5881
|
+
const splitOf = (values) => ({
|
|
5882
|
+
reserve: {
|
|
5883
|
+
cpus: values["reserve.cpus"],
|
|
5884
|
+
memoryMb: values["reserve.memoryMb"]
|
|
5885
|
+
},
|
|
5886
|
+
monitor: {
|
|
5887
|
+
cpus: values["monitor.cpus"],
|
|
5888
|
+
memoryMb: values["monitor.memoryMb"],
|
|
5889
|
+
slots: values["monitor.slots"]
|
|
5890
|
+
},
|
|
5891
|
+
executor: {
|
|
5892
|
+
cpus: values["executor.cpus"],
|
|
5893
|
+
memoryMb: values["executor.memoryMb"],
|
|
5894
|
+
slots: values["executor.slots"]
|
|
5895
|
+
}
|
|
5896
|
+
});
|
|
5897
|
+
const isComplete = (values) => splitFields.every((field) => values[field] !== void 0);
|
|
5898
|
+
const changeOfParts = ({ reserve, monitor, executor }) => {
|
|
5899
|
+
const given = {
|
|
5900
|
+
"reserve.cpus": reserve?.cpus,
|
|
5901
|
+
"reserve.memoryMb": reserve?.memoryMb,
|
|
5902
|
+
"monitor.cpus": monitor?.cpus,
|
|
5903
|
+
"monitor.memoryMb": monitor?.memoryMb,
|
|
5904
|
+
"monitor.slots": monitor?.slots,
|
|
5905
|
+
"executor.cpus": executor?.cpus,
|
|
5906
|
+
"executor.memoryMb": executor?.memoryMb,
|
|
5907
|
+
"executor.slots": executor?.slots
|
|
5908
|
+
};
|
|
5909
|
+
return Object.fromEntries(Object.entries(given).filter((entry) => entry[1] !== void 0));
|
|
5910
|
+
};
|
|
5911
|
+
const cpusText = (cpus) => String(Math.round(cpus * 100) / 100);
|
|
5912
|
+
const hostText = (host) => `${cpusText(host.cpus)} CPU and ${hostMemoryMbOf(host)} MB`;
|
|
5913
|
+
const excessText = ({ cpus, memoryMb }) => [...cpus > 0 ? [`${cpusText(cpus)} CPU`] : [], ...memoryMb > 0 ? [`${memoryMb} MB`] : []].join(" and ");
|
|
5914
|
+
const fittingSplit = (node, change, nameOf) => {
|
|
5915
|
+
const merged = {
|
|
5916
|
+
...node.split === null ? {} : valuesOf(node.split),
|
|
5917
|
+
...change
|
|
5918
|
+
};
|
|
5919
|
+
if (!isComplete(merged)) {
|
|
5920
|
+
const missing = splitFields.filter((field) => merged[field] === void 0);
|
|
5921
|
+
throw new UsageError(`${node.name} has no split yet, so a split needs every part; ${missing.map(nameOf).join(", ")} ${missing.length === 1 ? "is" : "are"} missing.`, "Once the node has a split, a part left out keeps its value.");
|
|
5922
|
+
}
|
|
5923
|
+
const split = splitOf(merged);
|
|
5924
|
+
validated(nodeSplitSchema, split, "a split of a node");
|
|
5925
|
+
if (node.host === null) throw new CliError(`${node.name} has not reported its CPUs and memory yet, so it cannot be split.`, "The node reports them once its service runs; kcloud nodes show <node> shows its state.");
|
|
5926
|
+
if (!splitFits(split, node.host)) throw new CliError(`The split asks for ${excessText(splitOverflowOf(split, node.host))} more than ${node.name} has.`, `${node.name} has ${hostText(node.host)}; shrink a pool or the reserve.`);
|
|
5927
|
+
return split;
|
|
5928
|
+
};
|
|
5929
|
+
const splitViewOf = (node) => {
|
|
5930
|
+
const overflow = node.split === null || node.host === null ? null : splitOverflowOf(node.split, node.host);
|
|
5931
|
+
return {
|
|
5932
|
+
id: node.id,
|
|
5933
|
+
name: node.name,
|
|
5934
|
+
revision: node.revision,
|
|
5935
|
+
host: node.host === null ? null : {
|
|
5936
|
+
cpus: node.host.cpus,
|
|
5937
|
+
memoryMb: hostMemoryMbOf(node.host)
|
|
5938
|
+
},
|
|
5939
|
+
split: node.split,
|
|
5940
|
+
free: overflow === null ? null : {
|
|
5941
|
+
cpus: -overflow.cpus,
|
|
5942
|
+
memoryMb: -overflow.memoryMb
|
|
5943
|
+
}
|
|
5944
|
+
};
|
|
5945
|
+
};
|
|
5946
|
+
const resourceCells = ({ cpus, memoryMb }) => [cpusText(cpus), `${memoryMb} MB`];
|
|
5947
|
+
const oneSlotOf = ({ cpus, memoryMb, slots }) => `${cpusText(cpus / slots)} CPU, ${Math.floor(memoryMb / slots)} MB`;
|
|
5948
|
+
const hostLineOf = (view) => view.host === null ? `${view.name} has not reported its CPUs and memory yet.` : `The host has ${cpusText(view.host.cpus)} CPU and ${view.host.memoryMb} MB.`;
|
|
5949
|
+
const splitTableOf = (view, split) => {
|
|
5950
|
+
const poolRow = (role) => [
|
|
5951
|
+
role,
|
|
5952
|
+
...resourceCells(split[role]),
|
|
5953
|
+
String(split[role].slots),
|
|
5954
|
+
oneSlotOf(split[role])
|
|
5955
|
+
];
|
|
5956
|
+
const rows = [
|
|
5957
|
+
[
|
|
5958
|
+
"reserve",
|
|
5959
|
+
...resourceCells(split.reserve),
|
|
5960
|
+
"-",
|
|
5961
|
+
"-"
|
|
5962
|
+
],
|
|
5963
|
+
poolRow("monitor"),
|
|
5964
|
+
poolRow("executor"),
|
|
5965
|
+
...view.free === null ? [] : [[
|
|
5966
|
+
"free",
|
|
5967
|
+
...resourceCells(view.free),
|
|
5968
|
+
"-",
|
|
5969
|
+
"-"
|
|
5970
|
+
]],
|
|
5971
|
+
...view.host === null ? [] : [[
|
|
5972
|
+
"host",
|
|
5973
|
+
...resourceCells(view.host),
|
|
5974
|
+
"-",
|
|
5975
|
+
"-"
|
|
5976
|
+
]]
|
|
5977
|
+
];
|
|
5978
|
+
const over = view.free === null ? "" : excessText({
|
|
5979
|
+
cpus: -view.free.cpus,
|
|
5980
|
+
memoryMb: -view.free.memoryMb
|
|
5981
|
+
});
|
|
5982
|
+
return [
|
|
5983
|
+
renderTable([
|
|
5984
|
+
{ header: "PART" },
|
|
5985
|
+
{
|
|
5986
|
+
header: "CPU",
|
|
5987
|
+
align: "right"
|
|
5988
|
+
},
|
|
5989
|
+
{
|
|
5990
|
+
header: "MEMORY",
|
|
5991
|
+
align: "right"
|
|
5992
|
+
},
|
|
5993
|
+
{
|
|
5994
|
+
header: "SLOTS",
|
|
5995
|
+
align: "right"
|
|
5996
|
+
},
|
|
5997
|
+
{ header: "ONE SLOT" }
|
|
5998
|
+
], rows),
|
|
5999
|
+
...view.host === null ? ["", hostLineOf(view)] : [],
|
|
6000
|
+
...over === "" ? [] : ["", `The split asks for ${over} more than the host has.`]
|
|
6001
|
+
].join("\n");
|
|
6002
|
+
};
|
|
6003
|
+
const splitText = (view) => view.split === null ? [
|
|
6004
|
+
`${view.name} has no split, so its slots are counted from its host and no pool has a hard limit on CPU or memory.`,
|
|
6005
|
+
hostLineOf(view),
|
|
6006
|
+
`kcloud nodes split set ${view.name} with every part splits it.`
|
|
6007
|
+
].join("\n") : splitTableOf(view, view.split);
|
|
6008
|
+
const splitSummaryOf = (split) => {
|
|
6009
|
+
if (split === null) return "none, slots counted from the host without hard limits";
|
|
6010
|
+
const { reserve, monitor, executor } = split;
|
|
6011
|
+
const pool = (role, { cpus, memoryMb, slots }) => `${role} ${cpusText(cpus)} CPU, ${memoryMb} MB, ${slots} slots`;
|
|
6012
|
+
return [
|
|
6013
|
+
`reserve ${cpusText(reserve.cpus)} CPU, ${reserve.memoryMb} MB`,
|
|
6014
|
+
pool("monitor", monitor),
|
|
6015
|
+
pool("executor", executor)
|
|
6016
|
+
].join("; ");
|
|
6017
|
+
};
|
|
6018
|
+
const cpusPattern = /^\d+(?:\.\d+)?$/u;
|
|
6019
|
+
const slotsPattern = /^\d+$/u;
|
|
6020
|
+
const memoryPattern = /^(\d+(?:\.\d+)?)\s*(m|mb|mib|g|gb|gib)?$/iu;
|
|
6021
|
+
const megabytesPerGigabyte = 1024;
|
|
6022
|
+
const memoryMbOf = (given) => {
|
|
6023
|
+
const match = memoryPattern.exec(given.trim());
|
|
6024
|
+
if (match === null) return null;
|
|
6025
|
+
const [, amount = "", unit = "m"] = match;
|
|
6026
|
+
const megabytes = Number(amount) * (unit.toLowerCase().startsWith("g") ? megabytesPerGigabyte : 1);
|
|
6027
|
+
return Number.isSafeInteger(megabytes) ? megabytes : null;
|
|
6028
|
+
};
|
|
6029
|
+
const readCpus = (flag, given) => {
|
|
6030
|
+
if (!cpusPattern.test(given)) throw new UsageError(`--${flag} ${given} is not a number of CPUs.`, "Give a decimal number, as in 2 or 0.5.");
|
|
6031
|
+
return Number(given);
|
|
6032
|
+
};
|
|
6033
|
+
const readMemory = (flag, given) => {
|
|
6034
|
+
const megabytes = memoryMbOf(given);
|
|
6035
|
+
if (megabytes === null) throw new UsageError(`--${flag} ${given} is not a whole number of megabytes.`, "Give megabytes, or a number with M or G, as in 2048, 2048M or 2G; G is 1024 MB.");
|
|
6036
|
+
return megabytes;
|
|
6037
|
+
};
|
|
6038
|
+
const readSlots = (flag, given) => {
|
|
6039
|
+
if (!slotsPattern.test(given)) throw new UsageError(`--${flag} ${given} is not a whole number of slots.`);
|
|
6040
|
+
return Number(given);
|
|
6041
|
+
};
|
|
6042
|
+
const splitOptions = {
|
|
6043
|
+
"reserve-cpus": { type: "string" },
|
|
6044
|
+
"reserve-memory": { type: "string" },
|
|
6045
|
+
"monitor-cpus": { type: "string" },
|
|
6046
|
+
"monitor-memory": { type: "string" },
|
|
6047
|
+
"monitor-slots": { type: "string" },
|
|
6048
|
+
"executor-cpus": { type: "string" },
|
|
6049
|
+
"executor-memory": { type: "string" },
|
|
6050
|
+
"executor-slots": { type: "string" }
|
|
6051
|
+
};
|
|
6052
|
+
const splitOptionOf = {
|
|
6053
|
+
"reserve.cpus": {
|
|
6054
|
+
flag: "reserve-cpus",
|
|
6055
|
+
read: readCpus
|
|
6056
|
+
},
|
|
6057
|
+
"reserve.memoryMb": {
|
|
6058
|
+
flag: "reserve-memory",
|
|
6059
|
+
read: readMemory
|
|
6060
|
+
},
|
|
6061
|
+
"monitor.cpus": {
|
|
6062
|
+
flag: "monitor-cpus",
|
|
6063
|
+
read: readCpus
|
|
6064
|
+
},
|
|
6065
|
+
"monitor.memoryMb": {
|
|
6066
|
+
flag: "monitor-memory",
|
|
6067
|
+
read: readMemory
|
|
6068
|
+
},
|
|
6069
|
+
"monitor.slots": {
|
|
6070
|
+
flag: "monitor-slots",
|
|
6071
|
+
read: readSlots
|
|
6072
|
+
},
|
|
6073
|
+
"executor.cpus": {
|
|
6074
|
+
flag: "executor-cpus",
|
|
6075
|
+
read: readCpus
|
|
6076
|
+
},
|
|
6077
|
+
"executor.memoryMb": {
|
|
6078
|
+
flag: "executor-memory",
|
|
6079
|
+
read: readMemory
|
|
6080
|
+
},
|
|
6081
|
+
"executor.slots": {
|
|
6082
|
+
flag: "executor-slots",
|
|
6083
|
+
read: readSlots
|
|
6084
|
+
}
|
|
6085
|
+
};
|
|
6086
|
+
const flagOf = (field) => `--${splitOptionOf[field].flag}`;
|
|
6087
|
+
const changeOf = (values) => Object.fromEntries(splitFields.flatMap((field) => {
|
|
6088
|
+
const { flag, read } = splitOptionOf[field];
|
|
6089
|
+
const given = values[flag];
|
|
6090
|
+
return given === void 0 ? [] : [[field, read(flag, given)]];
|
|
6091
|
+
}));
|
|
6092
|
+
const createSplitCommand = (adaptersOf) => defineGroup({
|
|
6093
|
+
name: "split",
|
|
6094
|
+
summary: "How a node is split into a reserve and two pools, and changing it.",
|
|
6095
|
+
usage: "nodes split [set|clear] <node>",
|
|
6096
|
+
fallback: defineCommand({
|
|
6097
|
+
name: "split",
|
|
6098
|
+
summary: "Show the split of a node, its host and what one slot of each pool gets.",
|
|
6099
|
+
usage: "nodes split <node>",
|
|
6100
|
+
optionsUsage: nodeUsage,
|
|
6101
|
+
options: {},
|
|
6102
|
+
positionals: {
|
|
6103
|
+
min: 1,
|
|
6104
|
+
max: 1
|
|
6105
|
+
},
|
|
6106
|
+
run: async (invocation) => {
|
|
6107
|
+
const run = await runOf(adaptersOf, invocation);
|
|
6108
|
+
const reference = await resolveNode(run, invocation.positionals[0] ?? "");
|
|
6109
|
+
return printed(run, splitViewOf(await nodeDetailOf(run.api, reference)), splitText);
|
|
6110
|
+
}
|
|
6111
|
+
}),
|
|
6112
|
+
subcommands: [defineCommand({
|
|
6113
|
+
name: "set",
|
|
6114
|
+
summary: "Split a node into a reserve and two pools, or change parts of its split.",
|
|
6115
|
+
usage: "nodes split set <node> [--<part>-cpus <n>] [--<part>-memory <size>] [--<pool>-slots <n>]",
|
|
6116
|
+
optionsUsage: [
|
|
6117
|
+
...nodeUsage,
|
|
6118
|
+
["--reserve-cpus <n>", "the CPUs kept for the host itself"],
|
|
6119
|
+
["--reserve-memory <size>", "the memory kept for the host itself"],
|
|
6120
|
+
["--monitor-cpus <n>", "the CPUs the monitor sessions share"],
|
|
6121
|
+
["--monitor-memory <size>", "the memory the monitor sessions share"],
|
|
6122
|
+
["--monitor-slots <n>", "how many monitor sessions run at once"],
|
|
6123
|
+
["--executor-cpus <n>", "the CPUs the executor sessions share"],
|
|
6124
|
+
["--executor-memory <size>", "the memory the executor sessions share"],
|
|
6125
|
+
["--executor-slots <n>", "how many executor sessions run at once"],
|
|
6126
|
+
["", "a size is megabytes, or a number with M or G, as in 2048 or 2G"],
|
|
6127
|
+
["", "a part left out keeps its value; a node without a split needs all eight"]
|
|
6128
|
+
],
|
|
6129
|
+
options: splitOptions,
|
|
6130
|
+
positionals: {
|
|
6131
|
+
min: 1,
|
|
6132
|
+
max: 1
|
|
6133
|
+
},
|
|
6134
|
+
run: async (invocation) => {
|
|
6135
|
+
const change = changeOf(invocation.values);
|
|
6136
|
+
if (Object.keys(change).length === 0) throw new UsageError("nodes split set needs at least one part of the split.", "kcloud nodes split set --help lists the parts.");
|
|
6137
|
+
const run = await runOf(adaptersOf, invocation);
|
|
6138
|
+
const reference = await resolveNode(run, invocation.positionals[0] ?? "");
|
|
6139
|
+
const node = await nodeDetailOf(run.api, reference);
|
|
6140
|
+
const split = fittingSplit(node, change, flagOf);
|
|
6141
|
+
const changed = await splitNode(run.api, reference, node.revision, split);
|
|
6142
|
+
return printed(run, splitViewOf(changed), splitText);
|
|
6143
|
+
}
|
|
6144
|
+
}), defineCommand({
|
|
6145
|
+
name: "clear",
|
|
6146
|
+
summary: "Remove the split of a node, so its slots are counted from its host.",
|
|
6147
|
+
usage: "nodes split clear <node>",
|
|
6148
|
+
optionsUsage: nodeUsage,
|
|
6149
|
+
options: {},
|
|
6150
|
+
positionals: {
|
|
6151
|
+
min: 1,
|
|
6152
|
+
max: 1
|
|
6153
|
+
},
|
|
6154
|
+
run: async (invocation) => {
|
|
6155
|
+
const run = await runOf(adaptersOf, invocation);
|
|
6156
|
+
const given = invocation.positionals[0] ?? "";
|
|
6157
|
+
const reference = await resolveNode(run, given);
|
|
6158
|
+
const node = await nodeDetailOf(run.api, reference);
|
|
6159
|
+
if (node.split === null) return printed(run, splitViewOf(node), () => `${node.name} has no split; nothing was changed.`);
|
|
6160
|
+
await confirmed({
|
|
6161
|
+
prompt: run.prompt,
|
|
6162
|
+
settings: run.settings,
|
|
6163
|
+
question: `Clear the split of ${given}? Its pools lose their limits on CPU and memory.`
|
|
6164
|
+
});
|
|
6165
|
+
const cleared = await splitNode(run.api, reference, node.revision, null);
|
|
6166
|
+
return printed(run, splitViewOf(cleared), splitText);
|
|
6167
|
+
}
|
|
6168
|
+
})]
|
|
6169
|
+
});
|
|
6170
|
+
//#endregion
|
|
6171
|
+
//#region src/mcp/tools/nodes.ts
|
|
6172
|
+
const nodeField = z.string().trim().min(1).describe("The node, by its name or its id, from nodes_list.");
|
|
6173
|
+
const poolPart = (role) => nodePoolSplitSchema.partial().optional().describe(`The CPUs, the memory in MB and the slots the ${role} sessions share; a field left out keeps its value.`);
|
|
6174
|
+
const nodeTools = [
|
|
6175
|
+
defineTool({
|
|
6176
|
+
name: "nodes_list",
|
|
6177
|
+
title: "List nodes",
|
|
6178
|
+
description: "List the nodes of the organization this server acts in, the hosts its agents run on, with their state, engine, version, capabilities, split and agent count.",
|
|
6179
|
+
operations: ["listNodes"],
|
|
6180
|
+
safety: "read",
|
|
6181
|
+
input: z.object({}),
|
|
6182
|
+
run: async (_, run) => nodesOf(run.api, (await run.organization()).id)
|
|
6183
|
+
}),
|
|
6184
|
+
defineTool({
|
|
6185
|
+
name: "node_show",
|
|
6186
|
+
title: "Show a node",
|
|
6187
|
+
description: "Show one node with its host, its capabilities, its split, the agents placed on it with their pool limits, and the runtime and pools it reported last.",
|
|
6188
|
+
operations: ["getNode"],
|
|
6189
|
+
safety: "read",
|
|
6190
|
+
input: z.object({ node: nodeField }),
|
|
6191
|
+
run: async ({ node }, run) => nodeDetailOf(run.api, await resolveNode(run, node))
|
|
6192
|
+
}),
|
|
6193
|
+
defineTool({
|
|
6194
|
+
name: "node_split_set",
|
|
6195
|
+
title: "Split a node into pools",
|
|
6196
|
+
description: "Split a node into a reserve kept for the host, a monitor pool and an executor pool, each with CPUs and memory in MB, the pools also with slots. A part left out keeps its value; a node without a split needs every part. The split has to fit the host. Returns the split with the host and what is left free. Takes an admin of the organization.",
|
|
6197
|
+
operations: ["getNode", "updateNodeSplit"],
|
|
6198
|
+
safety: "write",
|
|
6199
|
+
idempotent: true,
|
|
6200
|
+
input: z.object({
|
|
6201
|
+
node: nodeField,
|
|
6202
|
+
reserve: nodeReserveSchema.partial().optional().describe("The CPUs and the memory in MB kept for the host itself; a field left out keeps its value."),
|
|
6203
|
+
monitor: poolPart("monitor"),
|
|
6204
|
+
executor: poolPart("executor")
|
|
6205
|
+
}),
|
|
6206
|
+
run: async ({ node, ...parts }, run) => {
|
|
6207
|
+
const change = changeOfParts(parts);
|
|
6208
|
+
if (Object.keys(change).length === 0) throw new CliError("Name at least one part of the split to change.");
|
|
6209
|
+
const reference = await resolveNode(run, node);
|
|
6210
|
+
const detail = await nodeDetailOf(run.api, reference);
|
|
6211
|
+
const split = fittingSplit(detail, change, (field) => field);
|
|
6212
|
+
return splitViewOf(await splitNode(run.api, reference, detail.revision, split));
|
|
6213
|
+
}
|
|
6214
|
+
}),
|
|
6215
|
+
defineTool({
|
|
6216
|
+
name: "node_split_clear",
|
|
6217
|
+
title: "Clear the split of a node",
|
|
6218
|
+
description: "Remove the split of a node, so its slots are counted from its host again and its pools lose their limits on CPU and memory. Takes an admin of the organization.",
|
|
6219
|
+
operations: ["getNode", "updateNodeSplit"],
|
|
6220
|
+
safety: "destructive",
|
|
6221
|
+
idempotent: true,
|
|
6222
|
+
input: z.object({ node: nodeField }),
|
|
6223
|
+
run: async ({ node }, run) => {
|
|
6224
|
+
const reference = await resolveNode(run, node);
|
|
6225
|
+
const detail = await nodeDetailOf(run.api, reference);
|
|
6226
|
+
if (detail.split === null) return splitViewOf(detail);
|
|
6227
|
+
return splitViewOf(await splitNode(run.api, reference, detail.revision, null));
|
|
6228
|
+
}
|
|
6229
|
+
}),
|
|
6230
|
+
defineTool({
|
|
6231
|
+
name: "node_delete",
|
|
6232
|
+
title: "Delete a node",
|
|
6233
|
+
description: "Delete a node, after which its host can no longer reach the platform. The instance refuses while agents are placed on it.",
|
|
6234
|
+
operations: ["deleteNode"],
|
|
6235
|
+
safety: "destructive",
|
|
6236
|
+
input: z.object({ node: nodeField }),
|
|
6237
|
+
run: async ({ node }, run) => {
|
|
6238
|
+
const reference = await resolveNode(run, node);
|
|
6239
|
+
await deleteNode(run.api, reference);
|
|
6240
|
+
return { deleted: reference.nodeId };
|
|
6241
|
+
}
|
|
6242
|
+
})
|
|
6243
|
+
];
|
|
6244
|
+
//#endregion
|
|
5372
6245
|
//#region src/commands/notifications.ts
|
|
5373
6246
|
const notificationHints = { "notification.not_found": "kcloud notifications list shows them." };
|
|
5374
6247
|
const createNotificationsCommand = (adaptersOf) => defineGroup({
|
|
@@ -6583,18 +7456,6 @@ const projectToolTools = [
|
|
|
6583
7456
|
})
|
|
6584
7457
|
];
|
|
6585
7458
|
//#endregion
|
|
6586
|
-
//#region src/commands/projects/shared.ts
|
|
6587
|
-
const defaultProjectColor = "#7c86ff";
|
|
6588
|
-
const projectHint = {
|
|
6589
|
-
"project.not_found": "kcloud projects list shows the projects you are a member of.",
|
|
6590
|
-
"project.slug_taken": "Another project of the organization has that slug.",
|
|
6591
|
-
"project.invalid_slug": "A slug holds lowercase letters, digits and hyphens.",
|
|
6592
|
-
"project.not_empty": "Delete its agents, credentials, repositories and tools first.",
|
|
6593
|
-
"project.member_not_found": "kcloud projects members list <project> shows them.",
|
|
6594
|
-
"project.member_must_be_active": "kcloud orgs members enable <member> enables the account first."
|
|
6595
|
-
};
|
|
6596
|
-
const projectOf = async (api, id) => dataOf("describe the project", await api.GET("/projects/{id}", { params: { path: { id } } }), projectHint);
|
|
6597
|
-
//#endregion
|
|
6598
7459
|
//#region src/commands/projects/members.ts
|
|
6599
7460
|
const projectMembersOf = async (api, projectId) => dataOf("list the members of the project", await api.GET("/projects/{id}/members", { params: { path: { id: projectId } } }), projectHint).items;
|
|
6600
7461
|
const resolveProjectMember = async (api, projectId, reference) => {
|
|
@@ -7400,6 +8261,7 @@ const kcloudTools = [
|
|
|
7400
8261
|
...credentialTools,
|
|
7401
8262
|
...repositoryTools,
|
|
7402
8263
|
...projectToolTools,
|
|
8264
|
+
...nodeTools,
|
|
7403
8265
|
...agentTools,
|
|
7404
8266
|
...taskTools,
|
|
7405
8267
|
...observabilityTools,
|
|
@@ -7407,6 +8269,7 @@ const kcloudTools = [
|
|
|
7407
8269
|
...releaseTools
|
|
7408
8270
|
];
|
|
7409
8271
|
const passwordPrinted = (what) => `a secret never passes through the context of a model, and kcloud prints ${what}`;
|
|
8272
|
+
passwordPrinted("the one-time registration token of the node");
|
|
7410
8273
|
passwordPrinted("the temporary password of the new member"), passwordPrinted("the new temporary password");
|
|
7411
8274
|
//#endregion
|
|
7412
8275
|
//#region src/mcp/server.ts
|
|
@@ -7477,6 +8340,195 @@ const createMcpCommand = (adaptersOf, cliVersion) => defineCommand({
|
|
|
7477
8340
|
}
|
|
7478
8341
|
});
|
|
7479
8342
|
//#endregion
|
|
8343
|
+
//#region src/commands/nodes/index.ts
|
|
8344
|
+
const gibibytes = (bytes) => `${(bytes / 1024 ** 3).toFixed(1)} GiB`;
|
|
8345
|
+
const hostOf = (node) => node.host === null ? "-" : `${node.host.cpus} CPU, ${gibibytes(node.host.memoryBytes)} of memory`;
|
|
8346
|
+
const capabilitiesOf = (node) => {
|
|
8347
|
+
if (node.capabilities === null) return "-";
|
|
8348
|
+
const { sandbox, uidIsolation } = node.capabilities;
|
|
8349
|
+
return `sandbox ${sandbox ? "yes" : "no"}, uid isolation ${uidIsolation ? "yes" : "no"}`;
|
|
8350
|
+
};
|
|
8351
|
+
const registrationOf = (node) => node.registrationExpiresAt === null ? "-" : `a token waits until ${node.registrationExpiresAt}`;
|
|
8352
|
+
const storageOf = ({ state, freeBytes, totalBytes, path }) => `${state}, ${gibibytes(freeBytes)} free of ${gibibytes(totalBytes)} at ${path}`;
|
|
8353
|
+
const runtimeRowsOf = (runtime) => [
|
|
8354
|
+
["reported", runtime.reportedAt],
|
|
8355
|
+
["docker", runtime.docker.lastError === null ? runtime.docker.state : `${runtime.docker.state}: ${runtime.docker.lastError}`],
|
|
8356
|
+
["data storage", storageOf(runtime.storage.data)],
|
|
8357
|
+
["engine storage", storageOf(runtime.storage.engine)],
|
|
8358
|
+
["last reconcile", runtime.reconcile.lastTickError === null ? runtime.reconcile.lastTickAt ?? "never" : `${runtime.reconcile.lastTickAt ?? "never"}: ${runtime.reconcile.lastTickError}`],
|
|
8359
|
+
["agents", `${runtime.reconcile.agentsActive} active, ${runtime.reconcile.agentsQueued} queued`],
|
|
8360
|
+
["log readers", String(runtime.ingest.readers)]
|
|
8361
|
+
];
|
|
8362
|
+
const nodeDetailsText = (node) => [
|
|
8363
|
+
renderTable([{ header: "FIELD" }, { header: "VALUE" }], [
|
|
8364
|
+
["name", node.name],
|
|
8365
|
+
["state", node.state],
|
|
8366
|
+
["provider", node.provider],
|
|
8367
|
+
["engine", node.engine ?? "-"],
|
|
8368
|
+
["version", node.version ?? "-"],
|
|
8369
|
+
["host", hostOf(node)],
|
|
8370
|
+
["server", node.host?.serverVersion ?? "-"],
|
|
8371
|
+
["data root", node.host?.dataRoot ?? "-"],
|
|
8372
|
+
["capabilities", capabilitiesOf(node)],
|
|
8373
|
+
["offers", node.offered.length === 0 ? "-" : node.offered.join(", ")],
|
|
8374
|
+
["split", splitSummaryOf(node.split)],
|
|
8375
|
+
["registration", registrationOf(node)],
|
|
8376
|
+
["connected", node.connectedAt ?? "never"],
|
|
8377
|
+
["last seen", node.lastSeenAt ?? "never"],
|
|
8378
|
+
["created", node.createdAt],
|
|
8379
|
+
["id", node.id]
|
|
8380
|
+
]),
|
|
8381
|
+
"",
|
|
8382
|
+
node.agents.length === 0 ? "No agent is placed on the node." : renderTable([
|
|
8383
|
+
{ header: "PROJECT" },
|
|
8384
|
+
{ header: "AGENT" },
|
|
8385
|
+
{ header: "STATE" },
|
|
8386
|
+
{ header: "POOL LIMIT" },
|
|
8387
|
+
{ header: "ID" }
|
|
8388
|
+
], node.agents.map((agent) => [
|
|
8389
|
+
agent.projectName,
|
|
8390
|
+
agent.slug,
|
|
8391
|
+
agent.state,
|
|
8392
|
+
poolLimitText(agent.poolLimit),
|
|
8393
|
+
agent.id
|
|
8394
|
+
])),
|
|
8395
|
+
"",
|
|
8396
|
+
...node.runtime === null ? ["The node has reported no runtime yet."] : [
|
|
8397
|
+
renderTable([{ header: "RUNTIME" }, { header: "VALUE" }], runtimeRowsOf(node.runtime)),
|
|
8398
|
+
"",
|
|
8399
|
+
poolsText(node.runtime.pools)
|
|
8400
|
+
]
|
|
8401
|
+
].join("\n");
|
|
8402
|
+
const startHintOf = (origin) => [
|
|
8403
|
+
"Start the node with it as docs/install.md of kikimora-cloud describes. On a Linux host, after read -rs token:",
|
|
8404
|
+
` printf '%s\\n' "$token" | /opt/kikimora-cloud/infra/node/install.sh --platform-url ${origin} --image ghcr.io/kikimoradev/kikimora-cloud/node:<version> --registration-token -`,
|
|
8405
|
+
"On a Windows PC:",
|
|
8406
|
+
` C:\\kikimora-cloud\\src\\infra\\windows\\install.ps1 -PlatformUrl ${origin} -RegistrationToken <token>`
|
|
8407
|
+
].join("\n");
|
|
8408
|
+
const printedRegistration = (run, created) => {
|
|
8409
|
+
if (run.settings.json) {
|
|
8410
|
+
run.context.terminal.json(created);
|
|
8411
|
+
return exitCodes.ok;
|
|
8412
|
+
}
|
|
8413
|
+
run.context.terminal.line(created.registration.token);
|
|
8414
|
+
run.context.terminal.error([`The registration token of ${created.node.name} is shown only here and works once, until ${created.registration.expiresAt}.`, startHintOf(run.origin)].join("\n"));
|
|
8415
|
+
return exitCodes.ok;
|
|
8416
|
+
};
|
|
8417
|
+
const createNodesCommand = (adaptersOf) => defineGroup({
|
|
8418
|
+
name: "nodes",
|
|
8419
|
+
summary: "The nodes of the organization, the hosts its agents run on.",
|
|
8420
|
+
usage: "nodes <command>",
|
|
8421
|
+
subcommands: [
|
|
8422
|
+
defineCommand({
|
|
8423
|
+
name: "list",
|
|
8424
|
+
summary: "List the nodes of the organization.",
|
|
8425
|
+
usage: "nodes list",
|
|
8426
|
+
options: {},
|
|
8427
|
+
positionals: {
|
|
8428
|
+
min: 0,
|
|
8429
|
+
max: 0
|
|
8430
|
+
},
|
|
8431
|
+
run: async (invocation) => {
|
|
8432
|
+
const run = await runOf(adaptersOf, invocation);
|
|
8433
|
+
const organization = await run.organization();
|
|
8434
|
+
const items = await nodesOf(run.api, organization.id);
|
|
8435
|
+
return printed(run, items, (nodes) => nodes.length === 0 ? `${organization.slug} has no node yet; kcloud nodes create <name> creates one.` : renderTable([
|
|
8436
|
+
{ header: "NAME" },
|
|
8437
|
+
{ header: "STATE" },
|
|
8438
|
+
{ header: "ENGINE" },
|
|
8439
|
+
{ header: "VERSION" },
|
|
8440
|
+
{
|
|
8441
|
+
header: "AGENTS",
|
|
8442
|
+
align: "right"
|
|
8443
|
+
},
|
|
8444
|
+
{ header: "LAST SEEN" },
|
|
8445
|
+
{ header: "ID" }
|
|
8446
|
+
], nodes.map((node) => [
|
|
8447
|
+
node.name,
|
|
8448
|
+
node.state,
|
|
8449
|
+
node.engine ?? "-",
|
|
8450
|
+
node.version ?? "-",
|
|
8451
|
+
String(node.agentCount),
|
|
8452
|
+
node.lastSeenAt ?? "never",
|
|
8453
|
+
node.id
|
|
8454
|
+
])));
|
|
8455
|
+
}
|
|
8456
|
+
}),
|
|
8457
|
+
defineCommand({
|
|
8458
|
+
name: "show",
|
|
8459
|
+
summary: "Show a node with its host, its split, the agents placed on it, its runtime and its pools.",
|
|
8460
|
+
usage: "nodes show <node>",
|
|
8461
|
+
optionsUsage: nodeUsage,
|
|
8462
|
+
options: {},
|
|
8463
|
+
positionals: {
|
|
8464
|
+
min: 1,
|
|
8465
|
+
max: 1
|
|
8466
|
+
},
|
|
8467
|
+
run: async (invocation) => {
|
|
8468
|
+
const run = await runOf(adaptersOf, invocation);
|
|
8469
|
+
const reference = await resolveNode(run, invocation.positionals[0] ?? "");
|
|
8470
|
+
return printed(run, await nodeDetailOf(run.api, reference), nodeDetailsText);
|
|
8471
|
+
}
|
|
8472
|
+
}),
|
|
8473
|
+
createSplitCommand(adaptersOf),
|
|
8474
|
+
defineCommand({
|
|
8475
|
+
name: "create",
|
|
8476
|
+
summary: "Create a node and print its one-time registration token.",
|
|
8477
|
+
usage: "nodes create <name>",
|
|
8478
|
+
optionsUsage: [["", "the name holds lowercase letters, digits and hyphens"], ["", "the token goes to stdout alone, how to start the node to stderr"]],
|
|
8479
|
+
options: {},
|
|
8480
|
+
positionals: {
|
|
8481
|
+
min: 1,
|
|
8482
|
+
max: 1
|
|
8483
|
+
},
|
|
8484
|
+
run: async (invocation) => {
|
|
8485
|
+
const run = await runOf(adaptersOf, invocation);
|
|
8486
|
+
const organization = await run.organization();
|
|
8487
|
+
return printedRegistration(run, await createdNode(run.api, organization.id, invocation.positionals[0] ?? ""));
|
|
8488
|
+
}
|
|
8489
|
+
}),
|
|
8490
|
+
defineCommand({
|
|
8491
|
+
name: "token",
|
|
8492
|
+
summary: "Issue a new registration token for a node that has not joined yet.",
|
|
8493
|
+
usage: "nodes token <node>",
|
|
8494
|
+
optionsUsage: [...nodeUsage, ["", "the token replaces the one issued before, which stops working"]],
|
|
8495
|
+
options: {},
|
|
8496
|
+
positionals: {
|
|
8497
|
+
min: 1,
|
|
8498
|
+
max: 1
|
|
8499
|
+
},
|
|
8500
|
+
run: async (invocation) => {
|
|
8501
|
+
const run = await runOf(adaptersOf, invocation);
|
|
8502
|
+
const reference = await resolveNode(run, invocation.positionals[0] ?? "");
|
|
8503
|
+
return printedRegistration(run, await renewedRegistration(run.api, reference));
|
|
8504
|
+
}
|
|
8505
|
+
}),
|
|
8506
|
+
defineCommand({
|
|
8507
|
+
name: "delete",
|
|
8508
|
+
summary: "Delete a node that runs no agent.",
|
|
8509
|
+
usage: "nodes delete <node>",
|
|
8510
|
+
optionsUsage: nodeUsage,
|
|
8511
|
+
options: {},
|
|
8512
|
+
positionals: {
|
|
8513
|
+
min: 1,
|
|
8514
|
+
max: 1
|
|
8515
|
+
},
|
|
8516
|
+
run: async (invocation) => {
|
|
8517
|
+
const run = await runOf(adaptersOf, invocation);
|
|
8518
|
+
const given = invocation.positionals[0] ?? "";
|
|
8519
|
+
const reference = await resolveNode(run, given);
|
|
8520
|
+
await confirmed({
|
|
8521
|
+
prompt: run.prompt,
|
|
8522
|
+
settings: run.settings,
|
|
8523
|
+
question: `Delete the node ${given}? Its host can no longer reach the platform.`
|
|
8524
|
+
});
|
|
8525
|
+
await deleteNode(run.api, reference);
|
|
8526
|
+
return said(run, `Deleted the node ${given}.`);
|
|
8527
|
+
}
|
|
8528
|
+
})
|
|
8529
|
+
]
|
|
8530
|
+
});
|
|
8531
|
+
//#endregion
|
|
7480
8532
|
//#region src/commands/releases.ts
|
|
7481
8533
|
const createReleasesCommand = (adaptersOf) => defineGroup({
|
|
7482
8534
|
name: "releases",
|
|
@@ -7523,6 +8575,7 @@ const kcloudCommands = () => [
|
|
|
7523
8575
|
createCredentialsCommand(createAdapters),
|
|
7524
8576
|
createRepositoriesCommand(createAdapters),
|
|
7525
8577
|
createToolsCommand(createAdapters),
|
|
8578
|
+
createNodesCommand(createAdapters),
|
|
7526
8579
|
createAgentsCommand(createAdapters),
|
|
7527
8580
|
createNotificationsCommand(createAdapters),
|
|
7528
8581
|
createReleasesCommand(createAdapters),
|