@kikimoradev/kcloud 0.13.0 → 0.15.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 +17 -12
- package/dist/main.js +1680 -368
- package/package.json +1 -1
package/dist/main.js
CHANGED
|
@@ -431,9 +431,10 @@ const createTerminal = ({ stdout, stderr }) => ({
|
|
|
431
431
|
//#region src/cli/cli.ts
|
|
432
432
|
const program = "kcloud";
|
|
433
433
|
const debugVariable = "KCLOUD_DEBUG";
|
|
434
|
+
const defaultOrigin = "https://cloud.kikimora.dev";
|
|
434
435
|
const globalOptions = {
|
|
435
|
-
profile: { type: "string" },
|
|
436
436
|
url: { type: "string" },
|
|
437
|
+
org: { type: "string" },
|
|
437
438
|
json: {
|
|
438
439
|
type: "boolean",
|
|
439
440
|
default: false
|
|
@@ -449,23 +450,28 @@ const globalOptions = {
|
|
|
449
450
|
}
|
|
450
451
|
};
|
|
451
452
|
const globalOptionsUsage = [
|
|
452
|
-
["--
|
|
453
|
-
["--
|
|
453
|
+
["--url <origin>", `the instance to act on (default: ${defaultOrigin})`],
|
|
454
|
+
["--org <organization>", "the organization to act in, by slug, name or id"],
|
|
454
455
|
["--json", "print machine-readable output"],
|
|
455
456
|
["--yes", "ask nothing; every input comes from a flag, a file or stdin"],
|
|
456
457
|
["-h, --help", "show help"]
|
|
457
458
|
];
|
|
458
459
|
const globalValuesSchema = z.looseObject({
|
|
459
|
-
profile: z.string().optional(),
|
|
460
460
|
url: z.string().optional(),
|
|
461
|
+
org: z.string().optional(),
|
|
461
462
|
json: z.boolean(),
|
|
462
463
|
yes: z.boolean()
|
|
463
464
|
});
|
|
465
|
+
const organizationOf = (given) => {
|
|
466
|
+
if (given === void 0) return null;
|
|
467
|
+
if (given.trim() === "") throw new UsageError("--org needs an organization.", "Name it by its slug, name or id.");
|
|
468
|
+
return given.trim();
|
|
469
|
+
};
|
|
464
470
|
const settingsOf = (values) => {
|
|
465
471
|
const global = globalValuesSchema.parse(values);
|
|
466
472
|
return {
|
|
467
|
-
profile: global.profile ?? null,
|
|
468
473
|
origin: global.url ?? null,
|
|
474
|
+
organization: organizationOf(global.org),
|
|
469
475
|
json: global.json,
|
|
470
476
|
yes: global.yes
|
|
471
477
|
};
|
|
@@ -819,7 +825,8 @@ const workerSettingsSchema = z.strictObject({
|
|
|
819
825
|
//#region ../../packages/contracts/src/common.ts
|
|
820
826
|
const idSchema = z.uuid().toLowerCase();
|
|
821
827
|
const localeSchema = z.enum(["en", "pl"]);
|
|
822
|
-
const
|
|
828
|
+
const organizationRoleSchema = z.enum(["admin", "member"]);
|
|
829
|
+
const projectRoleSchema = z.enum(["admin", "member"]);
|
|
823
830
|
const cursorSchema = z.string().max(512);
|
|
824
831
|
const cursorQuerySchema = z.object({
|
|
825
832
|
cursor: cursorSchema.optional(),
|
|
@@ -925,13 +932,16 @@ const errorCodes = Object.keys({
|
|
|
925
932
|
"user.disabled": "forbidden",
|
|
926
933
|
"user.cannot_modify_self": "rule_violation",
|
|
927
934
|
"user.last_admin": "rule_violation",
|
|
935
|
+
"organization.not_found": "not_found",
|
|
936
|
+
"organization.invalid_slug": "rule_violation",
|
|
937
|
+
"organization.slug_taken": "conflict",
|
|
928
938
|
"project.not_found": "not_found",
|
|
929
939
|
"project.invalid_slug": "rule_violation",
|
|
930
940
|
"project.slug_taken": "conflict",
|
|
931
|
-
"project.member_exists": "conflict",
|
|
932
941
|
"project.docker_not_allowed": "rule_violation",
|
|
933
942
|
"project.member_not_found": "not_found",
|
|
934
943
|
"project.member_must_be_active": "rule_violation",
|
|
944
|
+
"project.not_empty": "rule_violation",
|
|
935
945
|
"credential.not_found": "not_found",
|
|
936
946
|
"credential.label_taken": "conflict",
|
|
937
947
|
"credential.in_use": "conflict",
|
|
@@ -978,6 +988,15 @@ const errorCodes = Object.keys({
|
|
|
978
988
|
"runtime.monitor_draining": "conflict",
|
|
979
989
|
"runtime.monitor_limited": "conflict",
|
|
980
990
|
"runtime.monitor_check_unsupported": "rule_violation",
|
|
991
|
+
"node.not_found": "not_found",
|
|
992
|
+
"node.name_taken": "conflict",
|
|
993
|
+
"node.offline": "rule_violation",
|
|
994
|
+
"node.not_registered": "rule_violation",
|
|
995
|
+
"node.registration_invalid": "unauthenticated",
|
|
996
|
+
"node.registration_used": "conflict",
|
|
997
|
+
"node.in_use": "conflict",
|
|
998
|
+
"project.last_admin": "conflict",
|
|
999
|
+
"agent.node_unassigned": "rule_violation",
|
|
981
1000
|
"stream.too_many": "rate_limited",
|
|
982
1001
|
"infrastructure.not_configured": "rule_violation",
|
|
983
1002
|
"infrastructure.provider_error": "internal",
|
|
@@ -1319,7 +1338,8 @@ z.object({
|
|
|
1319
1338
|
unavailableReason: z.enum([
|
|
1320
1339
|
"not_found",
|
|
1321
1340
|
"tampered",
|
|
1322
|
-
"too_large"
|
|
1341
|
+
"too_large",
|
|
1342
|
+
"node_offline"
|
|
1323
1343
|
]).nullable(),
|
|
1324
1344
|
truncated: z.boolean()
|
|
1325
1345
|
}).meta({ id: "TranscriptPage" });
|
|
@@ -1354,7 +1374,7 @@ const costPointSchema = z.object({
|
|
|
1354
1374
|
costUsd: z.number().min(0),
|
|
1355
1375
|
runs: z.int().nonnegative()
|
|
1356
1376
|
}).meta({ id: "CostPoint" });
|
|
1357
|
-
z.object({
|
|
1377
|
+
const costSeriesSchema = z.object({
|
|
1358
1378
|
items: z.array(costPointSchema),
|
|
1359
1379
|
totalCostUsd: z.number().nonnegative(),
|
|
1360
1380
|
totalRuns: z.int().nonnegative(),
|
|
@@ -1364,6 +1384,12 @@ const costQuerySchema = z.object({
|
|
|
1364
1384
|
from: z.iso.date(),
|
|
1365
1385
|
to: z.iso.date()
|
|
1366
1386
|
});
|
|
1387
|
+
const projectCostSchema = z.object({
|
|
1388
|
+
projectId: idSchema,
|
|
1389
|
+
costUsd: z.number().min(0),
|
|
1390
|
+
runs: z.int().nonnegative()
|
|
1391
|
+
}).meta({ id: "ProjectCost" });
|
|
1392
|
+
costSeriesSchema.extend({ projects: z.array(projectCostSchema) }).meta({ id: "OrganizationCosts" });
|
|
1367
1393
|
pageSchema(z.object({
|
|
1368
1394
|
taskId: z.string(),
|
|
1369
1395
|
title: z.string(),
|
|
@@ -1376,23 +1402,142 @@ pageSchema(z.object({
|
|
|
1376
1402
|
finishedAt: z.iso.datetime().nullable()
|
|
1377
1403
|
}).meta({ id: "TaskCost" })).meta({ id: "TaskCostPage" });
|
|
1378
1404
|
costQuerySchema.extend({ cursor: cursorSchema.optional() });
|
|
1405
|
+
const nodeStateSchema = z.enum([
|
|
1406
|
+
"pending",
|
|
1407
|
+
"online",
|
|
1408
|
+
"offline"
|
|
1409
|
+
]);
|
|
1410
|
+
const nodeProviderSchema = z.enum(["self_hosted"]);
|
|
1411
|
+
const nodeNameSchema = z.string().trim().regex(slugPattern);
|
|
1412
|
+
const nodeHostSchema = z.object({
|
|
1413
|
+
cpus: z.number().nonnegative(),
|
|
1414
|
+
memoryBytes: z.number().nonnegative(),
|
|
1415
|
+
dataRoot: z.string(),
|
|
1416
|
+
serverVersion: z.string()
|
|
1417
|
+
}).meta({ id: "NodeHost" });
|
|
1418
|
+
const nodeCapabilitiesSchema = z.object({
|
|
1419
|
+
sandbox: z.boolean(),
|
|
1420
|
+
uidIsolation: z.boolean()
|
|
1421
|
+
}).meta({ id: "NodeCapabilities" });
|
|
1422
|
+
const nodeSchema = z.object({
|
|
1423
|
+
id: idSchema,
|
|
1424
|
+
name: nodeNameSchema,
|
|
1425
|
+
provider: nodeProviderSchema,
|
|
1426
|
+
state: nodeStateSchema,
|
|
1427
|
+
engine: z.enum(runtimeEngineKinds).nullable(),
|
|
1428
|
+
version: z.string().nullable(),
|
|
1429
|
+
host: nodeHostSchema.nullable(),
|
|
1430
|
+
capabilities: nodeCapabilitiesSchema.nullable(),
|
|
1431
|
+
agentCount: z.int().nonnegative(),
|
|
1432
|
+
connectedAt: z.iso.datetime().nullable(),
|
|
1433
|
+
lastSeenAt: z.iso.datetime().nullable(),
|
|
1434
|
+
registrationExpiresAt: z.iso.datetime().nullable(),
|
|
1435
|
+
createdAt: z.iso.datetime()
|
|
1436
|
+
}).meta({ id: "Node" });
|
|
1437
|
+
const nodeAgentSchema = z.object({
|
|
1438
|
+
id: idSchema,
|
|
1439
|
+
name: z.string(),
|
|
1440
|
+
slug: z.string(),
|
|
1441
|
+
projectId: idSchema,
|
|
1442
|
+
projectName: z.string(),
|
|
1443
|
+
state: agentStateSchema
|
|
1444
|
+
}).meta({ id: "NodeAgent" });
|
|
1445
|
+
const nodeRuntimeSchema = z.object({
|
|
1446
|
+
reportedAt: z.iso.datetime(),
|
|
1447
|
+
docker: z.object({
|
|
1448
|
+
state: z.enum([
|
|
1449
|
+
"ok",
|
|
1450
|
+
"unavailable",
|
|
1451
|
+
"disabled",
|
|
1452
|
+
"adopting"
|
|
1453
|
+
]),
|
|
1454
|
+
lastError: z.string().nullable()
|
|
1455
|
+
}),
|
|
1456
|
+
storage: z.object({
|
|
1457
|
+
data: storageStateSchema,
|
|
1458
|
+
engine: storageStateSchema
|
|
1459
|
+
}),
|
|
1460
|
+
reconcile: z.object({
|
|
1461
|
+
lastTickAt: z.iso.datetime().nullable(),
|
|
1462
|
+
lastTickError: z.string().nullable(),
|
|
1463
|
+
agentsQueued: z.int().nonnegative(),
|
|
1464
|
+
agentsActive: z.int().nonnegative()
|
|
1465
|
+
}),
|
|
1466
|
+
ingest: z.object({ readers: z.int().nonnegative() }),
|
|
1467
|
+
sessions: z.object({
|
|
1468
|
+
monitor: z.object({
|
|
1469
|
+
effective: z.int().nonnegative(),
|
|
1470
|
+
inUse: z.int().nonnegative()
|
|
1471
|
+
}),
|
|
1472
|
+
executor: z.object({
|
|
1473
|
+
effective: z.int().nonnegative(),
|
|
1474
|
+
inUse: z.int().nonnegative()
|
|
1475
|
+
})
|
|
1476
|
+
})
|
|
1477
|
+
}).meta({ id: "NodeRuntime" });
|
|
1478
|
+
nodeSchema.extend({
|
|
1479
|
+
agents: z.array(nodeAgentSchema),
|
|
1480
|
+
runtime: nodeRuntimeSchema.nullable()
|
|
1481
|
+
}).meta({ id: "NodeDetail" });
|
|
1482
|
+
z.object({ items: z.array(nodeSchema) }).meta({ id: "NodeList" });
|
|
1483
|
+
z.object({ nodeId: idSchema });
|
|
1484
|
+
z.object({
|
|
1485
|
+
organizationId: idSchema,
|
|
1486
|
+
nodeId: idSchema
|
|
1487
|
+
});
|
|
1488
|
+
const createNodeBodySchema = z.object({ name: nodeNameSchema }).meta({ id: "CreateNodeBody" });
|
|
1489
|
+
const nodeRegistrationSchema = z.object({
|
|
1490
|
+
token: z.string(),
|
|
1491
|
+
expiresAt: z.iso.datetime()
|
|
1492
|
+
}).meta({ id: "NodeRegistration" });
|
|
1493
|
+
z.object({
|
|
1494
|
+
node: nodeSchema,
|
|
1495
|
+
registration: nodeRegistrationSchema
|
|
1496
|
+
}).meta({ id: "CreatedNode" }).meta({ id: "RenewedNodeRegistration" });
|
|
1497
|
+
z.object({
|
|
1498
|
+
token: z.string().min(1).max(256),
|
|
1499
|
+
version: z.string().min(1).max(128),
|
|
1500
|
+
engine: z.enum(runtimeEngineKinds),
|
|
1501
|
+
host: nodeHostSchema.nullable(),
|
|
1502
|
+
capabilities: nodeCapabilitiesSchema
|
|
1503
|
+
}).meta({ id: "RegisterNodeBody" });
|
|
1504
|
+
z.object({
|
|
1505
|
+
nodeId: idSchema,
|
|
1506
|
+
credential: z.string()
|
|
1507
|
+
}).meta({ id: "RegisteredNode" });
|
|
1379
1508
|
//#endregion
|
|
1380
1509
|
//#region ../../packages/contracts/src/identity.ts
|
|
1381
1510
|
const emailSchema = z.email().max(254);
|
|
1382
1511
|
const passwordSchema = z.string().min(12).max(128);
|
|
1383
1512
|
const userStatusSchema = z.enum(["active", "disabled"]);
|
|
1384
|
-
const
|
|
1513
|
+
const organizationNameSchema = z.string().trim().min(2).max(80);
|
|
1514
|
+
const organizationSlugSchema = z.string().regex(slugPattern);
|
|
1515
|
+
const organizationSchema = z.object({
|
|
1385
1516
|
id: idSchema,
|
|
1517
|
+
name: z.string(),
|
|
1518
|
+
slug: organizationSlugSchema,
|
|
1519
|
+
createdAt: z.iso.datetime()
|
|
1520
|
+
}).meta({ id: "Organization" });
|
|
1521
|
+
z.object({ items: z.array(organizationSchema) }).meta({ id: "OrganizationList" });
|
|
1522
|
+
z.object({ organizationId: idSchema });
|
|
1523
|
+
z.object({
|
|
1386
1524
|
organizationId: idSchema,
|
|
1525
|
+
userId: idSchema
|
|
1526
|
+
});
|
|
1527
|
+
const meSchema = z.object({
|
|
1528
|
+
id: idSchema,
|
|
1387
1529
|
email: emailSchema,
|
|
1388
|
-
|
|
1530
|
+
organization: organizationSchema,
|
|
1531
|
+
organizationRole: organizationRoleSchema,
|
|
1532
|
+
platformAdmin: z.boolean(),
|
|
1389
1533
|
locale: localeSchema,
|
|
1390
1534
|
mustChangePassword: z.boolean()
|
|
1391
1535
|
}).meta({ id: "Me" });
|
|
1392
1536
|
const userSchema = z.object({
|
|
1393
1537
|
id: idSchema,
|
|
1394
1538
|
email: emailSchema,
|
|
1395
|
-
|
|
1539
|
+
organizationRole: organizationRoleSchema,
|
|
1540
|
+
platformAdmin: z.boolean(),
|
|
1396
1541
|
status: userStatusSchema,
|
|
1397
1542
|
locale: localeSchema,
|
|
1398
1543
|
mustChangePassword: z.boolean(),
|
|
@@ -1408,20 +1553,33 @@ z.object({
|
|
|
1408
1553
|
newPassword: passwordSchema
|
|
1409
1554
|
}).meta({ id: "ChangePasswordBody" });
|
|
1410
1555
|
z.object({ locale: localeSchema }).meta({ id: "ChangeLocaleBody" });
|
|
1411
|
-
z.object({
|
|
1556
|
+
const createUserBodySchema = z.object({
|
|
1412
1557
|
email: emailSchema,
|
|
1413
|
-
|
|
1558
|
+
organizationRole: organizationRoleSchema,
|
|
1414
1559
|
locale: localeSchema,
|
|
1415
1560
|
initialPassword: passwordSchema.optional()
|
|
1416
1561
|
}).meta({ id: "CreateUserBody" });
|
|
1417
|
-
z.object({
|
|
1562
|
+
const createdUserSchema = z.object({
|
|
1418
1563
|
user: userSchema,
|
|
1419
1564
|
temporaryPassword: z.string().nullable()
|
|
1420
1565
|
}).meta({ id: "CreatedUser" });
|
|
1421
|
-
z.object({
|
|
1566
|
+
z.object({ organizationRole: organizationRoleSchema }).meta({ id: "UpdateUserBody" });
|
|
1567
|
+
z.object({
|
|
1568
|
+
name: organizationNameSchema,
|
|
1569
|
+
slug: organizationSlugSchema.optional(),
|
|
1570
|
+
admin: z.object({
|
|
1571
|
+
email: emailSchema,
|
|
1572
|
+
locale: localeSchema,
|
|
1573
|
+
initialPassword: passwordSchema.optional()
|
|
1574
|
+
})
|
|
1575
|
+
}).meta({ id: "CreateOrganizationBody" });
|
|
1422
1576
|
z.object({ temporaryPassword: z.string() }).meta({ id: "TemporaryPassword" });
|
|
1423
1577
|
z.object({ items: z.array(userSchema) }).meta({ id: "UserList" });
|
|
1424
1578
|
z.object({ id: idSchema });
|
|
1579
|
+
z.object({
|
|
1580
|
+
organization: organizationSchema,
|
|
1581
|
+
admin: createdUserSchema
|
|
1582
|
+
}).meta({ id: "CreatedOrganization" });
|
|
1425
1583
|
const accessTokenNameSchema = z.string().trim().min(1).max(80);
|
|
1426
1584
|
const accessTokenSchema = z.object({
|
|
1427
1585
|
id: idSchema,
|
|
@@ -1481,6 +1639,8 @@ z.discriminatedUnion("status", [cliTokenWaitingSchema, cliTokenReadySchema]).met
|
|
|
1481
1639
|
z.object({ version: z.string() }).meta({ id: "PlatformVersion" });
|
|
1482
1640
|
const auditActionSchema = z.enum([
|
|
1483
1641
|
"platform.bootstrapped",
|
|
1642
|
+
"organization.created",
|
|
1643
|
+
"organization.renamed",
|
|
1484
1644
|
"user.created",
|
|
1485
1645
|
"user.role_changed",
|
|
1486
1646
|
"user.disabled",
|
|
@@ -1495,7 +1655,9 @@ const auditActionSchema = z.enum([
|
|
|
1495
1655
|
"project.created",
|
|
1496
1656
|
"project.updated",
|
|
1497
1657
|
"project.member_added",
|
|
1658
|
+
"project.member_role_changed",
|
|
1498
1659
|
"project.member_removed",
|
|
1660
|
+
"project.deleted",
|
|
1499
1661
|
"project.docker_changed",
|
|
1500
1662
|
"credential.created",
|
|
1501
1663
|
"credential.rotated",
|
|
@@ -1526,6 +1688,10 @@ const auditActionSchema = z.enum([
|
|
|
1526
1688
|
"node.volume_resized",
|
|
1527
1689
|
"node.quiesced",
|
|
1528
1690
|
"node.resumed",
|
|
1691
|
+
"node.created",
|
|
1692
|
+
"node.registered",
|
|
1693
|
+
"node.registration_renewed",
|
|
1694
|
+
"node.deleted",
|
|
1529
1695
|
"master_key.reencrypted",
|
|
1530
1696
|
"dead_letter.retried",
|
|
1531
1697
|
"dead_letter.discarded"
|
|
@@ -1545,10 +1711,12 @@ const auditTargetTypeSchema = z.enum([
|
|
|
1545
1711
|
"backup",
|
|
1546
1712
|
"host_operation",
|
|
1547
1713
|
"volume",
|
|
1548
|
-
"queue"
|
|
1714
|
+
"queue",
|
|
1715
|
+
"node"
|
|
1549
1716
|
]);
|
|
1550
1717
|
const auditEntrySchema = z.object({
|
|
1551
1718
|
id: idSchema,
|
|
1719
|
+
organizationId: idSchema.nullable(),
|
|
1552
1720
|
occurredAt: z.iso.datetime(),
|
|
1553
1721
|
actor: z.object({
|
|
1554
1722
|
kind: z.enum(["user", "system"]),
|
|
@@ -1565,6 +1733,7 @@ const auditEntrySchema = z.object({
|
|
|
1565
1733
|
ip: z.string().nullable()
|
|
1566
1734
|
}).meta({ id: "AuditEntry" });
|
|
1567
1735
|
cursorQuerySchema.extend({
|
|
1736
|
+
organizationId: idSchema.optional(),
|
|
1568
1737
|
action: auditActionSchema.optional(),
|
|
1569
1738
|
actorUserId: idSchema.optional(),
|
|
1570
1739
|
from: z.iso.datetime().optional(),
|
|
@@ -1577,6 +1746,7 @@ const slugSchema = z.string().regex(slugPattern);
|
|
|
1577
1746
|
const colorSchema = z.string().regex(hexColorPattern);
|
|
1578
1747
|
const projectSchema = z.object({
|
|
1579
1748
|
id: idSchema,
|
|
1749
|
+
organizationId: idSchema,
|
|
1580
1750
|
name: z.string(),
|
|
1581
1751
|
slug: slugSchema,
|
|
1582
1752
|
description: z.string(),
|
|
@@ -1589,26 +1759,27 @@ const projectSchema = z.object({
|
|
|
1589
1759
|
const memberSchema = z.object({
|
|
1590
1760
|
userId: idSchema,
|
|
1591
1761
|
email: z.email(),
|
|
1592
|
-
role:
|
|
1762
|
+
role: projectRoleSchema,
|
|
1593
1763
|
status: userStatusSchema,
|
|
1594
1764
|
addedAt: z.iso.datetime()
|
|
1595
1765
|
}).meta({ id: "Member" });
|
|
1596
1766
|
projectSchema.extend({ members: z.array(memberSchema) }).meta({ id: "ProjectDetail" });
|
|
1597
1767
|
z.object({ items: z.array(projectSchema) }).meta({ id: "ProjectList" });
|
|
1598
1768
|
z.object({ items: z.array(memberSchema) }).meta({ id: "MemberList" });
|
|
1599
|
-
z.object({
|
|
1769
|
+
const createProjectBodySchema = z.object({
|
|
1600
1770
|
name: z.string().trim().min(2).max(80),
|
|
1601
1771
|
slug: slugSchema.optional(),
|
|
1602
1772
|
description: z.string().max(500),
|
|
1603
1773
|
color: colorSchema
|
|
1604
1774
|
}).meta({ id: "CreateProjectBody" });
|
|
1605
|
-
z.object({
|
|
1775
|
+
const updateProjectBodySchema = z.object({
|
|
1606
1776
|
name: z.string().trim().min(2).max(80),
|
|
1607
1777
|
slug: slugSchema,
|
|
1608
1778
|
description: z.string().max(500),
|
|
1609
1779
|
color: colorSchema
|
|
1610
1780
|
}).meta({ id: "UpdateProjectBody" });
|
|
1611
1781
|
z.object({ allowed: z.boolean() }).meta({ id: "SetProjectDockerBody" });
|
|
1782
|
+
z.object({ role: projectRoleSchema }).meta({ id: "SetProjectMemberBody" });
|
|
1612
1783
|
z.object({ id: idSchema });
|
|
1613
1784
|
z.object({
|
|
1614
1785
|
id: idSchema,
|
|
@@ -2063,6 +2234,11 @@ const agentListItemSchema = z.object({
|
|
|
2063
2234
|
availableActions: z.array(agentActionSchema),
|
|
2064
2235
|
updatedAt: z.iso.datetime()
|
|
2065
2236
|
}).meta({ id: "AgentListItem" });
|
|
2237
|
+
const agentNodeSchema = z.object({
|
|
2238
|
+
id: idSchema,
|
|
2239
|
+
name: nodeNameSchema,
|
|
2240
|
+
state: nodeStateSchema
|
|
2241
|
+
}).meta({ id: "AgentNode" });
|
|
2066
2242
|
agentListItemSchema.extend({
|
|
2067
2243
|
version: z.int().nonnegative(),
|
|
2068
2244
|
description: z.string(),
|
|
@@ -2077,6 +2253,7 @@ agentListItemSchema.extend({
|
|
|
2077
2253
|
toolAssignments: z.array(agentToolSchema),
|
|
2078
2254
|
release: releaseSummarySchema.nullable(),
|
|
2079
2255
|
runtime: agentRuntimeSchema,
|
|
2256
|
+
node: agentNodeSchema.nullable(),
|
|
2080
2257
|
runtimeUid: z.int().positive().nullable(),
|
|
2081
2258
|
sandbox: agentSandboxSchema.nullable(),
|
|
2082
2259
|
createdAt: z.iso.datetime()
|
|
@@ -2099,6 +2276,7 @@ const createAgentBodySchema = z.object({
|
|
|
2099
2276
|
slug: slugSchema.optional(),
|
|
2100
2277
|
description: agentDescriptionSchema,
|
|
2101
2278
|
kikimoraVersion: kikimoraVersionSchema,
|
|
2279
|
+
nodeId: idSchema,
|
|
2102
2280
|
claudeCredentialId: idSchema,
|
|
2103
2281
|
githubCredentialId: idSchema,
|
|
2104
2282
|
repositories: agentRepositoryLinksSchema,
|
|
@@ -2370,7 +2548,7 @@ z.object({
|
|
|
2370
2548
|
pending: z.array(idSchema)
|
|
2371
2549
|
}),
|
|
2372
2550
|
capacity: capacityForecastSchema
|
|
2373
|
-
}).meta({ id: "
|
|
2551
|
+
}).meta({ id: "PlatformNode" });
|
|
2374
2552
|
z.object({ role: volumeRoleSchema });
|
|
2375
2553
|
z.object({ sizeGb: z.int().positive() }).meta({ id: "ResizeVolumeBody" });
|
|
2376
2554
|
z.object({ agents: z.int().nonnegative() }).meta({ id: "QuiesceResult" });
|
|
@@ -2476,6 +2654,7 @@ z.object({
|
|
|
2476
2654
|
version: z.string(),
|
|
2477
2655
|
apiImage: installedImageSchema,
|
|
2478
2656
|
webImage: installedImageSchema,
|
|
2657
|
+
nodeImage: installedImageSchema,
|
|
2479
2658
|
latest: z.string().nullable(),
|
|
2480
2659
|
latestError: registryUnavailableSchema.nullable(),
|
|
2481
2660
|
versions: z.array(z.string()),
|
|
@@ -2566,11 +2745,16 @@ const agentUsageSchema = z.object({
|
|
|
2566
2745
|
blockReadBytes: byteCountSchema,
|
|
2567
2746
|
blockWriteBytes: byteCountSchema
|
|
2568
2747
|
}).meta({ id: "AgentUsage" });
|
|
2748
|
+
const nodeHostSeriesSchema = z.object({
|
|
2749
|
+
nodeId: idSchema,
|
|
2750
|
+
nodeName: z.string(),
|
|
2751
|
+
points: z.array(hostPointSchema)
|
|
2752
|
+
}).meta({ id: "NodeHostSeries" });
|
|
2569
2753
|
z.object({
|
|
2570
2754
|
resolution: metricsResolutionSchema,
|
|
2571
2755
|
from: instantSchema,
|
|
2572
2756
|
to: instantSchema,
|
|
2573
|
-
|
|
2757
|
+
nodes: z.array(nodeHostSeriesSchema),
|
|
2574
2758
|
agents: z.array(agentUsageSchema)
|
|
2575
2759
|
}).meta({ id: "HostMetrics" });
|
|
2576
2760
|
z.object({
|
|
@@ -2659,7 +2843,11 @@ z.strictObject({ label: textParam }), z.strictObject({
|
|
|
2659
2843
|
}), z.strictObject({
|
|
2660
2844
|
agentName: textParam,
|
|
2661
2845
|
failureCount: countParam
|
|
2662
|
-
}), z.strictObject({ agentName: textParam }), z.strictObject({
|
|
2846
|
+
}), z.strictObject({ agentName: textParam }), z.strictObject({
|
|
2847
|
+
node: textParam,
|
|
2848
|
+
ratio: ratioParam
|
|
2849
|
+
}), z.strictObject({
|
|
2850
|
+
node: textParam,
|
|
2663
2851
|
filesystem: filesystemNameSchema,
|
|
2664
2852
|
ratio: ratioParam
|
|
2665
2853
|
}), z.strictObject({ backupKind: backupKindSchema }), z.strictObject({ hours: z.int().positive() }), z.strictObject({
|
|
@@ -2842,10 +3030,90 @@ const createAdapters = (context) => ({
|
|
|
2842
3030
|
clientName: () => `kcloud on ${hostname()}`
|
|
2843
3031
|
});
|
|
2844
3032
|
//#endregion
|
|
2845
|
-
//#region src/
|
|
3033
|
+
//#region src/resolve.ts
|
|
3034
|
+
const organizationHints = { "organization.not_found": "kcloud orgs list shows the organizations you can see." };
|
|
3035
|
+
const isId = (reference) => idSchema.safeParse(reference).success;
|
|
3036
|
+
const once = (load) => {
|
|
3037
|
+
let loaded = null;
|
|
3038
|
+
return () => loaded ??= load();
|
|
3039
|
+
};
|
|
3040
|
+
const slugsOf = (items) => items.map(({ slug }) => slug).join(", ");
|
|
3041
|
+
const organizationIn = (organizations, reference) => {
|
|
3042
|
+
const found = organizations.filter((organization) => organization.id === reference.toLowerCase() || organization.slug === reference || organization.name === reference);
|
|
3043
|
+
const [first] = found;
|
|
3044
|
+
if (first === void 0) throw new CliError(`There is no organization ${reference} you can see.`, organizations.length === 0 ? "kcloud orgs list shows the organizations you can see." : `kcloud orgs list shows ${slugsOf(organizations)}.`);
|
|
3045
|
+
if (found.length > 1) throw new CliError(`${reference} names ${found.length} organizations.`, "Name it by its slug or by its id instead.");
|
|
3046
|
+
return first;
|
|
3047
|
+
};
|
|
3048
|
+
const scopeOf = (api, reference) => {
|
|
3049
|
+
const me = once(async () => dataOf("say who the token belongs to", await api.GET("/auth/me")));
|
|
3050
|
+
const organizations = once(async () => dataOf("list the organizations", await api.GET("/organizations")).items);
|
|
3051
|
+
return {
|
|
3052
|
+
api,
|
|
3053
|
+
me,
|
|
3054
|
+
organizations,
|
|
3055
|
+
organization: once(async () => reference === null ? (await me()).organization : organizationIn(await organizations(), reference))
|
|
3056
|
+
};
|
|
3057
|
+
};
|
|
3058
|
+
const projectsOf = async (scope) => {
|
|
3059
|
+
const { id } = await scope.organization();
|
|
3060
|
+
return dataOf("list the projects", await scope.api.GET("/organizations/{organizationId}/projects", { params: { path: { organizationId: id } } }), organizationHints).items;
|
|
3061
|
+
};
|
|
3062
|
+
const matches = (reference, project) => project.slug === reference || project.name === reference;
|
|
3063
|
+
const resolveProjectId = async (scope, reference) => {
|
|
3064
|
+
if (isId(reference)) return reference;
|
|
3065
|
+
const projects = await projectsOf(scope);
|
|
3066
|
+
const found = projects.filter((project) => matches(reference, project));
|
|
3067
|
+
const [first] = found;
|
|
3068
|
+
if (first === void 0) {
|
|
3069
|
+
const { slug } = await scope.organization();
|
|
3070
|
+
throw new CliError(`There is no project ${reference} you can see in ${slug}.`, projects.length === 0 ? "kcloud projects list shows the projects you are a member of." : `kcloud projects list shows ${slugsOf(projects)}.`);
|
|
3071
|
+
}
|
|
3072
|
+
if (found.length > 1) throw new CliError(`${reference} names ${found.length} projects.`, "Name it by its slug or by its id instead.");
|
|
3073
|
+
return first.id;
|
|
3074
|
+
};
|
|
3075
|
+
const agentsOfProject = async (api, projectId) => dataOf("list the agents of the project", await api.GET("/projects/{id}/agents", { params: { path: { id: projectId } } })).items;
|
|
3076
|
+
const resolveAgentId = async (scope, reference, project) => {
|
|
3077
|
+
if (isId(reference)) return reference;
|
|
3078
|
+
const [left, right] = reference.split("/");
|
|
3079
|
+
const projectReference = right === void 0 ? project : left;
|
|
3080
|
+
const agentReference = right ?? left ?? "";
|
|
3081
|
+
const searched = projectReference === void 0 ? await projectsOf(scope) : [{
|
|
3082
|
+
id: await resolveProjectId(scope, projectReference),
|
|
3083
|
+
slug: projectReference
|
|
3084
|
+
}];
|
|
3085
|
+
const found = [];
|
|
3086
|
+
for (const candidate of searched) for (const agent of await agentsOfProject(scope.api, candidate.id)) if (agent.slug === agentReference || agent.name === agentReference) found.push({
|
|
3087
|
+
agent,
|
|
3088
|
+
project: candidate
|
|
3089
|
+
});
|
|
3090
|
+
const [first] = found;
|
|
3091
|
+
if (first === void 0) throw new CliError(`There is no agent ${reference} you can see.`, "Name it as <project>/<agent>, or pass --project; kcloud agents list shows them.");
|
|
3092
|
+
if (found.length > 1) throw new CliError(`${reference} names an agent in ${found.map(({ project }) => project.slug).join(", ")}.`, "Name it as <project>/<agent> instead.");
|
|
3093
|
+
return first.agent.id;
|
|
3094
|
+
};
|
|
3095
|
+
const sameEmail = (left, right) => left.toLowerCase() === right.toLowerCase();
|
|
3096
|
+
const organizationMembersOf = async (api, organizationId) => dataOf("list the members of the organization", await api.GET("/organizations/{organizationId}/members", { params: { path: { organizationId } } }), organizationHints).items;
|
|
3097
|
+
const resolveOrganizationMember = async (api, organizationId, reference) => {
|
|
3098
|
+
const found = (await organizationMembersOf(api, organizationId)).find((member) => member.id === reference.toLowerCase() || sameEmail(member.email, reference));
|
|
3099
|
+
if (found === void 0) throw new CliError(`The organization has no member ${reference}.`, "kcloud orgs members list shows them.");
|
|
3100
|
+
return found;
|
|
3101
|
+
};
|
|
3102
|
+
//#endregion
|
|
3103
|
+
//#region src/instance/config.ts
|
|
2846
3104
|
const configFileName = "cli.json";
|
|
2847
3105
|
const stateDirectoryName = ".kikimora-cloud";
|
|
2848
|
-
const
|
|
3106
|
+
const configFileOf = (homeDirectory) => join(homeDirectory, stateDirectoryName, configFileName);
|
|
3107
|
+
const instanceSchema = z.strictObject({
|
|
3108
|
+
token: z.string().min(1),
|
|
3109
|
+
tokenId: z.string().min(1).nullable(),
|
|
3110
|
+
email: z.string().min(1).nullable(),
|
|
3111
|
+
organization: z.string().min(1).nullable()
|
|
3112
|
+
});
|
|
3113
|
+
const instanceStoreSchema = z.strictObject({
|
|
3114
|
+
version: z.literal(2),
|
|
3115
|
+
instances: z.record(z.url(), instanceSchema)
|
|
3116
|
+
});
|
|
2849
3117
|
const profileSchema = z.strictObject({
|
|
2850
3118
|
origin: z.url(),
|
|
2851
3119
|
token: z.string().min(1),
|
|
@@ -2857,97 +3125,88 @@ const profileStoreSchema = z.strictObject({
|
|
|
2857
3125
|
current: z.string().min(1).nullable(),
|
|
2858
3126
|
profiles: z.record(z.string().min(1), profileSchema)
|
|
2859
3127
|
});
|
|
2860
|
-
const
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
|
|
3128
|
+
const storedSchema = z.discriminatedUnion("version", [instanceStoreSchema, profileStoreSchema]);
|
|
3129
|
+
const emptyInstanceStore = {
|
|
3130
|
+
version: 2,
|
|
3131
|
+
instances: {}
|
|
2864
3132
|
};
|
|
2865
|
-
const
|
|
2866
|
-
const
|
|
3133
|
+
const instancesOfProfiles = ({ current, profiles }) => {
|
|
3134
|
+
const currentLast = Object.entries(profiles).toSorted(([left], [right]) => Number(left === current) - Number(right === current));
|
|
3135
|
+
return {
|
|
3136
|
+
version: 2,
|
|
3137
|
+
instances: Object.fromEntries(currentLast.map(([, { origin, token, tokenId, email }]) => [new URL(origin).origin, {
|
|
3138
|
+
token,
|
|
3139
|
+
tokenId,
|
|
3140
|
+
email,
|
|
3141
|
+
organization: null
|
|
3142
|
+
}]))
|
|
3143
|
+
};
|
|
3144
|
+
};
|
|
3145
|
+
const readInstanceStore = async (homeDirectory) => {
|
|
3146
|
+
const path = configFileOf(homeDirectory);
|
|
2867
3147
|
const content = await readPrivateFile(path);
|
|
2868
|
-
if (content === null) return
|
|
3148
|
+
if (content === null) return emptyInstanceStore;
|
|
2869
3149
|
let document;
|
|
2870
3150
|
try {
|
|
2871
3151
|
document = JSON.parse(content);
|
|
2872
3152
|
} catch {
|
|
2873
3153
|
throw new CliError(`${path} is not JSON.`, `Fix it or delete it, then log in again.`);
|
|
2874
3154
|
}
|
|
2875
|
-
const parsed =
|
|
3155
|
+
const parsed = storedSchema.safeParse(document);
|
|
2876
3156
|
if (!parsed.success) throw new CliError(`${path} is not what ${configFileName} should hold.`, z.prettifyError(parsed.error));
|
|
2877
|
-
return parsed.data;
|
|
3157
|
+
return parsed.data.version === 2 ? parsed.data : instancesOfProfiles(parsed.data);
|
|
2878
3158
|
};
|
|
2879
|
-
const
|
|
2880
|
-
await writePrivateFile(
|
|
3159
|
+
const writeInstanceStore = async (homeDirectory, store) => {
|
|
3160
|
+
await writePrivateFile(configFileOf(homeDirectory), `${JSON.stringify(store, null, 2)}\n`);
|
|
2881
3161
|
};
|
|
2882
|
-
const
|
|
2883
|
-
version:
|
|
2884
|
-
|
|
2885
|
-
|
|
2886
|
-
|
|
2887
|
-
[name]: profile
|
|
3162
|
+
const withInstance = (store, origin, instance) => ({
|
|
3163
|
+
version: 2,
|
|
3164
|
+
instances: {
|
|
3165
|
+
...store.instances,
|
|
3166
|
+
[origin]: instance
|
|
2888
3167
|
}
|
|
2889
3168
|
});
|
|
2890
|
-
const
|
|
2891
|
-
|
|
2892
|
-
|
|
2893
|
-
|
|
2894
|
-
version: 1,
|
|
2895
|
-
current: store.current === name ? remaining[0] ?? null : store.current,
|
|
2896
|
-
profiles
|
|
2897
|
-
};
|
|
2898
|
-
};
|
|
2899
|
-
const profileNameOf = (origin) => {
|
|
2900
|
-
const { hostname } = new URL(origin);
|
|
2901
|
-
return hostname;
|
|
2902
|
-
};
|
|
3169
|
+
const withoutInstance = (store, origin) => ({
|
|
3170
|
+
version: 2,
|
|
3171
|
+
instances: Object.fromEntries(Object.entries(store.instances).filter(([candidate]) => candidate !== origin))
|
|
3172
|
+
});
|
|
2903
3173
|
//#endregion
|
|
2904
|
-
//#region src/
|
|
3174
|
+
//#region src/instance/session.ts
|
|
2905
3175
|
const urlVariable = "KIKIMORA_CLOUD_URL";
|
|
2906
3176
|
const tokenVariable = "KIKIMORA_CLOUD_TOKEN";
|
|
2907
|
-
const
|
|
2908
|
-
const
|
|
2909
|
-
const
|
|
2910
|
-
|
|
2911
|
-
const known = Object.keys(store.profiles);
|
|
2912
|
-
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(", ")}.`);
|
|
2913
|
-
}
|
|
2914
|
-
return [name, profile];
|
|
3177
|
+
const organizationVariable = "KIKIMORA_CLOUD_ORG";
|
|
3178
|
+
const givenIn = (environment, name) => {
|
|
3179
|
+
const value = environment[name]?.trim();
|
|
3180
|
+
return value === void 0 || value === "" ? null : value;
|
|
2915
3181
|
};
|
|
2916
|
-
const
|
|
2917
|
-
|
|
2918
|
-
|
|
3182
|
+
const originFor = (settings, context) => originOf(settings.origin ?? givenIn(context.environment, "KIKIMORA_CLOUD_URL") ?? "https://cloud.kikimora.dev");
|
|
3183
|
+
const notSignedIn = (origin, store) => {
|
|
3184
|
+
const known = Object.keys(store.instances);
|
|
3185
|
+
return new CliError(`Not signed in to ${origin}.`, known.length === 0 ? "Run kcloud login (add --url for another instance)." : `Run kcloud login, or add --url for ${known.join(" or ")}.`);
|
|
2919
3186
|
};
|
|
2920
3187
|
const openSession = async ({ settings, context, api = createApi }) => {
|
|
2921
|
-
const
|
|
2922
|
-
const
|
|
2923
|
-
|
|
2924
|
-
|
|
2925
|
-
|
|
2926
|
-
|
|
2927
|
-
|
|
2928
|
-
origin: originOf(origin),
|
|
2929
|
-
token: fromEnvironment
|
|
2930
|
-
}),
|
|
2931
|
-
origin: originOf(origin),
|
|
2932
|
-
profileName: null,
|
|
2933
|
-
store
|
|
2934
|
-
};
|
|
2935
|
-
}
|
|
2936
|
-
const [name, profile] = settings.profile === null ? currentProfileOf(store) : namedProfileOf(store, settings.profile);
|
|
2937
|
-
const origin = originOf(settings.origin ?? profile.origin);
|
|
3188
|
+
const origin = originFor(settings, context);
|
|
3189
|
+
const store = await readInstanceStore(context.homeDirectory);
|
|
3190
|
+
const stored = store.instances[origin] ?? null;
|
|
3191
|
+
const fromEnvironment = givenIn(context.environment, tokenVariable);
|
|
3192
|
+
const token = fromEnvironment ?? stored?.token ?? null;
|
|
3193
|
+
if (token === null) throw notSignedIn(origin, store);
|
|
3194
|
+
const organization = settings.organization ?? givenIn(context.environment, "KIKIMORA_CLOUD_ORG") ?? stored?.organization ?? null;
|
|
2938
3195
|
return {
|
|
2939
|
-
api
|
|
3196
|
+
...scopeOf(api({
|
|
2940
3197
|
origin,
|
|
2941
|
-
token
|
|
2942
|
-
}),
|
|
3198
|
+
token
|
|
3199
|
+
}), organization),
|
|
2943
3200
|
origin,
|
|
2944
|
-
|
|
2945
|
-
|
|
3201
|
+
token,
|
|
3202
|
+
tokenFromEnvironment: fromEnvironment !== null,
|
|
3203
|
+
store,
|
|
3204
|
+
stored
|
|
2946
3205
|
};
|
|
2947
3206
|
};
|
|
2948
3207
|
//#endregion
|
|
2949
3208
|
//#region src/commands/auth/account.ts
|
|
2950
|
-
const passwordLength = `between ${passwordSchema.minLength ?? 0} and ${passwordSchema.maxLength ?? 0} characters`;
|
|
3209
|
+
const passwordLength$1 = `between ${passwordSchema.minLength ?? 0} and ${passwordSchema.maxLength ?? 0} characters`;
|
|
2951
3210
|
const createAuthCommand = (adaptersOf) => defineGroup({
|
|
2952
3211
|
name: "auth",
|
|
2953
3212
|
summary: "Your own account on this instance.",
|
|
@@ -2975,8 +3234,8 @@ const createAuthCommand = (adaptersOf) => defineGroup({
|
|
|
2975
3234
|
validate: (value) => value === "" ? "cannot be empty" : void 0
|
|
2976
3235
|
});
|
|
2977
3236
|
const newPassword = await prompt.password({
|
|
2978
|
-
message: `Your new password, ${passwordLength}`,
|
|
2979
|
-
validate: (value) => passwordSchema.safeParse(value).success ? void 0 : passwordLength
|
|
3237
|
+
message: `Your new password, ${passwordLength$1}`,
|
|
3238
|
+
validate: (value) => passwordSchema.safeParse(value).success ? void 0 : passwordLength$1
|
|
2980
3239
|
});
|
|
2981
3240
|
doneWith("change the password", await session.api.PATCH("/auth/me/password", { body: {
|
|
2982
3241
|
currentPassword,
|
|
@@ -3019,16 +3278,7 @@ const createAuthCommand = (adaptersOf) => defineGroup({
|
|
|
3019
3278
|
});
|
|
3020
3279
|
//#endregion
|
|
3021
3280
|
//#region src/commands/auth/login.ts
|
|
3022
|
-
const usage = "login [--url <origin>] [--with-token]
|
|
3023
|
-
const originFor = async (settings, context) => {
|
|
3024
|
-
const given = settings.origin ?? context.environment["KIKIMORA_CLOUD_URL"];
|
|
3025
|
-
if (given !== void 0 && given !== "") return originOf(given);
|
|
3026
|
-
const store = await readProfileStore(context.homeDirectory);
|
|
3027
|
-
const named = settings.profile ?? store.current;
|
|
3028
|
-
const known = named === null ? void 0 : store.profiles[named];
|
|
3029
|
-
if (known !== void 0) return originOf(known.origin);
|
|
3030
|
-
throw new UsageError("login needs the address of an instance.", `Pass --url <origin> or set ${urlVariable}.`);
|
|
3031
|
-
};
|
|
3281
|
+
const usage = "login [--url <origin>] [--with-token]";
|
|
3032
3282
|
const isTerminal$2 = (stream) => stream.isTTY === true;
|
|
3033
3283
|
const pipedText$2 = async (context) => {
|
|
3034
3284
|
if (isTerminal$2(context.io.stdin)) return "";
|
|
@@ -3037,8 +3287,8 @@ const pipedText$2 = async (context) => {
|
|
|
3037
3287
|
return Buffer.concat(chunks).toString("utf8").trim();
|
|
3038
3288
|
};
|
|
3039
3289
|
const readToken = async (context, settings) => {
|
|
3040
|
-
const fromEnvironment = context.environment
|
|
3041
|
-
if (fromEnvironment !==
|
|
3290
|
+
const fromEnvironment = givenIn(context.environment, tokenVariable);
|
|
3291
|
+
if (fromEnvironment !== null) return fromEnvironment;
|
|
3042
3292
|
const piped = await pipedText$2(context);
|
|
3043
3293
|
if (piped !== "") return piped;
|
|
3044
3294
|
if (settings.yes) throw new UsageError("login --with-token found no token to use.", `Pipe it on stdin or set ${tokenVariable}.`);
|
|
@@ -3046,9 +3296,13 @@ const readToken = async (context, settings) => {
|
|
|
3046
3296
|
};
|
|
3047
3297
|
const createLoginCommand = (adaptersOf) => defineCommand({
|
|
3048
3298
|
name: "login",
|
|
3049
|
-
summary: "Sign in to an instance and save its token
|
|
3299
|
+
summary: "Sign in to an instance and save its token for its address.",
|
|
3050
3300
|
usage,
|
|
3051
|
-
optionsUsage: [
|
|
3301
|
+
optionsUsage: [
|
|
3302
|
+
["--with-token", "take a token from stdin or the environment instead of a browser"],
|
|
3303
|
+
["", `the address comes from --url or ${urlVariable}, or is ${defaultOrigin}`],
|
|
3304
|
+
["", `the token of --with-token comes from stdin or ${tokenVariable}`]
|
|
3305
|
+
],
|
|
3052
3306
|
options: { "with-token": {
|
|
3053
3307
|
type: "boolean",
|
|
3054
3308
|
default: false
|
|
@@ -3059,7 +3313,7 @@ const createLoginCommand = (adaptersOf) => defineCommand({
|
|
|
3059
3313
|
},
|
|
3060
3314
|
run: async ({ values, settings, context }) => {
|
|
3061
3315
|
const adapters = adaptersOf(context);
|
|
3062
|
-
const origin =
|
|
3316
|
+
const origin = originFor(settings, context);
|
|
3063
3317
|
const prompt = adapters.prompter(settings);
|
|
3064
3318
|
const anonymous = adapters.api({
|
|
3065
3319
|
origin,
|
|
@@ -3079,16 +3333,16 @@ const createLoginCommand = (adaptersOf) => defineCommand({
|
|
|
3079
3333
|
const me = dataOf("say who the token belongs to", await identified.GET("/auth/me"));
|
|
3080
3334
|
await save({
|
|
3081
3335
|
context,
|
|
3082
|
-
settings,
|
|
3083
3336
|
origin,
|
|
3084
3337
|
secret,
|
|
3085
3338
|
me
|
|
3086
3339
|
});
|
|
3087
3340
|
const version = await instanceVersion(identified);
|
|
3088
|
-
prompt.outro(`Signed in to ${origin} as ${me.email}${version === null ? "" : ` on ${version}`}.`);
|
|
3341
|
+
prompt.outro(`Signed in to ${origin} as ${me.email} of ${me.organization.name}${version === null ? "" : ` on ${version}`}.`);
|
|
3089
3342
|
if (settings.json) context.terminal.json({
|
|
3090
3343
|
origin,
|
|
3091
3344
|
email: me.email,
|
|
3345
|
+
organization: me.organization.slug,
|
|
3092
3346
|
version
|
|
3093
3347
|
});
|
|
3094
3348
|
return exitCodes.ok;
|
|
@@ -3124,14 +3378,14 @@ const authorizeInBrowser = async ({ adapters, api, origin, prompt }) => {
|
|
|
3124
3378
|
}
|
|
3125
3379
|
}
|
|
3126
3380
|
};
|
|
3127
|
-
const save = async ({ context,
|
|
3128
|
-
const store = await
|
|
3129
|
-
const
|
|
3130
|
-
await
|
|
3131
|
-
origin,
|
|
3381
|
+
const save = async ({ context, origin, secret, me }) => {
|
|
3382
|
+
const store = await readInstanceStore(context.homeDirectory);
|
|
3383
|
+
const previous = store.instances[origin];
|
|
3384
|
+
await writeInstanceStore(context.homeDirectory, withInstance(store, origin, {
|
|
3132
3385
|
token: secret,
|
|
3133
3386
|
tokenId: null,
|
|
3134
|
-
email: me.email
|
|
3387
|
+
email: me.email,
|
|
3388
|
+
organization: previous?.email === me.email ? previous.organization : null
|
|
3135
3389
|
}));
|
|
3136
3390
|
};
|
|
3137
3391
|
const instanceVersion = async (api) => {
|
|
@@ -3140,11 +3394,15 @@ const instanceVersion = async (api) => {
|
|
|
3140
3394
|
};
|
|
3141
3395
|
//#endregion
|
|
3142
3396
|
//#region src/commands/auth/logout.ts
|
|
3397
|
+
const outcomeOf = (session, kept) => {
|
|
3398
|
+
if (session.tokenFromEnvironment) return kept ? `${tokenVariable} signs in to ${session.origin}, so nothing is stored here to forget.` : `Revoked the token of ${tokenVariable} on ${session.origin}.`;
|
|
3399
|
+
return kept ? `Forgot ${session.origin} here. Its token is still usable.` : `Revoked the token of ${session.origin} and forgot it here.`;
|
|
3400
|
+
};
|
|
3143
3401
|
const createLogoutCommand = (adaptersOf) => defineCommand({
|
|
3144
3402
|
name: "logout",
|
|
3145
|
-
summary: "Revoke the token of
|
|
3146
|
-
usage: "logout [--keep-token]",
|
|
3147
|
-
optionsUsage: [["--keep-token", "forget the
|
|
3403
|
+
summary: "Revoke the token of an instance and forget it here.",
|
|
3404
|
+
usage: "logout [--url <origin>] [--keep-token]",
|
|
3405
|
+
optionsUsage: [["--keep-token", "forget the instance here and leave its token usable elsewhere"]],
|
|
3148
3406
|
options: { "keep-token": {
|
|
3149
3407
|
type: "boolean",
|
|
3150
3408
|
default: false
|
|
@@ -3160,12 +3418,10 @@ const createLogoutCommand = (adaptersOf) => defineCommand({
|
|
|
3160
3418
|
context,
|
|
3161
3419
|
api: adapters.api
|
|
3162
3420
|
});
|
|
3163
|
-
|
|
3164
|
-
if (session.
|
|
3165
|
-
|
|
3166
|
-
|
|
3167
|
-
}
|
|
3168
|
-
context.terminal.line(values["keep-token"] ? `Forgot ${session.origin} here; its token is still usable.` : `Revoked the token of ${session.origin} and forgot it here.`);
|
|
3421
|
+
const kept = values["keep-token"];
|
|
3422
|
+
if (!kept) doneWith("revoke the token", await session.api.POST("/auth/logout"));
|
|
3423
|
+
if (!session.tokenFromEnvironment) await writeInstanceStore(context.homeDirectory, withoutInstance(session.store, session.origin));
|
|
3424
|
+
context.terminal.line(outcomeOf(session, kept));
|
|
3169
3425
|
return exitCodes.ok;
|
|
3170
3426
|
}
|
|
3171
3427
|
});
|
|
@@ -3228,9 +3484,10 @@ const createVersionCommand = (adaptersOf, cliVersion) => defineCommand({
|
|
|
3228
3484
|
});
|
|
3229
3485
|
//#endregion
|
|
3230
3486
|
//#region src/commands/auth/whoami.ts
|
|
3487
|
+
const organizationNameOf = ({ name, slug }) => `${name} (${slug})`;
|
|
3231
3488
|
const createWhoamiCommand = (adaptersOf) => defineCommand({
|
|
3232
3489
|
name: "whoami",
|
|
3233
|
-
summary: "Say which instance and
|
|
3490
|
+
summary: "Say which instance, account and organization kcloud acts with.",
|
|
3234
3491
|
usage: "whoami",
|
|
3235
3492
|
options: {},
|
|
3236
3493
|
positionals: {
|
|
@@ -3244,20 +3501,23 @@ const createWhoamiCommand = (adaptersOf) => defineCommand({
|
|
|
3244
3501
|
context,
|
|
3245
3502
|
api: adapters.api
|
|
3246
3503
|
});
|
|
3247
|
-
const me =
|
|
3504
|
+
const me = await session.me();
|
|
3505
|
+
const actsIn = await session.organization();
|
|
3248
3506
|
if (settings.json) {
|
|
3249
3507
|
context.terminal.json({
|
|
3250
3508
|
origin: session.origin,
|
|
3251
|
-
|
|
3252
|
-
|
|
3509
|
+
...me,
|
|
3510
|
+
actsIn
|
|
3253
3511
|
});
|
|
3254
3512
|
return exitCodes.ok;
|
|
3255
3513
|
}
|
|
3256
3514
|
context.terminal.line(renderTable([{ header: "FIELD" }, { header: "VALUE" }], [
|
|
3257
3515
|
["instance", session.origin],
|
|
3258
|
-
["profile", session.profileName ?? "the environment"],
|
|
3259
3516
|
["email", me.email],
|
|
3260
|
-
["
|
|
3517
|
+
["organization", organizationNameOf(me.organization)],
|
|
3518
|
+
["role", me.organizationRole],
|
|
3519
|
+
["platform admin", me.platformAdmin ? "yes" : "no"],
|
|
3520
|
+
["acts in", organizationNameOf(actsIn)],
|
|
3261
3521
|
["locale", me.locale]
|
|
3262
3522
|
]));
|
|
3263
3523
|
return exitCodes.ok;
|
|
@@ -3274,6 +3534,9 @@ const runOf = async (adaptersOf, { settings, context }) => {
|
|
|
3274
3534
|
});
|
|
3275
3535
|
return {
|
|
3276
3536
|
api: session.api,
|
|
3537
|
+
me: session.me,
|
|
3538
|
+
organizations: session.organizations,
|
|
3539
|
+
organization: session.organization,
|
|
3277
3540
|
origin: session.origin,
|
|
3278
3541
|
adapters,
|
|
3279
3542
|
prompt: adapters.prompter(settings),
|
|
@@ -3282,40 +3545,6 @@ const runOf = async (adaptersOf, { settings, context }) => {
|
|
|
3282
3545
|
};
|
|
3283
3546
|
};
|
|
3284
3547
|
//#endregion
|
|
3285
|
-
//#region src/resolve.ts
|
|
3286
|
-
const isId = (reference) => idSchema.safeParse(reference).success;
|
|
3287
|
-
const matches = (reference, project) => project.slug === reference || project.name === reference;
|
|
3288
|
-
const projectsOf = async (api) => dataOf("list the projects", await api.GET("/projects")).items;
|
|
3289
|
-
const resolveProjectId = async (api, reference) => {
|
|
3290
|
-
if (isId(reference)) return reference;
|
|
3291
|
-
const projects = await projectsOf(api);
|
|
3292
|
-
const found = projects.filter((project) => matches(reference, project));
|
|
3293
|
-
const [first] = found;
|
|
3294
|
-
if (first === void 0) throw new CliError(`There is no project ${reference} you can see.`, projects.length === 0 ? "kcloud projects list shows the projects you are a member of." : `kcloud projects list shows ${projects.map(({ slug }) => slug).join(", ")}.`);
|
|
3295
|
-
if (found.length > 1) throw new CliError(`${reference} names ${found.length} projects.`, "Name it by its slug or by its id instead.");
|
|
3296
|
-
return first.id;
|
|
3297
|
-
};
|
|
3298
|
-
const agentsOfProject = async (api, projectId) => dataOf("list the agents of the project", await api.GET("/projects/{id}/agents", { params: { path: { id: projectId } } })).items;
|
|
3299
|
-
const resolveAgentId = async (api, reference, project) => {
|
|
3300
|
-
if (isId(reference)) return reference;
|
|
3301
|
-
const [left, right] = reference.split("/");
|
|
3302
|
-
const projectReference = right === void 0 ? project : left;
|
|
3303
|
-
const agentReference = right ?? left ?? "";
|
|
3304
|
-
const searched = projectReference === void 0 ? await projectsOf(api) : [{
|
|
3305
|
-
id: await resolveProjectId(api, projectReference),
|
|
3306
|
-
slug: projectReference
|
|
3307
|
-
}];
|
|
3308
|
-
const found = [];
|
|
3309
|
-
for (const candidate of searched) for (const agent of await agentsOfProject(api, candidate.id)) if (agent.slug === agentReference || agent.name === agentReference) found.push({
|
|
3310
|
-
agent,
|
|
3311
|
-
project: candidate
|
|
3312
|
-
});
|
|
3313
|
-
const [first] = found;
|
|
3314
|
-
if (first === void 0) throw new CliError(`There is no agent ${reference} you can see.`, "Name it as <project>/<agent>, or pass --project; kcloud agents list shows them.");
|
|
3315
|
-
if (found.length > 1) throw new CliError(`${reference} names an agent in ${found.map(({ project }) => project.slug).join(", ")}.`, "Name it as <project>/<agent> instead.");
|
|
3316
|
-
return first.agent.id;
|
|
3317
|
-
};
|
|
3318
|
-
//#endregion
|
|
3319
3548
|
//#region src/commands/agents/shared.ts
|
|
3320
3549
|
const agentOption = { project: { type: "string" } };
|
|
3321
3550
|
const agentUsage = [["--project <name>", "the project of the agent, when the agent is named alone"]];
|
|
@@ -3324,11 +3553,12 @@ const agentHints = {
|
|
|
3324
3553
|
"project.not_found": "kcloud projects list shows the projects you are a member of.",
|
|
3325
3554
|
"agent.invalid_transition": "kcloud agents show <agent> lists the actions it takes now.",
|
|
3326
3555
|
"agent.busy": "The agent is working on another action; try again in a moment.",
|
|
3327
|
-
"concurrency.conflict": "Someone changed the agent meanwhile; read it again and retry."
|
|
3556
|
+
"concurrency.conflict": "Someone changed the agent meanwhile; read it again and retry.",
|
|
3557
|
+
"node.offline": "The node of the agent is not connected; kcloud nodes show <node> tells when it was last seen."
|
|
3328
3558
|
};
|
|
3329
3559
|
const agentFrom = async (run, reference, project) => {
|
|
3330
3560
|
if (reference === void 0 || reference === "") throw new UsageError("This command needs an agent.", "Name it as <project>/<agent>, or pass --project.");
|
|
3331
|
-
return resolveAgentId(run
|
|
3561
|
+
return resolveAgentId(run, reference, project);
|
|
3332
3562
|
};
|
|
3333
3563
|
//#endregion
|
|
3334
3564
|
//#region src/commands/agents/check.ts
|
|
@@ -3403,6 +3633,7 @@ const agentPlanOf = (agent) => ({
|
|
|
3403
3633
|
slug: agent.slug,
|
|
3404
3634
|
description: agent.description,
|
|
3405
3635
|
kikimoraVersion: agent.kikimoraVersion,
|
|
3636
|
+
...agent.node === null ? {} : { nodeId: agent.node.id },
|
|
3406
3637
|
claudeCredentialId: agent.claudeCredential.id,
|
|
3407
3638
|
githubCredentialId: agent.githubCredential.id,
|
|
3408
3639
|
repositories: agent.repositories.map(({ repositoryId, directory }) => ({
|
|
@@ -3420,17 +3651,17 @@ const agentPlanOf = (agent) => ({
|
|
|
3420
3651
|
});
|
|
3421
3652
|
//#endregion
|
|
3422
3653
|
//#region src/commands/agents/config.ts
|
|
3423
|
-
const agentsOf = async (
|
|
3424
|
-
const projects = project === void 0 ? (await projectsOf(
|
|
3654
|
+
const agentsOf = async (scope, project) => {
|
|
3655
|
+
const projects = project === void 0 ? (await projectsOf(scope)).map(({ id, slug }) => ({
|
|
3425
3656
|
id,
|
|
3426
3657
|
slug
|
|
3427
3658
|
})) : [{
|
|
3428
|
-
id: await resolveProjectId(
|
|
3659
|
+
id: await resolveProjectId(scope, project),
|
|
3429
3660
|
slug: project
|
|
3430
3661
|
}];
|
|
3431
3662
|
const listed = [];
|
|
3432
3663
|
for (const { id, slug } of projects) {
|
|
3433
|
-
const { items } = dataOf("list the agents of the project", await api.GET("/projects/{id}/agents", { params: { path: { id } } }), agentHints);
|
|
3664
|
+
const { items } = dataOf("list the agents of the project", await scope.api.GET("/projects/{id}/agents", { params: { path: { id } } }), agentHints);
|
|
3434
3665
|
for (const agent of items) listed.push({
|
|
3435
3666
|
project: slug,
|
|
3436
3667
|
...agent
|
|
@@ -3463,7 +3694,7 @@ const configCommands = (adaptersOf) => [
|
|
|
3463
3694
|
},
|
|
3464
3695
|
run: async (invocation) => {
|
|
3465
3696
|
const run = await runOf(adaptersOf, invocation);
|
|
3466
|
-
const listed = await agentsOf(run
|
|
3697
|
+
const listed = await agentsOf(run, invocation.values.project);
|
|
3467
3698
|
return printed(run, listed, (items) => items.length === 0 ? "There is no agent here yet; kcloud agents create makes one." : renderTable([
|
|
3468
3699
|
{ header: "PROJECT" },
|
|
3469
3700
|
{ header: "AGENT" },
|
|
@@ -3581,6 +3812,7 @@ const detailsOf = (agent) => [
|
|
|
3581
3812
|
["wanted", agent.desiredState],
|
|
3582
3813
|
["observed", agent.observedState],
|
|
3583
3814
|
["release", agent.kikimoraVersion],
|
|
3815
|
+
["node", agent.node === null ? "none" : `${agent.node.name} (${agent.node.state})`],
|
|
3584
3816
|
["timezone", agent.timezone],
|
|
3585
3817
|
["machine", `${agent.machine.cpus} vCPU, ${agent.machine.memoryMb} MB`],
|
|
3586
3818
|
["docker", agent.machine.docker ? "yes" : "no"],
|
|
@@ -3605,6 +3837,272 @@ const detailsOf = (agent) => [
|
|
|
3605
3837
|
]))
|
|
3606
3838
|
].join("\n");
|
|
3607
3839
|
//#endregion
|
|
3840
|
+
//#region src/commands/nodes.ts
|
|
3841
|
+
const nodeHints = {
|
|
3842
|
+
...organizationHints,
|
|
3843
|
+
"node.not_found": "kcloud nodes list shows the nodes of the organization.",
|
|
3844
|
+
"node.name_taken": "Another node of the organization has that name.",
|
|
3845
|
+
"node.in_use": "Delete the agents placed on it first; kcloud nodes show <node> lists them.",
|
|
3846
|
+
"node.registration_used": "The node has joined and holds its own credential; it needs no token."
|
|
3847
|
+
};
|
|
3848
|
+
const nodesOf = async (api, organizationId) => dataOf("list the nodes", await api.GET("/organizations/{organizationId}/nodes", { params: { path: { organizationId } } }), nodeHints).items;
|
|
3849
|
+
const namesOf = (nodes) => nodes.map(({ name }) => name).join(", ");
|
|
3850
|
+
const nodeIn = (nodes, reference, organization) => {
|
|
3851
|
+
const found = nodes.find((node) => node.id === reference.toLowerCase() || node.name === reference);
|
|
3852
|
+
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)}.`);
|
|
3853
|
+
return found;
|
|
3854
|
+
};
|
|
3855
|
+
const resolveNode = async (scope, reference) => {
|
|
3856
|
+
const organization = await scope.organization();
|
|
3857
|
+
if (isId(reference)) return {
|
|
3858
|
+
organizationId: organization.id,
|
|
3859
|
+
nodeId: reference
|
|
3860
|
+
};
|
|
3861
|
+
const node = nodeIn(await nodesOf(scope.api, organization.id), reference, organization.slug);
|
|
3862
|
+
return {
|
|
3863
|
+
organizationId: organization.id,
|
|
3864
|
+
nodeId: node.id
|
|
3865
|
+
};
|
|
3866
|
+
};
|
|
3867
|
+
const nodeDetailOf = async (api, { organizationId, nodeId }) => dataOf("describe the node", await api.GET("/organizations/{organizationId}/nodes/{nodeId}", { params: { path: {
|
|
3868
|
+
organizationId,
|
|
3869
|
+
nodeId
|
|
3870
|
+
} } }), nodeHints);
|
|
3871
|
+
const createdNode = async (api, organizationId, name) => {
|
|
3872
|
+
const body = { name };
|
|
3873
|
+
validated(createNodeBodySchema, body, "a node");
|
|
3874
|
+
return dataOf("create the node", await api.POST("/organizations/{organizationId}/nodes", {
|
|
3875
|
+
params: { path: { organizationId } },
|
|
3876
|
+
body
|
|
3877
|
+
}), nodeHints);
|
|
3878
|
+
};
|
|
3879
|
+
const renewedRegistration = async (api, { organizationId, nodeId }) => dataOf("issue a registration token for the node", await api.POST("/organizations/{organizationId}/nodes/{nodeId}/registration", { params: { path: {
|
|
3880
|
+
organizationId,
|
|
3881
|
+
nodeId
|
|
3882
|
+
} } }), nodeHints);
|
|
3883
|
+
const deleteNode = async (api, { organizationId, nodeId }) => {
|
|
3884
|
+
doneWith("delete the node", await api.DELETE("/organizations/{organizationId}/nodes/{nodeId}", { params: { path: {
|
|
3885
|
+
organizationId,
|
|
3886
|
+
nodeId
|
|
3887
|
+
} } }), nodeHints);
|
|
3888
|
+
};
|
|
3889
|
+
const gibibytes = (bytes) => `${(bytes / 1024 ** 3).toFixed(1)} GiB`;
|
|
3890
|
+
const hostOf = (node) => node.host === null ? "-" : `${node.host.cpus} CPU, ${gibibytes(node.host.memoryBytes)} of memory`;
|
|
3891
|
+
const capabilitiesOf = (node) => {
|
|
3892
|
+
if (node.capabilities === null) return "-";
|
|
3893
|
+
const { sandbox, uidIsolation } = node.capabilities;
|
|
3894
|
+
return `sandbox ${sandbox ? "yes" : "no"}, uid isolation ${uidIsolation ? "yes" : "no"}`;
|
|
3895
|
+
};
|
|
3896
|
+
const registrationOf = (node) => node.registrationExpiresAt === null ? "-" : `a token waits until ${node.registrationExpiresAt}`;
|
|
3897
|
+
const storageOf = ({ state, freeBytes, totalBytes, path }) => `${state}, ${gibibytes(freeBytes)} free of ${gibibytes(totalBytes)} at ${path}`;
|
|
3898
|
+
const runtimeRowsOf = (runtime) => [
|
|
3899
|
+
["reported", runtime.reportedAt],
|
|
3900
|
+
["docker", runtime.docker.lastError === null ? runtime.docker.state : `${runtime.docker.state}: ${runtime.docker.lastError}`],
|
|
3901
|
+
["data storage", storageOf(runtime.storage.data)],
|
|
3902
|
+
["engine storage", storageOf(runtime.storage.engine)],
|
|
3903
|
+
["last reconcile", runtime.reconcile.lastTickError === null ? runtime.reconcile.lastTickAt ?? "never" : `${runtime.reconcile.lastTickAt ?? "never"}: ${runtime.reconcile.lastTickError}`],
|
|
3904
|
+
["agents", `${runtime.reconcile.agentsActive} active, ${runtime.reconcile.agentsQueued} queued`],
|
|
3905
|
+
["monitor slots", `${runtime.sessions.monitor.inUse} of ${runtime.sessions.monitor.effective} in use`],
|
|
3906
|
+
["executor slots", `${runtime.sessions.executor.inUse} of ${runtime.sessions.executor.effective} in use`],
|
|
3907
|
+
["log readers", String(runtime.ingest.readers)]
|
|
3908
|
+
];
|
|
3909
|
+
const nodeDetailsText = (node) => [
|
|
3910
|
+
renderTable([{ header: "FIELD" }, { header: "VALUE" }], [
|
|
3911
|
+
["name", node.name],
|
|
3912
|
+
["state", node.state],
|
|
3913
|
+
["provider", node.provider],
|
|
3914
|
+
["engine", node.engine ?? "-"],
|
|
3915
|
+
["version", node.version ?? "-"],
|
|
3916
|
+
["host", hostOf(node)],
|
|
3917
|
+
["server", node.host?.serverVersion ?? "-"],
|
|
3918
|
+
["data root", node.host?.dataRoot ?? "-"],
|
|
3919
|
+
["capabilities", capabilitiesOf(node)],
|
|
3920
|
+
["registration", registrationOf(node)],
|
|
3921
|
+
["connected", node.connectedAt ?? "never"],
|
|
3922
|
+
["last seen", node.lastSeenAt ?? "never"],
|
|
3923
|
+
["created", node.createdAt],
|
|
3924
|
+
["id", node.id]
|
|
3925
|
+
]),
|
|
3926
|
+
"",
|
|
3927
|
+
node.agents.length === 0 ? "No agent is placed on the node." : renderTable([
|
|
3928
|
+
{ header: "PROJECT" },
|
|
3929
|
+
{ header: "AGENT" },
|
|
3930
|
+
{ header: "STATE" },
|
|
3931
|
+
{ header: "ID" }
|
|
3932
|
+
], node.agents.map((agent) => [
|
|
3933
|
+
agent.projectName,
|
|
3934
|
+
agent.slug,
|
|
3935
|
+
agent.state,
|
|
3936
|
+
agent.id
|
|
3937
|
+
])),
|
|
3938
|
+
"",
|
|
3939
|
+
node.runtime === null ? "The node has reported no runtime yet." : renderTable([{ header: "RUNTIME" }, { header: "VALUE" }], runtimeRowsOf(node.runtime))
|
|
3940
|
+
].join("\n");
|
|
3941
|
+
const startHintOf = (origin) => [
|
|
3942
|
+
"Start the node with it as docs/install.md of kikimora-cloud describes. On a Linux host, after read -rs token:",
|
|
3943
|
+
` printf '%s\\n' "$token" | /opt/kikimora-cloud/infra/node/install.sh --platform-url ${origin} --image ghcr.io/kikimoradev/kikimora-cloud/node:<version> --registration-token -`,
|
|
3944
|
+
"On a Windows PC:",
|
|
3945
|
+
` C:\\kikimora-cloud\\src\\infra\\windows\\install.ps1 -PlatformUrl ${origin} -RegistrationToken <token>`
|
|
3946
|
+
].join("\n");
|
|
3947
|
+
const printedRegistration = (run, created) => {
|
|
3948
|
+
if (run.settings.json) {
|
|
3949
|
+
run.context.terminal.json(created);
|
|
3950
|
+
return exitCodes.ok;
|
|
3951
|
+
}
|
|
3952
|
+
run.context.terminal.line(created.registration.token);
|
|
3953
|
+
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"));
|
|
3954
|
+
return exitCodes.ok;
|
|
3955
|
+
};
|
|
3956
|
+
const nodeUsage = [["", "a node is named by its name or its id"]];
|
|
3957
|
+
const createNodesCommand = (adaptersOf) => defineGroup({
|
|
3958
|
+
name: "nodes",
|
|
3959
|
+
summary: "The nodes of the organization, the hosts its agents run on.",
|
|
3960
|
+
usage: "nodes <command>",
|
|
3961
|
+
subcommands: [
|
|
3962
|
+
defineCommand({
|
|
3963
|
+
name: "list",
|
|
3964
|
+
summary: "List the nodes of the organization.",
|
|
3965
|
+
usage: "nodes list",
|
|
3966
|
+
options: {},
|
|
3967
|
+
positionals: {
|
|
3968
|
+
min: 0,
|
|
3969
|
+
max: 0
|
|
3970
|
+
},
|
|
3971
|
+
run: async (invocation) => {
|
|
3972
|
+
const run = await runOf(adaptersOf, invocation);
|
|
3973
|
+
const organization = await run.organization();
|
|
3974
|
+
const items = await nodesOf(run.api, organization.id);
|
|
3975
|
+
return printed(run, items, (nodes) => nodes.length === 0 ? `${organization.slug} has no node yet; kcloud nodes create <name> creates one.` : renderTable([
|
|
3976
|
+
{ header: "NAME" },
|
|
3977
|
+
{ header: "STATE" },
|
|
3978
|
+
{ header: "ENGINE" },
|
|
3979
|
+
{ header: "VERSION" },
|
|
3980
|
+
{
|
|
3981
|
+
header: "AGENTS",
|
|
3982
|
+
align: "right"
|
|
3983
|
+
},
|
|
3984
|
+
{ header: "LAST SEEN" },
|
|
3985
|
+
{ header: "ID" }
|
|
3986
|
+
], nodes.map((node) => [
|
|
3987
|
+
node.name,
|
|
3988
|
+
node.state,
|
|
3989
|
+
node.engine ?? "-",
|
|
3990
|
+
node.version ?? "-",
|
|
3991
|
+
String(node.agentCount),
|
|
3992
|
+
node.lastSeenAt ?? "never",
|
|
3993
|
+
node.id
|
|
3994
|
+
])));
|
|
3995
|
+
}
|
|
3996
|
+
}),
|
|
3997
|
+
defineCommand({
|
|
3998
|
+
name: "show",
|
|
3999
|
+
summary: "Show a node with its host, the agents placed on it and its runtime.",
|
|
4000
|
+
usage: "nodes show <node>",
|
|
4001
|
+
optionsUsage: nodeUsage,
|
|
4002
|
+
options: {},
|
|
4003
|
+
positionals: {
|
|
4004
|
+
min: 1,
|
|
4005
|
+
max: 1
|
|
4006
|
+
},
|
|
4007
|
+
run: async (invocation) => {
|
|
4008
|
+
const run = await runOf(adaptersOf, invocation);
|
|
4009
|
+
const reference = await resolveNode(run, invocation.positionals[0] ?? "");
|
|
4010
|
+
return printed(run, await nodeDetailOf(run.api, reference), nodeDetailsText);
|
|
4011
|
+
}
|
|
4012
|
+
}),
|
|
4013
|
+
defineCommand({
|
|
4014
|
+
name: "create",
|
|
4015
|
+
summary: "Create a node and print its one-time registration token.",
|
|
4016
|
+
usage: "nodes create <name>",
|
|
4017
|
+
optionsUsage: [["", "the name holds lowercase letters, digits and hyphens"], ["", "the token goes to stdout alone, how to start the node to stderr"]],
|
|
4018
|
+
options: {},
|
|
4019
|
+
positionals: {
|
|
4020
|
+
min: 1,
|
|
4021
|
+
max: 1
|
|
4022
|
+
},
|
|
4023
|
+
run: async (invocation) => {
|
|
4024
|
+
const run = await runOf(adaptersOf, invocation);
|
|
4025
|
+
const organization = await run.organization();
|
|
4026
|
+
return printedRegistration(run, await createdNode(run.api, organization.id, invocation.positionals[0] ?? ""));
|
|
4027
|
+
}
|
|
4028
|
+
}),
|
|
4029
|
+
defineCommand({
|
|
4030
|
+
name: "token",
|
|
4031
|
+
summary: "Issue a new registration token for a node that has not joined yet.",
|
|
4032
|
+
usage: "nodes token <node>",
|
|
4033
|
+
optionsUsage: [...nodeUsage, ["", "the token replaces the one issued before, which stops working"]],
|
|
4034
|
+
options: {},
|
|
4035
|
+
positionals: {
|
|
4036
|
+
min: 1,
|
|
4037
|
+
max: 1
|
|
4038
|
+
},
|
|
4039
|
+
run: async (invocation) => {
|
|
4040
|
+
const run = await runOf(adaptersOf, invocation);
|
|
4041
|
+
const reference = await resolveNode(run, invocation.positionals[0] ?? "");
|
|
4042
|
+
return printedRegistration(run, await renewedRegistration(run.api, reference));
|
|
4043
|
+
}
|
|
4044
|
+
}),
|
|
4045
|
+
defineCommand({
|
|
4046
|
+
name: "delete",
|
|
4047
|
+
summary: "Delete a node that runs no agent.",
|
|
4048
|
+
usage: "nodes delete <node>",
|
|
4049
|
+
optionsUsage: nodeUsage,
|
|
4050
|
+
options: {},
|
|
4051
|
+
positionals: {
|
|
4052
|
+
min: 1,
|
|
4053
|
+
max: 1
|
|
4054
|
+
},
|
|
4055
|
+
run: async (invocation) => {
|
|
4056
|
+
const run = await runOf(adaptersOf, invocation);
|
|
4057
|
+
const given = invocation.positionals[0] ?? "";
|
|
4058
|
+
const reference = await resolveNode(run, given);
|
|
4059
|
+
await confirmed({
|
|
4060
|
+
prompt: run.prompt,
|
|
4061
|
+
settings: run.settings,
|
|
4062
|
+
question: `Delete the node ${given}? Its host can no longer reach the platform.`
|
|
4063
|
+
});
|
|
4064
|
+
await deleteNode(run.api, reference);
|
|
4065
|
+
return said(run, `Deleted the node ${given}.`);
|
|
4066
|
+
}
|
|
4067
|
+
})
|
|
4068
|
+
]
|
|
4069
|
+
});
|
|
4070
|
+
//#endregion
|
|
4071
|
+
//#region src/commands/projects/shared.ts
|
|
4072
|
+
const defaultProjectColor = "#7c86ff";
|
|
4073
|
+
const projectHint = {
|
|
4074
|
+
"project.not_found": "kcloud projects list shows the projects you are a member of.",
|
|
4075
|
+
"project.slug_taken": "Another project of the organization has that slug.",
|
|
4076
|
+
"project.invalid_slug": "A slug holds lowercase letters, digits and hyphens.",
|
|
4077
|
+
"project.not_empty": "Delete its agents, credentials, repositories and tools first.",
|
|
4078
|
+
"project.member_not_found": "kcloud projects members list <project> shows them.",
|
|
4079
|
+
"project.member_must_be_active": "kcloud orgs members enable <member> enables the account first."
|
|
4080
|
+
};
|
|
4081
|
+
const projectOf = async (api, id) => dataOf("describe the project", await api.GET("/projects/{id}", { params: { path: { id } } }), projectHint);
|
|
4082
|
+
//#endregion
|
|
4083
|
+
//#region src/commands/agents/placement.ts
|
|
4084
|
+
const placedNodeId = async (run, { projectId, given, ask }) => {
|
|
4085
|
+
const project = await projectOf(run.api, projectId);
|
|
4086
|
+
const organization = `the organization of ${project.slug}`;
|
|
4087
|
+
const nodes = await nodesOf(run.api, project.organizationId);
|
|
4088
|
+
if (given !== void 0) return nodeIn(nodes, given, organization).id;
|
|
4089
|
+
const [only, ...others] = nodes;
|
|
4090
|
+
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.");
|
|
4091
|
+
if (others.length === 0) {
|
|
4092
|
+
run.context.terminal.error(`The agent runs on ${only.name}, the only node of ${organization}.`);
|
|
4093
|
+
return only.id;
|
|
4094
|
+
}
|
|
4095
|
+
if (ask) return run.prompt.select({
|
|
4096
|
+
message: "The node it runs on",
|
|
4097
|
+
choices: nodes.map((node) => ({
|
|
4098
|
+
value: node.id,
|
|
4099
|
+
label: node.name,
|
|
4100
|
+
hint: node.state
|
|
4101
|
+
}))
|
|
4102
|
+
});
|
|
4103
|
+
throw new UsageError(`agents create needs --node <name>, because ${organization} has ${nodes.length} nodes.`, `The nodes are ${nodes.map(({ name }) => name).join(", ")}.`);
|
|
4104
|
+
};
|
|
4105
|
+
//#endregion
|
|
3608
4106
|
//#region src/commands/agents/create.ts
|
|
3609
4107
|
const claudeKinds = /* @__PURE__ */ new Set(["anthropic_api_key", "claude_oauth_token"]);
|
|
3610
4108
|
const catalogueOf = async (api, projectId) => {
|
|
@@ -3708,6 +4206,11 @@ const promptText = async (run, file, question) => {
|
|
|
3708
4206
|
};
|
|
3709
4207
|
const localTimezone = () => Intl.DateTimeFormat().resolvedOptions().timeZone || "UTC";
|
|
3710
4208
|
const askedPlan = async (run, projectId, values) => {
|
|
4209
|
+
const nodeId = await placedNodeId(run, {
|
|
4210
|
+
projectId,
|
|
4211
|
+
given: values.node,
|
|
4212
|
+
ask: !run.settings.yes
|
|
4213
|
+
});
|
|
3711
4214
|
const catalogue = await catalogueOf(run.api, projectId);
|
|
3712
4215
|
const name = values.name ?? await run.prompt.text({
|
|
3713
4216
|
message: "The name of the agent",
|
|
@@ -3725,6 +4228,7 @@ const askedPlan = async (run, projectId, values) => {
|
|
|
3725
4228
|
name,
|
|
3726
4229
|
description: "",
|
|
3727
4230
|
kikimoraVersion: release,
|
|
4231
|
+
nodeId,
|
|
3728
4232
|
claudeCredentialId: await chosenCredential(run.prompt, catalogue, (kind) => claudeKinds.has(kind), "The Claude credential it works with"),
|
|
3729
4233
|
githubCredentialId: await chosenCredential(run.prompt, catalogue, (kind) => kind === "github_pat", "The GitHub credential it clones with"),
|
|
3730
4234
|
repositories: [...await chosenRepositories(run.prompt, catalogue)],
|
|
@@ -3743,17 +4247,27 @@ const askedPlan = async (run, projectId, values) => {
|
|
|
3743
4247
|
}
|
|
3744
4248
|
};
|
|
3745
4249
|
};
|
|
3746
|
-
const
|
|
4250
|
+
const isObject = (document) => typeof document === "object" && document !== null && !Array.isArray(document);
|
|
4251
|
+
const fileOf = async (run, projectId, { file, node }) => {
|
|
3747
4252
|
const document = await documentOf(run, file);
|
|
3748
|
-
|
|
3749
|
-
|
|
4253
|
+
const placed = !isObject(document) || node === void 0 && "nodeId" in document ? document : {
|
|
4254
|
+
...document,
|
|
4255
|
+
nodeId: await placedNodeId(run, {
|
|
4256
|
+
projectId,
|
|
4257
|
+
given: node,
|
|
4258
|
+
ask: false
|
|
4259
|
+
})
|
|
4260
|
+
};
|
|
4261
|
+
validated(createAgentBodySchema, placed, "an agent");
|
|
4262
|
+
return placed;
|
|
3750
4263
|
};
|
|
3751
4264
|
const createCommand = (adaptersOf) => defineCommand({
|
|
3752
4265
|
name: "create",
|
|
3753
4266
|
summary: "Create an agent from a file, from flags, or by asking step by step.",
|
|
3754
|
-
usage: "agents create --project <name> [--file <path>] [--name <name>]",
|
|
4267
|
+
usage: "agents create --project <name> [--node <node>] [--file <path>] [--name <name>]",
|
|
3755
4268
|
optionsUsage: [
|
|
3756
4269
|
["--project <name>", "the project that owns the agent"],
|
|
4270
|
+
["--node <node>", "the node it runs on, by name or id; needed when the organization has several"],
|
|
3757
4271
|
["--file <path>", "the whole agent as JSON, or - to read stdin"],
|
|
3758
4272
|
["--name <name>", "the name of the agent, instead of asking"],
|
|
3759
4273
|
["--release <x.y.z>", "the Kikimora release it runs, instead of asking"],
|
|
@@ -3762,11 +4276,13 @@ const createCommand = (adaptersOf) => defineCommand({
|
|
|
3762
4276
|
["--timezone <zone>", "the timezone of the agent (default: this machine's)"],
|
|
3763
4277
|
["--docker", "give the agent a Docker sidecar, when the project allows it"],
|
|
3764
4278
|
["--browser", "give the agent a browser"],
|
|
3765
|
-
["", "kcloud agents show <agent> --as-file prints what --file takes"]
|
|
4279
|
+
["", "kcloud agents show <agent> --as-file prints what --file takes"],
|
|
4280
|
+
["", "--node replaces the nodeId of the file"]
|
|
3766
4281
|
],
|
|
3767
4282
|
options: {
|
|
3768
4283
|
...fileOptions,
|
|
3769
4284
|
project: { type: "string" },
|
|
4285
|
+
node: { type: "string" },
|
|
3770
4286
|
name: { type: "string" },
|
|
3771
4287
|
release: { type: "string" },
|
|
3772
4288
|
"monitor-prompt-file": { type: "string" },
|
|
@@ -3789,8 +4305,11 @@ const createCommand = (adaptersOf) => defineCommand({
|
|
|
3789
4305
|
const { values } = invocation;
|
|
3790
4306
|
const run = await runOf(adaptersOf, invocation);
|
|
3791
4307
|
if (values.project === void 0) throw new UsageError("agents create needs --project <name>.", "kcloud projects list shows the projects you are a member of.");
|
|
3792
|
-
const projectId = await resolveProjectId(run
|
|
3793
|
-
const body = values.file === void 0 ? await askedPlan(run, projectId, values) : await fileOf(run,
|
|
4308
|
+
const projectId = await resolveProjectId(run, values.project);
|
|
4309
|
+
const body = values.file === void 0 ? await askedPlan(run, projectId, values) : await fileOf(run, projectId, {
|
|
4310
|
+
file: values.file,
|
|
4311
|
+
node: values.node
|
|
4312
|
+
});
|
|
3794
4313
|
const agent = dataOf("create the agent", await run.api.POST("/projects/{id}/agents", {
|
|
3795
4314
|
params: { path: { id: projectId } },
|
|
3796
4315
|
body
|
|
@@ -3897,10 +4416,10 @@ const pageOptions = {
|
|
|
3897
4416
|
};
|
|
3898
4417
|
const allUsage = ["--all", "follow the cursor to the end instead of one page"];
|
|
3899
4418
|
const pageUsage = [["--limit <n>", `how many rows (default: 50)`], allUsage];
|
|
3900
|
-
const dayOf = (date) => date.toISOString().slice(0, 10);
|
|
4419
|
+
const dayOf$1 = (date) => date.toISOString().slice(0, 10);
|
|
3901
4420
|
const costWindowOf = (days, now) => ({
|
|
3902
|
-
from: dayOf(/* @__PURE__ */ new Date(now.getTime() - (days - 1) * 24 * 60 * 6e4)),
|
|
3903
|
-
to: dayOf(now)
|
|
4421
|
+
from: dayOf$1(/* @__PURE__ */ new Date(now.getTime() - (days - 1) * 24 * 60 * 6e4)),
|
|
4422
|
+
to: dayOf$1(now)
|
|
3904
4423
|
});
|
|
3905
4424
|
const windowFrom = (days, now) => {
|
|
3906
4425
|
const span = days === void 0 ? 7 : Number(days);
|
|
@@ -4334,17 +4853,6 @@ const streamFrames = async (request) => {
|
|
|
4334
4853
|
}
|
|
4335
4854
|
};
|
|
4336
4855
|
//#endregion
|
|
4337
|
-
//#region src/profile/token.ts
|
|
4338
|
-
const tokenOf = async ({ settings, context }) => {
|
|
4339
|
-
const fromEnvironment = context.environment[tokenVariable];
|
|
4340
|
-
if (fromEnvironment !== void 0 && fromEnvironment !== "") return fromEnvironment;
|
|
4341
|
-
const store = await readProfileStore(context.homeDirectory);
|
|
4342
|
-
const name = settings.profile ?? store.current;
|
|
4343
|
-
const profile = name === null ? void 0 : store.profiles[name];
|
|
4344
|
-
if (profile === void 0) throw new CliError("No instance is signed in here.", "Run kcloud login --url <origin> first.");
|
|
4345
|
-
return profile.token;
|
|
4346
|
-
};
|
|
4347
|
-
//#endregion
|
|
4348
4856
|
//#region src/commands/agents/streams.ts
|
|
4349
4857
|
const streamOptions = {
|
|
4350
4858
|
...agentOption,
|
|
@@ -4397,15 +4905,12 @@ const streamCommands = (adaptersOf) => [{
|
|
|
4397
4905
|
api: adapters.api
|
|
4398
4906
|
});
|
|
4399
4907
|
const reference = positionals[0] ?? "";
|
|
4400
|
-
const agentId = await resolveAgentId(session
|
|
4908
|
+
const agentId = await resolveAgentId(session, reference, values.project);
|
|
4401
4909
|
const limit = values.limit;
|
|
4402
4910
|
if (limit !== void 0 && !/^[0-9]+$/u.test(limit)) throw new UsageError(`--limit ${limit} is not a whole number.`);
|
|
4403
4911
|
await streamFrames({
|
|
4404
4912
|
origin: session.origin,
|
|
4405
|
-
token:
|
|
4406
|
-
settings,
|
|
4407
|
-
context
|
|
4408
|
-
}),
|
|
4913
|
+
token: session.token,
|
|
4409
4914
|
path: path.replace("{id}", encodeURIComponent(agentId)),
|
|
4410
4915
|
query: {
|
|
4411
4916
|
...values.since === void 0 ? {} : { since: values.since },
|
|
@@ -4601,7 +5106,7 @@ const pipedText = async (stream) => {
|
|
|
4601
5106
|
for await (const chunk of stream) chunks.push(Buffer.from(chunk));
|
|
4602
5107
|
return Buffer.concat(chunks).toString("utf8");
|
|
4603
5108
|
};
|
|
4604
|
-
const secretOf = async ({ run, file, fromStdin = false, question, variable = secretVariable }) => {
|
|
5109
|
+
const secretOf = async ({ run, file, fromStdin = false, question, variable = secretVariable, stdinHint = "Pipe the secret in, or drop --secret-stdin to be asked for it." }) => {
|
|
4605
5110
|
if (file !== void 0) {
|
|
4606
5111
|
const path = resolve(run.context.workingDirectory, file);
|
|
4607
5112
|
try {
|
|
@@ -4611,7 +5116,7 @@ const secretOf = async ({ run, file, fromStdin = false, question, variable = sec
|
|
|
4611
5116
|
}
|
|
4612
5117
|
}
|
|
4613
5118
|
if (fromStdin) {
|
|
4614
|
-
if (isTerminal(run.context.io.stdin)) throw new UsageError("Nothing is piped on stdin.",
|
|
5119
|
+
if (isTerminal(run.context.io.stdin)) throw new UsageError("Nothing is piped on stdin.", stdinHint);
|
|
4615
5120
|
return (await pipedText(run.context.io.stdin)).trim();
|
|
4616
5121
|
}
|
|
4617
5122
|
const fromEnvironment = run.context.environment[variable];
|
|
@@ -4646,7 +5151,7 @@ const credentialHints = {
|
|
|
4646
5151
|
};
|
|
4647
5152
|
const projectFrom$2 = async (run, given) => {
|
|
4648
5153
|
if (given === void 0) throw new UsageError("This command needs a project.", "Pass --project <name>; kcloud projects list shows them.");
|
|
4649
|
-
return resolveProjectId(run
|
|
5154
|
+
return resolveProjectId(run, given);
|
|
4650
5155
|
};
|
|
4651
5156
|
const credentialsOf = async (api, projectId) => dataOf("list the credentials of the project", await api.GET("/projects/{id}/credentials", { params: { path: { id: projectId } } }), credentialHints).items;
|
|
4652
5157
|
const resolveCredentialId = async (api, projectId, reference) => {
|
|
@@ -5020,17 +5525,18 @@ const accountTools = [
|
|
|
5020
5525
|
defineTool({
|
|
5021
5526
|
name: "whoami",
|
|
5022
5527
|
title: "Who am I",
|
|
5023
|
-
description: "Say which instance and
|
|
5528
|
+
description: "Say which instance, account and organization this server acts with, and whether the version of kcloud matches the version of the instance.",
|
|
5024
5529
|
operations: ["getMe", "getPlatformVersion"],
|
|
5025
5530
|
safety: "read",
|
|
5026
5531
|
input: z.object({}),
|
|
5027
5532
|
run: async (_, run) => {
|
|
5028
|
-
const
|
|
5533
|
+
const user = await run.me();
|
|
5534
|
+
const actsIn = await run.organization();
|
|
5029
5535
|
const platform = dataOf("say which version it runs", await run.api.GET("/version"));
|
|
5030
5536
|
return {
|
|
5031
5537
|
origin: run.origin,
|
|
5032
|
-
|
|
5033
|
-
|
|
5538
|
+
user,
|
|
5539
|
+
actsIn,
|
|
5034
5540
|
kcloud: run.kcloudVersion,
|
|
5035
5541
|
instance: platform.version,
|
|
5036
5542
|
matched: platform.version === run.kcloudVersion
|
|
@@ -5084,8 +5590,8 @@ const windowOfPage = ({ limit, all }, fallback = 50) => ({
|
|
|
5084
5590
|
limit: limit ?? fallback,
|
|
5085
5591
|
all
|
|
5086
5592
|
});
|
|
5087
|
-
const projectIdOf = (run, project) => resolveProjectId(run
|
|
5088
|
-
const agentIdOf = (run, { agent, project }) => resolveAgentId(run
|
|
5593
|
+
const projectIdOf = (run, project) => resolveProjectId(run, project);
|
|
5594
|
+
const agentIdOf = (run, { agent, project }) => resolveAgentId(run, agent, project);
|
|
5089
5595
|
//#endregion
|
|
5090
5596
|
//#region src/mcp/tools/agents.ts
|
|
5091
5597
|
const actionOn = async (run, agentId, action, force) => dataOf(`${action} the agent`, await run.api.POST("/agents/{id}/actions/{action}", {
|
|
@@ -5103,7 +5609,7 @@ const agentTools = [
|
|
|
5103
5609
|
operations: ["listAgents"],
|
|
5104
5610
|
safety: "read",
|
|
5105
5611
|
input: z.object({ project: projectField.optional().describe("One project instead of all of them.") }),
|
|
5106
|
-
run: async ({ project }, run) => agentsOf(run
|
|
5612
|
+
run: async ({ project }, run) => agentsOf(run, project)
|
|
5107
5613
|
}),
|
|
5108
5614
|
defineTool({
|
|
5109
5615
|
name: "agent_show",
|
|
@@ -5123,7 +5629,7 @@ const agentTools = [
|
|
|
5123
5629
|
defineTool({
|
|
5124
5630
|
name: "agent_create",
|
|
5125
5631
|
title: "Create an agent",
|
|
5126
|
-
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.",
|
|
5632
|
+
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. The agent stays stopped until agent_action starts it.",
|
|
5127
5633
|
operations: ["createAgent"],
|
|
5128
5634
|
safety: "write",
|
|
5129
5635
|
input: z.object({
|
|
@@ -5288,11 +5794,47 @@ const credentialTools = [
|
|
|
5288
5794
|
})
|
|
5289
5795
|
];
|
|
5290
5796
|
//#endregion
|
|
5291
|
-
//#region src/
|
|
5292
|
-
const
|
|
5293
|
-
const
|
|
5294
|
-
|
|
5295
|
-
|
|
5797
|
+
//#region src/mcp/tools/nodes.ts
|
|
5798
|
+
const nodeField = z.string().trim().min(1).describe("The node, by its name or its id, from nodes_list.");
|
|
5799
|
+
const nodeTools = [
|
|
5800
|
+
defineTool({
|
|
5801
|
+
name: "nodes_list",
|
|
5802
|
+
title: "List nodes",
|
|
5803
|
+
description: "List the nodes of the organization this server acts in, the hosts its agents run on, with their state, engine, version and agent count.",
|
|
5804
|
+
operations: ["listNodes"],
|
|
5805
|
+
safety: "read",
|
|
5806
|
+
input: z.object({}),
|
|
5807
|
+
run: async (_, run) => nodesOf(run.api, (await run.organization()).id)
|
|
5808
|
+
}),
|
|
5809
|
+
defineTool({
|
|
5810
|
+
name: "node_show",
|
|
5811
|
+
title: "Show a node",
|
|
5812
|
+
description: "Show one node with its host, its capabilities, the agents placed on it and the runtime it reported last.",
|
|
5813
|
+
operations: ["getNode"],
|
|
5814
|
+
safety: "read",
|
|
5815
|
+
input: z.object({ node: nodeField }),
|
|
5816
|
+
run: async ({ node }, run) => nodeDetailOf(run.api, await resolveNode(run, node))
|
|
5817
|
+
}),
|
|
5818
|
+
defineTool({
|
|
5819
|
+
name: "node_delete",
|
|
5820
|
+
title: "Delete a node",
|
|
5821
|
+
description: "Delete a node, after which its host can no longer reach the platform. The instance refuses while agents are placed on it.",
|
|
5822
|
+
operations: ["deleteNode"],
|
|
5823
|
+
safety: "destructive",
|
|
5824
|
+
input: z.object({ node: nodeField }),
|
|
5825
|
+
run: async ({ node }, run) => {
|
|
5826
|
+
const reference = await resolveNode(run, node);
|
|
5827
|
+
await deleteNode(run.api, reference);
|
|
5828
|
+
return { deleted: reference.nodeId };
|
|
5829
|
+
}
|
|
5830
|
+
})
|
|
5831
|
+
];
|
|
5832
|
+
//#endregion
|
|
5833
|
+
//#region src/commands/notifications.ts
|
|
5834
|
+
const notificationHints = { "notification.not_found": "kcloud notifications list shows them." };
|
|
5835
|
+
const createNotificationsCommand = (adaptersOf) => defineGroup({
|
|
5836
|
+
name: "notifications",
|
|
5837
|
+
summary: "What the instance wants you to look at.",
|
|
5296
5838
|
usage: "notifications <command>",
|
|
5297
5839
|
subcommands: [
|
|
5298
5840
|
defineCommand({
|
|
@@ -5391,10 +5933,7 @@ const createNotificationsCommand = (adaptersOf) => defineGroup({
|
|
|
5391
5933
|
});
|
|
5392
5934
|
await streamFrames({
|
|
5393
5935
|
origin: session.origin,
|
|
5394
|
-
token:
|
|
5395
|
-
settings,
|
|
5396
|
-
context
|
|
5397
|
-
}),
|
|
5936
|
+
token: session.token,
|
|
5398
5937
|
path: "/notifications/stream",
|
|
5399
5938
|
follow: true,
|
|
5400
5939
|
action: "follow the notifications",
|
|
@@ -5649,6 +6188,534 @@ const observabilityTools = [
|
|
|
5649
6188
|
})
|
|
5650
6189
|
];
|
|
5651
6190
|
//#endregion
|
|
6191
|
+
//#region src/commands/roles.ts
|
|
6192
|
+
const roleOption = { role: { type: "string" } };
|
|
6193
|
+
const roleUsage = ["--role <role>", "admin or member"];
|
|
6194
|
+
const roleFrom = (given, command) => {
|
|
6195
|
+
if (given === void 0) throw new UsageError(`${command} needs --role admin or --role member.`);
|
|
6196
|
+
const parsed = organizationRoleSchema.safeParse(given);
|
|
6197
|
+
if (!parsed.success) throw new UsageError(`--role ${given} is not a role.`, "The roles are admin and member.");
|
|
6198
|
+
return parsed.data;
|
|
6199
|
+
};
|
|
6200
|
+
const asRole = (role) => role === "admin" ? "an admin" : "a member";
|
|
6201
|
+
//#endregion
|
|
6202
|
+
//#region src/commands/orgs/members.ts
|
|
6203
|
+
const memberHints = {
|
|
6204
|
+
...organizationHints,
|
|
6205
|
+
"user.not_found": "kcloud orgs members list shows them.",
|
|
6206
|
+
"user.email_taken": "An account with that e-mail exists already.",
|
|
6207
|
+
"user.cannot_modify_self": "Another admin of the organization has to make that change.",
|
|
6208
|
+
"user.last_admin": "Make another member an admin first.",
|
|
6209
|
+
"auth.forbidden": "Changing the members of an organization takes an admin of it."
|
|
6210
|
+
};
|
|
6211
|
+
const passwordLength = `between ${passwordSchema.minLength ?? 0} and ${passwordSchema.maxLength ?? 0} characters`;
|
|
6212
|
+
const memberPath = (organizationId, userId) => ({ params: { path: {
|
|
6213
|
+
organizationId,
|
|
6214
|
+
userId
|
|
6215
|
+
} } });
|
|
6216
|
+
const memberRoleChanged = async (api, organizationId, userId, role) => dataOf("change the role of the member", await api.PATCH("/organizations/{organizationId}/members/{userId}", {
|
|
6217
|
+
...memberPath(organizationId, userId),
|
|
6218
|
+
body: { organizationRole: role }
|
|
6219
|
+
}), memberHints);
|
|
6220
|
+
const memberDisabled = async (api, organizationId, userId) => dataOf("disable the member", await api.POST("/organizations/{organizationId}/members/{userId}/disable", memberPath(organizationId, userId)), memberHints);
|
|
6221
|
+
const memberEnabled = async (api, organizationId, userId) => dataOf("enable the member", await api.POST("/organizations/{organizationId}/members/{userId}/enable", memberPath(organizationId, userId)), memberHints);
|
|
6222
|
+
const passwordReset = async (api, organizationId, userId) => dataOf("reset the password of the member", await api.POST("/organizations/{organizationId}/members/{userId}/reset-password", memberPath(organizationId, userId)), memberHints);
|
|
6223
|
+
const chosenMember$1 = async (run, reference) => {
|
|
6224
|
+
const organization = await run.organization();
|
|
6225
|
+
return {
|
|
6226
|
+
organization,
|
|
6227
|
+
member: await resolveOrganizationMember(run.api, organization.id, reference)
|
|
6228
|
+
};
|
|
6229
|
+
};
|
|
6230
|
+
const localeFrom = (given) => {
|
|
6231
|
+
if (given === void 0) return "en";
|
|
6232
|
+
const parsed = localeSchema.safeParse(given);
|
|
6233
|
+
if (!parsed.success) throw new UsageError(`--locale ${given} is not a language of the panel.`, "The languages are en and pl.");
|
|
6234
|
+
return parsed.data;
|
|
6235
|
+
};
|
|
6236
|
+
const initialPasswordOf = async (run, source) => {
|
|
6237
|
+
const file = source["initial-password-file"];
|
|
6238
|
+
const fromStdin = source["initial-password-stdin"];
|
|
6239
|
+
if (file === void 0 && !fromStdin) return null;
|
|
6240
|
+
const password = await secretOf({
|
|
6241
|
+
run,
|
|
6242
|
+
file,
|
|
6243
|
+
fromStdin,
|
|
6244
|
+
question: "The initial password",
|
|
6245
|
+
stdinHint: "Pipe the password in, or drop --initial-password-stdin."
|
|
6246
|
+
});
|
|
6247
|
+
if (!passwordSchema.safeParse(password).success) throw new UsageError(`The initial password is not ${passwordLength}.`);
|
|
6248
|
+
return password;
|
|
6249
|
+
};
|
|
6250
|
+
const printedPassword = (run, password, note) => {
|
|
6251
|
+
run.context.terminal.line(password);
|
|
6252
|
+
run.context.terminal.error(note);
|
|
6253
|
+
return exitCodes.ok;
|
|
6254
|
+
};
|
|
6255
|
+
const memberUsage = [["", "the member is named by e-mail or id"]];
|
|
6256
|
+
const organizationMembersCommand = (adaptersOf) => defineGroup({
|
|
6257
|
+
name: "members",
|
|
6258
|
+
summary: "The people of the organization kcloud acts in.",
|
|
6259
|
+
usage: "orgs members <command>",
|
|
6260
|
+
subcommands: [
|
|
6261
|
+
defineCommand({
|
|
6262
|
+
name: "list",
|
|
6263
|
+
summary: "List the members of the organization.",
|
|
6264
|
+
usage: "orgs members list",
|
|
6265
|
+
options: {},
|
|
6266
|
+
positionals: {
|
|
6267
|
+
min: 0,
|
|
6268
|
+
max: 0
|
|
6269
|
+
},
|
|
6270
|
+
run: async (invocation) => {
|
|
6271
|
+
const run = await runOf(adaptersOf, invocation);
|
|
6272
|
+
const { id } = await run.organization();
|
|
6273
|
+
const items = await organizationMembersOf(run.api, id);
|
|
6274
|
+
return printed(run, items, (members) => renderTable([
|
|
6275
|
+
{ header: "EMAIL" },
|
|
6276
|
+
{ header: "ROLE" },
|
|
6277
|
+
{ header: "STATUS" },
|
|
6278
|
+
{ header: "LAST SIGN-IN" },
|
|
6279
|
+
{ header: "ID" }
|
|
6280
|
+
], members.map((member) => [
|
|
6281
|
+
member.email,
|
|
6282
|
+
member.platformAdmin ? `${member.organizationRole}, platform admin` : member.organizationRole,
|
|
6283
|
+
member.status,
|
|
6284
|
+
member.lastLoginAt ?? "never",
|
|
6285
|
+
member.id
|
|
6286
|
+
])));
|
|
6287
|
+
}
|
|
6288
|
+
}),
|
|
6289
|
+
defineCommand({
|
|
6290
|
+
name: "add",
|
|
6291
|
+
summary: "Add a person to the organization, printing a temporary password once.",
|
|
6292
|
+
usage: "orgs members add --email <email> --role <role> [--locale <locale>] [--initial-password-file <path> | --initial-password-stdin]",
|
|
6293
|
+
optionsUsage: [
|
|
6294
|
+
["--email <email>", "the e-mail the person signs in with"],
|
|
6295
|
+
roleUsage,
|
|
6296
|
+
["--locale <locale>", "the language of the panel for them, en or pl (default: en)"],
|
|
6297
|
+
["--initial-password-file <path>", "read the first password from a file"],
|
|
6298
|
+
["--initial-password-stdin", "read the first password from stdin"],
|
|
6299
|
+
["", "a password is never taken from a flag"],
|
|
6300
|
+
["", "without one the instance makes a temporary password and kcloud prints it"]
|
|
6301
|
+
],
|
|
6302
|
+
options: {
|
|
6303
|
+
email: { type: "string" },
|
|
6304
|
+
...roleOption,
|
|
6305
|
+
locale: { type: "string" },
|
|
6306
|
+
"initial-password-file": { type: "string" },
|
|
6307
|
+
"initial-password-stdin": {
|
|
6308
|
+
type: "boolean",
|
|
6309
|
+
default: false
|
|
6310
|
+
}
|
|
6311
|
+
},
|
|
6312
|
+
positionals: {
|
|
6313
|
+
min: 0,
|
|
6314
|
+
max: 0
|
|
6315
|
+
},
|
|
6316
|
+
run: async (invocation) => {
|
|
6317
|
+
const { values } = invocation;
|
|
6318
|
+
const email = values.email;
|
|
6319
|
+
if (email === void 0) throw new UsageError("orgs members add needs --email <email>.");
|
|
6320
|
+
const role = roleFrom(values.role, "orgs members add");
|
|
6321
|
+
const locale = localeFrom(values.locale);
|
|
6322
|
+
if (values["initial-password-file"] !== void 0 && values["initial-password-stdin"]) throw new UsageError("Pass --initial-password-file or --initial-password-stdin, not both.");
|
|
6323
|
+
const run = await runOf(adaptersOf, invocation);
|
|
6324
|
+
const initialPassword = await initialPasswordOf(run, values);
|
|
6325
|
+
const body = {
|
|
6326
|
+
email,
|
|
6327
|
+
organizationRole: role,
|
|
6328
|
+
locale,
|
|
6329
|
+
...initialPassword === null ? {} : { initialPassword }
|
|
6330
|
+
};
|
|
6331
|
+
validated(createUserBodySchema, body, "a new member");
|
|
6332
|
+
const organization = await run.organization();
|
|
6333
|
+
const created = dataOf("add the member", await run.api.POST("/organizations/{organizationId}/members", {
|
|
6334
|
+
params: { path: { organizationId: organization.id } },
|
|
6335
|
+
body
|
|
6336
|
+
}), memberHints);
|
|
6337
|
+
if (run.settings.json) {
|
|
6338
|
+
run.context.terminal.json(created);
|
|
6339
|
+
return exitCodes.ok;
|
|
6340
|
+
}
|
|
6341
|
+
const added = `Added ${created.user.email} to ${organization.slug} as ${asRole(role)}.`;
|
|
6342
|
+
return created.temporaryPassword === null ? said(run, added) : printedPassword(run, created.temporaryPassword, `${added} The temporary password is shown only here.`);
|
|
6343
|
+
}
|
|
6344
|
+
}),
|
|
6345
|
+
defineCommand({
|
|
6346
|
+
name: "set-role",
|
|
6347
|
+
summary: "Make a member an admin of the organization, or a member again.",
|
|
6348
|
+
usage: "orgs members set-role <member> --role <role>",
|
|
6349
|
+
optionsUsage: [roleUsage, ...memberUsage],
|
|
6350
|
+
options: roleOption,
|
|
6351
|
+
positionals: {
|
|
6352
|
+
min: 1,
|
|
6353
|
+
max: 1
|
|
6354
|
+
},
|
|
6355
|
+
run: async (invocation) => {
|
|
6356
|
+
const role = roleFrom(invocation.values.role, "orgs members set-role");
|
|
6357
|
+
const run = await runOf(adaptersOf, invocation);
|
|
6358
|
+
const { organization, member } = await chosenMember$1(run, invocation.positionals[0] ?? "");
|
|
6359
|
+
const user = await memberRoleChanged(run.api, organization.id, member.id, role);
|
|
6360
|
+
return printed(run, user, (shown) => `${shown.email} is ${asRole(shown.organizationRole)} of ${organization.slug} now.`);
|
|
6361
|
+
}
|
|
6362
|
+
}),
|
|
6363
|
+
defineCommand({
|
|
6364
|
+
name: "disable",
|
|
6365
|
+
summary: "Stop a member from signing in, keeping the account.",
|
|
6366
|
+
usage: "orgs members disable <member>",
|
|
6367
|
+
optionsUsage: memberUsage,
|
|
6368
|
+
options: {},
|
|
6369
|
+
positionals: {
|
|
6370
|
+
min: 1,
|
|
6371
|
+
max: 1
|
|
6372
|
+
},
|
|
6373
|
+
run: async (invocation) => {
|
|
6374
|
+
const run = await runOf(adaptersOf, invocation);
|
|
6375
|
+
const { organization, member } = await chosenMember$1(run, invocation.positionals[0] ?? "");
|
|
6376
|
+
await confirmed({
|
|
6377
|
+
prompt: run.prompt,
|
|
6378
|
+
settings: run.settings,
|
|
6379
|
+
question: `Disable ${member.email}? The account cannot sign in until it is enabled again.`
|
|
6380
|
+
});
|
|
6381
|
+
const user = await memberDisabled(run.api, organization.id, member.id);
|
|
6382
|
+
return printed(run, user, (shown) => `Disabled ${shown.email}.`);
|
|
6383
|
+
}
|
|
6384
|
+
}),
|
|
6385
|
+
defineCommand({
|
|
6386
|
+
name: "enable",
|
|
6387
|
+
summary: "Let a disabled member sign in again.",
|
|
6388
|
+
usage: "orgs members enable <member>",
|
|
6389
|
+
optionsUsage: memberUsage,
|
|
6390
|
+
options: {},
|
|
6391
|
+
positionals: {
|
|
6392
|
+
min: 1,
|
|
6393
|
+
max: 1
|
|
6394
|
+
},
|
|
6395
|
+
run: async (invocation) => {
|
|
6396
|
+
const run = await runOf(adaptersOf, invocation);
|
|
6397
|
+
const { organization, member } = await chosenMember$1(run, invocation.positionals[0] ?? "");
|
|
6398
|
+
const user = await memberEnabled(run.api, organization.id, member.id);
|
|
6399
|
+
return printed(run, user, (shown) => `Enabled ${shown.email}.`);
|
|
6400
|
+
}
|
|
6401
|
+
}),
|
|
6402
|
+
defineCommand({
|
|
6403
|
+
name: "reset-password",
|
|
6404
|
+
summary: "Give a member a new temporary password and print it once.",
|
|
6405
|
+
usage: "orgs members reset-password <member>",
|
|
6406
|
+
optionsUsage: memberUsage,
|
|
6407
|
+
options: {},
|
|
6408
|
+
positionals: {
|
|
6409
|
+
min: 1,
|
|
6410
|
+
max: 1
|
|
6411
|
+
},
|
|
6412
|
+
run: async (invocation) => {
|
|
6413
|
+
const run = await runOf(adaptersOf, invocation);
|
|
6414
|
+
const { organization, member } = await chosenMember$1(run, invocation.positionals[0] ?? "");
|
|
6415
|
+
await confirmed({
|
|
6416
|
+
prompt: run.prompt,
|
|
6417
|
+
settings: run.settings,
|
|
6418
|
+
question: `Reset the password of ${member.email}? The current one stops working.`
|
|
6419
|
+
});
|
|
6420
|
+
const reset = await passwordReset(run.api, organization.id, member.id);
|
|
6421
|
+
if (run.settings.json) {
|
|
6422
|
+
run.context.terminal.json(reset);
|
|
6423
|
+
return exitCodes.ok;
|
|
6424
|
+
}
|
|
6425
|
+
return printedPassword(run, reset.temporaryPassword, `The temporary password of ${member.email} is shown only here.`);
|
|
6426
|
+
}
|
|
6427
|
+
})
|
|
6428
|
+
]
|
|
6429
|
+
});
|
|
6430
|
+
//#endregion
|
|
6431
|
+
//#region src/commands/orgs/index.ts
|
|
6432
|
+
const organizationOrInUse = async (scope, reference) => reference === void 0 ? scope.organization() : organizationIn(await scope.organizations(), reference);
|
|
6433
|
+
const organizationsMarked = async (scope, warn) => {
|
|
6434
|
+
const organizations = await scope.organizations();
|
|
6435
|
+
const inUse = await scope.organization().then(({ id }) => id, (error) => {
|
|
6436
|
+
if (!(error instanceof CliError)) throw error;
|
|
6437
|
+
warn(error);
|
|
6438
|
+
return null;
|
|
6439
|
+
});
|
|
6440
|
+
return organizations.map((organization) => ({
|
|
6441
|
+
...organization,
|
|
6442
|
+
inUse: organization.id === inUse
|
|
6443
|
+
}));
|
|
6444
|
+
};
|
|
6445
|
+
const organizationCostsOf = async (api, organizationId, { from, to }) => dataOf("show what the organization spent", await api.GET("/organizations/{organizationId}/costs", { params: {
|
|
6446
|
+
path: { organizationId },
|
|
6447
|
+
query: {
|
|
6448
|
+
from,
|
|
6449
|
+
to
|
|
6450
|
+
}
|
|
6451
|
+
} }), organizationHints);
|
|
6452
|
+
const dayOf = (flag, given) => {
|
|
6453
|
+
if (!z.iso.date().safeParse(given).success) throw new UsageError(`--${flag} ${given} is not a day written as YYYY-MM-DD.`);
|
|
6454
|
+
return given;
|
|
6455
|
+
};
|
|
6456
|
+
const shiftedDay = (day, days) => new Date(Date.parse(`${day}T00:00:00.000Z`) + days * 24 * 60 * 6e4).toISOString().slice(0, 10);
|
|
6457
|
+
const costWindowFrom = (from, to, now) => {
|
|
6458
|
+
const last = to === void 0 ? now.toISOString().slice(0, 10) : dayOf("to", to);
|
|
6459
|
+
const first = from === void 0 ? shiftedDay(last, -6) : dayOf("from", from);
|
|
6460
|
+
if (first > last) throw new UsageError(`--from ${first} comes after --to ${last}.`);
|
|
6461
|
+
return {
|
|
6462
|
+
from: first,
|
|
6463
|
+
to: last
|
|
6464
|
+
};
|
|
6465
|
+
};
|
|
6466
|
+
const costsText = (costs, { from, to }, slugs) => [
|
|
6467
|
+
costs.items.length === 0 ? `The organization spent nothing between ${from} and ${to}.` : renderTable([
|
|
6468
|
+
{ header: "DAY" },
|
|
6469
|
+
{
|
|
6470
|
+
header: "COST",
|
|
6471
|
+
align: "right"
|
|
6472
|
+
},
|
|
6473
|
+
{
|
|
6474
|
+
header: "RUNS",
|
|
6475
|
+
align: "right"
|
|
6476
|
+
}
|
|
6477
|
+
], costs.items.map((point) => [
|
|
6478
|
+
point.day,
|
|
6479
|
+
point.costUsd.toFixed(2),
|
|
6480
|
+
String(point.runs)
|
|
6481
|
+
])),
|
|
6482
|
+
...costs.projects.length === 0 ? [] : ["", renderTable([
|
|
6483
|
+
{ header: "PROJECT" },
|
|
6484
|
+
{
|
|
6485
|
+
header: "COST",
|
|
6486
|
+
align: "right"
|
|
6487
|
+
},
|
|
6488
|
+
{
|
|
6489
|
+
header: "RUNS",
|
|
6490
|
+
align: "right"
|
|
6491
|
+
}
|
|
6492
|
+
], costs.projects.map((project) => [
|
|
6493
|
+
slugs.get(project.projectId) ?? project.projectId,
|
|
6494
|
+
project.costUsd.toFixed(2),
|
|
6495
|
+
String(project.runs)
|
|
6496
|
+
]))],
|
|
6497
|
+
"",
|
|
6498
|
+
`${costs.totalCostUsd.toFixed(2)} over ${costs.totalRuns} runs and ${costs.sessions} sessions.`
|
|
6499
|
+
].join("\n");
|
|
6500
|
+
const createOrgsCommand = (adaptersOf) => defineGroup({
|
|
6501
|
+
name: "orgs",
|
|
6502
|
+
summary: "The organizations you can see, and the one kcloud acts in.",
|
|
6503
|
+
usage: "orgs <command>",
|
|
6504
|
+
subcommands: [
|
|
6505
|
+
defineCommand({
|
|
6506
|
+
name: "list",
|
|
6507
|
+
summary: "List the organizations you can see, marking the one kcloud acts in.",
|
|
6508
|
+
usage: "orgs list",
|
|
6509
|
+
options: {},
|
|
6510
|
+
positionals: {
|
|
6511
|
+
min: 0,
|
|
6512
|
+
max: 0
|
|
6513
|
+
},
|
|
6514
|
+
run: async (invocation) => {
|
|
6515
|
+
const run = await runOf(adaptersOf, invocation);
|
|
6516
|
+
const items = await organizationsMarked(run, (problem) => {
|
|
6517
|
+
run.context.terminal.error(run.context.terminal.errorStyles.warning(`warning ${problem.message}`));
|
|
6518
|
+
});
|
|
6519
|
+
return printed(run, items, (listed) => listed.length === 0 ? "You can see no organization." : renderTable([
|
|
6520
|
+
{ header: "SLUG" },
|
|
6521
|
+
{ header: "NAME" },
|
|
6522
|
+
{ header: "ID" }
|
|
6523
|
+
], listed.map((organization) => [
|
|
6524
|
+
organization.inUse ? `${organization.slug} *` : organization.slug,
|
|
6525
|
+
organization.name,
|
|
6526
|
+
organization.id
|
|
6527
|
+
])));
|
|
6528
|
+
}
|
|
6529
|
+
}),
|
|
6530
|
+
defineCommand({
|
|
6531
|
+
name: "use",
|
|
6532
|
+
summary: "Act in an organization on this instance from now on.",
|
|
6533
|
+
usage: "orgs use <organization> | orgs use --clear",
|
|
6534
|
+
optionsUsage: [["--clear", "act in your own organization again"], ["", `--org and ${organizationVariable} still win over the one stored here`]],
|
|
6535
|
+
options: { clear: {
|
|
6536
|
+
type: "boolean",
|
|
6537
|
+
default: false
|
|
6538
|
+
} },
|
|
6539
|
+
positionals: {
|
|
6540
|
+
min: 0,
|
|
6541
|
+
max: 1
|
|
6542
|
+
},
|
|
6543
|
+
run: async ({ values, positionals, settings, context }) => {
|
|
6544
|
+
const [reference] = positionals;
|
|
6545
|
+
if (values.clear === (reference !== void 0)) throw new UsageError("orgs use needs an organization or --clear, and not both.", "kcloud orgs list shows the organizations you can see.");
|
|
6546
|
+
const adapters = adaptersOf(context);
|
|
6547
|
+
const session = await openSession({
|
|
6548
|
+
settings,
|
|
6549
|
+
context,
|
|
6550
|
+
api: adapters.api
|
|
6551
|
+
});
|
|
6552
|
+
if (session.stored === null) throw new CliError(`${tokenVariable} signs in to ${session.origin}, so nothing is stored here to keep the organization with.`, `Pass --org or set ${organizationVariable} instead.`);
|
|
6553
|
+
const organization = reference === void 0 ? null : organizationIn(await session.organizations(), reference);
|
|
6554
|
+
await writeInstanceStore(context.homeDirectory, withInstance(session.store, session.origin, {
|
|
6555
|
+
...session.stored,
|
|
6556
|
+
organization: organization?.slug ?? null
|
|
6557
|
+
}));
|
|
6558
|
+
if (givenIn(context.environment, "KIKIMORA_CLOUD_ORG") !== null) context.terminal.error(context.terminal.errorStyles.warning(`warning ${organizationVariable} still wins over it in this environment.`));
|
|
6559
|
+
return printed({
|
|
6560
|
+
settings,
|
|
6561
|
+
context
|
|
6562
|
+
}, {
|
|
6563
|
+
origin: session.origin,
|
|
6564
|
+
organization
|
|
6565
|
+
}, ({ origin }) => organization === null ? `kcloud acts in your own organization on ${origin} again.` : `kcloud acts in ${organizationNameOf(organization)} on ${origin} from now on.`);
|
|
6566
|
+
}
|
|
6567
|
+
}),
|
|
6568
|
+
defineCommand({
|
|
6569
|
+
name: "show",
|
|
6570
|
+
summary: "Show an organization, by default the one kcloud acts in.",
|
|
6571
|
+
usage: "orgs show [<organization>]",
|
|
6572
|
+
options: {},
|
|
6573
|
+
positionals: {
|
|
6574
|
+
min: 0,
|
|
6575
|
+
max: 1
|
|
6576
|
+
},
|
|
6577
|
+
run: async (invocation) => {
|
|
6578
|
+
const run = await runOf(adaptersOf, invocation);
|
|
6579
|
+
const { id } = await organizationOrInUse(run, invocation.positionals[0]);
|
|
6580
|
+
const organization = dataOf("describe the organization", await run.api.GET("/organizations/{organizationId}", { params: { path: { organizationId: id } } }), organizationHints);
|
|
6581
|
+
return printed(run, organization, (shown) => renderTable([{ header: "FIELD" }, { header: "VALUE" }], [
|
|
6582
|
+
["name", shown.name],
|
|
6583
|
+
["slug", shown.slug],
|
|
6584
|
+
["created", shown.createdAt],
|
|
6585
|
+
["id", shown.id]
|
|
6586
|
+
]));
|
|
6587
|
+
}
|
|
6588
|
+
}),
|
|
6589
|
+
defineCommand({
|
|
6590
|
+
name: "costs",
|
|
6591
|
+
summary: "Show what the organization spent, by day and by project.",
|
|
6592
|
+
usage: "orgs costs [--from <day>] [--to <day>]",
|
|
6593
|
+
optionsUsage: [["--from <day>", `the first day, as YYYY-MM-DD (default: 7 days up to --to)`], ["--to <day>", "the last day, as YYYY-MM-DD (default: today)"]],
|
|
6594
|
+
options: {
|
|
6595
|
+
from: { type: "string" },
|
|
6596
|
+
to: { type: "string" }
|
|
6597
|
+
},
|
|
6598
|
+
positionals: {
|
|
6599
|
+
min: 0,
|
|
6600
|
+
max: 0
|
|
6601
|
+
},
|
|
6602
|
+
run: async (invocation) => {
|
|
6603
|
+
const { values } = invocation;
|
|
6604
|
+
const run = await runOf(adaptersOf, invocation);
|
|
6605
|
+
const window = costWindowFrom(values.from, values.to, run.adapters.now());
|
|
6606
|
+
const organization = await run.organization();
|
|
6607
|
+
const costs = await organizationCostsOf(run.api, organization.id, window);
|
|
6608
|
+
if (run.settings.json) {
|
|
6609
|
+
run.context.terminal.json(costs);
|
|
6610
|
+
return exitCodes.ok;
|
|
6611
|
+
}
|
|
6612
|
+
const slugs = new Map(costs.projects.length === 0 ? [] : (await projectsOf(run)).map(({ id, slug }) => [id, slug]));
|
|
6613
|
+
run.context.terminal.line(costsText(costs, window, slugs));
|
|
6614
|
+
return exitCodes.ok;
|
|
6615
|
+
}
|
|
6616
|
+
}),
|
|
6617
|
+
organizationMembersCommand(adaptersOf)
|
|
6618
|
+
]
|
|
6619
|
+
});
|
|
6620
|
+
//#endregion
|
|
6621
|
+
//#region src/mcp/tools/organizations.ts
|
|
6622
|
+
const memberField = z.string().trim().min(1).describe("The member, by e-mail or id, from organization_members_list.");
|
|
6623
|
+
const dayField = z.iso.date();
|
|
6624
|
+
const chosenMember = async (run, member) => {
|
|
6625
|
+
const { id } = await run.organization();
|
|
6626
|
+
return {
|
|
6627
|
+
organizationId: id,
|
|
6628
|
+
userId: (await resolveOrganizationMember(run.api, id, member)).id
|
|
6629
|
+
};
|
|
6630
|
+
};
|
|
6631
|
+
const organizationTools = [
|
|
6632
|
+
defineTool({
|
|
6633
|
+
name: "organizations_list",
|
|
6634
|
+
title: "List organizations",
|
|
6635
|
+
description: "List the organizations you can see, marking the one this server acts in. A member sees one, a platform admin sees all.",
|
|
6636
|
+
operations: ["listOrganizations"],
|
|
6637
|
+
safety: "read",
|
|
6638
|
+
input: z.object({}),
|
|
6639
|
+
run: (_, run) => organizationsMarked(run, () => void 0)
|
|
6640
|
+
}),
|
|
6641
|
+
defineTool({
|
|
6642
|
+
name: "organization_show",
|
|
6643
|
+
title: "Show an organization",
|
|
6644
|
+
description: "Show an organization, by default the one this server acts in.",
|
|
6645
|
+
operations: ["getOrganization"],
|
|
6646
|
+
safety: "read",
|
|
6647
|
+
input: z.object({ organization: z.string().trim().min(1).optional().describe("The organization, by its slug, its name or its id.") }),
|
|
6648
|
+
run: async ({ organization }, run) => {
|
|
6649
|
+
const { id } = await organizationOrInUse(run, organization);
|
|
6650
|
+
return dataOf("describe the organization", await run.api.GET("/organizations/{organizationId}", { params: { path: { organizationId: id } } }), organizationHints);
|
|
6651
|
+
}
|
|
6652
|
+
}),
|
|
6653
|
+
defineTool({
|
|
6654
|
+
name: "organization_costs",
|
|
6655
|
+
title: "Show what the organization spent",
|
|
6656
|
+
description: "Show what the organization this server acts in spent, by day and by project, over the last seven days unless from and to say otherwise.",
|
|
6657
|
+
operations: ["getOrganizationCosts"],
|
|
6658
|
+
safety: "read",
|
|
6659
|
+
input: z.object({
|
|
6660
|
+
from: dayField.optional().describe("The first day, as YYYY-MM-DD."),
|
|
6661
|
+
to: dayField.optional().describe("The last day, as YYYY-MM-DD, today by default.")
|
|
6662
|
+
}),
|
|
6663
|
+
run: async ({ from, to }, run) => {
|
|
6664
|
+
const { id } = await run.organization();
|
|
6665
|
+
return organizationCostsOf(run.api, id, costWindowFrom(from, to, run.now()));
|
|
6666
|
+
}
|
|
6667
|
+
}),
|
|
6668
|
+
defineTool({
|
|
6669
|
+
name: "organization_members_list",
|
|
6670
|
+
title: "List the members of the organization",
|
|
6671
|
+
description: "List the people of the organization this server acts in, with their role and status.",
|
|
6672
|
+
operations: ["listOrganizationMembers"],
|
|
6673
|
+
safety: "read",
|
|
6674
|
+
input: z.object({}),
|
|
6675
|
+
run: async (_, run) => organizationMembersOf(run.api, (await run.organization()).id)
|
|
6676
|
+
}),
|
|
6677
|
+
defineTool({
|
|
6678
|
+
name: "organization_member_set_role",
|
|
6679
|
+
title: "Set the role of a member",
|
|
6680
|
+
description: "Make a member an admin of the organization, or a member again. It takes an admin of the organization.",
|
|
6681
|
+
operations: ["updateOrganizationMember"],
|
|
6682
|
+
safety: "write",
|
|
6683
|
+
idempotent: true,
|
|
6684
|
+
input: z.object({
|
|
6685
|
+
member: memberField,
|
|
6686
|
+
role: organizationRoleSchema
|
|
6687
|
+
}),
|
|
6688
|
+
run: async ({ member, role }, run) => {
|
|
6689
|
+
const { organizationId, userId } = await chosenMember(run, member);
|
|
6690
|
+
return memberRoleChanged(run.api, organizationId, userId, role);
|
|
6691
|
+
}
|
|
6692
|
+
}),
|
|
6693
|
+
defineTool({
|
|
6694
|
+
name: "organization_member_disable",
|
|
6695
|
+
title: "Disable a member",
|
|
6696
|
+
description: "Stop a member from signing in, keeping the account. It takes an admin of the organization.",
|
|
6697
|
+
operations: ["disableOrganizationMember"],
|
|
6698
|
+
safety: "destructive",
|
|
6699
|
+
input: z.object({ member: memberField }),
|
|
6700
|
+
run: async ({ member }, run) => {
|
|
6701
|
+
const { organizationId, userId } = await chosenMember(run, member);
|
|
6702
|
+
return memberDisabled(run.api, organizationId, userId);
|
|
6703
|
+
}
|
|
6704
|
+
}),
|
|
6705
|
+
defineTool({
|
|
6706
|
+
name: "organization_member_enable",
|
|
6707
|
+
title: "Enable a member",
|
|
6708
|
+
description: "Let a disabled member sign in again. It takes an admin of the organization.",
|
|
6709
|
+
operations: ["enableOrganizationMember"],
|
|
6710
|
+
safety: "write",
|
|
6711
|
+
input: z.object({ member: memberField }),
|
|
6712
|
+
run: async ({ member }, run) => {
|
|
6713
|
+
const { organizationId, userId } = await chosenMember(run, member);
|
|
6714
|
+
return memberEnabled(run.api, organizationId, userId);
|
|
6715
|
+
}
|
|
6716
|
+
})
|
|
6717
|
+
];
|
|
6718
|
+
//#endregion
|
|
5652
6719
|
//#region src/commands/tools.ts
|
|
5653
6720
|
const projectOption$1 = { project: { type: "string" } };
|
|
5654
6721
|
const projectUsage$1 = [["--project <name>", "the project the tool belongs to, by slug, name or id"]];
|
|
@@ -5663,7 +6730,7 @@ const toolHints = {
|
|
|
5663
6730
|
};
|
|
5664
6731
|
const projectFrom$1 = async (run, given) => {
|
|
5665
6732
|
if (given === void 0) throw new UsageError("This command needs a project.", "Pass --project <name>; kcloud projects list shows them.");
|
|
5666
|
-
return resolveProjectId(run
|
|
6733
|
+
return resolveProjectId(run, given);
|
|
5667
6734
|
};
|
|
5668
6735
|
const toolsOf = async (api, projectId) => dataOf("list the tools of the project", await api.GET("/projects/{id}/tools", { params: { path: { id: projectId } } }), toolHints).items;
|
|
5669
6736
|
const resolveTool = async (api, projectId, reference) => {
|
|
@@ -5688,6 +6755,13 @@ const changedTool = async (api, projectId, tool, document) => {
|
|
|
5688
6755
|
body: change
|
|
5689
6756
|
}), toolHints);
|
|
5690
6757
|
};
|
|
6758
|
+
const copiedTool = async (api, projectId, toolId, targetProjectId) => dataOf("copy the tool", await api.POST("/projects/{id}/tools/{toolId}/copy", {
|
|
6759
|
+
params: { path: {
|
|
6760
|
+
id: projectId,
|
|
6761
|
+
toolId
|
|
6762
|
+
} },
|
|
6763
|
+
body: { targetProjectId }
|
|
6764
|
+
}), toolHints);
|
|
5691
6765
|
const createToolsCommand = (adaptersOf) => defineGroup({
|
|
5692
6766
|
name: "tools",
|
|
5693
6767
|
summary: "The HTTP APIs and MCP servers a project offers its agents.",
|
|
@@ -5833,6 +6907,31 @@ const createToolsCommand = (adaptersOf) => defineGroup({
|
|
|
5833
6907
|
} } }), toolHints);
|
|
5834
6908
|
return said(run, `Deleted the tool ${tool.name}.`);
|
|
5835
6909
|
}
|
|
6910
|
+
}),
|
|
6911
|
+
defineCommand({
|
|
6912
|
+
name: "copy",
|
|
6913
|
+
summary: "Copy a tool to another project, without its secrets.",
|
|
6914
|
+
usage: "tools copy <tool> --project <name> --to <project>",
|
|
6915
|
+
optionsUsage: [...projectUsage$1, ["--to <project>", "the project that receives the copy, by slug, name or id"]],
|
|
6916
|
+
options: {
|
|
6917
|
+
...projectOption$1,
|
|
6918
|
+
to: { type: "string" }
|
|
6919
|
+
},
|
|
6920
|
+
positionals: {
|
|
6921
|
+
min: 1,
|
|
6922
|
+
max: 1
|
|
6923
|
+
},
|
|
6924
|
+
run: async (invocation) => {
|
|
6925
|
+
const { values } = invocation;
|
|
6926
|
+
const target = values.to;
|
|
6927
|
+
if (target === void 0) throw new UsageError("tools copy needs --to <project>.", "kcloud projects list shows the projects.");
|
|
6928
|
+
const run = await runOf(adaptersOf, invocation);
|
|
6929
|
+
const projectId = await projectFrom$1(run, values.project);
|
|
6930
|
+
const tool = await resolveTool(run.api, projectId, invocation.positionals[0] ?? "");
|
|
6931
|
+
const targetProjectId = await resolveProjectId(run, target);
|
|
6932
|
+
const copied = await copiedTool(run.api, projectId, tool.id, targetProjectId);
|
|
6933
|
+
return printed(run, copied, (shown) => [`Copied ${tool.name} to ${target} as ${shown.id}.`, ...shown.complete ? [] : ["Its secrets stay behind. kcloud tools update --file sets them."]].join(" "));
|
|
6934
|
+
}
|
|
5836
6935
|
})
|
|
5837
6936
|
]
|
|
5838
6937
|
});
|
|
@@ -5906,6 +7005,23 @@ const projectToolTools = [
|
|
|
5906
7005
|
});
|
|
5907
7006
|
}
|
|
5908
7007
|
}),
|
|
7008
|
+
defineTool({
|
|
7009
|
+
name: "tool_copy",
|
|
7010
|
+
title: "Copy a tool",
|
|
7011
|
+
description: "Copy a tool to another project of the organization. Its secrets stay behind, so a copy that needs them is incomplete until someone sets them.",
|
|
7012
|
+
operations: ["copyTool"],
|
|
7013
|
+
safety: "write",
|
|
7014
|
+
input: z.object({
|
|
7015
|
+
project: projectField,
|
|
7016
|
+
tool: toolField,
|
|
7017
|
+
to: projectField.describe("The project that receives the copy, by slug, name or id.")
|
|
7018
|
+
}),
|
|
7019
|
+
run: async ({ project, tool, to }, run) => {
|
|
7020
|
+
const projectId = await projectIdOf(run, project);
|
|
7021
|
+
const found = await resolveTool(run.api, projectId, tool);
|
|
7022
|
+
return copiedTool(run.api, projectId, found.id, await projectIdOf(run, to));
|
|
7023
|
+
}
|
|
7024
|
+
}),
|
|
5909
7025
|
defineTool({
|
|
5910
7026
|
name: "tool_delete",
|
|
5911
7027
|
title: "Delete a tool",
|
|
@@ -5928,16 +7044,166 @@ const projectToolTools = [
|
|
|
5928
7044
|
})
|
|
5929
7045
|
];
|
|
5930
7046
|
//#endregion
|
|
5931
|
-
//#region src/commands/projects.ts
|
|
5932
|
-
const
|
|
7047
|
+
//#region src/commands/projects/members.ts
|
|
7048
|
+
const projectMembersOf = async (api, projectId) => dataOf("list the members of the project", await api.GET("/projects/{id}/members", { params: { path: { id: projectId } } }), projectHint).items;
|
|
7049
|
+
const resolveProjectMember = async (api, projectId, reference) => {
|
|
7050
|
+
const found = (await projectMembersOf(api, projectId)).find((member) => member.userId === reference.toLowerCase() || sameEmail(member.email, reference));
|
|
7051
|
+
if (found === void 0) throw new CliError(`The project has no member ${reference}.`, "kcloud projects members list <project> shows them.");
|
|
7052
|
+
return found;
|
|
7053
|
+
};
|
|
7054
|
+
const projectUserIdOf = async (api, projectId, reference) => {
|
|
7055
|
+
if (isId(reference)) return reference.toLowerCase();
|
|
7056
|
+
const { organizationId } = await projectOf(api, projectId);
|
|
7057
|
+
return (await resolveOrganizationMember(api, organizationId, reference)).id;
|
|
7058
|
+
};
|
|
7059
|
+
const setProjectMember = async (api, projectId, userId, role) => {
|
|
7060
|
+
doneWith("set the member of the project", await api.PUT("/projects/{id}/members/{userId}", {
|
|
7061
|
+
params: { path: {
|
|
7062
|
+
id: projectId,
|
|
7063
|
+
userId
|
|
7064
|
+
} },
|
|
7065
|
+
body: { role }
|
|
7066
|
+
}), projectHint);
|
|
7067
|
+
};
|
|
7068
|
+
const removeProjectMember = async (api, projectId, userId) => {
|
|
7069
|
+
doneWith("remove the member of the project", await api.DELETE("/projects/{id}/members/{userId}", { params: { path: {
|
|
7070
|
+
id: projectId,
|
|
7071
|
+
userId
|
|
7072
|
+
} } }), projectHint);
|
|
7073
|
+
};
|
|
7074
|
+
const projectMembersCommand = (adaptersOf) => defineGroup({
|
|
7075
|
+
name: "members",
|
|
7076
|
+
summary: "The members of a project and their project role.",
|
|
7077
|
+
usage: "projects members <command>",
|
|
7078
|
+
subcommands: [
|
|
7079
|
+
defineCommand({
|
|
7080
|
+
name: "list",
|
|
7081
|
+
summary: "List the members of one project.",
|
|
7082
|
+
usage: "projects members list <project>",
|
|
7083
|
+
options: {},
|
|
7084
|
+
positionals: {
|
|
7085
|
+
min: 1,
|
|
7086
|
+
max: 1
|
|
7087
|
+
},
|
|
7088
|
+
run: async (invocation) => {
|
|
7089
|
+
const run = await runOf(adaptersOf, invocation);
|
|
7090
|
+
const id = await resolveProjectId(run, invocation.positionals[0] ?? "");
|
|
7091
|
+
const items = await projectMembersOf(run.api, id);
|
|
7092
|
+
return printed(run, items, (members) => members.length === 0 ? "The project has no member." : renderTable([
|
|
7093
|
+
{ header: "EMAIL" },
|
|
7094
|
+
{ header: "ROLE" },
|
|
7095
|
+
{ header: "STATUS" },
|
|
7096
|
+
{ header: "ADDED" },
|
|
7097
|
+
{ header: "ID" }
|
|
7098
|
+
], members.map((member) => [
|
|
7099
|
+
member.email,
|
|
7100
|
+
member.role,
|
|
7101
|
+
member.status,
|
|
7102
|
+
member.addedAt,
|
|
7103
|
+
member.userId
|
|
7104
|
+
])));
|
|
7105
|
+
}
|
|
7106
|
+
}),
|
|
7107
|
+
defineCommand({
|
|
7108
|
+
name: "set",
|
|
7109
|
+
summary: "Add a member of the organization to a project, or change their role.",
|
|
7110
|
+
usage: "projects members set <project> <user> --role <role>",
|
|
7111
|
+
optionsUsage: [roleUsage, ["", "the user is named by e-mail or id, and belongs to the organization"]],
|
|
7112
|
+
options: roleOption,
|
|
7113
|
+
positionals: {
|
|
7114
|
+
min: 2,
|
|
7115
|
+
max: 2
|
|
7116
|
+
},
|
|
7117
|
+
run: async (invocation) => {
|
|
7118
|
+
const [project = "", user = ""] = invocation.positionals;
|
|
7119
|
+
const role = roleFrom(invocation.values.role, "projects members set");
|
|
7120
|
+
const run = await runOf(adaptersOf, invocation);
|
|
7121
|
+
const id = await resolveProjectId(run, project);
|
|
7122
|
+
const userId = await projectUserIdOf(run.api, id, user);
|
|
7123
|
+
await setProjectMember(run.api, id, userId, role);
|
|
7124
|
+
return said(run, `${user} is ${asRole(role)} of the project ${project} now.`);
|
|
7125
|
+
}
|
|
7126
|
+
}),
|
|
7127
|
+
defineCommand({
|
|
7128
|
+
name: "remove",
|
|
7129
|
+
summary: "Take a member off a project.",
|
|
7130
|
+
usage: "projects members remove <project> <user>",
|
|
7131
|
+
optionsUsage: [["", "the user is named by e-mail or id"]],
|
|
7132
|
+
options: {},
|
|
7133
|
+
positionals: {
|
|
7134
|
+
min: 2,
|
|
7135
|
+
max: 2
|
|
7136
|
+
},
|
|
7137
|
+
run: async (invocation) => {
|
|
7138
|
+
const [project = "", user = ""] = invocation.positionals;
|
|
7139
|
+
const run = await runOf(adaptersOf, invocation);
|
|
7140
|
+
const id = await resolveProjectId(run, project);
|
|
7141
|
+
const member = await resolveProjectMember(run.api, id, user);
|
|
7142
|
+
await confirmed({
|
|
7143
|
+
prompt: run.prompt,
|
|
7144
|
+
settings: run.settings,
|
|
7145
|
+
question: `Remove ${member.email} from the project ${project}?`
|
|
7146
|
+
});
|
|
7147
|
+
await removeProjectMember(run.api, id, member.userId);
|
|
7148
|
+
return said(run, `Removed ${member.email} from the project ${project}.`);
|
|
7149
|
+
}
|
|
7150
|
+
})
|
|
7151
|
+
]
|
|
7152
|
+
});
|
|
7153
|
+
//#endregion
|
|
7154
|
+
//#region src/commands/projects/index.ts
|
|
7155
|
+
const fieldOptions = {
|
|
7156
|
+
slug: { type: "string" },
|
|
7157
|
+
description: { type: "string" },
|
|
7158
|
+
color: { type: "string" }
|
|
7159
|
+
};
|
|
7160
|
+
const fieldUsage = [
|
|
7161
|
+
["--slug <slug>", "lowercase letters, digits and hyphens (default: made from the name)"],
|
|
7162
|
+
["--description <text>", "what the project is for"],
|
|
7163
|
+
["--color <#rrggbb>", `the color of the project in the panel (default: ${defaultProjectColor})`]
|
|
7164
|
+
];
|
|
7165
|
+
const createdProject = async (scope, name, { slug, description, color }) => {
|
|
7166
|
+
const body = {
|
|
7167
|
+
name,
|
|
7168
|
+
...slug === void 0 ? {} : { slug },
|
|
7169
|
+
description: description ?? "",
|
|
7170
|
+
color: color ?? "#7c86ff"
|
|
7171
|
+
};
|
|
7172
|
+
validated(createProjectBodySchema, body, "a project");
|
|
7173
|
+
const organization = await scope.organization();
|
|
7174
|
+
return {
|
|
7175
|
+
project: dataOf("create the project", await scope.api.POST("/organizations/{organizationId}/projects", {
|
|
7176
|
+
params: { path: { organizationId: organization.id } },
|
|
7177
|
+
body
|
|
7178
|
+
}), projectHint),
|
|
7179
|
+
organization
|
|
7180
|
+
};
|
|
7181
|
+
};
|
|
7182
|
+
const changedProject = async (api, id, change) => {
|
|
7183
|
+
const current = await projectOf(api, id);
|
|
7184
|
+
const body = {
|
|
7185
|
+
name: change.name ?? current.name,
|
|
7186
|
+
slug: change.slug ?? current.slug,
|
|
7187
|
+
description: change.description ?? current.description,
|
|
7188
|
+
color: change.color ?? current.color
|
|
7189
|
+
};
|
|
7190
|
+
validated(updateProjectBodySchema, body, "a change to a project");
|
|
7191
|
+
return dataOf("change the project", await api.PATCH("/projects/{id}", {
|
|
7192
|
+
params: { path: { id } },
|
|
7193
|
+
body
|
|
7194
|
+
}), projectHint);
|
|
7195
|
+
};
|
|
7196
|
+
const deleteProject = async (api, id) => {
|
|
7197
|
+
doneWith("delete the project", await api.DELETE("/projects/{id}", { params: { path: { id } } }), projectHint);
|
|
7198
|
+
};
|
|
5933
7199
|
const createProjectsCommand = (adaptersOf) => defineGroup({
|
|
5934
7200
|
name: "projects",
|
|
5935
|
-
summary: "The projects
|
|
7201
|
+
summary: "The projects of the organization you can see.",
|
|
5936
7202
|
usage: "projects <command>",
|
|
5937
7203
|
subcommands: [
|
|
5938
7204
|
defineCommand({
|
|
5939
7205
|
name: "list",
|
|
5940
|
-
summary: "List the projects you can see.",
|
|
7206
|
+
summary: "List the projects of the organization you can see.",
|
|
5941
7207
|
usage: "projects list",
|
|
5942
7208
|
options: {},
|
|
5943
7209
|
positionals: {
|
|
@@ -5946,8 +7212,9 @@ const createProjectsCommand = (adaptersOf) => defineGroup({
|
|
|
5946
7212
|
},
|
|
5947
7213
|
run: async (invocation) => {
|
|
5948
7214
|
const run = await runOf(adaptersOf, invocation);
|
|
5949
|
-
const
|
|
5950
|
-
|
|
7215
|
+
const items = await projectsOf(run);
|
|
7216
|
+
const { slug } = await run.organization();
|
|
7217
|
+
return printed(run, items, (projects) => projects.length === 0 ? `You see no project in ${slug} yet.` : renderTable([
|
|
5951
7218
|
{ header: "SLUG" },
|
|
5952
7219
|
{ header: "NAME" },
|
|
5953
7220
|
{
|
|
@@ -5976,12 +7243,13 @@ const createProjectsCommand = (adaptersOf) => defineGroup({
|
|
|
5976
7243
|
},
|
|
5977
7244
|
run: async (invocation) => {
|
|
5978
7245
|
const run = await runOf(adaptersOf, invocation);
|
|
5979
|
-
const id = await resolveProjectId(run
|
|
7246
|
+
const id = await resolveProjectId(run, invocation.positionals[0] ?? "");
|
|
5980
7247
|
const project = dataOf("describe the project", await run.api.GET("/projects/{id}", { params: { path: { id } } }), projectHint);
|
|
5981
7248
|
return printed(run, project, (shown) => renderTable([{ header: "FIELD" }, { header: "VALUE" }], [
|
|
5982
7249
|
["name", shown.name],
|
|
5983
7250
|
["slug", shown.slug],
|
|
5984
7251
|
["description", shown.description === "" ? "-" : shown.description],
|
|
7252
|
+
["color", shown.color],
|
|
5985
7253
|
["docker", shown.dockerAllowed ? "allowed" : "not allowed"],
|
|
5986
7254
|
["members", String(shown.memberCount)],
|
|
5987
7255
|
["created", shown.createdAt],
|
|
@@ -5990,9 +7258,52 @@ const createProjectsCommand = (adaptersOf) => defineGroup({
|
|
|
5990
7258
|
}
|
|
5991
7259
|
}),
|
|
5992
7260
|
defineCommand({
|
|
5993
|
-
name: "
|
|
5994
|
-
summary: "
|
|
5995
|
-
usage: "projects
|
|
7261
|
+
name: "create",
|
|
7262
|
+
summary: "Create a project in the organization.",
|
|
7263
|
+
usage: "projects create <name> [--slug <slug>] [--description <text>] [--color <#rrggbb>]",
|
|
7264
|
+
optionsUsage: fieldUsage,
|
|
7265
|
+
options: fieldOptions,
|
|
7266
|
+
positionals: {
|
|
7267
|
+
min: 1,
|
|
7268
|
+
max: 1
|
|
7269
|
+
},
|
|
7270
|
+
run: async (invocation) => {
|
|
7271
|
+
const run = await runOf(adaptersOf, invocation);
|
|
7272
|
+
const { project, organization } = await createdProject(run, invocation.positionals[0] ?? "", invocation.values);
|
|
7273
|
+
return printed(run, project, (created) => `Created ${created.name} (${created.slug}) in ${organization.slug} as ${created.id}.`);
|
|
7274
|
+
}
|
|
7275
|
+
}),
|
|
7276
|
+
defineCommand({
|
|
7277
|
+
name: "update",
|
|
7278
|
+
summary: "Change the name, slug, description or color of a project.",
|
|
7279
|
+
usage: "projects update <project> [--name <name>] [--slug <slug>] [--description <text>] [--color <#rrggbb>]",
|
|
7280
|
+
optionsUsage: [["--name <name>", "the new name"], ...fieldUsage],
|
|
7281
|
+
options: {
|
|
7282
|
+
name: { type: "string" },
|
|
7283
|
+
...fieldOptions
|
|
7284
|
+
},
|
|
7285
|
+
positionals: {
|
|
7286
|
+
min: 1,
|
|
7287
|
+
max: 1
|
|
7288
|
+
},
|
|
7289
|
+
run: async (invocation) => {
|
|
7290
|
+
const { values } = invocation;
|
|
7291
|
+
if ([
|
|
7292
|
+
values.name,
|
|
7293
|
+
values.slug,
|
|
7294
|
+
values.description,
|
|
7295
|
+
values.color
|
|
7296
|
+
].every((value) => value === void 0)) throw new UsageError("projects update needs --name, --slug, --description or --color.");
|
|
7297
|
+
const run = await runOf(adaptersOf, invocation);
|
|
7298
|
+
const id = await resolveProjectId(run, invocation.positionals[0] ?? "");
|
|
7299
|
+
const project = await changedProject(run.api, id, values);
|
|
7300
|
+
return printed(run, project, (shown) => `Changed ${shown.name} (${shown.slug}).`);
|
|
7301
|
+
}
|
|
7302
|
+
}),
|
|
7303
|
+
defineCommand({
|
|
7304
|
+
name: "delete",
|
|
7305
|
+
summary: "Delete a project that holds no agent, credential, repository or tool.",
|
|
7306
|
+
usage: "projects delete <project>",
|
|
5996
7307
|
options: {},
|
|
5997
7308
|
positionals: {
|
|
5998
7309
|
min: 1,
|
|
@@ -6000,50 +7311,136 @@ const createProjectsCommand = (adaptersOf) => defineGroup({
|
|
|
6000
7311
|
},
|
|
6001
7312
|
run: async (invocation) => {
|
|
6002
7313
|
const run = await runOf(adaptersOf, invocation);
|
|
6003
|
-
const id = await resolveProjectId(run
|
|
6004
|
-
const
|
|
6005
|
-
|
|
6006
|
-
|
|
6007
|
-
|
|
6008
|
-
|
|
6009
|
-
|
|
6010
|
-
|
|
6011
|
-
|
|
6012
|
-
member.role,
|
|
6013
|
-
member.status,
|
|
6014
|
-
member.addedAt
|
|
6015
|
-
])));
|
|
7314
|
+
const id = await resolveProjectId(run, invocation.positionals[0] ?? "");
|
|
7315
|
+
const project = await projectOf(run.api, id);
|
|
7316
|
+
await confirmed({
|
|
7317
|
+
prompt: run.prompt,
|
|
7318
|
+
settings: run.settings,
|
|
7319
|
+
question: `Delete the project ${project.name} (${project.slug})?`
|
|
7320
|
+
});
|
|
7321
|
+
await deleteProject(run.api, id);
|
|
7322
|
+
return said(run, `Deleted the project ${project.name}.`);
|
|
6016
7323
|
}
|
|
6017
|
-
})
|
|
7324
|
+
}),
|
|
7325
|
+
projectMembersCommand(adaptersOf)
|
|
6018
7326
|
]
|
|
6019
7327
|
});
|
|
6020
7328
|
//#endregion
|
|
6021
7329
|
//#region src/mcp/tools/projects.ts
|
|
6022
|
-
const
|
|
6023
|
-
|
|
6024
|
-
|
|
6025
|
-
description:
|
|
6026
|
-
|
|
6027
|
-
|
|
6028
|
-
|
|
6029
|
-
|
|
6030
|
-
|
|
6031
|
-
|
|
6032
|
-
|
|
6033
|
-
|
|
6034
|
-
|
|
6035
|
-
|
|
6036
|
-
|
|
6037
|
-
|
|
6038
|
-
|
|
6039
|
-
|
|
6040
|
-
|
|
6041
|
-
|
|
6042
|
-
|
|
6043
|
-
|
|
6044
|
-
}
|
|
6045
|
-
|
|
6046
|
-
|
|
7330
|
+
const userField = z.string().trim().min(1).describe("The user, by e-mail or id, a member of the organization of the project.");
|
|
7331
|
+
const fields = {
|
|
7332
|
+
slug: slugSchema.optional().describe("Lowercase letters, digits and hyphens."),
|
|
7333
|
+
description: z.string().max(500).optional(),
|
|
7334
|
+
color: colorSchema.optional().describe("The color in the panel, as #rrggbb.")
|
|
7335
|
+
};
|
|
7336
|
+
const projectTools = [
|
|
7337
|
+
defineTool({
|
|
7338
|
+
name: "projects_list",
|
|
7339
|
+
title: "List projects",
|
|
7340
|
+
description: "List the projects you can see in the organization this server acts in.",
|
|
7341
|
+
operations: ["listProjects"],
|
|
7342
|
+
safety: "read",
|
|
7343
|
+
input: z.object({}),
|
|
7344
|
+
run: async (_, run) => projectsOf(run)
|
|
7345
|
+
}),
|
|
7346
|
+
defineTool({
|
|
7347
|
+
name: "project_show",
|
|
7348
|
+
title: "Show a project",
|
|
7349
|
+
description: "Show one project together with its members.",
|
|
7350
|
+
operations: ["getProject", "listProjectMembers"],
|
|
7351
|
+
safety: "read",
|
|
7352
|
+
input: z.object({ project: projectField }),
|
|
7353
|
+
run: async ({ project }, run) => {
|
|
7354
|
+
const id = await projectIdOf(run, project);
|
|
7355
|
+
return {
|
|
7356
|
+
...dataOf("describe the project", await run.api.GET("/projects/{id}", { params: { path: { id } } }), projectHint),
|
|
7357
|
+
members: await projectMembersOf(run.api, id)
|
|
7358
|
+
};
|
|
7359
|
+
}
|
|
7360
|
+
}),
|
|
7361
|
+
defineTool({
|
|
7362
|
+
name: "project_create",
|
|
7363
|
+
title: "Create a project",
|
|
7364
|
+
description: "Create a project in the organization this server acts in.",
|
|
7365
|
+
operations: ["createProject"],
|
|
7366
|
+
safety: "write",
|
|
7367
|
+
input: z.object({
|
|
7368
|
+
name: z.string().trim().min(2).max(80),
|
|
7369
|
+
...fields
|
|
7370
|
+
}),
|
|
7371
|
+
run: async ({ name, ...rest }, run) => (await createdProject(run, name, rest)).project
|
|
7372
|
+
}),
|
|
7373
|
+
defineTool({
|
|
7374
|
+
name: "project_update",
|
|
7375
|
+
title: "Change a project",
|
|
7376
|
+
description: "Change the name, slug, description or color of a project. A field left out keeps its value.",
|
|
7377
|
+
operations: ["updateProject"],
|
|
7378
|
+
safety: "write",
|
|
7379
|
+
idempotent: true,
|
|
7380
|
+
input: z.object({
|
|
7381
|
+
project: projectField,
|
|
7382
|
+
name: z.string().trim().min(2).max(80).optional(),
|
|
7383
|
+
...fields
|
|
7384
|
+
}),
|
|
7385
|
+
run: async ({ project, ...change }, run) => changedProject(run.api, await projectIdOf(run, project), change)
|
|
7386
|
+
}),
|
|
7387
|
+
defineTool({
|
|
7388
|
+
name: "project_delete",
|
|
7389
|
+
title: "Delete a project",
|
|
7390
|
+
description: "Delete a project. The instance refuses while it holds agents, credentials, repositories or tools.",
|
|
7391
|
+
operations: ["deleteProject"],
|
|
7392
|
+
safety: "destructive",
|
|
7393
|
+
input: z.object({ project: projectField }),
|
|
7394
|
+
run: async ({ project }, run) => {
|
|
7395
|
+
const id = await projectIdOf(run, project);
|
|
7396
|
+
await deleteProject(run.api, id);
|
|
7397
|
+
return { deleted: id };
|
|
7398
|
+
}
|
|
7399
|
+
}),
|
|
7400
|
+
defineTool({
|
|
7401
|
+
name: "project_member_set",
|
|
7402
|
+
title: "Set a member of a project",
|
|
7403
|
+
description: "Add a member of the organization to a project with a project role, or change the role of a member.",
|
|
7404
|
+
operations: ["setProjectMember"],
|
|
7405
|
+
safety: "write",
|
|
7406
|
+
idempotent: true,
|
|
7407
|
+
input: z.object({
|
|
7408
|
+
project: projectField,
|
|
7409
|
+
user: userField,
|
|
7410
|
+
role: projectRoleSchema
|
|
7411
|
+
}),
|
|
7412
|
+
run: async ({ project, user, role }, run) => {
|
|
7413
|
+
const id = await projectIdOf(run, project);
|
|
7414
|
+
const userId = await projectUserIdOf(run.api, id, user);
|
|
7415
|
+
await setProjectMember(run.api, id, userId, role);
|
|
7416
|
+
return {
|
|
7417
|
+
projectId: id,
|
|
7418
|
+
userId,
|
|
7419
|
+
role
|
|
7420
|
+
};
|
|
7421
|
+
}
|
|
7422
|
+
}),
|
|
7423
|
+
defineTool({
|
|
7424
|
+
name: "project_member_remove",
|
|
7425
|
+
title: "Remove a member of a project",
|
|
7426
|
+
description: "Take a member off a project. The account stays in the organization.",
|
|
7427
|
+
operations: ["removeProjectMember"],
|
|
7428
|
+
safety: "destructive",
|
|
7429
|
+
input: z.object({
|
|
7430
|
+
project: projectField,
|
|
7431
|
+
user: userField
|
|
7432
|
+
}),
|
|
7433
|
+
run: async ({ project, user }, run) => {
|
|
7434
|
+
const id = await projectIdOf(run, project);
|
|
7435
|
+
const member = await resolveProjectMember(run.api, id, user);
|
|
7436
|
+
await removeProjectMember(run.api, id, member.userId);
|
|
7437
|
+
return {
|
|
7438
|
+
projectId: id,
|
|
7439
|
+
removed: member.userId
|
|
7440
|
+
};
|
|
7441
|
+
}
|
|
7442
|
+
})
|
|
7443
|
+
];
|
|
6047
7444
|
//#endregion
|
|
6048
7445
|
//#region src/mcp/tools/releases.ts
|
|
6049
7446
|
const releaseTools = [defineTool({
|
|
@@ -6071,7 +7468,7 @@ const repositoryHints = {
|
|
|
6071
7468
|
};
|
|
6072
7469
|
const projectFrom = async (run, given) => {
|
|
6073
7470
|
if (given === void 0) throw new UsageError("This command needs a project.", "Pass --project <name>; kcloud projects list shows them.");
|
|
6074
|
-
return resolveProjectId(run
|
|
7471
|
+
return resolveProjectId(run, given);
|
|
6075
7472
|
};
|
|
6076
7473
|
const repositoriesOf = async (api, projectId) => dataOf("list the repositories of the project", await api.GET("/projects/{id}/repositories", { params: { path: { id: projectId } } }), repositoryHints).items;
|
|
6077
7474
|
const resolveRepositoryId = async (api, projectId, reference) => {
|
|
@@ -6447,23 +7844,28 @@ const taskTools = [
|
|
|
6447
7844
|
//#region src/mcp/tools/index.ts
|
|
6448
7845
|
const kcloudTools = [
|
|
6449
7846
|
...accountTools,
|
|
7847
|
+
...organizationTools,
|
|
6450
7848
|
...projectTools,
|
|
6451
7849
|
...credentialTools,
|
|
6452
7850
|
...repositoryTools,
|
|
6453
7851
|
...projectToolTools,
|
|
7852
|
+
...nodeTools,
|
|
6454
7853
|
...agentTools,
|
|
6455
7854
|
...taskTools,
|
|
6456
7855
|
...observabilityTools,
|
|
6457
7856
|
...notificationTools,
|
|
6458
7857
|
...releaseTools
|
|
6459
7858
|
];
|
|
7859
|
+
const passwordPrinted = (what) => `a secret never passes through the context of a model, and kcloud prints ${what}`;
|
|
7860
|
+
passwordPrinted("the one-time registration token of the node");
|
|
7861
|
+
passwordPrinted("the temporary password of the new member"), passwordPrinted("the new temporary password");
|
|
6460
7862
|
//#endregion
|
|
6461
7863
|
//#region src/mcp/server.ts
|
|
6462
7864
|
const instructions = [
|
|
6463
|
-
"These tools act on one Kikimora Cloud instance as the account kcloud is signed in with
|
|
7865
|
+
"These tools act on one Kikimora Cloud instance, as the account kcloud is signed in with, in one organization of it. whoami names all three.",
|
|
6464
7866
|
"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.",
|
|
6465
7867
|
"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.",
|
|
6466
|
-
"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."
|
|
7868
|
+
"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, kcloud orgs members add, kcloud orgs members reset-password, or sets the secrets of a tool in the panel."
|
|
6467
7869
|
].join("\n");
|
|
6468
7870
|
const createKcloudServer = ({ version, open }) => {
|
|
6469
7871
|
const server = new McpServer({
|
|
@@ -6484,8 +7886,8 @@ const createKcloudServer = ({ version, open }) => {
|
|
|
6484
7886
|
const createMcpCommand = (adaptersOf, cliVersion) => defineCommand({
|
|
6485
7887
|
name: "mcp",
|
|
6486
7888
|
summary: "Serve the work of kcloud to an MCP client over stdio.",
|
|
6487
|
-
usage: "mcp [--
|
|
6488
|
-
optionsUsage: [["", "an MCP client starts it, as in: claude mcp add kcloud -- kcloud mcp"], ["", "each tool call
|
|
7889
|
+
usage: "mcp [--url <origin>] [--org <organization>]",
|
|
7890
|
+
optionsUsage: [["", "an MCP client starts it, as in: claude mcp add kcloud -- kcloud mcp"], ["", "each tool call reads the token and the organization of that moment"]],
|
|
6489
7891
|
options: {},
|
|
6490
7892
|
positionals: {
|
|
6491
7893
|
min: 0,
|
|
@@ -6504,13 +7906,12 @@ const createMcpCommand = (adaptersOf, cliVersion) => defineCommand({
|
|
|
6504
7906
|
});
|
|
6505
7907
|
return {
|
|
6506
7908
|
api: session.api,
|
|
7909
|
+
me: session.me,
|
|
7910
|
+
organizations: session.organizations,
|
|
7911
|
+
organization: session.organization,
|
|
6507
7912
|
origin: session.origin,
|
|
6508
|
-
profileName: session.profileName,
|
|
6509
7913
|
kcloudVersion: cliVersion,
|
|
6510
|
-
token: () =>
|
|
6511
|
-
settings,
|
|
6512
|
-
context
|
|
6513
|
-
}),
|
|
7914
|
+
token: () => Promise.resolve(session.token),
|
|
6514
7915
|
wait: adapters.wait,
|
|
6515
7916
|
now: adapters.now
|
|
6516
7917
|
};
|
|
@@ -6527,96 +7928,6 @@ const createMcpCommand = (adaptersOf, cliVersion) => defineCommand({
|
|
|
6527
7928
|
}
|
|
6528
7929
|
});
|
|
6529
7930
|
//#endregion
|
|
6530
|
-
//#region src/commands/profiles.ts
|
|
6531
|
-
const rowsOf = (store) => Object.entries(store.profiles).map(([name, profile]) => [
|
|
6532
|
-
name === store.current ? `${name} *` : name,
|
|
6533
|
-
profile.origin,
|
|
6534
|
-
profile.email ?? "unknown"
|
|
6535
|
-
]);
|
|
6536
|
-
const namedProfile = (store, name) => {
|
|
6537
|
-
if (store.profiles[name] === void 0) {
|
|
6538
|
-
const known = Object.keys(store.profiles);
|
|
6539
|
-
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(", ")}.`);
|
|
6540
|
-
}
|
|
6541
|
-
};
|
|
6542
|
-
const createProfilesCommand = () => defineGroup({
|
|
6543
|
-
name: "profiles",
|
|
6544
|
-
summary: "The instances this machine is signed in to.",
|
|
6545
|
-
usage: "profiles <command>",
|
|
6546
|
-
subcommands: [
|
|
6547
|
-
defineCommand({
|
|
6548
|
-
name: "list",
|
|
6549
|
-
summary: "List the saved profiles, marking the current one.",
|
|
6550
|
-
usage: "profiles list",
|
|
6551
|
-
options: {},
|
|
6552
|
-
positionals: {
|
|
6553
|
-
min: 0,
|
|
6554
|
-
max: 0
|
|
6555
|
-
},
|
|
6556
|
-
run: async ({ settings, context }) => {
|
|
6557
|
-
const store = await readProfileStore(context.homeDirectory);
|
|
6558
|
-
if (settings.json) {
|
|
6559
|
-
context.terminal.json({
|
|
6560
|
-
current: store.current,
|
|
6561
|
-
profiles: Object.entries(store.profiles).map(([name, profile]) => ({
|
|
6562
|
-
name,
|
|
6563
|
-
origin: profile.origin,
|
|
6564
|
-
email: profile.email
|
|
6565
|
-
}))
|
|
6566
|
-
});
|
|
6567
|
-
return exitCodes.ok;
|
|
6568
|
-
}
|
|
6569
|
-
const rows = rowsOf(store);
|
|
6570
|
-
context.terminal.line(rows.length === 0 ? "No instance is signed in here; kcloud login --url <origin> signs in." : renderTable([
|
|
6571
|
-
{ header: "PROFILE" },
|
|
6572
|
-
{ header: "INSTANCE" },
|
|
6573
|
-
{ header: "ACCOUNT" }
|
|
6574
|
-
], rows));
|
|
6575
|
-
return exitCodes.ok;
|
|
6576
|
-
}
|
|
6577
|
-
}),
|
|
6578
|
-
defineCommand({
|
|
6579
|
-
name: "use",
|
|
6580
|
-
summary: "Make one of the saved profiles the current one.",
|
|
6581
|
-
usage: "profiles use <name>",
|
|
6582
|
-
options: {},
|
|
6583
|
-
positionals: {
|
|
6584
|
-
min: 1,
|
|
6585
|
-
max: 1
|
|
6586
|
-
},
|
|
6587
|
-
run: async ({ positionals, context }) => {
|
|
6588
|
-
const [name = ""] = positionals;
|
|
6589
|
-
const store = await readProfileStore(context.homeDirectory);
|
|
6590
|
-
namedProfile(store, name);
|
|
6591
|
-
await writeProfileStore(context.homeDirectory, {
|
|
6592
|
-
...store,
|
|
6593
|
-
current: name
|
|
6594
|
-
});
|
|
6595
|
-
context.terminal.line(`${name} is the current profile.`);
|
|
6596
|
-
return exitCodes.ok;
|
|
6597
|
-
}
|
|
6598
|
-
}),
|
|
6599
|
-
defineCommand({
|
|
6600
|
-
name: "remove",
|
|
6601
|
-
summary: "Forget a saved profile without revoking its token.",
|
|
6602
|
-
usage: "profiles remove <name>",
|
|
6603
|
-
options: {},
|
|
6604
|
-
positionals: {
|
|
6605
|
-
min: 1,
|
|
6606
|
-
max: 1
|
|
6607
|
-
},
|
|
6608
|
-
run: async ({ positionals, context }) => {
|
|
6609
|
-
const [name = ""] = positionals;
|
|
6610
|
-
const store = await readProfileStore(context.homeDirectory);
|
|
6611
|
-
namedProfile(store, name);
|
|
6612
|
-
await writeProfileStore(context.homeDirectory, withoutProfile(store, name));
|
|
6613
|
-
context.terminal.line(`Forgot ${name}; its token stays usable until it is revoked.`);
|
|
6614
|
-
return exitCodes.ok;
|
|
6615
|
-
}
|
|
6616
|
-
})
|
|
6617
|
-
]
|
|
6618
|
-
});
|
|
6619
|
-
//#endregion
|
|
6620
7931
|
//#region src/commands/releases.ts
|
|
6621
7932
|
const createReleasesCommand = (adaptersOf) => defineGroup({
|
|
6622
7933
|
name: "releases",
|
|
@@ -6656,13 +7967,14 @@ const kcloudCommands = () => [
|
|
|
6656
7967
|
createLogoutCommand(createAdapters),
|
|
6657
7968
|
createWhoamiCommand(createAdapters),
|
|
6658
7969
|
createVersionCommand(createAdapters, cliVersion),
|
|
6659
|
-
|
|
7970
|
+
createOrgsCommand(createAdapters),
|
|
6660
7971
|
createTokensCommand(createAdapters),
|
|
6661
7972
|
createAuthCommand(createAdapters),
|
|
6662
7973
|
createProjectsCommand(createAdapters),
|
|
6663
7974
|
createCredentialsCommand(createAdapters),
|
|
6664
7975
|
createRepositoriesCommand(createAdapters),
|
|
6665
7976
|
createToolsCommand(createAdapters),
|
|
7977
|
+
createNodesCommand(createAdapters),
|
|
6666
7978
|
createAgentsCommand(createAdapters),
|
|
6667
7979
|
createNotificationsCommand(createAdapters),
|
|
6668
7980
|
createReleasesCommand(createAdapters),
|