@kikimoradev/kcloud 1.10.0 → 1.11.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/main.js +1552 -241
- package/package.json +1 -1
package/dist/main.js
CHANGED
|
@@ -282,14 +282,14 @@ const quietPrompter = (prompter, warn) => ({
|
|
|
282
282
|
//#region ../../packages/cli-kit/src/table.ts
|
|
283
283
|
const columnGap = " ";
|
|
284
284
|
const visibleWidth = (text) => stripVTControlCharacters(text).length;
|
|
285
|
-
const pad = (text, width, align) => {
|
|
285
|
+
const pad$1 = (text, width, align) => {
|
|
286
286
|
const padding = " ".repeat(Math.max(0, width - visibleWidth(text)));
|
|
287
287
|
return align === "right" ? `${padding}${text}` : `${text}${padding}`;
|
|
288
288
|
};
|
|
289
289
|
const alignRows = (rows, alignments = []) => {
|
|
290
290
|
const columnCount = Math.max(0, ...rows.map((row) => row.length));
|
|
291
291
|
const widths = Array.from({ length: columnCount }, (_, index) => Math.max(0, ...rows.map((row) => visibleWidth(row[index] ?? ""))));
|
|
292
|
-
return rows.map((row) => widths.map((width, index) => pad(row[index] ?? "", width, alignments[index] ?? "left")).join(columnGap).trimEnd()).join("\n");
|
|
292
|
+
return rows.map((row) => widths.map((width, index) => pad$1(row[index] ?? "", width, alignments[index] ?? "left")).join(columnGap).trimEnd()).join("\n");
|
|
293
293
|
};
|
|
294
294
|
const renderTable = (columns, rows) => alignRows([columns.map(({ header }) => header), ...rows], columns.map(({ align }) => align ?? "left"));
|
|
295
295
|
//#endregion
|
|
@@ -635,10 +635,60 @@ const pageSchema = (itemSchema) => z.object({
|
|
|
635
635
|
const credentialKinds = [
|
|
636
636
|
"anthropic_api_key",
|
|
637
637
|
"claude_oauth_token",
|
|
638
|
-
"github_pat"
|
|
638
|
+
"github_pat",
|
|
639
|
+
"gitlab_token",
|
|
640
|
+
"bitbucket_token"
|
|
639
641
|
];
|
|
640
642
|
const credentialKindSchema = z.enum(credentialKinds);
|
|
641
|
-
|
|
643
|
+
const claudeCredentialKinds = ["anthropic_api_key", "claude_oauth_token"];
|
|
644
|
+
const claudeCredentialKindSchema = z.enum(claudeCredentialKinds);
|
|
645
|
+
const gitProviders = [
|
|
646
|
+
"github",
|
|
647
|
+
"gitlab",
|
|
648
|
+
"bitbucket"
|
|
649
|
+
];
|
|
650
|
+
const gitProviderSchema = z.enum(gitProviders);
|
|
651
|
+
const gitCredentialKinds = [
|
|
652
|
+
"github_pat",
|
|
653
|
+
"gitlab_token",
|
|
654
|
+
"bitbucket_token"
|
|
655
|
+
];
|
|
656
|
+
z.enum(gitCredentialKinds);
|
|
657
|
+
const gitCredentialKindOf = {
|
|
658
|
+
github: "github_pat",
|
|
659
|
+
gitlab: "gitlab_token",
|
|
660
|
+
bitbucket: "bitbucket_token"
|
|
661
|
+
};
|
|
662
|
+
const defaultServerUrls = {
|
|
663
|
+
github: "https://github.com",
|
|
664
|
+
gitlab: "https://gitlab.com",
|
|
665
|
+
bitbucket: "https://bitbucket.org"
|
|
666
|
+
};
|
|
667
|
+
const isGitCredentialKind = (kind) => gitCredentialKinds.includes(kind);
|
|
668
|
+
const providerOfKind = (kind) => gitProviders.find((provider) => gitCredentialKindOf[provider] === kind) ?? null;
|
|
669
|
+
const maxServerUrlLength = 2048;
|
|
670
|
+
const parsedServerUrl = (value) => {
|
|
671
|
+
try {
|
|
672
|
+
return new URL(value);
|
|
673
|
+
} catch {
|
|
674
|
+
return null;
|
|
675
|
+
}
|
|
676
|
+
};
|
|
677
|
+
const isWellFormedServerUrl = (value) => {
|
|
678
|
+
const url = parsedServerUrl(value);
|
|
679
|
+
return url !== null && (url.protocol === "https:" || url.protocol === "http:") && url.hostname !== "" && url.username === "" && url.password === "" && url.search === "" && url.hash === "" && !value.includes("?") && !value.includes("#");
|
|
680
|
+
};
|
|
681
|
+
const gitServerUrlSchema = z.string().trim().min(1).max(maxServerUrlLength).refine(isWellFormedServerUrl, { message: "expected an http or https address without credentials, query or fragment" });
|
|
682
|
+
const providerTokenKindSchema = z.enum([
|
|
683
|
+
"gitlab_personal",
|
|
684
|
+
"gitlab_group",
|
|
685
|
+
"gitlab_project",
|
|
686
|
+
"bitbucket_api_token",
|
|
687
|
+
"bitbucket_access_token",
|
|
688
|
+
"bitbucket_http_token"
|
|
689
|
+
]);
|
|
690
|
+
const providerFailureSchema = z.enum(["rejected", "unreachable"]);
|
|
691
|
+
const identitySourceSchema = z.enum(["provider", "user"]);
|
|
642
692
|
const verificationStatusSchema = z.enum([
|
|
643
693
|
"pending",
|
|
644
694
|
"valid",
|
|
@@ -661,7 +711,14 @@ const credentialMetadataSchema = z.object({
|
|
|
661
711
|
commitEmail: z.string().optional(),
|
|
662
712
|
scopes: z.array(z.string()).optional(),
|
|
663
713
|
checkedAt: z.iso.datetime().optional(),
|
|
664
|
-
failureReason: z.string().optional()
|
|
714
|
+
failureReason: z.string().optional(),
|
|
715
|
+
serverUrl: z.string().optional(),
|
|
716
|
+
accountName: z.string().optional(),
|
|
717
|
+
accountUrl: z.string().optional(),
|
|
718
|
+
accountEmail: z.string().optional(),
|
|
719
|
+
providerTokenKind: providerTokenKindSchema.optional(),
|
|
720
|
+
providerFailure: providerFailureSchema.optional(),
|
|
721
|
+
identitySource: identitySourceSchema.optional()
|
|
665
722
|
}).meta({ id: "CredentialMetadata" });
|
|
666
723
|
const usageWindowStatusSchema = z.enum([
|
|
667
724
|
"allowed",
|
|
@@ -679,9 +736,14 @@ const subscriptionUsageSchema = z.object({
|
|
|
679
736
|
readAt: z.iso.datetime(),
|
|
680
737
|
stale: z.boolean()
|
|
681
738
|
}).meta({ id: "SubscriptionUsage" });
|
|
739
|
+
const claudeAccountQueuePlaceSchema = z.object({
|
|
740
|
+
projectId: idSchema,
|
|
741
|
+
projectName: z.string(),
|
|
742
|
+
position: z.int().positive()
|
|
743
|
+
}).meta({ id: "ClaudeAccountQueuePlace" });
|
|
682
744
|
const credentialSchema = z.object({
|
|
683
745
|
id: idSchema,
|
|
684
|
-
projectId: idSchema,
|
|
746
|
+
projectId: idSchema.nullable(),
|
|
685
747
|
version: z.int().nonnegative(),
|
|
686
748
|
kind: credentialKindSchema,
|
|
687
749
|
label: z.string(),
|
|
@@ -691,23 +753,54 @@ const credentialSchema = z.object({
|
|
|
691
753
|
expiresAt: z.iso.datetime().nullable(),
|
|
692
754
|
expiresAtComputed: z.boolean(),
|
|
693
755
|
usage: subscriptionUsageSchema.nullable(),
|
|
756
|
+
queues: z.array(claudeAccountQueuePlaceSchema),
|
|
757
|
+
creditsExhaustedAt: z.iso.datetime().nullable(),
|
|
758
|
+
limitedUntil: z.iso.datetime().nullable(),
|
|
694
759
|
createdAt: z.iso.datetime(),
|
|
695
760
|
updatedAt: z.iso.datetime()
|
|
696
761
|
}).meta({ id: "Credential" });
|
|
697
762
|
z.object({ items: z.array(credentialSchema) }).meta({ id: "CredentialList" });
|
|
698
763
|
const credentialSummarySchema = z.object({
|
|
699
764
|
id: idSchema,
|
|
700
|
-
projectId: idSchema,
|
|
765
|
+
projectId: idSchema.nullable(),
|
|
701
766
|
kind: credentialKindSchema,
|
|
702
767
|
label: z.string(),
|
|
703
768
|
commitName: z.string().nullable(),
|
|
704
|
-
commitEmail: z.string().nullable()
|
|
769
|
+
commitEmail: z.string().nullable(),
|
|
770
|
+
provider: gitProviderSchema.nullable(),
|
|
771
|
+
serverUrl: z.string().nullable()
|
|
705
772
|
}).meta({ id: "CredentialSummary" });
|
|
773
|
+
const gitCredentialSummarySchema = credentialSummarySchema.extend({
|
|
774
|
+
provider: gitProviderSchema,
|
|
775
|
+
serverUrl: z.string()
|
|
776
|
+
}).meta({ id: "GitCredentialSummary" });
|
|
777
|
+
const commitNameSchema = z.string().trim().min(1).max(255);
|
|
778
|
+
const commitEmailSchema = z.email().max(255);
|
|
779
|
+
const accountEmailSchema = z.email().max(255);
|
|
780
|
+
const hasWholeIdentity = (body) => body.commitName === void 0 === (body.commitEmail === void 0);
|
|
781
|
+
const carriesGitFields = (body) => body.serverUrl !== void 0 || body.accountEmail !== void 0 || body.commitName !== void 0 || body.commitEmail !== void 0;
|
|
706
782
|
z.object({
|
|
707
783
|
kind: credentialKindSchema,
|
|
708
784
|
label: credentialLabelSchema,
|
|
709
|
-
secret: credentialSecretSchema
|
|
785
|
+
secret: credentialSecretSchema,
|
|
786
|
+
serverUrl: gitServerUrlSchema.optional(),
|
|
787
|
+
accountEmail: accountEmailSchema.optional(),
|
|
788
|
+
commitName: commitNameSchema.optional(),
|
|
789
|
+
commitEmail: commitEmailSchema.optional()
|
|
790
|
+
}).refine(hasWholeIdentity, {
|
|
791
|
+
message: "expected both the commit name and the commit email, or neither",
|
|
792
|
+
path: ["commitEmail"]
|
|
793
|
+
}).refine((body) => isGitCredentialKind(body.kind) || !carriesGitFields(body), {
|
|
794
|
+
message: "expected no server, account or commit identity for a Claude credential",
|
|
795
|
+
path: ["kind"]
|
|
796
|
+
}).refine((body) => body.kind !== "github_pat" || body.serverUrl === void 0, {
|
|
797
|
+
message: "expected no server address for a GitHub token",
|
|
798
|
+
path: ["serverUrl"]
|
|
710
799
|
}).meta({ id: "CreateCredentialBody" });
|
|
800
|
+
z.object({
|
|
801
|
+
commitName: commitNameSchema,
|
|
802
|
+
commitEmail: commitEmailSchema
|
|
803
|
+
}).meta({ id: "SetCredentialCommitIdentityBody" });
|
|
711
804
|
z.object({ label: credentialLabelSchema }).meta({ id: "RenameCredentialBody" });
|
|
712
805
|
z.object({
|
|
713
806
|
secret: credentialSecretSchema,
|
|
@@ -717,6 +810,57 @@ z.object({
|
|
|
717
810
|
id: idSchema,
|
|
718
811
|
credentialId: idSchema
|
|
719
812
|
});
|
|
813
|
+
const switchThresholdSchema = z.int().min(1).max(100);
|
|
814
|
+
z.object({ items: z.array(credentialSchema) }).meta({ id: "ClaudeAccountList" });
|
|
815
|
+
z.object({
|
|
816
|
+
kind: claudeCredentialKindSchema,
|
|
817
|
+
label: credentialLabelSchema,
|
|
818
|
+
secret: credentialSecretSchema
|
|
819
|
+
}).meta({ id: "CreateClaudeAccountBody" });
|
|
820
|
+
z.object({
|
|
821
|
+
organizationId: idSchema,
|
|
822
|
+
credentialId: idSchema
|
|
823
|
+
});
|
|
824
|
+
const projectClaudeAccountStateSchema = z.enum([
|
|
825
|
+
"inUse",
|
|
826
|
+
"available",
|
|
827
|
+
"exhausted",
|
|
828
|
+
"noCredits",
|
|
829
|
+
"invalid"
|
|
830
|
+
]);
|
|
831
|
+
const claudeAccountWaitReasonSchema = z.enum([
|
|
832
|
+
"exhausted",
|
|
833
|
+
"blocked",
|
|
834
|
+
"empty"
|
|
835
|
+
]);
|
|
836
|
+
z.enum([
|
|
837
|
+
"queue",
|
|
838
|
+
"threshold",
|
|
839
|
+
"limit",
|
|
840
|
+
"reset",
|
|
841
|
+
"credits",
|
|
842
|
+
"invalid",
|
|
843
|
+
"removed"
|
|
844
|
+
]);
|
|
845
|
+
const projectClaudeAccountEntrySchema = z.object({
|
|
846
|
+
credential: credentialSchema,
|
|
847
|
+
position: z.int().positive(),
|
|
848
|
+
state: projectClaudeAccountStateSchema,
|
|
849
|
+
until: z.iso.datetime().nullable(),
|
|
850
|
+
agents: z.int().nonnegative()
|
|
851
|
+
}).meta({ id: "ProjectClaudeAccountEntry" });
|
|
852
|
+
z.object({
|
|
853
|
+
entries: z.array(projectClaudeAccountEntrySchema),
|
|
854
|
+
activeCredentialId: idSchema.nullable(),
|
|
855
|
+
nearestResetAt: z.iso.datetime().nullable(),
|
|
856
|
+
waitReason: claudeAccountWaitReasonSchema.nullable(),
|
|
857
|
+
version: z.int().nonnegative()
|
|
858
|
+
}).meta({ id: "ProjectClaudeAccounts" });
|
|
859
|
+
const hasUniqueCredentials = (ids) => new Set(ids).size === ids.length;
|
|
860
|
+
z.object({
|
|
861
|
+
credentialIds: z.array(idSchema).max(50).refine(hasUniqueCredentials, { message: "expected every account once" }),
|
|
862
|
+
version: z.int().nonnegative()
|
|
863
|
+
}).meta({ id: "SetProjectClaudeAccountsBody" });
|
|
720
864
|
const errorCodes = Object.keys({
|
|
721
865
|
"validation.failed": "validation",
|
|
722
866
|
"http.route_not_found": "not_found",
|
|
@@ -760,10 +904,18 @@ const errorCodes = Object.keys({
|
|
|
760
904
|
"credential.kind_mismatch": "rule_violation",
|
|
761
905
|
"credential.rejected": "rule_violation",
|
|
762
906
|
"credential.usage_unavailable": "rule_violation",
|
|
907
|
+
"credential.server_refused": "validation",
|
|
908
|
+
"credential.server_mismatch": "rule_violation",
|
|
909
|
+
"account.not_found": "not_found",
|
|
910
|
+
"account.label_taken": "conflict",
|
|
911
|
+
"account.in_queue": "conflict",
|
|
912
|
+
"account.queue_empty": "rule_violation",
|
|
913
|
+
"account.kind_mismatch": "rule_violation",
|
|
763
914
|
"repository.not_found": "not_found",
|
|
764
915
|
"repository.already_registered": "conflict",
|
|
765
916
|
"repository.in_use": "conflict",
|
|
766
917
|
"repository.unreachable": "rule_violation",
|
|
918
|
+
"repository.catalog_unavailable": "rule_violation",
|
|
767
919
|
"tool.not_found": "not_found",
|
|
768
920
|
"tool.name_taken": "conflict",
|
|
769
921
|
"tool.slug_taken": "conflict",
|
|
@@ -777,11 +929,14 @@ const errorCodes = Object.keys({
|
|
|
777
929
|
"release.hidden": "rule_violation",
|
|
778
930
|
"release.unavailable": "rule_violation",
|
|
779
931
|
"release.unsupported": "rule_violation",
|
|
932
|
+
"release.none_stable": "rule_violation",
|
|
780
933
|
"agent.not_found": "not_found",
|
|
781
934
|
"agent.invalid_slug": "rule_violation",
|
|
782
935
|
"agent.slug_taken": "conflict",
|
|
783
936
|
"agent.directory_conflict": "rule_violation",
|
|
784
937
|
"agent.repository_required": "rule_violation",
|
|
938
|
+
"agent.provider_token_required": "rule_violation",
|
|
939
|
+
"agent.provider_token_conflict": "rule_violation",
|
|
785
940
|
"agent.tool_duplicate": "rule_violation",
|
|
786
941
|
"agent.context_too_long": "rule_violation",
|
|
787
942
|
"agent.docker_unavailable": "rule_violation",
|
|
@@ -1011,7 +1166,11 @@ const agentEventSchema = z.discriminatedUnion("event", [
|
|
|
1011
1166
|
z.looseObject({
|
|
1012
1167
|
...roleEnvelope("task.started"),
|
|
1013
1168
|
taskId: z.string(),
|
|
1014
|
-
title: z.string()
|
|
1169
|
+
title: z.string(),
|
|
1170
|
+
sourceLabel: z.string().optional(),
|
|
1171
|
+
sourceUrl: z.string().optional(),
|
|
1172
|
+
attempt: countSchema.optional(),
|
|
1173
|
+
maxAttempts: countSchema.optional()
|
|
1015
1174
|
}),
|
|
1016
1175
|
z.looseObject({
|
|
1017
1176
|
...roleEnvelope("task.finished"),
|
|
@@ -1025,7 +1184,11 @@ const agentEventSchema = z.discriminatedUnion("event", [
|
|
|
1025
1184
|
attempt: countSchema.optional(),
|
|
1026
1185
|
maxAttempts: countSchema.optional(),
|
|
1027
1186
|
sessionId: z.string().optional(),
|
|
1028
|
-
error: z.string().optional()
|
|
1187
|
+
error: z.string().optional(),
|
|
1188
|
+
sourceLabel: z.string().optional(),
|
|
1189
|
+
sourceUrl: z.string().optional(),
|
|
1190
|
+
handedOff: z.boolean().optional(),
|
|
1191
|
+
handoffReason: z.string().optional()
|
|
1029
1192
|
}),
|
|
1030
1193
|
z.looseObject({
|
|
1031
1194
|
...roleEnvelope("task.retryScheduled"),
|
|
@@ -1124,7 +1287,8 @@ agentEventSchema.options.map((option) => option.shape.event.value);
|
|
|
1124
1287
|
provisioned: "platform.provisioned",
|
|
1125
1288
|
sandboxStarted: "platform.sandboxStarted",
|
|
1126
1289
|
sandboxStopped: "platform.sandboxStopped",
|
|
1127
|
-
restartedByNode: "platform.restartedByNode"
|
|
1290
|
+
restartedByNode: "platform.restartedByNode",
|
|
1291
|
+
claudeAccountChanged: "platform.claudeAccountChanged"
|
|
1128
1292
|
}).rawLog;
|
|
1129
1293
|
const agentStateSchema = z.enum([
|
|
1130
1294
|
"created",
|
|
@@ -1316,6 +1480,108 @@ z.object({
|
|
|
1316
1480
|
source: agentTaskSourceSchema,
|
|
1317
1481
|
staleReason: agentTaskStaleReasonSchema
|
|
1318
1482
|
}).meta({ id: "AgentTaskView" });
|
|
1483
|
+
const taskSourceSchema = z.object({
|
|
1484
|
+
label: z.string(),
|
|
1485
|
+
url: z.string().nullable()
|
|
1486
|
+
}).meta({ id: "TaskSource" });
|
|
1487
|
+
const overviewTaskOutcomeSchema = z.enum([
|
|
1488
|
+
"running",
|
|
1489
|
+
"done",
|
|
1490
|
+
"failed",
|
|
1491
|
+
"retrying",
|
|
1492
|
+
"handedOff"
|
|
1493
|
+
]);
|
|
1494
|
+
const overviewTaskSchema = z.object({
|
|
1495
|
+
taskId: z.string(),
|
|
1496
|
+
title: z.string(),
|
|
1497
|
+
source: taskSourceSchema.nullable(),
|
|
1498
|
+
attempts: z.int().nonnegative(),
|
|
1499
|
+
maxAttempts: z.int().nullable(),
|
|
1500
|
+
startedAt: z.iso.datetime(),
|
|
1501
|
+
finishedAt: z.iso.datetime().nullable(),
|
|
1502
|
+
outcome: overviewTaskOutcomeSchema,
|
|
1503
|
+
error: z.string().nullable(),
|
|
1504
|
+
costUsd: z.number(),
|
|
1505
|
+
durationMs: z.int().nonnegative(),
|
|
1506
|
+
headline: z.string().nullable(),
|
|
1507
|
+
handoffReason: z.string().nullable()
|
|
1508
|
+
}).meta({ id: "OverviewTask" });
|
|
1509
|
+
const overviewGroupSchema = z.object({
|
|
1510
|
+
source: taskSourceSchema.nullable(),
|
|
1511
|
+
title: z.string(),
|
|
1512
|
+
tasks: z.array(overviewTaskSchema),
|
|
1513
|
+
totals: z.object({
|
|
1514
|
+
durationMs: z.int().nonnegative(),
|
|
1515
|
+
costUsd: z.number()
|
|
1516
|
+
})
|
|
1517
|
+
}).meta({ id: "OverviewGroup" });
|
|
1518
|
+
const attentionBase = {
|
|
1519
|
+
taskId: z.string(),
|
|
1520
|
+
title: z.string(),
|
|
1521
|
+
source: taskSourceSchema.nullable(),
|
|
1522
|
+
finishedAt: z.iso.datetime()
|
|
1523
|
+
};
|
|
1524
|
+
const attentionItemSchema = z.discriminatedUnion("kind", [
|
|
1525
|
+
z.object({
|
|
1526
|
+
kind: z.literal("failed"),
|
|
1527
|
+
...attentionBase,
|
|
1528
|
+
attempts: z.int().nonnegative(),
|
|
1529
|
+
maxAttempts: z.int().nullable(),
|
|
1530
|
+
error: z.string().nullable()
|
|
1531
|
+
}),
|
|
1532
|
+
z.object({
|
|
1533
|
+
kind: z.literal("retried"),
|
|
1534
|
+
...attentionBase,
|
|
1535
|
+
attempts: z.int().nonnegative()
|
|
1536
|
+
}),
|
|
1537
|
+
z.object({
|
|
1538
|
+
kind: z.literal("handedOff"),
|
|
1539
|
+
...attentionBase,
|
|
1540
|
+
reason: z.string().nullable()
|
|
1541
|
+
})
|
|
1542
|
+
]).meta({ id: "AttentionItem" });
|
|
1543
|
+
const workloadSchema = z.object({
|
|
1544
|
+
tasksDone: z.int().nonnegative(),
|
|
1545
|
+
tasksFailed: z.int().nonnegative(),
|
|
1546
|
+
tasksHandedOff: z.int().nonnegative(),
|
|
1547
|
+
tasksCostUsd: z.number(),
|
|
1548
|
+
averageTaskMs: z.int().nonnegative().nullable(),
|
|
1549
|
+
cycles: z.int().nonnegative(),
|
|
1550
|
+
cyclesCostUsd: z.number()
|
|
1551
|
+
}).meta({ id: "Workload" });
|
|
1552
|
+
z.object({
|
|
1553
|
+
window: z.object({
|
|
1554
|
+
from: z.iso.datetime(),
|
|
1555
|
+
to: z.iso.datetime()
|
|
1556
|
+
}),
|
|
1557
|
+
source: agentTaskSourceSchema,
|
|
1558
|
+
staleReason: agentTaskStaleReasonSchema,
|
|
1559
|
+
lastEventAt: z.iso.datetime().nullable(),
|
|
1560
|
+
current: overviewTaskSchema.nullable(),
|
|
1561
|
+
last: overviewTaskSchema.nullable(),
|
|
1562
|
+
lastCycle: z.object({
|
|
1563
|
+
finishedAt: z.iso.datetime(),
|
|
1564
|
+
ok: z.boolean().nullable(),
|
|
1565
|
+
addedTasks: z.int().nonnegative().nullable()
|
|
1566
|
+
}).nullable(),
|
|
1567
|
+
queue: z.object({
|
|
1568
|
+
pending: z.int().nonnegative(),
|
|
1569
|
+
inProgress: z.int().nonnegative(),
|
|
1570
|
+
failed: z.int().nonnegative()
|
|
1571
|
+
}).nullable(),
|
|
1572
|
+
lastSession: z.object({
|
|
1573
|
+
role: agentRoleSchema.nullable(),
|
|
1574
|
+
startedAt: z.iso.datetime()
|
|
1575
|
+
}).nullable(),
|
|
1576
|
+
costTodayUsd: z.number(),
|
|
1577
|
+
attention: z.array(attentionItemSchema),
|
|
1578
|
+
groups: z.array(overviewGroupSchema),
|
|
1579
|
+
moreTasks: z.int().nonnegative(),
|
|
1580
|
+
health: z.object({
|
|
1581
|
+
day: workloadSchema,
|
|
1582
|
+
week: workloadSchema
|
|
1583
|
+
})
|
|
1584
|
+
}).meta({ id: "AgentOverview" });
|
|
1319
1585
|
z.object({
|
|
1320
1586
|
title: z.string().trim().min(1).max(200).optional(),
|
|
1321
1587
|
description: z.string().trim().min(1).max(maxTaskDescriptionLength)
|
|
@@ -2052,6 +2318,7 @@ const organizationSchema = z.object({
|
|
|
2052
2318
|
slug: organizationSlugSchema,
|
|
2053
2319
|
color: colorSchema,
|
|
2054
2320
|
logoHash: logoHashSchema.nullable(),
|
|
2321
|
+
claudeSwitchThreshold: switchThresholdSchema,
|
|
2055
2322
|
createdAt: z.iso.datetime()
|
|
2056
2323
|
}).meta({ id: "Organization" });
|
|
2057
2324
|
z.object({ items: z.array(organizationSchema) }).meta({ id: "OrganizationList" });
|
|
@@ -2112,7 +2379,8 @@ z.object({
|
|
|
2112
2379
|
const updateOrganizationBodySchema = z.object({
|
|
2113
2380
|
name: organizationNameSchema,
|
|
2114
2381
|
slug: organizationSlugSchema,
|
|
2115
|
-
color: colorSchema
|
|
2382
|
+
color: colorSchema,
|
|
2383
|
+
claudeSwitchThreshold: switchThresholdSchema.optional()
|
|
2116
2384
|
}).meta({ id: "UpdateOrganizationBody" });
|
|
2117
2385
|
z.object({ temporaryPassword: z.string() }).meta({ id: "TemporaryPassword" });
|
|
2118
2386
|
z.object({ items: z.array(userSchema) }).meta({ id: "UserList" });
|
|
@@ -2212,6 +2480,8 @@ const auditActionSchema = z.enum([
|
|
|
2212
2480
|
"credential.rotated",
|
|
2213
2481
|
"credential.deleted",
|
|
2214
2482
|
"credential.verified",
|
|
2483
|
+
"credential.identity_set",
|
|
2484
|
+
"project.claude_accounts_changed",
|
|
2215
2485
|
"repository.registered",
|
|
2216
2486
|
"repository.deleted",
|
|
2217
2487
|
"tool.created",
|
|
@@ -2219,6 +2489,7 @@ const auditActionSchema = z.enum([
|
|
|
2219
2489
|
"tool.deleted",
|
|
2220
2490
|
"tool.copied",
|
|
2221
2491
|
"release.visibility_changed",
|
|
2492
|
+
"release.stability_changed",
|
|
2222
2493
|
"agent.created",
|
|
2223
2494
|
"agent.updated",
|
|
2224
2495
|
"agent.deleted",
|
|
@@ -2356,6 +2627,8 @@ z.object({
|
|
|
2356
2627
|
//#endregion
|
|
2357
2628
|
//#region ../../packages/contracts/src/releases.ts
|
|
2358
2629
|
const kikimoraVersionSchema = z.string().regex(versionTagPattern);
|
|
2630
|
+
const latestKikimoraVersion = "latest";
|
|
2631
|
+
const kikimoraVersionChoiceSchema = z.union([kikimoraVersionSchema, z.literal(latestKikimoraVersion)]);
|
|
2359
2632
|
const releaseSummarySchema = z.object({
|
|
2360
2633
|
version: kikimoraVersionSchema,
|
|
2361
2634
|
claudeCodeVersion: z.string().nullable(),
|
|
@@ -2363,14 +2636,20 @@ const releaseSummarySchema = z.object({
|
|
|
2363
2636
|
imageCreatedAt: z.iso.datetime().nullable(),
|
|
2364
2637
|
gitRevision: z.string().nullable(),
|
|
2365
2638
|
releaseNotesUrl: z.url().nullable(),
|
|
2366
|
-
supported: z.boolean()
|
|
2639
|
+
supported: z.boolean(),
|
|
2640
|
+
stable: z.boolean()
|
|
2367
2641
|
}).meta({ id: "ReleaseSummary" });
|
|
2368
|
-
z.object({
|
|
2642
|
+
z.object({
|
|
2643
|
+
items: z.array(releaseSummarySchema),
|
|
2644
|
+
latest: kikimoraVersionSchema.nullable()
|
|
2645
|
+
}).meta({ id: "ReleaseList" });
|
|
2369
2646
|
const releaseAdminSchema = releaseSummarySchema.extend({
|
|
2370
2647
|
available: z.boolean(),
|
|
2371
2648
|
hidden: z.boolean(),
|
|
2372
2649
|
hiddenAt: z.iso.datetime().nullable(),
|
|
2373
2650
|
hiddenBy: idSchema.nullable(),
|
|
2651
|
+
stableAt: z.iso.datetime().nullable(),
|
|
2652
|
+
stableBy: idSchema.nullable(),
|
|
2374
2653
|
syncedAt: z.iso.datetime(),
|
|
2375
2654
|
lastSyncError: z.string().nullable()
|
|
2376
2655
|
}).meta({ id: "ReleaseAdmin" });
|
|
@@ -2380,6 +2659,7 @@ z.object({
|
|
|
2380
2659
|
lastSyncError: z.string().nullable()
|
|
2381
2660
|
}).meta({ id: "ReleaseAdminList" });
|
|
2382
2661
|
z.object({ hidden: z.boolean() }).meta({ id: "SetReleaseVisibilityBody" });
|
|
2662
|
+
z.object({ stable: z.boolean() }).meta({ id: "SetReleaseStabilityBody" });
|
|
2383
2663
|
z.object({ version: kikimoraVersionSchema });
|
|
2384
2664
|
const repositoryStatusSchema = z.enum([
|
|
2385
2665
|
"ok",
|
|
@@ -2389,14 +2669,21 @@ const repositoryStatusSchema = z.enum([
|
|
|
2389
2669
|
const repositoryNamePattern = /^[A-Za-z0-9](?:[A-Za-z0-9._-]{0,98}[A-Za-z0-9])?$/;
|
|
2390
2670
|
const forbiddenBranchCharacters = /[\s\\~^:?*[\p{Cc}]/u;
|
|
2391
2671
|
const isSafeBranchName = (value) => value.length > 0 && value.length <= 255 && !value.startsWith("-") && !forbiddenBranchCharacters.test(value);
|
|
2392
|
-
const
|
|
2672
|
+
const isRepositoryOwner = (value) => {
|
|
2673
|
+
const segments = value.split("/");
|
|
2674
|
+
return value.length <= 255 && segments.length <= 20 && segments.every((segment) => repositoryNamePattern.test(segment));
|
|
2675
|
+
};
|
|
2676
|
+
const repositoryOwnerSchema = z.string().refine(isRepositoryOwner, { message: "expected one to twenty names separated by a slash" });
|
|
2393
2677
|
const repositoryNameSchema = z.string().regex(repositoryNamePattern);
|
|
2394
2678
|
const defaultBranchSchema = z.string().refine(isSafeBranchName, { message: "expected a branch name git and the TSV accept" });
|
|
2395
2679
|
const repositorySchema = z.object({
|
|
2396
2680
|
id: idSchema,
|
|
2397
2681
|
projectId: idSchema,
|
|
2398
2682
|
version: z.int().nonnegative(),
|
|
2399
|
-
githubId: z.int().positive(),
|
|
2683
|
+
githubId: z.int().positive().nullable(),
|
|
2684
|
+
provider: gitProviderSchema,
|
|
2685
|
+
serverUrl: z.string(),
|
|
2686
|
+
providerRepositoryId: z.string(),
|
|
2400
2687
|
owner: repositoryOwnerSchema,
|
|
2401
2688
|
name: repositoryNameSchema,
|
|
2402
2689
|
defaultBranch: defaultBranchSchema,
|
|
@@ -2418,16 +2705,18 @@ z.object({
|
|
|
2418
2705
|
id: idSchema,
|
|
2419
2706
|
repositoryId: idSchema
|
|
2420
2707
|
});
|
|
2421
|
-
const
|
|
2708
|
+
const availableRepositorySchema = z.object({
|
|
2422
2709
|
owner: repositoryOwnerSchema,
|
|
2423
2710
|
name: repositoryNameSchema,
|
|
2424
|
-
defaultBranch: defaultBranchSchema,
|
|
2711
|
+
defaultBranch: defaultBranchSchema.nullable(),
|
|
2425
2712
|
isPrivate: z.boolean()
|
|
2426
|
-
}).meta({ id: "
|
|
2713
|
+
}).meta({ id: "AvailableRepository" });
|
|
2427
2714
|
z.object({
|
|
2428
|
-
items: z.array(
|
|
2429
|
-
truncated: z.boolean()
|
|
2430
|
-
|
|
2715
|
+
items: z.array(availableRepositorySchema),
|
|
2716
|
+
truncated: z.boolean(),
|
|
2717
|
+
provider: gitProviderSchema,
|
|
2718
|
+
serverUrl: z.string()
|
|
2719
|
+
}).meta({ id: "AvailableRepositoryList" });
|
|
2431
2720
|
z.object({ credentialId: idSchema });
|
|
2432
2721
|
const toolKindSchema = z.enum([
|
|
2433
2722
|
"http_api",
|
|
@@ -2462,7 +2751,8 @@ const reservedEnvironmentPrefixes = [
|
|
|
2462
2751
|
"ANTHROPIC_",
|
|
2463
2752
|
"GH_",
|
|
2464
2753
|
"DOCKER_",
|
|
2465
|
-
"BC_"
|
|
2754
|
+
"BC_",
|
|
2755
|
+
"KC_GIT_"
|
|
2466
2756
|
];
|
|
2467
2757
|
const reservedEnvironmentNames = /* @__PURE__ */ new Set([
|
|
2468
2758
|
"PATH",
|
|
@@ -2680,7 +2970,8 @@ const restartReasonSchema = z.enum([
|
|
|
2680
2970
|
"tool_changed",
|
|
2681
2971
|
"spec_drift",
|
|
2682
2972
|
"spec_version",
|
|
2683
|
-
"release_unsupported"
|
|
2973
|
+
"release_unsupported",
|
|
2974
|
+
"latest_release"
|
|
2684
2975
|
]);
|
|
2685
2976
|
const containerHealthSchema = z.enum([
|
|
2686
2977
|
"none",
|
|
@@ -2742,7 +3033,9 @@ const agentRepositorySchema = z.object({
|
|
|
2742
3033
|
owner: repositoryOwnerSchema,
|
|
2743
3034
|
name: repositoryNameSchema,
|
|
2744
3035
|
defaultBranch: defaultBranchSchema,
|
|
2745
|
-
status: repositoryStatusSchema
|
|
3036
|
+
status: repositoryStatusSchema,
|
|
3037
|
+
provider: gitProviderSchema,
|
|
3038
|
+
serverUrl: z.string()
|
|
2746
3039
|
}).meta({ id: "AgentRepository" });
|
|
2747
3040
|
const agentRuntimeSchema = z.object({
|
|
2748
3041
|
available: z.boolean(),
|
|
@@ -2770,6 +3063,8 @@ const agentListItemSchema = z.object({
|
|
|
2770
3063
|
name: z.string(),
|
|
2771
3064
|
slug: slugSchema,
|
|
2772
3065
|
kikimoraVersion: z.string(),
|
|
3066
|
+
followsLatest: z.boolean(),
|
|
3067
|
+
pendingKikimoraVersion: z.string().nullable(),
|
|
2773
3068
|
state: agentStateSchema,
|
|
2774
3069
|
observedState: agentObservedStateSchema,
|
|
2775
3070
|
desiredState: agentDesiredStateSchema,
|
|
@@ -2779,6 +3074,7 @@ const agentListItemSchema = z.object({
|
|
|
2779
3074
|
retryAt: z.iso.datetime().nullable(),
|
|
2780
3075
|
progress: agentProgressSchema.nullable(),
|
|
2781
3076
|
availableActions: z.array(agentActionSchema),
|
|
3077
|
+
stateSince: z.iso.datetime().nullable(),
|
|
2782
3078
|
updatedAt: z.iso.datetime()
|
|
2783
3079
|
}).meta({ id: "AgentListItem" });
|
|
2784
3080
|
const agentNodeSchema = z.object({
|
|
@@ -2786,6 +3082,17 @@ const agentNodeSchema = z.object({
|
|
|
2786
3082
|
name: nodeNameSchema,
|
|
2787
3083
|
state: nodeStateSchema
|
|
2788
3084
|
}).meta({ id: "AgentNode" });
|
|
3085
|
+
const agentClaudeAccountSchema = z.object({
|
|
3086
|
+
current: z.object({
|
|
3087
|
+
id: idSchema,
|
|
3088
|
+
label: z.string(),
|
|
3089
|
+
kind: claudeCredentialKindSchema,
|
|
3090
|
+
position: z.int().positive().nullable()
|
|
3091
|
+
}).nullable(),
|
|
3092
|
+
queueSize: z.int().nonnegative(),
|
|
3093
|
+
waitReason: claudeAccountWaitReasonSchema.nullable(),
|
|
3094
|
+
waitUntil: z.iso.datetime().nullable()
|
|
3095
|
+
}).meta({ id: "AgentClaudeAccount" });
|
|
2789
3096
|
agentListItemSchema.extend({
|
|
2790
3097
|
version: z.int().nonnegative(),
|
|
2791
3098
|
description: z.string(),
|
|
@@ -2794,8 +3101,10 @@ agentListItemSchema.extend({
|
|
|
2794
3101
|
settings: kikimoraSettingsSchema,
|
|
2795
3102
|
effectiveSettings: workerSettingsSchema,
|
|
2796
3103
|
prompts: agentPromptsSchema,
|
|
2797
|
-
claudeCredential: credentialSummarySchema,
|
|
2798
|
-
|
|
3104
|
+
claudeCredential: credentialSummarySchema.nullable(),
|
|
3105
|
+
claudeAccount: agentClaudeAccountSchema,
|
|
3106
|
+
githubCredential: credentialSummarySchema.nullable(),
|
|
3107
|
+
gitCredentials: z.array(gitCredentialSummarySchema),
|
|
2799
3108
|
repositories: z.array(agentRepositorySchema),
|
|
2800
3109
|
toolAssignments: z.array(agentToolSchema),
|
|
2801
3110
|
release: releaseSummarySchema.nullable(),
|
|
@@ -2812,10 +3121,7 @@ const uniqueDirectories = {
|
|
|
2812
3121
|
message: "expected every repository to use its own directory",
|
|
2813
3122
|
path: ["repositories"]
|
|
2814
3123
|
};
|
|
2815
|
-
const
|
|
2816
|
-
message: "expected the Claude and the GitHub credential to be two credentials",
|
|
2817
|
-
path: ["githubCredentialId"]
|
|
2818
|
-
};
|
|
3124
|
+
const agentGitCredentialIdsSchema = z.array(idSchema).max(8).refine((ids) => new Set(ids).size === ids.length, { message: "expected every credential once" });
|
|
2819
3125
|
const hasUniqueTools = (assignments) => new Set(assignments.map((assignment) => assignment.toolId)).size === assignments.length;
|
|
2820
3126
|
const agentRepositoryLinksSchema = z.array(agentRepositoryLinkSchema).min(1).max(32);
|
|
2821
3127
|
const agentToolAssignmentsSchema = z.array(toolAssignmentSchema).max(32).refine(hasUniqueTools, { message: "expected every tool to be assigned once" });
|
|
@@ -2825,29 +3131,31 @@ const createAgentBodySchema = z.object({
|
|
|
2825
3131
|
name: agentNameSchema,
|
|
2826
3132
|
slug: slugSchema.optional(),
|
|
2827
3133
|
description: agentDescriptionSchema,
|
|
2828
|
-
kikimoraVersion:
|
|
3134
|
+
kikimoraVersion: kikimoraVersionChoiceSchema,
|
|
2829
3135
|
nodeId: idSchema,
|
|
2830
3136
|
requiredCapabilities: requiredCapabilitiesSchema.optional(),
|
|
2831
|
-
claudeCredentialId: idSchema,
|
|
2832
|
-
githubCredentialId: idSchema,
|
|
3137
|
+
claudeCredentialId: idSchema.optional(),
|
|
3138
|
+
githubCredentialId: idSchema.optional(),
|
|
3139
|
+
gitCredentialIds: agentGitCredentialIdsSchema.default([]),
|
|
2833
3140
|
repositories: agentRepositoryLinksSchema,
|
|
2834
3141
|
toolAssignments: agentToolAssignmentsSchema,
|
|
2835
3142
|
prompts: agentPromptsSchema,
|
|
2836
3143
|
settings: kikimoraSettingsSchema,
|
|
2837
3144
|
timezone: timezoneSchema,
|
|
2838
3145
|
machine: agentMachineInputSchema
|
|
2839
|
-
}).refine((body) => hasUniqueDirectories(body.repositories), uniqueDirectories).
|
|
3146
|
+
}).refine((body) => hasUniqueDirectories(body.repositories), uniqueDirectories).meta({ id: "CreateAgentBody" });
|
|
2840
3147
|
const updateAgentSectionsSchema = z.object({
|
|
2841
3148
|
general: z.object({
|
|
2842
3149
|
name: agentNameSchema,
|
|
2843
3150
|
slug: slugSchema.optional(),
|
|
2844
3151
|
description: agentDescriptionSchema
|
|
2845
3152
|
}).optional(),
|
|
2846
|
-
version:
|
|
3153
|
+
version: kikimoraVersionChoiceSchema.optional(),
|
|
2847
3154
|
credentials: z.object({
|
|
2848
|
-
claudeCredentialId: idSchema,
|
|
2849
|
-
githubCredentialId: idSchema
|
|
2850
|
-
|
|
3155
|
+
claudeCredentialId: idSchema.optional(),
|
|
3156
|
+
githubCredentialId: idSchema.optional(),
|
|
3157
|
+
gitCredentialIds: agentGitCredentialIdsSchema.default([])
|
|
3158
|
+
}).optional(),
|
|
2851
3159
|
repositories: agentRepositoryLinksSchema.optional(),
|
|
2852
3160
|
tools: agentToolAssignmentsSchema.optional(),
|
|
2853
3161
|
prompts: agentPromptsSchema.optional(),
|
|
@@ -3410,7 +3718,8 @@ const notificationKindSchema = z.enum([
|
|
|
3410
3718
|
"backup.overdue",
|
|
3411
3719
|
"host_operation.finished",
|
|
3412
3720
|
"dead_letter.queued",
|
|
3413
|
-
"node.resumed"
|
|
3721
|
+
"node.resumed",
|
|
3722
|
+
"project.claude_accounts_exhausted"
|
|
3414
3723
|
]);
|
|
3415
3724
|
const notificationSeveritySchema = z.enum([
|
|
3416
3725
|
"info",
|
|
@@ -3424,7 +3733,8 @@ const notificationTargetTypeSchema = z.enum([
|
|
|
3424
3733
|
"queue",
|
|
3425
3734
|
"volume",
|
|
3426
3735
|
"platform",
|
|
3427
|
-
"node"
|
|
3736
|
+
"node",
|
|
3737
|
+
"project"
|
|
3428
3738
|
]);
|
|
3429
3739
|
const hostOperationOutcomes = [
|
|
3430
3740
|
"succeeded",
|
|
@@ -3458,7 +3768,10 @@ z.discriminatedUnion("operation", [
|
|
|
3458
3768
|
role: volumeRoleSchema
|
|
3459
3769
|
})
|
|
3460
3770
|
]);
|
|
3461
|
-
z.strictObject({
|
|
3771
|
+
z.strictObject({
|
|
3772
|
+
label: textParam,
|
|
3773
|
+
kind: credentialKindSchema
|
|
3774
|
+
}), z.strictObject({
|
|
3462
3775
|
label: textParam,
|
|
3463
3776
|
expiresAt: instantParam
|
|
3464
3777
|
}), z.strictObject({
|
|
@@ -3491,7 +3804,10 @@ z.strictObject({ label: textParam }), z.strictObject({
|
|
|
3491
3804
|
}).refine((params) => params.pausedAgents === 0 === (params.pausedAgentNames === ""), {
|
|
3492
3805
|
message: "expected the names of the paused agents exactly when there are some",
|
|
3493
3806
|
path: ["pausedAgentNames"]
|
|
3494
|
-
})
|
|
3807
|
+
}), z.union([z.strictObject({
|
|
3808
|
+
blocked: z.literal(false),
|
|
3809
|
+
nearestResetAt: instantParam
|
|
3810
|
+
}), z.strictObject({ blocked: z.literal(true) })]);
|
|
3495
3811
|
const notificationParamsSchema = z.record(z.string(), z.union([
|
|
3496
3812
|
z.string(),
|
|
3497
3813
|
z.number(),
|
|
@@ -4434,6 +4750,22 @@ const runOf = async (adaptersOf, { settings, context }) => {
|
|
|
4434
4750
|
};
|
|
4435
4751
|
};
|
|
4436
4752
|
//#endregion
|
|
4753
|
+
//#region src/git-providers.ts
|
|
4754
|
+
const providerNames = {
|
|
4755
|
+
github: "GitHub",
|
|
4756
|
+
gitlab: "GitLab",
|
|
4757
|
+
bitbucket: "Bitbucket"
|
|
4758
|
+
};
|
|
4759
|
+
const isGitProvider = (value) => gitProviders.some((provider) => provider === value);
|
|
4760
|
+
const providerNameOf = (provider) => isGitProvider(provider) ? providerNames[provider] : "the provider";
|
|
4761
|
+
const gitProviderOf = (credential) => providerOfKind(credential.kind);
|
|
4762
|
+
const credentialServerOf = (credential) => {
|
|
4763
|
+
const provider = gitProviderOf(credential);
|
|
4764
|
+
if (provider === null) return null;
|
|
4765
|
+
return credential.metadata.serverUrl ?? defaultServerUrls[provider];
|
|
4766
|
+
};
|
|
4767
|
+
const kindOfProvider = (provider) => isGitProvider(provider) ? gitCredentialKindOf[provider] : "<kind>";
|
|
4768
|
+
//#endregion
|
|
4437
4769
|
//#region src/commands/agents/shared.ts
|
|
4438
4770
|
const agentOption = { project: { type: "string" } };
|
|
4439
4771
|
const agentUsage = [["--project <name>", "the project of the agent, when the agent is named alone"]];
|
|
@@ -4442,7 +4774,11 @@ const actionsAfterPause = /* @__PURE__ */ new Set([
|
|
|
4442
4774
|
"restart",
|
|
4443
4775
|
"resume"
|
|
4444
4776
|
]);
|
|
4777
|
+
const serverOf = (serverUrl) => typeof serverUrl === "string" ? serverUrl : "its server";
|
|
4445
4778
|
const agentHints = {
|
|
4779
|
+
"agent.provider_token_required": ({ provider, serverUrl }) => `The project needs a ${providerNameOf(provider)} token for ${serverOf(serverUrl)}: kcloud credentials add --project <name> --kind ${kindOfProvider(provider)}${provider === "github" ? "" : ` --server-url ${serverOf(serverUrl)}`} --secret-file <path> stores one, and gitCredentialIds names it.`,
|
|
4780
|
+
"agent.provider_token_conflict": ({ provider, serverUrl }) => `Two credentials of the agent are for ${providerNameOf(provider)} at ${serverOf(serverUrl)}; gitCredentialIds takes one for each server.`,
|
|
4781
|
+
"credential.kind_mismatch": "claudeCredentialId takes a Claude credential, and gitCredentialIds the github_pat, gitlab_token or bitbucket_token credentials of the project.",
|
|
4446
4782
|
"agent.not_found": "kcloud agents list --project <name> shows the agents.",
|
|
4447
4783
|
"project.not_found": "kcloud projects list shows the projects you are a member of.",
|
|
4448
4784
|
"agent.invalid_transition": ({ state, action }) => state === "pausing" && actionsAfterPause.has(String(action)) ? "The agent finishes its current session before it pauses; the action goes through once kcloud agents show <agent> says paused." : "kcloud agents show <agent> lists the actions it takes now.",
|
|
@@ -4450,6 +4786,8 @@ const agentHints = {
|
|
|
4450
4786
|
"concurrency.conflict": "Someone changed the agent meanwhile; read it again and retry.",
|
|
4451
4787
|
"agent.node_unsuitable": "The node lacks a capability the agent requires; kcloud nodes show <node> lists what it offers.",
|
|
4452
4788
|
"node.offline": "The node of the agent is not connected; kcloud nodes show <node> tells when it was last seen.",
|
|
4789
|
+
"account.queue_empty": "The queue of Claude accounts of the project is empty; kcloud projects accounts add <account> --project <name> puts one in it.",
|
|
4790
|
+
"release.none_stable": "No release is stable yet, so latest points at no release; kcloud releases list shows the releases to pick one of.",
|
|
4453
4791
|
"node.outdated": "The node of the agent runs an older release; kcloud nodes show <node> names the release it runs and its update."
|
|
4454
4792
|
};
|
|
4455
4793
|
const agentFrom = async (run, reference, project) => {
|
|
@@ -4799,11 +5137,10 @@ const agentPlanOf = (agent) => ({
|
|
|
4799
5137
|
name: agent.name,
|
|
4800
5138
|
slug: agent.slug,
|
|
4801
5139
|
description: agent.description,
|
|
4802
|
-
kikimoraVersion: agent.kikimoraVersion,
|
|
5140
|
+
kikimoraVersion: agent.followsLatest ? latestKikimoraVersion : agent.kikimoraVersion,
|
|
4803
5141
|
...agent.node === null ? {} : { nodeId: agent.node.id },
|
|
4804
5142
|
requiredCapabilities: agent.requiredCapabilities,
|
|
4805
|
-
|
|
4806
|
-
githubCredentialId: agent.githubCredential.id,
|
|
5143
|
+
gitCredentialIds: agent.gitCredentials.map(({ id }) => id),
|
|
4807
5144
|
repositories: agent.repositories.map(({ repositoryId, directory }) => ({
|
|
4808
5145
|
repositoryId,
|
|
4809
5146
|
directory
|
|
@@ -4823,9 +5160,12 @@ const agentPlanOf = (agent) => ({
|
|
|
4823
5160
|
});
|
|
4824
5161
|
const machineLimitKeys = /* @__PURE__ */ new Set(["cpus", "memoryMb"]);
|
|
4825
5162
|
const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4826
|
-
const
|
|
5163
|
+
const warned = ({ context }, text) => {
|
|
5164
|
+
context.terminal.error(context.terminal.errorStyles.warning(`warning ${text}`));
|
|
5165
|
+
};
|
|
5166
|
+
const warnIgnored = (run, keys) => {
|
|
4827
5167
|
const named = keys.map((key) => `machine.${key}`).join(" and ");
|
|
4828
|
-
|
|
5168
|
+
warned(run, `${named} ${keys.length === 1 ? "is" : "are"} ignored: an agent has no limits of its own, and the pools of its node limit its sessions.`);
|
|
4829
5169
|
};
|
|
4830
5170
|
const machineWithoutLimits = (run, machine) => {
|
|
4831
5171
|
if (!isRecord(machine)) return machine;
|
|
@@ -4834,19 +5174,45 @@ const machineWithoutLimits = (run, machine) => {
|
|
|
4834
5174
|
warnIgnored(run, ignored);
|
|
4835
5175
|
return Object.fromEntries(Object.entries(machine).filter(([key]) => !machineLimitKeys.has(key)));
|
|
4836
5176
|
};
|
|
4837
|
-
const
|
|
4838
|
-
|
|
4839
|
-
|
|
4840
|
-
} :
|
|
4841
|
-
|
|
4842
|
-
|
|
4843
|
-
|
|
4844
|
-
|
|
4845
|
-
|
|
4846
|
-
|
|
4847
|
-
|
|
5177
|
+
const claudeCredentialKey = "claudeCredentialId";
|
|
5178
|
+
const withoutClaudeCredential = (run, fields) => {
|
|
5179
|
+
if (!(claudeCredentialKey in fields)) return fields;
|
|
5180
|
+
warned(run, `${claudeCredentialKey} is ignored: an agent works on the queue of Claude accounts of its project, which kcloud projects accounts list shows.`);
|
|
5181
|
+
return Object.fromEntries(Object.entries(fields).filter(([key]) => key !== claudeCredentialKey));
|
|
5182
|
+
};
|
|
5183
|
+
const acceptedAgentFile = (run, document) => {
|
|
5184
|
+
if (!isRecord(document)) return document;
|
|
5185
|
+
const kept = withoutClaudeCredential(run, document);
|
|
5186
|
+
return "machine" in kept ? {
|
|
5187
|
+
...kept,
|
|
5188
|
+
machine: machineWithoutLimits(run, kept.machine)
|
|
5189
|
+
} : kept;
|
|
5190
|
+
};
|
|
5191
|
+
const credentialsSectionOf = (run, sections) => {
|
|
5192
|
+
if (!isRecord(sections.credentials)) return sections;
|
|
5193
|
+
const credentials = withoutClaudeCredential(run, sections.credentials);
|
|
5194
|
+
if (Object.keys(credentials).length > 0) return {
|
|
5195
|
+
...sections,
|
|
5196
|
+
credentials
|
|
5197
|
+
};
|
|
5198
|
+
return Object.fromEntries(Object.entries(sections).filter(([key]) => key !== "credentials"));
|
|
5199
|
+
};
|
|
5200
|
+
const acceptedAgentChange = (run, document) => {
|
|
5201
|
+
if (!isRecord(document) || !isRecord(document.sections)) return document;
|
|
5202
|
+
const sections = credentialsSectionOf(run, document.sections);
|
|
5203
|
+
return {
|
|
5204
|
+
...document,
|
|
5205
|
+
sections: "machine" in sections ? {
|
|
5206
|
+
...sections,
|
|
5207
|
+
machine: machineWithoutLimits(run, sections.machine)
|
|
5208
|
+
} : sections
|
|
5209
|
+
};
|
|
5210
|
+
};
|
|
4848
5211
|
//#endregion
|
|
4849
5212
|
//#region src/commands/agents/config.ts
|
|
5213
|
+
const releaseOf = ({ kikimoraVersion, followsLatest }) => followsLatest ? `latest (${kikimoraVersion})` : kikimoraVersion;
|
|
5214
|
+
const nodeAppliedReasons = /* @__PURE__ */ new Set(["latest_release"]);
|
|
5215
|
+
const awaitsRestart = ({ restartRequired, restartReasons }) => restartRequired && (restartReasons.length === 0 || restartReasons.some((reason) => !nodeAppliedReasons.has(reason)));
|
|
4850
5216
|
const agentsOf = async (scope, project) => {
|
|
4851
5217
|
const projects = project === void 0 ? (await projectsOf(scope)).map(({ id, slug }) => ({
|
|
4852
5218
|
id,
|
|
@@ -4907,8 +5273,8 @@ const configCommands = (adaptersOf) => [
|
|
|
4907
5273
|
agent.project,
|
|
4908
5274
|
agent.slug,
|
|
4909
5275
|
agent.state,
|
|
4910
|
-
agent
|
|
4911
|
-
agent
|
|
5276
|
+
releaseOf(agent),
|
|
5277
|
+
awaitsRestart(agent) ? "restart" : "-",
|
|
4912
5278
|
agent.id
|
|
4913
5279
|
])));
|
|
4914
5280
|
}
|
|
@@ -4960,8 +5326,8 @@ const configCommands = (adaptersOf) => [
|
|
|
4960
5326
|
const agentId = await agentFrom(run, invocation.positionals[0], invocation.values.project);
|
|
4961
5327
|
const file = invocation.values.file;
|
|
4962
5328
|
if (file === void 0) throw new UsageError("agents set needs --file <path>.", "The file holds the sections to change; kcloud agents show <agent> --json shows the current ones.");
|
|
4963
|
-
const agent = await changedAgent(run.api, agentId,
|
|
4964
|
-
return printed(run, agent, (changed) => changed
|
|
5329
|
+
const agent = await changedAgent(run.api, agentId, acceptedAgentChange(run, await documentOf(run, file)));
|
|
5330
|
+
return printed(run, agent, (changed) => awaitsRestart(changed) ? `Changed ${changed.name}; it needs ${changeTakerOf(changed)} to take the change.` : `Changed ${changed.name}.`);
|
|
4965
5331
|
}
|
|
4966
5332
|
}),
|
|
4967
5333
|
defineCommand({
|
|
@@ -5013,13 +5379,13 @@ const waitTexts = {
|
|
|
5013
5379
|
};
|
|
5014
5380
|
const reasonOf$1 = ({ reason }) => reason ?? "slot";
|
|
5015
5381
|
const isKnownReason = (reason) => Object.hasOwn(waitTexts, reason);
|
|
5016
|
-
const waitTextOf = (reason) => isKnownReason(reason) ? waitTexts[reason] : `held by the node (${reason})`;
|
|
5382
|
+
const waitTextOf$1 = (reason) => isKnownReason(reason) ? waitTexts[reason] : `held by the node (${reason})`;
|
|
5017
5383
|
const waitRowsOf = ({ runtime }) => {
|
|
5018
5384
|
const { admission } = runtime;
|
|
5019
5385
|
if (admission === null) return [];
|
|
5020
5386
|
const waits = ["monitor", "executor"].flatMap((role) => {
|
|
5021
5387
|
const state = admission[role];
|
|
5022
|
-
return state?.state === "waiting" ? [`${role} ${waitTextOf(reasonOf$1(state))}`] : [];
|
|
5388
|
+
return state?.state === "waiting" ? [`${role} ${waitTextOf$1(reasonOf$1(state))}`] : [];
|
|
5023
5389
|
});
|
|
5024
5390
|
return waits.length === 0 ? [] : [["admissions", waits.join("; ")]];
|
|
5025
5391
|
};
|
|
@@ -5038,10 +5404,34 @@ const claudeCodeOf = ({ runtime, release }) => {
|
|
|
5038
5404
|
if (running?.source === "host") return "host";
|
|
5039
5405
|
return running?.version ?? release?.claudeCodeVersion ?? "unknown";
|
|
5040
5406
|
};
|
|
5407
|
+
const claudeAccountWaitOf = ({ waitReason, waitUntil }) => {
|
|
5408
|
+
switch (waitReason) {
|
|
5409
|
+
case "exhausted": return waitUntil === null ? "every account is exhausted" : `every account is exhausted until ${waitUntil}`;
|
|
5410
|
+
case "blocked": return "no account of the queue can work";
|
|
5411
|
+
case "empty": return "the queue of the project is empty";
|
|
5412
|
+
default: return null;
|
|
5413
|
+
}
|
|
5414
|
+
};
|
|
5415
|
+
const claudeAccountOf = ({ claudeAccount }) => {
|
|
5416
|
+
const { current, queueSize } = claudeAccount;
|
|
5417
|
+
const account = current === null ? "none" : `${current.label} (${current.kind}), ${current.position === null ? "outside the queue" : `${String(current.position)} of ${String(queueSize)}`}`;
|
|
5418
|
+
const wait = claudeAccountWaitOf(claudeAccount);
|
|
5419
|
+
return wait === null ? account : `${account}; ${wait}`;
|
|
5420
|
+
};
|
|
5041
5421
|
const failureRowsOf = ({ failureReason, retryAt }) => {
|
|
5042
5422
|
if (failureReason === null) return [];
|
|
5043
5423
|
return [["failure", retryAt === null ? failureReason : `${failureReason}, retry at ${retryAt}`]];
|
|
5044
5424
|
};
|
|
5425
|
+
const gitCredentialLinesOf = ({ gitCredentials }) => gitCredentials.length === 0 ? ["No git credential is set."] : ["GIT CREDENTIALS", renderTable([
|
|
5426
|
+
{ header: "PROVIDER" },
|
|
5427
|
+
{ header: "SERVER" },
|
|
5428
|
+
{ header: "LABEL" }
|
|
5429
|
+
], gitCredentials.map(({ provider, serverUrl, label }) => [
|
|
5430
|
+
provider,
|
|
5431
|
+
serverUrl,
|
|
5432
|
+
label
|
|
5433
|
+
]))];
|
|
5434
|
+
const nextReleaseRowsOf = ({ pendingKikimoraVersion }) => pendingKikimoraVersion == null ? [] : [["next release", `${pendingKikimoraVersion} after the current session`]];
|
|
5045
5435
|
const detailsOf = (agent) => [
|
|
5046
5436
|
renderTable([{ header: "FIELD" }, { header: "VALUE" }], [
|
|
5047
5437
|
["name", agent.name],
|
|
@@ -5049,8 +5439,10 @@ const detailsOf = (agent) => [
|
|
|
5049
5439
|
["state", agent.state],
|
|
5050
5440
|
["wanted", agent.desiredState],
|
|
5051
5441
|
["observed", agent.observedState],
|
|
5052
|
-
["release", agent
|
|
5442
|
+
["release", releaseOf(agent)],
|
|
5443
|
+
...nextReleaseRowsOf(agent),
|
|
5053
5444
|
["claude code", claudeCodeOf(agent)],
|
|
5445
|
+
["claude account", claudeAccountOf(agent)],
|
|
5054
5446
|
["node", agent.node === null ? "none" : `${agent.node.name} (${agent.node.state})`],
|
|
5055
5447
|
["requires", agent.requiredCapabilities.length === 0 ? "nothing" : agent.requiredCapabilities.join(", ")],
|
|
5056
5448
|
["timezone", agent.timezone],
|
|
@@ -5063,7 +5455,7 @@ const detailsOf = (agent) => [
|
|
|
5063
5455
|
...admissionRowsOf(agent),
|
|
5064
5456
|
...admissionHoldRowsOf(agent),
|
|
5065
5457
|
...poolLimitRowsOf(agent),
|
|
5066
|
-
["pending", agent
|
|
5458
|
+
["pending", awaitsRestart(agent) ? agent.restartReasons.join(", ") : "-"],
|
|
5067
5459
|
["actions", agent.availableActions.join(", ")],
|
|
5068
5460
|
["version", String(agent.version)],
|
|
5069
5461
|
["id", agent.id]
|
|
@@ -5071,13 +5463,19 @@ const detailsOf = (agent) => [
|
|
|
5071
5463
|
"",
|
|
5072
5464
|
agent.repositories.length === 0 ? "No repository is cloned into the workspace." : renderTable([
|
|
5073
5465
|
{ header: "REPOSITORY" },
|
|
5466
|
+
{ header: "PROVIDER" },
|
|
5467
|
+
{ header: "SERVER" },
|
|
5074
5468
|
{ header: "DIRECTORY" },
|
|
5075
5469
|
{ header: "STATUS" }
|
|
5076
5470
|
], agent.repositories.map((repository) => [
|
|
5077
5471
|
`${repository.owner}/${repository.name}`,
|
|
5472
|
+
repository.provider,
|
|
5473
|
+
repository.serverUrl,
|
|
5078
5474
|
repository.directory,
|
|
5079
5475
|
repository.status
|
|
5080
|
-
]))
|
|
5476
|
+
])),
|
|
5477
|
+
"",
|
|
5478
|
+
...gitCredentialLinesOf(agent)
|
|
5081
5479
|
].join("\n");
|
|
5082
5480
|
//#endregion
|
|
5083
5481
|
//#region src/commands/agents/placement.ts
|
|
@@ -5104,27 +5502,49 @@ const placedNodeId = async (run, { projectId, given, ask }) => {
|
|
|
5104
5502
|
};
|
|
5105
5503
|
//#endregion
|
|
5106
5504
|
//#region src/commands/agents/create.ts
|
|
5107
|
-
const claudeKinds = /* @__PURE__ */ new Set(["anthropic_api_key", "claude_oauth_token"]);
|
|
5108
5505
|
const catalogueOf = async (api, projectId) => {
|
|
5109
5506
|
const path = { params: { path: { id: projectId } } };
|
|
5507
|
+
const credentials = dataOf("list the credentials of the project", await api.GET("/projects/{id}/credentials", path), agentHints).items;
|
|
5508
|
+
const repositories = dataOf("list the repositories of the project", await api.GET("/projects/{id}/repositories", path), agentHints).items;
|
|
5509
|
+
const tools = dataOf("list the tools of the project", await api.GET("/projects/{id}/tools", path), agentHints).items;
|
|
5510
|
+
const { items, latest } = dataOf("list the releases", await api.GET("/releases"));
|
|
5110
5511
|
return {
|
|
5111
|
-
credentials
|
|
5112
|
-
repositories
|
|
5113
|
-
tools
|
|
5114
|
-
releases:
|
|
5512
|
+
credentials,
|
|
5513
|
+
repositories,
|
|
5514
|
+
tools,
|
|
5515
|
+
releases: items.map(({ version }) => version),
|
|
5516
|
+
latest
|
|
5115
5517
|
};
|
|
5116
5518
|
};
|
|
5117
|
-
const
|
|
5118
|
-
const
|
|
5119
|
-
|
|
5120
|
-
|
|
5121
|
-
|
|
5122
|
-
|
|
5123
|
-
|
|
5124
|
-
|
|
5125
|
-
|
|
5126
|
-
|
|
5127
|
-
}
|
|
5519
|
+
const serversOf = (catalogue, chosen) => {
|
|
5520
|
+
const servers = /* @__PURE__ */ new Map();
|
|
5521
|
+
for (const { repositoryId } of chosen) {
|
|
5522
|
+
const repository = catalogue.repositories.find(({ id }) => id === repositoryId);
|
|
5523
|
+
if (repository === void 0) continue;
|
|
5524
|
+
const { provider, serverUrl } = repository;
|
|
5525
|
+
servers.set(`${provider} ${serverUrl}`, {
|
|
5526
|
+
provider,
|
|
5527
|
+
serverUrl
|
|
5528
|
+
});
|
|
5529
|
+
}
|
|
5530
|
+
return [...servers.values()].sort((first, second) => gitProviders.indexOf(first.provider) - gitProviders.indexOf(second.provider) || first.serverUrl.localeCompare(second.serverUrl));
|
|
5531
|
+
};
|
|
5532
|
+
const gitCredentialQuestionOf = ({ provider, serverUrl }) => provider === "github" ? "The GitHub credential it clones with" : `The ${providerNameOf(provider)} credential for ${serverUrl} it clones with`;
|
|
5533
|
+
const chosenGitCredentials = async (prompt, catalogue, chosen) => {
|
|
5534
|
+
const ids = [];
|
|
5535
|
+
for (const server of serversOf(catalogue, chosen)) {
|
|
5536
|
+
const choices = catalogue.credentials.filter((credential) => gitProviderOf(credential) === server.provider && credentialServerOf(credential) === server.serverUrl).map((credential) => ({
|
|
5537
|
+
value: credential.id,
|
|
5538
|
+
label: credential.label,
|
|
5539
|
+
hint: credential.kind
|
|
5540
|
+
}));
|
|
5541
|
+
if (choices.length === 0) throw new CliError(`The project holds no ${providerNameOf(server.provider)} credential for ${server.serverUrl}.`, `kcloud credentials add --project <name> --kind ${kindOfProvider(server.provider)}${server.provider === "github" ? "" : ` --server-url ${server.serverUrl}`} stores one.`);
|
|
5542
|
+
ids.push(await prompt.select({
|
|
5543
|
+
message: gitCredentialQuestionOf(server),
|
|
5544
|
+
choices
|
|
5545
|
+
}));
|
|
5546
|
+
}
|
|
5547
|
+
return ids;
|
|
5128
5548
|
};
|
|
5129
5549
|
const chosenRepositories = async (prompt, catalogue) => {
|
|
5130
5550
|
if (catalogue.repositories.length === 0) throw new CliError("The project has no repository to clone.", "kcloud repositories add <owner/name> --project <name> registers one.");
|
|
@@ -5137,7 +5557,7 @@ const chosenRepositories = async (prompt, catalogue) => {
|
|
|
5137
5557
|
choices: [...left.map((repository) => ({
|
|
5138
5558
|
value: repository.id,
|
|
5139
5559
|
label: `${repository.owner}/${repository.name}`,
|
|
5140
|
-
hint: repository.defaultBranch
|
|
5560
|
+
hint: `${repository.provider} ${repository.defaultBranch}`
|
|
5141
5561
|
})), ...chosen.length === 0 ? [] : [{
|
|
5142
5562
|
value: "",
|
|
5143
5563
|
label: "no more repositories",
|
|
@@ -5213,6 +5633,21 @@ const requiredCapabilitiesOf = (given) => {
|
|
|
5213
5633
|
};
|
|
5214
5634
|
const requirementOf = (required) => required === void 0 ? {} : { requiredCapabilities: [...required] };
|
|
5215
5635
|
const localTimezone = () => Intl.DateTimeFormat().resolvedOptions().timeZone || "UTC";
|
|
5636
|
+
const releaseChoicesOf = ({ releases, latest }) => [...latest === null ? [] : [{
|
|
5637
|
+
value: latestKikimoraVersion,
|
|
5638
|
+
label: `latest (now ${latest})`
|
|
5639
|
+
}], ...releases.map((version) => ({
|
|
5640
|
+
value: version,
|
|
5641
|
+
label: version
|
|
5642
|
+
}))];
|
|
5643
|
+
const checkedRelease = ({ releases, latest }, release) => {
|
|
5644
|
+
if (release === "latest") {
|
|
5645
|
+
if (latest !== null) return release;
|
|
5646
|
+
throw new CliError("No release is stable yet, so latest points at no release.", "kcloud releases list shows the releases to pick one of.");
|
|
5647
|
+
}
|
|
5648
|
+
if (releases.includes(release)) return release;
|
|
5649
|
+
throw new CliError(`${release} is not a release an agent can run.`, releases.length === 0 ? "The catalogue holds no release yet; an administrator syncs it under Releases in the panel." : `kcloud releases list shows ${releases.join(", ")}.`);
|
|
5650
|
+
};
|
|
5216
5651
|
const askedPlan = async (run, projectId, values, required) => {
|
|
5217
5652
|
const nodeId = await placedNodeId(run, {
|
|
5218
5653
|
projectId,
|
|
@@ -5224,23 +5659,20 @@ const askedPlan = async (run, projectId, values, required) => {
|
|
|
5224
5659
|
message: "The name of the agent",
|
|
5225
5660
|
validate: (value) => value.trim() === "" ? "cannot be empty" : void 0
|
|
5226
5661
|
});
|
|
5227
|
-
const release = values.release ?? await run.prompt.select({
|
|
5662
|
+
const release = checkedRelease(catalogue, values.release ?? await run.prompt.select({
|
|
5228
5663
|
message: "The Kikimora release it runs",
|
|
5229
|
-
choices: catalogue
|
|
5230
|
-
|
|
5231
|
-
|
|
5232
|
-
|
|
5233
|
-
});
|
|
5234
|
-
if (!catalogue.releases.includes(release)) throw new CliError(`${release} is not a release an agent can run.`, catalogue.releases.length === 0 ? "The catalogue holds no release yet; an administrator syncs it under Releases in the panel." : `kcloud releases list shows ${catalogue.releases.join(", ")}.`);
|
|
5664
|
+
choices: releaseChoicesOf(catalogue)
|
|
5665
|
+
}));
|
|
5666
|
+
const repositories = await chosenRepositories(run.prompt, catalogue);
|
|
5667
|
+
const gitCredentialIds = await chosenGitCredentials(run.prompt, catalogue, repositories);
|
|
5235
5668
|
return {
|
|
5236
5669
|
name,
|
|
5237
5670
|
description: "",
|
|
5238
5671
|
kikimoraVersion: release,
|
|
5239
5672
|
nodeId,
|
|
5240
5673
|
...requirementOf(required),
|
|
5241
|
-
|
|
5242
|
-
|
|
5243
|
-
repositories: [...await chosenRepositories(run.prompt, catalogue)],
|
|
5674
|
+
gitCredentialIds: [...gitCredentialIds],
|
|
5675
|
+
repositories: [...repositories],
|
|
5244
5676
|
toolAssignments: [...await chosenTools(run.prompt, catalogue)],
|
|
5245
5677
|
prompts: {
|
|
5246
5678
|
monitor: await promptText(run, values["monitor-prompt-file"], "What the monitor should watch for"),
|
|
@@ -5256,7 +5688,7 @@ const askedPlan = async (run, projectId, values, required) => {
|
|
|
5256
5688
|
};
|
|
5257
5689
|
const isObject = (document) => typeof document === "object" && document !== null && !Array.isArray(document);
|
|
5258
5690
|
const fileOf = async (run, projectId, { file, node }, required) => {
|
|
5259
|
-
const document =
|
|
5691
|
+
const document = acceptedAgentFile(run, await documentOf(run, file));
|
|
5260
5692
|
const placed = !isObject(document) || node === void 0 && "nodeId" in document ? document : {
|
|
5261
5693
|
...document,
|
|
5262
5694
|
nodeId: await placedNodeId(run, {
|
|
@@ -5282,13 +5714,14 @@ const createCommand = (adaptersOf) => defineCommand({
|
|
|
5282
5714
|
["--require <capability,...>", `what its node has to offer: ${nodeCapabilityNames.join(", ")}`],
|
|
5283
5715
|
["--file <path>", "the whole agent as JSON, or - to read stdin"],
|
|
5284
5716
|
["--name <name>", "the name of the agent, instead of asking"],
|
|
5285
|
-
["--release <x.y.z>", "the Kikimora release it runs, instead of asking"],
|
|
5717
|
+
["--release <x.y.z|latest>", "the Kikimora release it runs, or latest to follow the newest stable one, instead of asking"],
|
|
5286
5718
|
["--monitor-prompt-file <path>", "the prompt of the monitor, instead of asking"],
|
|
5287
5719
|
["--executor-prompt-file <path>", "the prompt of the executor, instead of asking"],
|
|
5288
5720
|
["--timezone <zone>", "the timezone of the agent (default: this machine's)"],
|
|
5289
5721
|
["--docker", "give the agent a Docker sidecar, when the project allows it"],
|
|
5290
5722
|
["--browser", "give the agent a browser"],
|
|
5291
5723
|
["", "kcloud agents show <agent> --as-file prints what --file takes"],
|
|
5724
|
+
["", "the file names the git tokens as gitCredentialIds, one for each server of its repositories"],
|
|
5292
5725
|
["", "--node and --require replace the nodeId and requiredCapabilities of the file"]
|
|
5293
5726
|
],
|
|
5294
5727
|
options: {
|
|
@@ -5498,7 +5931,7 @@ const workKinds = /* @__PURE__ */ new Set([
|
|
|
5498
5931
|
"summarising"
|
|
5499
5932
|
]);
|
|
5500
5933
|
const organizationDashboardOf = async (api, organizationId) => dataOf("show the dashboard of the organization", await api.GET("/organizations/{organizationId}/dashboard", { params: { path: { organizationId } } }), organizationHints);
|
|
5501
|
-
const statusOf = (agent) => agent.status === "transitioning" ? `${agent.status} (${agent.state})` : agent.status;
|
|
5934
|
+
const statusOf$1 = (agent) => agent.status === "transitioning" ? `${agent.status} (${agent.state})` : agent.status;
|
|
5502
5935
|
const taskOf = (agent) => {
|
|
5503
5936
|
const { activity } = agent;
|
|
5504
5937
|
if (activity === null) return noTask;
|
|
@@ -5511,7 +5944,7 @@ const dashboardText = (dashboard) => {
|
|
|
5511
5944
|
const rows = dashboard.projects.flatMap((project) => project.agents.map((agent) => [
|
|
5512
5945
|
project.slug,
|
|
5513
5946
|
agent.slug,
|
|
5514
|
-
statusOf(agent),
|
|
5947
|
+
statusOf$1(agent),
|
|
5515
5948
|
agent.since,
|
|
5516
5949
|
taskOf(agent),
|
|
5517
5950
|
agent.costTodayUsd.toFixed(2)
|
|
@@ -5544,7 +5977,7 @@ const frameLineOf = (names, frame, at) => {
|
|
|
5544
5977
|
slug: agent.slug
|
|
5545
5978
|
});
|
|
5546
5979
|
const project = names.projects.get(agent.projectId) ?? agent.projectId;
|
|
5547
|
-
return `${at.toISOString()} ${project}/${agent.slug} ${statusOf(agent)} ${taskOf(agent)}`;
|
|
5980
|
+
return `${at.toISOString()} ${project}/${agent.slug} ${statusOf$1(agent)} ${taskOf(agent)}`;
|
|
5548
5981
|
}
|
|
5549
5982
|
const removed = dashboardRemovalSchema.safeParse(frame.data);
|
|
5550
5983
|
if (frame.event === "dashboard.removed" && removed.success) {
|
|
@@ -5711,10 +6144,10 @@ const pageOptions = {
|
|
|
5711
6144
|
};
|
|
5712
6145
|
const allUsage = ["--all", "follow the cursor to the end instead of one page"];
|
|
5713
6146
|
const pageUsage = [["--limit <n>", `how many rows (default: 50)`], allUsage];
|
|
5714
|
-
const dayOf$
|
|
6147
|
+
const dayOf$2 = (date) => date.toISOString().slice(0, 10);
|
|
5715
6148
|
const costWindowOf = (days, now) => ({
|
|
5716
|
-
from: dayOf$
|
|
5717
|
-
to: dayOf$
|
|
6149
|
+
from: dayOf$2(/* @__PURE__ */ new Date(now.getTime() - (days - 1) * 24 * 60 * 6e4)),
|
|
6150
|
+
to: dayOf$2(now)
|
|
5718
6151
|
});
|
|
5719
6152
|
const windowFrom = (days, now) => {
|
|
5720
6153
|
const span = days === void 0 ? 7 : Number(days);
|
|
@@ -6025,6 +6458,165 @@ const observabilityCommands = (adaptersOf) => [
|
|
|
6025
6458
|
})
|
|
6026
6459
|
];
|
|
6027
6460
|
//#endregion
|
|
6461
|
+
//#region src/commands/agents/overview.ts
|
|
6462
|
+
const none = "-";
|
|
6463
|
+
const agentOverviewOf = async (api, agentId) => dataOf("show the overview of the agent", await api.GET("/agents/{id}/overview", { params: { path: { id: agentId } } }), agentHints);
|
|
6464
|
+
const staleReasons = {
|
|
6465
|
+
stopped: "the agent is not running",
|
|
6466
|
+
unreachable: "the agent did not answer on its control socket",
|
|
6467
|
+
timeout: "the agent did not answer in time"
|
|
6468
|
+
};
|
|
6469
|
+
const nameOf = (task) => task.source === null ? task.taskId : `${task.source.label} (${task.taskId})`;
|
|
6470
|
+
const money = (amount) => `$${amount.toFixed(2)}`;
|
|
6471
|
+
const minutesOf = (ms) => `${Math.round(ms / 6e4)} min`;
|
|
6472
|
+
const counted = (count, one, many) => `${count} ${count === 1 ? one : many}`;
|
|
6473
|
+
const attemptsOf = (task) => task.maxAttempts === null ? `attempt ${task.attempts}` : `attempt ${task.attempts} of ${task.maxAttempts}`;
|
|
6474
|
+
const nowText = (overview) => {
|
|
6475
|
+
const lines = [];
|
|
6476
|
+
const { current, last, queue, lastCycle } = overview;
|
|
6477
|
+
if (current !== null && overview.source === "projection") {
|
|
6478
|
+
lines.push(`Last known: ${nameOf(current)} ${current.title}, started ${current.startedAt}, ${attemptsOf(current)}`);
|
|
6479
|
+
if (overview.staleReason !== null) lines.push(`Not current: ${staleReasons[overview.staleReason]}.`);
|
|
6480
|
+
} else if (current !== null) {
|
|
6481
|
+
lines.push(`Working on ${nameOf(current)} ${current.title}, since ${current.startedAt}, ${attemptsOf(current)}`);
|
|
6482
|
+
if (current.source?.url != null) lines.push(`Source: ${current.source.url}`);
|
|
6483
|
+
} else if (last !== null) {
|
|
6484
|
+
lines.push(`Last: ${nameOf(last)} ${last.title}, ${last.outcome}, finished ${last.finishedAt ?? none}`);
|
|
6485
|
+
if (last.headline !== null) lines.push(`Result: ${last.headline}`);
|
|
6486
|
+
} else lines.push("No task in the last 7 days.");
|
|
6487
|
+
if (queue !== null) lines.push(`Queue: ${queue.pending} pending, ${queue.failed} failed`);
|
|
6488
|
+
if (lastCycle !== null) {
|
|
6489
|
+
const added = lastCycle.addedTasks === null ? "" : lastCycle.addedTasks === 0 ? ", found nothing new" : `, added ${counted(lastCycle.addedTasks, "task", "tasks")}`;
|
|
6490
|
+
lines.push(`Last review of new work: ${lastCycle.finishedAt}${added}`);
|
|
6491
|
+
}
|
|
6492
|
+
return lines.join("\n");
|
|
6493
|
+
};
|
|
6494
|
+
const attentionLine = (item) => {
|
|
6495
|
+
const name = `${nameOf(item)} ${item.title}`;
|
|
6496
|
+
switch (item.kind) {
|
|
6497
|
+
case "failed": return `failed after ${counted(item.attempts, "attempt", "attempts")}: ${name}${item.error === null ? "" : `: ${item.error}`}`;
|
|
6498
|
+
case "retried": return `succeeded on attempt ${item.attempts}: ${name}`;
|
|
6499
|
+
case "handedOff": return `handed off to a person: ${name}${item.reason === null ? "" : `: ${item.reason}`}`;
|
|
6500
|
+
}
|
|
6501
|
+
};
|
|
6502
|
+
const attentionText = (overview) => overview.attention.length === 0 ? "Nothing needs attention." : overview.attention.map(attentionLine).join("\n");
|
|
6503
|
+
const groupRows = (group) => {
|
|
6504
|
+
const [newest] = group.tasks;
|
|
6505
|
+
const head = [
|
|
6506
|
+
group.source?.label ?? newest?.taskId ?? none,
|
|
6507
|
+
group.title,
|
|
6508
|
+
String(group.tasks.length),
|
|
6509
|
+
newest?.outcome ?? none,
|
|
6510
|
+
minutesOf(group.totals.durationMs),
|
|
6511
|
+
money(group.totals.costUsd),
|
|
6512
|
+
newest?.finishedAt ?? none
|
|
6513
|
+
];
|
|
6514
|
+
if (group.tasks.length < 2) return [head];
|
|
6515
|
+
return [head, ...group.tasks.map((task) => [
|
|
6516
|
+
` ${task.taskId}`,
|
|
6517
|
+
task.title,
|
|
6518
|
+
"",
|
|
6519
|
+
task.attempts > 1 ? `${task.outcome}, ${counted(task.attempts, "attempt", "attempts")}` : task.outcome,
|
|
6520
|
+
minutesOf(task.durationMs),
|
|
6521
|
+
money(task.costUsd),
|
|
6522
|
+
task.finishedAt ?? none
|
|
6523
|
+
])];
|
|
6524
|
+
};
|
|
6525
|
+
const recentText = (overview, agent) => {
|
|
6526
|
+
if (overview.groups.length === 0) return "No work in the last 7 days.";
|
|
6527
|
+
const lines = [renderTable([
|
|
6528
|
+
{ header: "SOURCE" },
|
|
6529
|
+
{ header: "TITLE" },
|
|
6530
|
+
{
|
|
6531
|
+
header: "TASKS",
|
|
6532
|
+
align: "right"
|
|
6533
|
+
},
|
|
6534
|
+
{ header: "OUTCOME" },
|
|
6535
|
+
{
|
|
6536
|
+
header: "TIME",
|
|
6537
|
+
align: "right"
|
|
6538
|
+
},
|
|
6539
|
+
{
|
|
6540
|
+
header: "COST",
|
|
6541
|
+
align: "right"
|
|
6542
|
+
},
|
|
6543
|
+
{ header: "FINISHED" }
|
|
6544
|
+
], overview.groups.flatMap(groupRows))];
|
|
6545
|
+
if (overview.moreTasks > 0) lines.push(`${counted(overview.moreTasks, "more task", "more tasks")}: kcloud agents tasks list ${agent}`);
|
|
6546
|
+
if (overview.source === "projection" && overview.staleReason !== null) lines.push(`No results from memory: ${staleReasons[overview.staleReason]}.`);
|
|
6547
|
+
return lines.join("\n");
|
|
6548
|
+
};
|
|
6549
|
+
const healthRow = (label, workload) => [
|
|
6550
|
+
label,
|
|
6551
|
+
String(workload.tasksDone),
|
|
6552
|
+
String(workload.tasksFailed),
|
|
6553
|
+
String(workload.tasksHandedOff),
|
|
6554
|
+
money(workload.tasksCostUsd),
|
|
6555
|
+
workload.averageTaskMs === null ? none : minutesOf(workload.averageTaskMs),
|
|
6556
|
+
`${workload.cycles} (${money(workload.cyclesCostUsd)})`
|
|
6557
|
+
];
|
|
6558
|
+
const healthText = (overview) => {
|
|
6559
|
+
const lines = [renderTable([
|
|
6560
|
+
{ header: "WINDOW" },
|
|
6561
|
+
{
|
|
6562
|
+
header: "DONE",
|
|
6563
|
+
align: "right"
|
|
6564
|
+
},
|
|
6565
|
+
{
|
|
6566
|
+
header: "FAILED",
|
|
6567
|
+
align: "right"
|
|
6568
|
+
},
|
|
6569
|
+
{
|
|
6570
|
+
header: "HANDED OFF",
|
|
6571
|
+
align: "right"
|
|
6572
|
+
},
|
|
6573
|
+
{
|
|
6574
|
+
header: "TASK COST",
|
|
6575
|
+
align: "right"
|
|
6576
|
+
},
|
|
6577
|
+
{
|
|
6578
|
+
header: "AVERAGE",
|
|
6579
|
+
align: "right"
|
|
6580
|
+
},
|
|
6581
|
+
{
|
|
6582
|
+
header: "REVIEWS",
|
|
6583
|
+
align: "right"
|
|
6584
|
+
}
|
|
6585
|
+
], [healthRow("24 hours", overview.health.day), healthRow("7 days", overview.health.week)]), `Cost today: ${money(overview.costTodayUsd)}`];
|
|
6586
|
+
if (overview.lastSession !== null) lines.push(`Last session: ${overview.lastSession.role ?? "unknown"}, ${overview.lastSession.startedAt}`);
|
|
6587
|
+
return lines.join("\n");
|
|
6588
|
+
};
|
|
6589
|
+
const overviewText = (overview, agent = "<agent>") => [
|
|
6590
|
+
"NOW",
|
|
6591
|
+
nowText(overview),
|
|
6592
|
+
"",
|
|
6593
|
+
`NEEDS ATTENTION (${overview.attention.length})`,
|
|
6594
|
+
attentionText(overview),
|
|
6595
|
+
"",
|
|
6596
|
+
"RECENT WORK",
|
|
6597
|
+
recentText(overview, agent),
|
|
6598
|
+
"",
|
|
6599
|
+
"HOW IT GOES",
|
|
6600
|
+
healthText(overview)
|
|
6601
|
+
].join("\n");
|
|
6602
|
+
const overviewCommand = (adaptersOf) => defineCommand({
|
|
6603
|
+
name: "overview",
|
|
6604
|
+
summary: "Show what an agent does now, what needs attention and how its work goes.",
|
|
6605
|
+
usage: "agents overview <agent> [--project <name>]",
|
|
6606
|
+
optionsUsage: [...agentUsage],
|
|
6607
|
+
options: { ...agentOption },
|
|
6608
|
+
positionals: {
|
|
6609
|
+
min: 1,
|
|
6610
|
+
max: 1
|
|
6611
|
+
},
|
|
6612
|
+
run: async (invocation) => {
|
|
6613
|
+
const run = await runOf(adaptersOf, invocation);
|
|
6614
|
+
const agent = invocation.positionals[0];
|
|
6615
|
+
const agentId = await agentFrom(run, agent, invocation.values.project);
|
|
6616
|
+
return printed(run, await agentOverviewOf(run.api, agentId), (overview) => overviewText(overview, agent));
|
|
6617
|
+
}
|
|
6618
|
+
});
|
|
6619
|
+
//#endregion
|
|
6028
6620
|
//#region src/commands/agents/streams.ts
|
|
6029
6621
|
const streamOptions = {
|
|
6030
6622
|
...agentOption,
|
|
@@ -6389,6 +6981,7 @@ const createAgentsCommand = (adaptersOf) => defineGroup({
|
|
|
6389
6981
|
tasksCommand(adaptersOf),
|
|
6390
6982
|
backupsCommand(adaptersOf),
|
|
6391
6983
|
restoreCommand(adaptersOf),
|
|
6984
|
+
overviewCommand(adaptersOf),
|
|
6392
6985
|
...observabilityCommands(adaptersOf),
|
|
6393
6986
|
...streamCommands(adaptersOf),
|
|
6394
6987
|
dashboardCommand(adaptersOf)
|
|
@@ -6438,16 +7031,57 @@ const secretUsage = [
|
|
|
6438
7031
|
];
|
|
6439
7032
|
//#endregion
|
|
6440
7033
|
//#region src/commands/credentials.ts
|
|
6441
|
-
const projectOption$
|
|
6442
|
-
const projectUsage$
|
|
6443
|
-
const usageUnavailable = "Only a Claude Code OAuth token reports the usage of a subscription.";
|
|
7034
|
+
const projectOption$3 = { project: { type: "string" } };
|
|
7035
|
+
const projectUsage$3 = [["--project <name>", "the project the credential belongs to, by slug, name or id"]];
|
|
7036
|
+
const usageUnavailable$1 = "Only a Claude Code OAuth token reports the usage of a subscription; kcloud claude-accounts usage shows it.";
|
|
7037
|
+
const defaultKind = "github_pat";
|
|
7038
|
+
const countOf = (value) => typeof value === "number" ? value : 0;
|
|
7039
|
+
const inUseHint = ({ agents, repositories }) => {
|
|
7040
|
+
return `Point the ${[...countOf(agents) > 0 || countOf(repositories) === 0 ? ["agents"] : [], ...countOf(repositories) > 0 ? ["repositories"] : []].join(" and the ")} at another credential first.`;
|
|
7041
|
+
};
|
|
6444
7042
|
const credentialHints = {
|
|
6445
7043
|
"project.not_found": "kcloud projects list shows the projects you are a member of.",
|
|
6446
7044
|
"credential.not_found": "kcloud credentials list --project <name> shows them.",
|
|
6447
7045
|
"credential.label_taken": "Another credential of the project already has that label.",
|
|
6448
|
-
"credential.in_use":
|
|
6449
|
-
"credential.usage_unavailable": usageUnavailable
|
|
7046
|
+
"credential.in_use": inUseHint,
|
|
7047
|
+
"credential.usage_unavailable": usageUnavailable$1,
|
|
7048
|
+
"credential.kind_mismatch": "A project holds GitHub, GitLab and Bitbucket tokens alone, and only they carry a commit identity; kcloud claude-accounts add stores a Claude subscription or an Anthropic API key in the organization."
|
|
7049
|
+
};
|
|
7050
|
+
const addHints = {
|
|
7051
|
+
...credentialHints,
|
|
7052
|
+
"credential.server_refused": "Give the https address of a public host, without credentials, query or fragment, as --server-url.",
|
|
7053
|
+
"validation.failed": "Check the options: --server-url takes an http or https address, and a Bitbucket Cloud API token (ATATT...) needs --account-email."
|
|
7054
|
+
};
|
|
7055
|
+
const gitOptionsUsage = [
|
|
7056
|
+
["--server-url <url>", "the server of a GitLab or Bitbucket token (default: gitlab.com or bitbucket.org)"],
|
|
7057
|
+
["--account-email <email>", "the Atlassian account of a Bitbucket Cloud API token"],
|
|
7058
|
+
["--commit-name <name>", "the name its commits carry, with --commit-email"],
|
|
7059
|
+
["--commit-email <email>", "the e-mail its commits carry, with --commit-name"]
|
|
7060
|
+
];
|
|
7061
|
+
const gitOptions = {
|
|
7062
|
+
"server-url": { type: "string" },
|
|
7063
|
+
"account-email": { type: "string" },
|
|
7064
|
+
"commit-name": { type: "string" },
|
|
7065
|
+
"commit-email": { type: "string" }
|
|
7066
|
+
};
|
|
7067
|
+
const gitFieldsOf = (kind, values) => {
|
|
7068
|
+
const given = Object.entries(values).filter(([name, value]) => name in gitOptions && value !== void 0).map(([name]) => `--${name}`);
|
|
7069
|
+
if (!isGitCredentialKind(kind)) {
|
|
7070
|
+
if (given.length === 0) return {};
|
|
7071
|
+
throw new UsageError(`${given.join(", ")} only suits a ${gitCredentialKinds.join(", ")} credential, not ${kind}.`);
|
|
7072
|
+
}
|
|
7073
|
+
if (kind === "github_pat" && values["server-url"] !== void 0) throw new UsageError("--server-url does not suit a github_pat credential, which always acts on github.com.");
|
|
7074
|
+
if (values["commit-name"] === void 0 !== (values["commit-email"] === void 0)) throw new UsageError("--commit-name and --commit-email go together.", "Pass both, or neither to take the identity the provider reports.");
|
|
7075
|
+
return {
|
|
7076
|
+
...values["server-url"] === void 0 ? {} : { serverUrl: values["server-url"] },
|
|
7077
|
+
...values["account-email"] === void 0 ? {} : { accountEmail: values["account-email"] },
|
|
7078
|
+
...values["commit-name"] === void 0 ? {} : { commitName: values["commit-name"] },
|
|
7079
|
+
...values["commit-email"] === void 0 ? {} : { commitEmail: values["commit-email"] }
|
|
7080
|
+
};
|
|
6450
7081
|
};
|
|
7082
|
+
const missingIdentityNotes = (credentials, project) => credentials.filter(({ kind, verification, metadata }) => (kind === "gitlab_token" || kind === "bitbucket_token") && verification === "valid" && (metadata.commitName === void 0 || metadata.commitEmail === void 0)).map(({ label }) => `${label} has no commit identity yet: kcloud credentials set-identity ${shellWord(label)} --project ${project === void 0 ? "<name>" : shellWord(project)} --commit-name <name> --commit-email <email>`);
|
|
7083
|
+
const plainShellWord = /^[A-Za-z0-9_./@:-]+$/;
|
|
7084
|
+
const shellWord = (value) => plainShellWord.test(value) ? value : `'${value.replaceAll("'", `'\\''`)}'`;
|
|
6451
7085
|
const percent = 100;
|
|
6452
7086
|
const notApplicable = "-";
|
|
6453
7087
|
const usedOf = (window) => window === null ? notApplicable : `${String(Math.round(window.utilization * percent))}%`;
|
|
@@ -6474,12 +7108,13 @@ const usageReportOf = (credential) => {
|
|
|
6474
7108
|
window?.resetsAt ?? notApplicable
|
|
6475
7109
|
]))}\n${usage.stale ? `Read at ${usage.readAt}; the latest reading failed.` : `Read at ${usage.readAt}.`}`;
|
|
6476
7110
|
};
|
|
7111
|
+
const accountOf = ({ metadata }) => metadata.accountName ?? metadata.githubLogin ?? notApplicable;
|
|
6477
7112
|
const isoDateLength = 10;
|
|
6478
7113
|
const expiryOf = ({ expiresAt, expiresAtComputed }) => {
|
|
6479
7114
|
if (expiresAt === null) return "never";
|
|
6480
7115
|
return expiresAtComputed ? `about ${expiresAt.slice(0, isoDateLength)}` : expiresAt;
|
|
6481
7116
|
};
|
|
6482
|
-
const projectFrom$
|
|
7117
|
+
const projectFrom$3 = async (run, given) => {
|
|
6483
7118
|
if (given === void 0) throw new UsageError("This command needs a project.", "Pass --project <name>; kcloud projects list shows them.");
|
|
6484
7119
|
return resolveProjectId(run, given);
|
|
6485
7120
|
};
|
|
@@ -6495,55 +7130,60 @@ const resolveCredential = async (api, projectId, reference) => {
|
|
|
6495
7130
|
const resolveCredentialId = async (api, projectId, reference) => (await resolveCredential(api, projectId, reference)).id;
|
|
6496
7131
|
const createCredentialsCommand = (adaptersOf) => defineGroup({
|
|
6497
7132
|
name: "credentials",
|
|
6498
|
-
summary: "The
|
|
7133
|
+
summary: "The GitHub, GitLab and Bitbucket tokens of a project.",
|
|
6499
7134
|
usage: "credentials <command>",
|
|
6500
7135
|
subcommands: [
|
|
6501
7136
|
defineCommand({
|
|
6502
7137
|
name: "list",
|
|
6503
7138
|
summary: "List the credentials of a project.",
|
|
6504
7139
|
usage: "credentials list --project <name>",
|
|
6505
|
-
optionsUsage: projectUsage$
|
|
6506
|
-
options: projectOption$
|
|
7140
|
+
optionsUsage: projectUsage$3,
|
|
7141
|
+
options: projectOption$3,
|
|
6507
7142
|
positionals: {
|
|
6508
7143
|
min: 0,
|
|
6509
7144
|
max: 0
|
|
6510
7145
|
},
|
|
6511
7146
|
run: async (invocation) => {
|
|
6512
7147
|
const run = await runOf(adaptersOf, invocation);
|
|
6513
|
-
const projectId = await projectFrom$
|
|
7148
|
+
const projectId = await projectFrom$3(run, invocation.values.project);
|
|
6514
7149
|
const items = await credentialsOf(run.api, projectId);
|
|
6515
|
-
return printed(run, items, (credentials) => credentials.length === 0 ? "The project holds no credential yet." : renderTable([
|
|
7150
|
+
return printed(run, items, (credentials) => credentials.length === 0 ? "The project holds no credential yet." : [renderTable([
|
|
6516
7151
|
{ header: "LABEL" },
|
|
6517
7152
|
{ header: "KIND" },
|
|
7153
|
+
{ header: "SERVER" },
|
|
7154
|
+
{ header: "ACCOUNT" },
|
|
6518
7155
|
{ header: "FINGERPRINT" },
|
|
6519
7156
|
{ header: "VERIFIED" },
|
|
6520
7157
|
{ header: "EXPIRES" },
|
|
6521
|
-
{ header: "USAGE" },
|
|
6522
7158
|
{ header: "ID" }
|
|
6523
7159
|
], credentials.map((credential) => [
|
|
6524
7160
|
credential.label,
|
|
6525
7161
|
credential.kind,
|
|
7162
|
+
credentialServerOf(credential) ?? notApplicable,
|
|
7163
|
+
accountOf(credential),
|
|
6526
7164
|
credential.fingerprint,
|
|
6527
7165
|
credential.verification,
|
|
6528
7166
|
expiryOf(credential),
|
|
6529
|
-
usageSummaryOf(credential),
|
|
6530
7167
|
credential.id
|
|
6531
|
-
])));
|
|
7168
|
+
])), ...missingIdentityNotes(credentials, invocation.values.project).flatMap((note, index) => index === 0 ? ["", note] : [note])].join("\n"));
|
|
6532
7169
|
}
|
|
6533
7170
|
}),
|
|
6534
7171
|
defineCommand({
|
|
6535
7172
|
name: "add",
|
|
6536
7173
|
summary: "Store a credential in a project, asking for the secret.",
|
|
6537
|
-
usage: "credentials add --project <name> --kind <kind> --label <label> [--secret-file <path>]",
|
|
7174
|
+
usage: "credentials add --project <name> [--kind <kind>] --label <label> [--server-url <url>] [--secret-file <path>]",
|
|
6538
7175
|
optionsUsage: [
|
|
6539
|
-
...projectUsage$
|
|
6540
|
-
["--kind <kind>", `
|
|
7176
|
+
...projectUsage$3,
|
|
7177
|
+
["--kind <kind>", `the kind of the credential (default: ${defaultKind})`],
|
|
7178
|
+
["", "kcloud claude-accounts add stores a Claude account in the organization"],
|
|
6541
7179
|
["--label <label>", "how the panel and the agents name it"],
|
|
7180
|
+
...gitOptionsUsage,
|
|
6542
7181
|
...secretUsage
|
|
6543
7182
|
],
|
|
6544
7183
|
options: {
|
|
6545
|
-
...projectOption$
|
|
7184
|
+
...projectOption$3,
|
|
6546
7185
|
...secretOptions,
|
|
7186
|
+
...gitOptions,
|
|
6547
7187
|
kind: { type: "string" },
|
|
6548
7188
|
label: { type: "string" }
|
|
6549
7189
|
},
|
|
@@ -6554,11 +7194,13 @@ const createCredentialsCommand = (adaptersOf) => defineGroup({
|
|
|
6554
7194
|
run: async (invocation) => {
|
|
6555
7195
|
const { values } = invocation;
|
|
6556
7196
|
const run = await runOf(adaptersOf, invocation);
|
|
6557
|
-
const projectId = await projectFrom$
|
|
6558
|
-
const
|
|
6559
|
-
|
|
7197
|
+
const projectId = await projectFrom$3(run, values.project);
|
|
7198
|
+
const given = values.kind ?? defaultKind;
|
|
7199
|
+
const kind = credentialKinds.find((candidate) => candidate === given);
|
|
7200
|
+
if (kind === void 0) throw new UsageError(`--kind ${given} is not a kind of credential.`, `The kinds are ${credentialKinds.join(", ")}.`);
|
|
6560
7201
|
const label = values.label ?? "";
|
|
6561
7202
|
if (label.trim() === "") throw new UsageError("credentials add needs --label <label>.");
|
|
7203
|
+
const gitFields = gitFieldsOf(kind, values);
|
|
6562
7204
|
const secret = await secretOf({
|
|
6563
7205
|
run,
|
|
6564
7206
|
file: values["secret-file"],
|
|
@@ -6570,19 +7212,60 @@ const createCredentialsCommand = (adaptersOf) => defineGroup({
|
|
|
6570
7212
|
body: {
|
|
6571
7213
|
kind,
|
|
6572
7214
|
label,
|
|
6573
|
-
secret
|
|
7215
|
+
secret,
|
|
7216
|
+
...gitFields
|
|
6574
7217
|
}
|
|
6575
|
-
}),
|
|
7218
|
+
}), addHints);
|
|
6576
7219
|
return printed(run, credential, (stored) => `Stored ${stored.label} (${stored.fingerprint}) as ${stored.id}.`);
|
|
6577
7220
|
}
|
|
6578
7221
|
}),
|
|
7222
|
+
defineCommand({
|
|
7223
|
+
name: "set-identity",
|
|
7224
|
+
summary: "Set the name and e-mail the commits of a git token carry.",
|
|
7225
|
+
usage: "credentials set-identity <credential> --project <name> --commit-name <name> --commit-email <email>",
|
|
7226
|
+
optionsUsage: [
|
|
7227
|
+
...projectUsage$3,
|
|
7228
|
+
["--commit-name <name>", "the name its commits carry"],
|
|
7229
|
+
["--commit-email <email>", "the e-mail its commits carry"],
|
|
7230
|
+
["", "the agents that use the token need a restart to sign with it"]
|
|
7231
|
+
],
|
|
7232
|
+
options: {
|
|
7233
|
+
...projectOption$3,
|
|
7234
|
+
"commit-name": { type: "string" },
|
|
7235
|
+
"commit-email": { type: "string" }
|
|
7236
|
+
},
|
|
7237
|
+
positionals: {
|
|
7238
|
+
min: 1,
|
|
7239
|
+
max: 1
|
|
7240
|
+
},
|
|
7241
|
+
run: async (invocation) => {
|
|
7242
|
+
const { values } = invocation;
|
|
7243
|
+
const commitName = values["commit-name"] ?? "";
|
|
7244
|
+
const commitEmail = values["commit-email"] ?? "";
|
|
7245
|
+
if (commitName.trim() === "" || commitEmail.trim() === "") throw new UsageError("credentials set-identity needs --commit-name <name> and --commit-email <email>.");
|
|
7246
|
+
const run = await runOf(adaptersOf, invocation);
|
|
7247
|
+
const projectId = await projectFrom$3(run, values.project);
|
|
7248
|
+
const credentialId = await resolveCredentialId(run.api, projectId, invocation.positionals[0] ?? "");
|
|
7249
|
+
const credential = dataOf("set the commit identity of the credential", await run.api.PUT("/projects/{id}/credentials/{credentialId}/commit-identity", {
|
|
7250
|
+
params: { path: {
|
|
7251
|
+
id: projectId,
|
|
7252
|
+
credentialId
|
|
7253
|
+
} },
|
|
7254
|
+
body: {
|
|
7255
|
+
commitName,
|
|
7256
|
+
commitEmail
|
|
7257
|
+
}
|
|
7258
|
+
}), credentialHints);
|
|
7259
|
+
return printed(run, credential, (changed) => `${changed.label} signs commits as ${commitName} <${commitEmail}> now.`);
|
|
7260
|
+
}
|
|
7261
|
+
}),
|
|
6579
7262
|
defineCommand({
|
|
6580
7263
|
name: "rename",
|
|
6581
7264
|
summary: "Change the label of a credential.",
|
|
6582
7265
|
usage: "credentials rename <credential> --project <name> --label <label>",
|
|
6583
|
-
optionsUsage: [...projectUsage$
|
|
7266
|
+
optionsUsage: [...projectUsage$3, ["--label <label>", "the new label"]],
|
|
6584
7267
|
options: {
|
|
6585
|
-
...projectOption$
|
|
7268
|
+
...projectOption$3,
|
|
6586
7269
|
label: { type: "string" }
|
|
6587
7270
|
},
|
|
6588
7271
|
positionals: {
|
|
@@ -6592,7 +7275,7 @@ const createCredentialsCommand = (adaptersOf) => defineGroup({
|
|
|
6592
7275
|
run: async (invocation) => {
|
|
6593
7276
|
const { values } = invocation;
|
|
6594
7277
|
const run = await runOf(adaptersOf, invocation);
|
|
6595
|
-
const projectId = await projectFrom$
|
|
7278
|
+
const projectId = await projectFrom$3(run, values.project);
|
|
6596
7279
|
const credentialId = await resolveCredentialId(run.api, projectId, invocation.positionals[0] ?? "");
|
|
6597
7280
|
const label = values.label ?? "";
|
|
6598
7281
|
if (label.trim() === "") throw new UsageError("credentials rename needs --label <label>.");
|
|
@@ -6611,12 +7294,12 @@ const createCredentialsCommand = (adaptersOf) => defineGroup({
|
|
|
6611
7294
|
summary: "Replace the secret of a credential.",
|
|
6612
7295
|
usage: "credentials rotate <credential> --project <name> [--restart-agents] [--secret-file <path>]",
|
|
6613
7296
|
optionsUsage: [
|
|
6614
|
-
...projectUsage$
|
|
7297
|
+
...projectUsage$3,
|
|
6615
7298
|
["--restart-agents", "restart the agents that use it once the secret is in"],
|
|
6616
7299
|
...secretUsage
|
|
6617
7300
|
],
|
|
6618
7301
|
options: {
|
|
6619
|
-
...projectOption$
|
|
7302
|
+
...projectOption$3,
|
|
6620
7303
|
...secretOptions,
|
|
6621
7304
|
"restart-agents": {
|
|
6622
7305
|
type: "boolean",
|
|
@@ -6630,7 +7313,7 @@ const createCredentialsCommand = (adaptersOf) => defineGroup({
|
|
|
6630
7313
|
run: async (invocation) => {
|
|
6631
7314
|
const { values } = invocation;
|
|
6632
7315
|
const run = await runOf(adaptersOf, invocation);
|
|
6633
|
-
const projectId = await projectFrom$
|
|
7316
|
+
const projectId = await projectFrom$3(run, values.project);
|
|
6634
7317
|
const credentialId = await resolveCredentialId(run.api, projectId, invocation.positionals[0] ?? "");
|
|
6635
7318
|
const secret = await secretOf({
|
|
6636
7319
|
run,
|
|
@@ -6655,15 +7338,15 @@ const createCredentialsCommand = (adaptersOf) => defineGroup({
|
|
|
6655
7338
|
name: "verify",
|
|
6656
7339
|
summary: "Ask the instance to verify a credential against its service.",
|
|
6657
7340
|
usage: "credentials verify <credential> --project <name>",
|
|
6658
|
-
optionsUsage: projectUsage$
|
|
6659
|
-
options: projectOption$
|
|
7341
|
+
optionsUsage: projectUsage$3,
|
|
7342
|
+
options: projectOption$3,
|
|
6660
7343
|
positionals: {
|
|
6661
7344
|
min: 1,
|
|
6662
7345
|
max: 1
|
|
6663
7346
|
},
|
|
6664
7347
|
run: async (invocation) => {
|
|
6665
7348
|
const run = await runOf(adaptersOf, invocation);
|
|
6666
|
-
const projectId = await projectFrom$
|
|
7349
|
+
const projectId = await projectFrom$3(run, invocation.values.project);
|
|
6667
7350
|
const credentialId = await resolveCredentialId(run.api, projectId, invocation.positionals[0] ?? "");
|
|
6668
7351
|
doneWith("verify the credential", await run.api.POST("/projects/{id}/credentials/{credentialId}/verify", { params: { path: {
|
|
6669
7352
|
id: projectId,
|
|
@@ -6676,9 +7359,9 @@ const createCredentialsCommand = (adaptersOf) => defineGroup({
|
|
|
6676
7359
|
name: "usage",
|
|
6677
7360
|
summary: "Show the usage of the Claude subscription behind a Claude Code OAuth token.",
|
|
6678
7361
|
usage: "credentials usage <credential> --project <name> [--refresh]",
|
|
6679
|
-
optionsUsage: [...projectUsage$
|
|
7362
|
+
optionsUsage: [...projectUsage$3, ["--refresh", "read it from Anthropic now; within 30 seconds the last reading"]],
|
|
6680
7363
|
options: {
|
|
6681
|
-
...projectOption$
|
|
7364
|
+
...projectOption$3,
|
|
6682
7365
|
refresh: {
|
|
6683
7366
|
type: "boolean",
|
|
6684
7367
|
default: false
|
|
@@ -6690,9 +7373,9 @@ const createCredentialsCommand = (adaptersOf) => defineGroup({
|
|
|
6690
7373
|
},
|
|
6691
7374
|
run: async (invocation) => {
|
|
6692
7375
|
const run = await runOf(adaptersOf, invocation);
|
|
6693
|
-
const projectId = await projectFrom$
|
|
7376
|
+
const projectId = await projectFrom$3(run, invocation.values.project);
|
|
6694
7377
|
const stored = await resolveCredential(run.api, projectId, invocation.positionals[0] ?? "");
|
|
6695
|
-
if (stored.kind !== "claude_oauth_token") throw new CliError(`${stored.label} is a ${stored.kind}, which reports no usage.`, usageUnavailable);
|
|
7378
|
+
if (stored.kind !== "claude_oauth_token") throw new CliError(`${stored.label} is a ${stored.kind}, which reports no usage.`, usageUnavailable$1);
|
|
6696
7379
|
const credential = invocation.values.refresh ? dataOf("read the usage of the credential", await run.api.POST("/projects/{id}/credentials/{credentialId}/usage", { params: { path: {
|
|
6697
7380
|
id: projectId,
|
|
6698
7381
|
credentialId: stored.id
|
|
@@ -6704,15 +7387,15 @@ const createCredentialsCommand = (adaptersOf) => defineGroup({
|
|
|
6704
7387
|
name: "delete",
|
|
6705
7388
|
summary: "Delete a credential no agent uses.",
|
|
6706
7389
|
usage: "credentials delete <credential> --project <name>",
|
|
6707
|
-
optionsUsage: projectUsage$
|
|
6708
|
-
options: projectOption$
|
|
7390
|
+
optionsUsage: projectUsage$3,
|
|
7391
|
+
options: projectOption$3,
|
|
6709
7392
|
positionals: {
|
|
6710
7393
|
min: 1,
|
|
6711
7394
|
max: 1
|
|
6712
7395
|
},
|
|
6713
7396
|
run: async (invocation) => {
|
|
6714
7397
|
const run = await runOf(adaptersOf, invocation);
|
|
6715
|
-
const projectId = await projectFrom$
|
|
7398
|
+
const projectId = await projectFrom$3(run, invocation.values.project);
|
|
6716
7399
|
const reference = invocation.positionals[0] ?? "";
|
|
6717
7400
|
const credentialId = await resolveCredentialId(run.api, projectId, reference);
|
|
6718
7401
|
await confirmed({
|
|
@@ -6730,48 +7413,305 @@ const createCredentialsCommand = (adaptersOf) => defineGroup({
|
|
|
6730
7413
|
]
|
|
6731
7414
|
});
|
|
6732
7415
|
//#endregion
|
|
6733
|
-
//#region src/
|
|
6734
|
-
const
|
|
6735
|
-
|
|
6736
|
-
|
|
6737
|
-
|
|
6738
|
-
|
|
6739
|
-
|
|
6740
|
-
|
|
6741
|
-
|
|
6742
|
-
|
|
6743
|
-
|
|
6744
|
-
openWorldHint: false
|
|
6745
|
-
});
|
|
6746
|
-
const textOf = (text, isError) => ({
|
|
6747
|
-
content: [{
|
|
6748
|
-
type: "text",
|
|
6749
|
-
text
|
|
6750
|
-
}],
|
|
6751
|
-
...isError ? { isError } : {}
|
|
6752
|
-
});
|
|
6753
|
-
const apiErrorOf = (error) => {
|
|
6754
|
-
if (error instanceof ApiError) return error;
|
|
6755
|
-
return error instanceof Error ? apiErrorOf(error.cause) : null;
|
|
6756
|
-
};
|
|
6757
|
-
const refusalOf = (error) => {
|
|
6758
|
-
const details = apiErrorOf(error)?.details ?? {};
|
|
6759
|
-
return [
|
|
6760
|
-
error.message,
|
|
6761
|
-
...error.hint === null ? [] : [error.hint],
|
|
6762
|
-
...Object.keys(details).length === 0 ? [] : [`Details: ${JSON.stringify(details)}`]
|
|
6763
|
-
].join("\n");
|
|
7416
|
+
//#region src/commands/claude-accounts.ts
|
|
7417
|
+
const accountUsage$1 = [["", "an account is named by its label or its id"]];
|
|
7418
|
+
const usageUnavailable = "Only a Claude subscription reports its usage.";
|
|
7419
|
+
const queuesText = (projects) => typeof projects === "string" && projects !== "" ? projects : "some projects";
|
|
7420
|
+
const claudeAccountHints = {
|
|
7421
|
+
...organizationHints,
|
|
7422
|
+
"auth.forbidden": "Changing a Claude account takes an admin of the organization.",
|
|
7423
|
+
"account.not_found": "kcloud claude-accounts list shows the Claude accounts of the organization.",
|
|
7424
|
+
"account.label_taken": "Another Claude account of the organization already has that label.",
|
|
7425
|
+
"account.in_queue": ({ projects }) => `It stands in the queue of ${queuesText(projects)}; kcloud projects accounts remove <account> --project <project> takes it out.`,
|
|
7426
|
+
"credential.usage_unavailable": usageUnavailable
|
|
6764
7427
|
};
|
|
6765
|
-
const
|
|
6766
|
-
|
|
6767
|
-
|
|
6768
|
-
|
|
6769
|
-
|
|
6770
|
-
|
|
6771
|
-
|
|
6772
|
-
}
|
|
7428
|
+
const queuesOf = (queues) => queues.length === 0 ? "-" : queues.map(({ projectName, position }) => `${projectName} #${String(position)}`).join(", ");
|
|
7429
|
+
const statusOf = ({ verification, creditsExhaustedAt }) => creditsExhaustedAt === null ? verification : "no credits";
|
|
7430
|
+
const claudeAccountsOf = async (api, organizationId) => dataOf("list the Claude accounts of the organization", await api.GET("/organizations/{organizationId}/claude-accounts", { params: { path: { organizationId } } }), claudeAccountHints).items;
|
|
7431
|
+
const claudeAccountIn = (accounts, reference) => {
|
|
7432
|
+
const found = accounts.filter((account) => account.id === reference.toLowerCase() || account.label === reference);
|
|
7433
|
+
const [first] = found;
|
|
7434
|
+
if (first === void 0) throw new CliError(`The organization has no Claude account ${reference}.`, accounts.length === 0 ? "kcloud claude-accounts add stores one." : `Its Claude accounts are ${accounts.map(({ label }) => label).join(", ")}.`);
|
|
7435
|
+
if (found.length > 1) throw new CliError(`${reference} names ${found.length} Claude accounts.`, "Name it by its id instead.");
|
|
7436
|
+
return first;
|
|
6773
7437
|
};
|
|
6774
|
-
|
|
7438
|
+
const resolveClaudeAccount = async (scope, reference) => {
|
|
7439
|
+
const { id } = await scope.organization();
|
|
7440
|
+
return {
|
|
7441
|
+
organizationId: id,
|
|
7442
|
+
account: claudeAccountIn(await claudeAccountsOf(scope.api, id), reference)
|
|
7443
|
+
};
|
|
7444
|
+
};
|
|
7445
|
+
const renamedClaudeAccount = async (api, organizationId, credentialId, label) => dataOf("rename the Claude account", await api.PATCH("/organizations/{organizationId}/claude-accounts/{credentialId}", {
|
|
7446
|
+
params: { path: {
|
|
7447
|
+
organizationId,
|
|
7448
|
+
credentialId
|
|
7449
|
+
} },
|
|
7450
|
+
body: { label }
|
|
7451
|
+
}), claudeAccountHints);
|
|
7452
|
+
const verifyClaudeAccount = async (api, organizationId, credentialId) => {
|
|
7453
|
+
doneWith("verify the Claude account", await api.POST("/organizations/{organizationId}/claude-accounts/{credentialId}/verify", { params: { path: {
|
|
7454
|
+
organizationId,
|
|
7455
|
+
credentialId
|
|
7456
|
+
} } }), claudeAccountHints);
|
|
7457
|
+
};
|
|
7458
|
+
const refreshedClaudeAccount = async (api, organizationId, credentialId) => dataOf("read the usage of the Claude account", await api.POST("/organizations/{organizationId}/claude-accounts/{credentialId}/usage", { params: { path: {
|
|
7459
|
+
organizationId,
|
|
7460
|
+
credentialId
|
|
7461
|
+
} } }), claudeAccountHints);
|
|
7462
|
+
const deleteClaudeAccount = async (api, organizationId, credentialId) => {
|
|
7463
|
+
doneWith("delete the Claude account", await api.DELETE("/organizations/{organizationId}/claude-accounts/{credentialId}", { params: { path: {
|
|
7464
|
+
organizationId,
|
|
7465
|
+
credentialId
|
|
7466
|
+
} } }), claudeAccountHints);
|
|
7467
|
+
};
|
|
7468
|
+
const createClaudeAccountsCommand = (adaptersOf) => defineGroup({
|
|
7469
|
+
name: "claude-accounts",
|
|
7470
|
+
summary: "The Claude subscriptions and Anthropic API keys of the organization.",
|
|
7471
|
+
usage: "claude-accounts <command>",
|
|
7472
|
+
subcommands: [
|
|
7473
|
+
defineCommand({
|
|
7474
|
+
name: "list",
|
|
7475
|
+
summary: "List the Claude accounts of the organization and the queues they are in.",
|
|
7476
|
+
usage: "claude-accounts list",
|
|
7477
|
+
options: {},
|
|
7478
|
+
positionals: {
|
|
7479
|
+
min: 0,
|
|
7480
|
+
max: 0
|
|
7481
|
+
},
|
|
7482
|
+
run: async (invocation) => {
|
|
7483
|
+
const run = await runOf(adaptersOf, invocation);
|
|
7484
|
+
const organization = await run.organization();
|
|
7485
|
+
const items = await claudeAccountsOf(run.api, organization.id);
|
|
7486
|
+
return printed(run, items, (accounts) => accounts.length === 0 ? `${organization.slug} has no Claude account yet; kcloud claude-accounts add stores one.` : renderTable([
|
|
7487
|
+
{ header: "KIND" },
|
|
7488
|
+
{ header: "LABEL" },
|
|
7489
|
+
{ header: "STATUS" },
|
|
7490
|
+
{ header: "USAGE" },
|
|
7491
|
+
{ header: "QUEUES" },
|
|
7492
|
+
{ header: "ID" }
|
|
7493
|
+
], accounts.map((account) => [
|
|
7494
|
+
account.kind,
|
|
7495
|
+
account.label,
|
|
7496
|
+
statusOf(account),
|
|
7497
|
+
usageSummaryOf(account),
|
|
7498
|
+
queuesOf(account.queues),
|
|
7499
|
+
account.id
|
|
7500
|
+
])));
|
|
7501
|
+
}
|
|
7502
|
+
}),
|
|
7503
|
+
defineCommand({
|
|
7504
|
+
name: "add",
|
|
7505
|
+
summary: "Store a Claude subscription or an Anthropic API key, asking for it.",
|
|
7506
|
+
usage: "claude-accounts add --kind <kind> --label <label> [--secret-file <path>]",
|
|
7507
|
+
optionsUsage: [
|
|
7508
|
+
["--kind <kind>", "claude_oauth_token for a subscription, anthropic_api_key for an API key"],
|
|
7509
|
+
["--label <label>", "how the panel and the queues name it"],
|
|
7510
|
+
...secretUsage,
|
|
7511
|
+
["", "kcloud projects accounts add puts it in the queue of a project"]
|
|
7512
|
+
],
|
|
7513
|
+
options: {
|
|
7514
|
+
...secretOptions,
|
|
7515
|
+
kind: { type: "string" },
|
|
7516
|
+
label: { type: "string" }
|
|
7517
|
+
},
|
|
7518
|
+
positionals: {
|
|
7519
|
+
min: 0,
|
|
7520
|
+
max: 0
|
|
7521
|
+
},
|
|
7522
|
+
run: async (invocation) => {
|
|
7523
|
+
const { values } = invocation;
|
|
7524
|
+
const kind = claudeCredentialKinds.find((candidate) => candidate === values.kind);
|
|
7525
|
+
if (kind === void 0) throw new UsageError(`--kind ${values.kind ?? ""} is not a kind of Claude account.`, `The kinds are ${claudeCredentialKinds.join(", ")}.`);
|
|
7526
|
+
const label = values.label ?? "";
|
|
7527
|
+
if (label.trim() === "") throw new UsageError("claude-accounts add needs --label <label>.");
|
|
7528
|
+
const run = await runOf(adaptersOf, invocation);
|
|
7529
|
+
const organization = await run.organization();
|
|
7530
|
+
const secret = await secretOf({
|
|
7531
|
+
run,
|
|
7532
|
+
file: values["secret-file"],
|
|
7533
|
+
fromStdin: values["secret-stdin"],
|
|
7534
|
+
question: `The secret of ${label}`
|
|
7535
|
+
});
|
|
7536
|
+
const account = dataOf("store the Claude account", await run.api.POST("/organizations/{organizationId}/claude-accounts", {
|
|
7537
|
+
params: { path: { organizationId: organization.id } },
|
|
7538
|
+
body: {
|
|
7539
|
+
kind,
|
|
7540
|
+
label,
|
|
7541
|
+
secret
|
|
7542
|
+
}
|
|
7543
|
+
}), claudeAccountHints);
|
|
7544
|
+
return printed(run, account, (stored) => `Stored ${stored.label} (${stored.fingerprint}) as ${stored.id}; the instance is verifying it.`);
|
|
7545
|
+
}
|
|
7546
|
+
}),
|
|
7547
|
+
defineCommand({
|
|
7548
|
+
name: "rename",
|
|
7549
|
+
summary: "Change the label of a Claude account.",
|
|
7550
|
+
usage: "claude-accounts rename <account> --label <label>",
|
|
7551
|
+
optionsUsage: [...accountUsage$1, ["--label <label>", "the new label"]],
|
|
7552
|
+
options: { label: { type: "string" } },
|
|
7553
|
+
positionals: {
|
|
7554
|
+
min: 1,
|
|
7555
|
+
max: 1
|
|
7556
|
+
},
|
|
7557
|
+
run: async (invocation) => {
|
|
7558
|
+
const label = invocation.values.label ?? "";
|
|
7559
|
+
if (label.trim() === "") throw new UsageError("claude-accounts rename needs --label <label>.");
|
|
7560
|
+
const run = await runOf(adaptersOf, invocation);
|
|
7561
|
+
const { organizationId, account } = await resolveClaudeAccount(run, invocation.positionals[0] ?? "");
|
|
7562
|
+
const renamed = await renamedClaudeAccount(run.api, organizationId, account.id, label);
|
|
7563
|
+
return printed(run, renamed, (shown) => `The Claude account is ${shown.label} now.`);
|
|
7564
|
+
}
|
|
7565
|
+
}),
|
|
7566
|
+
defineCommand({
|
|
7567
|
+
name: "rotate",
|
|
7568
|
+
summary: "Replace the secret of a Claude account.",
|
|
7569
|
+
usage: "claude-accounts rotate <account> [--restart-agents] [--secret-file <path>]",
|
|
7570
|
+
optionsUsage: [
|
|
7571
|
+
...accountUsage$1,
|
|
7572
|
+
["--restart-agents", "restart the agents that work on it once the secret is in"],
|
|
7573
|
+
...secretUsage
|
|
7574
|
+
],
|
|
7575
|
+
options: {
|
|
7576
|
+
...secretOptions,
|
|
7577
|
+
"restart-agents": {
|
|
7578
|
+
type: "boolean",
|
|
7579
|
+
default: false
|
|
7580
|
+
}
|
|
7581
|
+
},
|
|
7582
|
+
positionals: {
|
|
7583
|
+
min: 1,
|
|
7584
|
+
max: 1
|
|
7585
|
+
},
|
|
7586
|
+
run: async (invocation) => {
|
|
7587
|
+
const { values } = invocation;
|
|
7588
|
+
const run = await runOf(adaptersOf, invocation);
|
|
7589
|
+
const { organizationId, account } = await resolveClaudeAccount(run, invocation.positionals[0] ?? "");
|
|
7590
|
+
const secret = await secretOf({
|
|
7591
|
+
run,
|
|
7592
|
+
file: values["secret-file"],
|
|
7593
|
+
fromStdin: values["secret-stdin"],
|
|
7594
|
+
question: `The new secret of ${account.label}`
|
|
7595
|
+
});
|
|
7596
|
+
const rotated = dataOf("rotate the Claude account", await run.api.POST("/organizations/{organizationId}/claude-accounts/{credentialId}/rotate", {
|
|
7597
|
+
params: { path: {
|
|
7598
|
+
organizationId,
|
|
7599
|
+
credentialId: account.id
|
|
7600
|
+
} },
|
|
7601
|
+
body: {
|
|
7602
|
+
secret,
|
|
7603
|
+
restartAgents: values["restart-agents"]
|
|
7604
|
+
}
|
|
7605
|
+
}), claudeAccountHints);
|
|
7606
|
+
return printed(run, rotated, (shown) => `Rotated ${shown.label} to ${shown.fingerprint}${values["restart-agents"] ? "; its agents restart" : ""}.`);
|
|
7607
|
+
}
|
|
7608
|
+
}),
|
|
7609
|
+
defineCommand({
|
|
7610
|
+
name: "verify",
|
|
7611
|
+
summary: "Ask the instance to verify a Claude account against Anthropic.",
|
|
7612
|
+
usage: "claude-accounts verify <account>",
|
|
7613
|
+
optionsUsage: accountUsage$1,
|
|
7614
|
+
options: {},
|
|
7615
|
+
positionals: {
|
|
7616
|
+
min: 1,
|
|
7617
|
+
max: 1
|
|
7618
|
+
},
|
|
7619
|
+
run: async (invocation) => {
|
|
7620
|
+
const run = await runOf(adaptersOf, invocation);
|
|
7621
|
+
const { organizationId, account } = await resolveClaudeAccount(run, invocation.positionals[0] ?? "");
|
|
7622
|
+
await verifyClaudeAccount(run.api, organizationId, account.id);
|
|
7623
|
+
return said(run, `The instance is verifying ${account.label}; kcloud claude-accounts list shows the answer.`);
|
|
7624
|
+
}
|
|
7625
|
+
}),
|
|
7626
|
+
defineCommand({
|
|
7627
|
+
name: "usage",
|
|
7628
|
+
summary: "Show the usage of a Claude subscription.",
|
|
7629
|
+
usage: "claude-accounts usage <account> [--refresh]",
|
|
7630
|
+
optionsUsage: [...accountUsage$1, ["--refresh", "read it from Anthropic now; within 30 seconds the last reading"]],
|
|
7631
|
+
options: { refresh: {
|
|
7632
|
+
type: "boolean",
|
|
7633
|
+
default: false
|
|
7634
|
+
} },
|
|
7635
|
+
positionals: {
|
|
7636
|
+
min: 1,
|
|
7637
|
+
max: 1
|
|
7638
|
+
},
|
|
7639
|
+
run: async (invocation) => {
|
|
7640
|
+
const run = await runOf(adaptersOf, invocation);
|
|
7641
|
+
const { organizationId, account } = await resolveClaudeAccount(run, invocation.positionals[0] ?? "");
|
|
7642
|
+
if (account.kind !== "claude_oauth_token") throw new CliError(`${account.label} is an ${account.kind}, which reports no usage.`, usageUnavailable);
|
|
7643
|
+
const read = invocation.values.refresh ? await refreshedClaudeAccount(run.api, organizationId, account.id) : account;
|
|
7644
|
+
return printed(run, read, usageReportOf);
|
|
7645
|
+
}
|
|
7646
|
+
}),
|
|
7647
|
+
defineCommand({
|
|
7648
|
+
name: "delete",
|
|
7649
|
+
summary: "Delete a Claude account that stands in no queue.",
|
|
7650
|
+
usage: "claude-accounts delete <account>",
|
|
7651
|
+
optionsUsage: accountUsage$1,
|
|
7652
|
+
options: {},
|
|
7653
|
+
positionals: {
|
|
7654
|
+
min: 1,
|
|
7655
|
+
max: 1
|
|
7656
|
+
},
|
|
7657
|
+
run: async (invocation) => {
|
|
7658
|
+
const run = await runOf(adaptersOf, invocation);
|
|
7659
|
+
const reference = invocation.positionals[0] ?? "";
|
|
7660
|
+
const { organizationId, account } = await resolveClaudeAccount(run, reference);
|
|
7661
|
+
await confirmed({
|
|
7662
|
+
prompt: run.prompt,
|
|
7663
|
+
settings: run.settings,
|
|
7664
|
+
question: `Delete the Claude account ${account.label}? The platform erases its secret.`
|
|
7665
|
+
});
|
|
7666
|
+
await deleteClaudeAccount(run.api, organizationId, account.id);
|
|
7667
|
+
return said(run, `Deleted the Claude account ${account.label}.`);
|
|
7668
|
+
}
|
|
7669
|
+
})
|
|
7670
|
+
]
|
|
7671
|
+
});
|
|
7672
|
+
//#endregion
|
|
7673
|
+
//#region src/mcp/tool.ts
|
|
7674
|
+
const defineTool = (definition) => ({
|
|
7675
|
+
...definition,
|
|
7676
|
+
idempotent: definition.idempotent ?? definition.safety === "read",
|
|
7677
|
+
run: (input, run) => definition.run(definition.input.parse(input), run)
|
|
7678
|
+
});
|
|
7679
|
+
const annotationsOf = (tool) => ({
|
|
7680
|
+
title: tool.title,
|
|
7681
|
+
readOnlyHint: tool.safety === "read",
|
|
7682
|
+
destructiveHint: tool.safety === "destructive",
|
|
7683
|
+
idempotentHint: tool.idempotent,
|
|
7684
|
+
openWorldHint: false
|
|
7685
|
+
});
|
|
7686
|
+
const textOf = (text, isError) => ({
|
|
7687
|
+
content: [{
|
|
7688
|
+
type: "text",
|
|
7689
|
+
text
|
|
7690
|
+
}],
|
|
7691
|
+
...isError ? { isError } : {}
|
|
7692
|
+
});
|
|
7693
|
+
const apiErrorOf = (error) => {
|
|
7694
|
+
if (error instanceof ApiError) return error;
|
|
7695
|
+
return error instanceof Error ? apiErrorOf(error.cause) : null;
|
|
7696
|
+
};
|
|
7697
|
+
const refusalOf = (error) => {
|
|
7698
|
+
const details = apiErrorOf(error)?.details ?? {};
|
|
7699
|
+
return [
|
|
7700
|
+
error.message,
|
|
7701
|
+
...error.hint === null ? [] : [error.hint],
|
|
7702
|
+
...Object.keys(details).length === 0 ? [] : [`Details: ${JSON.stringify(details)}`]
|
|
7703
|
+
].join("\n");
|
|
7704
|
+
};
|
|
7705
|
+
const resultOf = async (answer) => {
|
|
7706
|
+
try {
|
|
7707
|
+
return textOf(JSON.stringify(await answer()), false);
|
|
7708
|
+
} catch (error) {
|
|
7709
|
+
if (error instanceof z.ZodError) return textOf(z.prettifyError(error), true);
|
|
7710
|
+
if (error instanceof CliError) return textOf(refusalOf(error), true);
|
|
7711
|
+
throw error;
|
|
7712
|
+
}
|
|
7713
|
+
};
|
|
7714
|
+
//#endregion
|
|
6775
7715
|
//#region src/commands/tokens.ts
|
|
6776
7716
|
const lifetimeOf = (given) => {
|
|
6777
7717
|
if (given === void 0) return null;
|
|
@@ -6990,7 +7930,7 @@ const agentTools = [
|
|
|
6990
7930
|
defineTool({
|
|
6991
7931
|
name: "agent_create",
|
|
6992
7932
|
title: "Create an agent",
|
|
6993
|
-
description: "Create an agent in a project. The node it runs on, the credentials, repositories and tools are named by the ids that nodes_list, credentials_list, repositories_list and tools_list return; agent_show with asPlan shows a complete example. requiredCapabilities names what the node has to offer, from the offered list of nodes_list; the instance refuses a node that lacks one. The agent stays stopped until agent_action starts it.",
|
|
7933
|
+
description: "Create an agent in a project. The node it runs on, the git provider credentials, repositories and tools are named by the ids that nodes_list, credentials_list, repositories_list and tools_list return; gitCredentialIds takes one git provider credential for each server of its repositories; agent_show with asPlan shows a complete example. The agent works on the queue of Claude accounts of its project, which project_accounts_list shows. requiredCapabilities names what the node has to offer, from the offered list of nodes_list; the instance refuses a node that lacks one. kikimoraVersion takes a release of releases_list, or \"latest\" to follow the newest stable release, which the agent then moves to between sessions; the instance refuses \"latest\" with release.none_stable while no release is stable. The agent stays stopped until agent_action starts it.",
|
|
6994
7934
|
operations: ["createAgent"],
|
|
6995
7935
|
safety: "write",
|
|
6996
7936
|
input: z.object({
|
|
@@ -7005,7 +7945,7 @@ const agentTools = [
|
|
|
7005
7945
|
defineTool({
|
|
7006
7946
|
name: "agent_update",
|
|
7007
7947
|
title: "Change an agent",
|
|
7008
|
-
description: "Change sections of an agent. Without version the change applies to the version read just before it. When restartRequired comes back true, agent_action start or update applies the change, whichever availableActions offers.",
|
|
7948
|
+
description: "Change sections of an agent; the credentials section names its git provider credentials as gitCredentialIds, and takes the repositories section in the same change. sections.version names the Kikimora release, one of releases_list, or \"latest\" to follow the newest stable release. Without version the change applies to the version read just before it. When restartRequired comes back true with a reason other than latest_release, agent_action start or update applies the change, whichever availableActions offers. latest_release alone needs no action: the node moves the agent onto the pending release after its current session, and a restart would replace the container in the middle of one.",
|
|
7009
7949
|
operations: ["updateAgent"],
|
|
7010
7950
|
safety: "write",
|
|
7011
7951
|
input: z.object({
|
|
@@ -7695,13 +8635,311 @@ const agentBackupTools = [
|
|
|
7695
8635
|
})
|
|
7696
8636
|
];
|
|
7697
8637
|
//#endregion
|
|
8638
|
+
//#region src/commands/projects/accounts.ts
|
|
8639
|
+
const projectAccountsHints = {
|
|
8640
|
+
...projectHint,
|
|
8641
|
+
"auth.forbidden": "Changing the queue of a project takes an admin of the project.",
|
|
8642
|
+
"account.not_found": "kcloud claude-accounts list shows the Claude accounts of the organization.",
|
|
8643
|
+
"account.kind_mismatch": "A queue holds Claude subscriptions and Anthropic API keys alone.",
|
|
8644
|
+
"concurrency.conflict": "Someone changed the queue meanwhile; kcloud projects accounts list shows it now."
|
|
8645
|
+
};
|
|
8646
|
+
const projectOption$2 = { project: { type: "string" } };
|
|
8647
|
+
const projectUsage$2 = [["--project <name>", "the project whose queue it is, by slug, name or id"], ["", "an account is named by its label or its id"]];
|
|
8648
|
+
const pad = (value) => String(value).padStart(2, "0");
|
|
8649
|
+
const dayOf$1 = (moment) => `${String(moment.getFullYear())}-${pad(moment.getMonth() + 1)}-${pad(moment.getDate())}`;
|
|
8650
|
+
const clockOf = (at, now) => {
|
|
8651
|
+
const moment = new Date(at);
|
|
8652
|
+
const time = `${pad(moment.getHours())}:${pad(moment.getMinutes())}`;
|
|
8653
|
+
return dayOf$1(moment) === dayOf$1(now) ? time : `${dayOf$1(moment)} ${time}`;
|
|
8654
|
+
};
|
|
8655
|
+
const stateOf$1 = ({ state, until }, now) => {
|
|
8656
|
+
switch (state) {
|
|
8657
|
+
case "inUse": return "in use";
|
|
8658
|
+
case "exhausted": return until === null ? "exhausted" : `exhausted until ${clockOf(until, now)}`;
|
|
8659
|
+
case "noCredits": return "no credits";
|
|
8660
|
+
default: return state;
|
|
8661
|
+
}
|
|
8662
|
+
};
|
|
8663
|
+
const waitTextOf = ({ waitReason, nearestResetAt }, now) => {
|
|
8664
|
+
switch (waitReason) {
|
|
8665
|
+
case "exhausted": return nearestResetAt === null ? "Every account of the queue is exhausted; the agents wait for a reset." : `Every account of the queue is exhausted; the agents wait until ${clockOf(nearestResetAt, now)}.`;
|
|
8666
|
+
case "blocked": return "No account of the queue can work: top up the credits of a key, or fix or replace an invalid account.";
|
|
8667
|
+
case "empty": return "The queue is empty, so the agents of the project cannot start; kcloud projects accounts add <account> --project <name> adds one.";
|
|
8668
|
+
default: return null;
|
|
8669
|
+
}
|
|
8670
|
+
};
|
|
8671
|
+
const queueText = (queue, now) => {
|
|
8672
|
+
const wait = waitTextOf(queue, now);
|
|
8673
|
+
if (queue.entries.length === 0) return wait ?? "The queue is empty.";
|
|
8674
|
+
const table = renderTable([
|
|
8675
|
+
{
|
|
8676
|
+
header: "#",
|
|
8677
|
+
align: "right"
|
|
8678
|
+
},
|
|
8679
|
+
{ header: "LABEL" },
|
|
8680
|
+
{ header: "KIND" },
|
|
8681
|
+
{ header: "STATE" },
|
|
8682
|
+
{ header: "USAGE" },
|
|
8683
|
+
{
|
|
8684
|
+
header: "AGENTS",
|
|
8685
|
+
align: "right"
|
|
8686
|
+
},
|
|
8687
|
+
{ header: "ID" }
|
|
8688
|
+
], queue.entries.map((entry) => [
|
|
8689
|
+
String(entry.position),
|
|
8690
|
+
entry.credential.label,
|
|
8691
|
+
entry.credential.kind,
|
|
8692
|
+
stateOf$1(entry, now),
|
|
8693
|
+
usageSummaryOf(entry.credential),
|
|
8694
|
+
String(entry.agents),
|
|
8695
|
+
entry.credential.id
|
|
8696
|
+
]));
|
|
8697
|
+
return wait === null ? table : `${table}\n\n${wait}`;
|
|
8698
|
+
};
|
|
8699
|
+
const projectClaudeAccountsOf = async (api, projectId) => dataOf("list the queue of Claude accounts of the project", await api.GET("/projects/{id}/claude-accounts", { params: { path: { id: projectId } } }), projectAccountsHints);
|
|
8700
|
+
const queueSet = async (api, projectId, credentialIds, version) => dataOf("set the queue of Claude accounts of the project", await api.PUT("/projects/{id}/claude-accounts", {
|
|
8701
|
+
params: { path: { id: projectId } },
|
|
8702
|
+
body: {
|
|
8703
|
+
credentialIds: [...credentialIds],
|
|
8704
|
+
version
|
|
8705
|
+
}
|
|
8706
|
+
}), projectAccountsHints);
|
|
8707
|
+
const organizationAccountIds = async (api, projectId, references) => {
|
|
8708
|
+
if (references.length === 0) return [];
|
|
8709
|
+
const { organizationId } = await projectOf(api, projectId);
|
|
8710
|
+
const accounts = await claudeAccountsOf(api, organizationId);
|
|
8711
|
+
const ids = references.map((reference) => claudeAccountIn(accounts, reference).id);
|
|
8712
|
+
const twice = references.filter((_, index) => ids.indexOf(ids[index] ?? "") !== index);
|
|
8713
|
+
if (twice.length > 0) throw new UsageError(`${twice.join(", ")} ${twice.length === 1 ? "names an account" : "name accounts"} named before.`, "A queue holds each account once.");
|
|
8714
|
+
return ids;
|
|
8715
|
+
};
|
|
8716
|
+
const queuedIndexOf = (queue, reference) => {
|
|
8717
|
+
const found = queue.entries.findIndex(({ credential }) => credential.id === reference.toLowerCase() || credential.label === reference);
|
|
8718
|
+
if (found === -1) throw new CliError(`${reference} stands in no place of the queue.`, queue.entries.length === 0 ? "The queue is empty." : `The queue holds ${queue.entries.map(({ credential }) => credential.label).join(", ")}.`);
|
|
8719
|
+
return found;
|
|
8720
|
+
};
|
|
8721
|
+
const idsOf = (queue) => queue.entries.map(({ credential }) => credential.id);
|
|
8722
|
+
const queueWith = async (api, projectId, reference) => {
|
|
8723
|
+
const queue = await projectClaudeAccountsOf(api, projectId);
|
|
8724
|
+
const [id = ""] = await organizationAccountIds(api, projectId, [reference]);
|
|
8725
|
+
const place = queue.entries.find(({ credential }) => credential.id === id);
|
|
8726
|
+
if (place !== void 0) throw new CliError(`${place.credential.label} already stands at place ${String(place.position)} of the queue.`, "kcloud projects accounts move <account> --to <n> --project <name> moves it.");
|
|
8727
|
+
return queueSet(api, projectId, [...idsOf(queue), id], queue.version);
|
|
8728
|
+
};
|
|
8729
|
+
const queueWithout = async (api, projectId, reference) => {
|
|
8730
|
+
const queue = await projectClaudeAccountsOf(api, projectId);
|
|
8731
|
+
const index = queuedIndexOf(queue, reference);
|
|
8732
|
+
const ids = idsOf(queue);
|
|
8733
|
+
ids.splice(index, 1);
|
|
8734
|
+
return queueSet(api, projectId, ids, queue.version);
|
|
8735
|
+
};
|
|
8736
|
+
const queueMoved = async (api, projectId, reference, place) => {
|
|
8737
|
+
const queue = await projectClaudeAccountsOf(api, projectId);
|
|
8738
|
+
if (place > queue.entries.length) throw new UsageError(`--to ${String(place)} lies beyond the queue, which holds ${String(queue.entries.length)} account${queue.entries.length === 1 ? "" : "s"}.`);
|
|
8739
|
+
const ids = idsOf(queue);
|
|
8740
|
+
const [moved = ""] = ids.splice(queuedIndexOf(queue, reference), 1);
|
|
8741
|
+
ids.splice(place - 1, 0, moved);
|
|
8742
|
+
return queueSet(api, projectId, ids, queue.version);
|
|
8743
|
+
};
|
|
8744
|
+
const placeOf = (given) => {
|
|
8745
|
+
if (given === void 0) throw new UsageError("projects accounts move needs --to <n>.");
|
|
8746
|
+
if (!/^[0-9]+$/u.test(given) || Number(given) < 1) throw new UsageError(`--to ${given} is not a place in the queue, counted from 1.`);
|
|
8747
|
+
return Number(given);
|
|
8748
|
+
};
|
|
8749
|
+
const projectFrom$2 = async (scope, given) => {
|
|
8750
|
+
if (given === void 0) throw new UsageError("This command needs a project.", "Pass --project <name>; kcloud projects list shows them.");
|
|
8751
|
+
return resolveProjectId(scope, given);
|
|
8752
|
+
};
|
|
8753
|
+
const printedQueue = (run, queue) => printed(run, queue, (shown) => queueText(shown, run.adapters.now()));
|
|
8754
|
+
const projectAccountsCommand = (adaptersOf) => defineGroup({
|
|
8755
|
+
name: "accounts",
|
|
8756
|
+
summary: "The queue of Claude accounts the agents of a project work on.",
|
|
8757
|
+
usage: "projects accounts <command>",
|
|
8758
|
+
subcommands: [
|
|
8759
|
+
defineCommand({
|
|
8760
|
+
name: "list",
|
|
8761
|
+
summary: "List the queue of Claude accounts of a project with their state.",
|
|
8762
|
+
usage: "projects accounts list --project <name>",
|
|
8763
|
+
optionsUsage: projectUsage$2,
|
|
8764
|
+
options: projectOption$2,
|
|
8765
|
+
positionals: {
|
|
8766
|
+
min: 0,
|
|
8767
|
+
max: 0
|
|
8768
|
+
},
|
|
8769
|
+
run: async (invocation) => {
|
|
8770
|
+
const run = await runOf(adaptersOf, invocation);
|
|
8771
|
+
const projectId = await projectFrom$2(run, invocation.values.project);
|
|
8772
|
+
return printedQueue(run, await projectClaudeAccountsOf(run.api, projectId));
|
|
8773
|
+
}
|
|
8774
|
+
}),
|
|
8775
|
+
defineCommand({
|
|
8776
|
+
name: "set",
|
|
8777
|
+
summary: "Set the whole queue of Claude accounts of a project, in order.",
|
|
8778
|
+
usage: "projects accounts set [<account>...] --project <name>",
|
|
8779
|
+
optionsUsage: [...projectUsage$2, ["", "the first account named comes first; none empties the queue"]],
|
|
8780
|
+
options: projectOption$2,
|
|
8781
|
+
positionals: {
|
|
8782
|
+
min: 0,
|
|
8783
|
+
max: 50
|
|
8784
|
+
},
|
|
8785
|
+
run: async (invocation) => {
|
|
8786
|
+
const run = await runOf(adaptersOf, invocation);
|
|
8787
|
+
const projectId = await projectFrom$2(run, invocation.values.project);
|
|
8788
|
+
const ids = await organizationAccountIds(run.api, projectId, invocation.positionals);
|
|
8789
|
+
const { version } = await projectClaudeAccountsOf(run.api, projectId);
|
|
8790
|
+
return printedQueue(run, await queueSet(run.api, projectId, ids, version));
|
|
8791
|
+
}
|
|
8792
|
+
}),
|
|
8793
|
+
defineCommand({
|
|
8794
|
+
name: "add",
|
|
8795
|
+
summary: "Put a Claude account of the organization at the end of the queue.",
|
|
8796
|
+
usage: "projects accounts add <account> --project <name>",
|
|
8797
|
+
optionsUsage: projectUsage$2,
|
|
8798
|
+
options: projectOption$2,
|
|
8799
|
+
positionals: {
|
|
8800
|
+
min: 1,
|
|
8801
|
+
max: 1
|
|
8802
|
+
},
|
|
8803
|
+
run: async (invocation) => {
|
|
8804
|
+
const run = await runOf(adaptersOf, invocation);
|
|
8805
|
+
const projectId = await projectFrom$2(run, invocation.values.project);
|
|
8806
|
+
return printedQueue(run, await queueWith(run.api, projectId, invocation.positionals[0] ?? ""));
|
|
8807
|
+
}
|
|
8808
|
+
}),
|
|
8809
|
+
defineCommand({
|
|
8810
|
+
name: "remove",
|
|
8811
|
+
summary: "Take a Claude account out of the queue; it stays in the organization.",
|
|
8812
|
+
usage: "projects accounts remove <account> --project <name>",
|
|
8813
|
+
optionsUsage: projectUsage$2,
|
|
8814
|
+
options: projectOption$2,
|
|
8815
|
+
positionals: {
|
|
8816
|
+
min: 1,
|
|
8817
|
+
max: 1
|
|
8818
|
+
},
|
|
8819
|
+
run: async (invocation) => {
|
|
8820
|
+
const run = await runOf(adaptersOf, invocation);
|
|
8821
|
+
const projectId = await projectFrom$2(run, invocation.values.project);
|
|
8822
|
+
return printedQueue(run, await queueWithout(run.api, projectId, invocation.positionals[0] ?? ""));
|
|
8823
|
+
}
|
|
8824
|
+
}),
|
|
8825
|
+
defineCommand({
|
|
8826
|
+
name: "move",
|
|
8827
|
+
summary: "Move a Claude account to another place of the queue.",
|
|
8828
|
+
usage: "projects accounts move <account> --to <n> --project <name>",
|
|
8829
|
+
optionsUsage: [...projectUsage$2, ["--to <n>", "the new place, counted from 1"]],
|
|
8830
|
+
options: {
|
|
8831
|
+
...projectOption$2,
|
|
8832
|
+
to: { type: "string" }
|
|
8833
|
+
},
|
|
8834
|
+
positionals: {
|
|
8835
|
+
min: 1,
|
|
8836
|
+
max: 1
|
|
8837
|
+
},
|
|
8838
|
+
run: async (invocation) => {
|
|
8839
|
+
const place = placeOf(invocation.values.to);
|
|
8840
|
+
const run = await runOf(adaptersOf, invocation);
|
|
8841
|
+
const projectId = await projectFrom$2(run, invocation.values.project);
|
|
8842
|
+
return printedQueue(run, await queueMoved(run.api, projectId, invocation.positionals[0] ?? "", place));
|
|
8843
|
+
}
|
|
8844
|
+
})
|
|
8845
|
+
]
|
|
8846
|
+
});
|
|
8847
|
+
//#endregion
|
|
8848
|
+
//#region src/mcp/tools/claude-accounts.ts
|
|
8849
|
+
const accountField$1 = z.string().trim().min(1).describe("The Claude account, by its label or its id, from claude_accounts_list.");
|
|
8850
|
+
const claudeAccountTools = [
|
|
8851
|
+
defineTool({
|
|
8852
|
+
name: "claude_accounts_list",
|
|
8853
|
+
title: "List the Claude accounts of the organization",
|
|
8854
|
+
description: "List the Claude subscriptions and Anthropic API keys of the organization this server acts in, with their fingerprint, verification, usage and the queues of the projects they stand in, never their secrets.",
|
|
8855
|
+
operations: ["listClaudeAccounts"],
|
|
8856
|
+
safety: "read",
|
|
8857
|
+
input: z.object({}),
|
|
8858
|
+
run: async (_, run) => claudeAccountsOf(run.api, (await run.organization()).id)
|
|
8859
|
+
}),
|
|
8860
|
+
defineTool({
|
|
8861
|
+
name: "claude_account_update",
|
|
8862
|
+
title: "Rename, verify or read the usage of a Claude account",
|
|
8863
|
+
description: "rename gives a Claude account a new label; verify asks the instance to check it against Anthropic, and claude_accounts_list shows the answer; refresh_usage reads the usage of a Claude subscription now, or answers the last reading within 30 seconds of the previous one. Each action takes an admin of the organization.",
|
|
8864
|
+
operations: [
|
|
8865
|
+
"renameClaudeAccount",
|
|
8866
|
+
"verifyClaudeAccount",
|
|
8867
|
+
"refreshClaudeAccountUsage"
|
|
8868
|
+
],
|
|
8869
|
+
safety: "write",
|
|
8870
|
+
input: z.object({
|
|
8871
|
+
account: accountField$1,
|
|
8872
|
+
action: z.enum([
|
|
8873
|
+
"rename",
|
|
8874
|
+
"verify",
|
|
8875
|
+
"refresh_usage"
|
|
8876
|
+
]),
|
|
8877
|
+
label: z.string().trim().min(1).optional().describe("The new label, for rename.")
|
|
8878
|
+
}),
|
|
8879
|
+
run: async ({ account, action, label }, run) => {
|
|
8880
|
+
if (action === "rename" && label === void 0) throw new UsageError("rename needs a label.");
|
|
8881
|
+
const { organizationId, account: found } = await resolveClaudeAccount(run, account);
|
|
8882
|
+
if (action === "refresh_usage") return refreshedClaudeAccount(run.api, organizationId, found.id);
|
|
8883
|
+
if (action === "verify") {
|
|
8884
|
+
await verifyClaudeAccount(run.api, organizationId, found.id);
|
|
8885
|
+
return {
|
|
8886
|
+
id: found.id,
|
|
8887
|
+
verifying: true
|
|
8888
|
+
};
|
|
8889
|
+
}
|
|
8890
|
+
return renamedClaudeAccount(run.api, organizationId, found.id, label ?? "");
|
|
8891
|
+
}
|
|
8892
|
+
}),
|
|
8893
|
+
defineTool({
|
|
8894
|
+
name: "claude_account_delete",
|
|
8895
|
+
title: "Delete a Claude account",
|
|
8896
|
+
description: "Delete a Claude account of the organization and erase its secret. The instance refuses while it stands in the queue of a project, and names those projects. Takes an admin of the organization.",
|
|
8897
|
+
operations: ["deleteClaudeAccount"],
|
|
8898
|
+
safety: "destructive",
|
|
8899
|
+
input: z.object({ account: accountField$1 }),
|
|
8900
|
+
run: async ({ account }, run) => {
|
|
8901
|
+
const { organizationId, account: found } = await resolveClaudeAccount(run, account);
|
|
8902
|
+
await deleteClaudeAccount(run.api, organizationId, found.id);
|
|
8903
|
+
return { deleted: found.id };
|
|
8904
|
+
}
|
|
8905
|
+
}),
|
|
8906
|
+
defineTool({
|
|
8907
|
+
name: "project_accounts_list",
|
|
8908
|
+
title: "List the queue of Claude accounts of a project",
|
|
8909
|
+
description: "List the queue of Claude accounts the agents of a project work on, in order, each with its state: inUse, available, exhausted until a moment, noCredits or invalid. activeCredentialId names the account in use, and waitReason says why the agents wait: exhausted, blocked or empty.",
|
|
8910
|
+
operations: ["listProjectClaudeAccounts"],
|
|
8911
|
+
safety: "read",
|
|
8912
|
+
input: z.object({ project: projectField }),
|
|
8913
|
+
run: async ({ project }, run) => projectClaudeAccountsOf(run.api, await projectIdOf(run, project))
|
|
8914
|
+
}),
|
|
8915
|
+
defineTool({
|
|
8916
|
+
name: "project_accounts_set",
|
|
8917
|
+
title: "Set the queue of Claude accounts of a project",
|
|
8918
|
+
description: "Set the whole queue of Claude accounts of a project, in order: the first account comes first, an account left out leaves the queue and stays in the organization, and an empty list empties the queue. Without version the change applies to the queue read just before it. Takes an admin of the project.",
|
|
8919
|
+
operations: ["listProjectClaudeAccounts", "setProjectClaudeAccounts"],
|
|
8920
|
+
safety: "write",
|
|
8921
|
+
idempotent: true,
|
|
8922
|
+
input: z.object({
|
|
8923
|
+
project: projectField,
|
|
8924
|
+
accounts: z.array(accountField$1).max(50).describe("The Claude accounts of the queue, by label or id, first one first."),
|
|
8925
|
+
version: z.int().nonnegative().optional().describe("The version of the queue the change is based on, to refuse a change made meanwhile.")
|
|
8926
|
+
}),
|
|
8927
|
+
run: async ({ project, accounts, version }, run) => {
|
|
8928
|
+
const projectId = await projectIdOf(run, project);
|
|
8929
|
+
const ids = await organizationAccountIds(run.api, projectId, accounts);
|
|
8930
|
+
const based = version ?? (await projectClaudeAccountsOf(run.api, projectId)).version;
|
|
8931
|
+
return queueSet(run.api, projectId, ids, based);
|
|
8932
|
+
}
|
|
8933
|
+
})
|
|
8934
|
+
];
|
|
8935
|
+
//#endregion
|
|
7698
8936
|
//#region src/mcp/tools/credentials.ts
|
|
7699
8937
|
const credentialField$1 = z.string().trim().min(1).describe("The credential, by its label or its id.");
|
|
7700
8938
|
const credentialTools = [
|
|
7701
8939
|
defineTool({
|
|
7702
8940
|
name: "credentials_list",
|
|
7703
8941
|
title: "List credentials",
|
|
7704
|
-
description: "List the
|
|
8942
|
+
description: "List the git provider credentials of a project (GitHub, GitLab and Bitbucket), with their server, account, commit identity, fingerprints and verification, never their secrets. The Claude accounts belong to the organization: claude_accounts_list.",
|
|
7705
8943
|
operations: ["listCredentials"],
|
|
7706
8944
|
safety: "read",
|
|
7707
8945
|
input: z.object({ project: projectField }),
|
|
@@ -7709,11 +8947,12 @@ const credentialTools = [
|
|
|
7709
8947
|
}),
|
|
7710
8948
|
defineTool({
|
|
7711
8949
|
name: "credential_update",
|
|
7712
|
-
title: "Rename, verify or read the usage of a credential",
|
|
7713
|
-
description: "rename gives a credential a new label; verify asks the instance to check it against its service, and credentials_list shows the answer; refresh_usage reads the usage of the Claude subscription behind a Claude Code OAuth token now, or answers the last reading within 30 seconds of the previous one.",
|
|
8950
|
+
title: "Rename, verify, sign commits as or read the usage of a credential",
|
|
8951
|
+
description: "rename gives a credential a new label; verify asks the instance to check it against its service, and credentials_list shows the answer; set_identity sets the commitName and commitEmail the commits of a git provider token carry, which the agents that use it pick up after a restart; refresh_usage reads the usage of the Claude subscription behind a Claude Code OAuth token now, or answers the last reading within 30 seconds of the previous one.",
|
|
7714
8952
|
operations: [
|
|
7715
8953
|
"renameCredential",
|
|
7716
8954
|
"verifyCredential",
|
|
8955
|
+
"setCredentialCommitIdentity",
|
|
7717
8956
|
"refreshCredentialUsage"
|
|
7718
8957
|
],
|
|
7719
8958
|
safety: "write",
|
|
@@ -7723,11 +8962,14 @@ const credentialTools = [
|
|
|
7723
8962
|
action: z.enum([
|
|
7724
8963
|
"rename",
|
|
7725
8964
|
"verify",
|
|
8965
|
+
"set_identity",
|
|
7726
8966
|
"refresh_usage"
|
|
7727
8967
|
]),
|
|
7728
|
-
label: z.string().trim().min(1).optional().describe("The new label, for rename.")
|
|
8968
|
+
label: z.string().trim().min(1).optional().describe("The new label, for rename."),
|
|
8969
|
+
commitName: commitNameSchema.optional().describe("The name its commits carry, for set_identity."),
|
|
8970
|
+
commitEmail: commitEmailSchema.optional().describe("The e-mail its commits carry, for set_identity.")
|
|
7729
8971
|
}),
|
|
7730
|
-
run: async ({ project, credential, action, label }, run) => {
|
|
8972
|
+
run: async ({ project, credential, action, label, commitName, commitEmail }, run) => {
|
|
7731
8973
|
const projectId = await projectIdOf(run, project);
|
|
7732
8974
|
const credentialId = await resolveCredentialId(run.api, projectId, credential);
|
|
7733
8975
|
if (action === "refresh_usage") return dataOf("read the usage of the credential", await run.api.POST("/projects/{id}/credentials/{credentialId}/usage", { params: { path: {
|
|
@@ -7744,6 +8986,19 @@ const credentialTools = [
|
|
|
7744
8986
|
verifying: true
|
|
7745
8987
|
};
|
|
7746
8988
|
}
|
|
8989
|
+
if (action === "set_identity") {
|
|
8990
|
+
if (commitName === void 0 || commitEmail === void 0) throw new UsageError("set_identity needs a commitName and a commitEmail.");
|
|
8991
|
+
return dataOf("set the commit identity of the credential", await run.api.PUT("/projects/{id}/credentials/{credentialId}/commit-identity", {
|
|
8992
|
+
params: { path: {
|
|
8993
|
+
id: projectId,
|
|
8994
|
+
credentialId
|
|
8995
|
+
} },
|
|
8996
|
+
body: {
|
|
8997
|
+
commitName,
|
|
8998
|
+
commitEmail
|
|
8999
|
+
}
|
|
9000
|
+
}), credentialHints);
|
|
9001
|
+
}
|
|
7747
9002
|
if (label === void 0) throw new UsageError("rename needs a label.");
|
|
7748
9003
|
return dataOf("rename the credential", await run.api.PATCH("/projects/{id}/credentials/{credentialId}", {
|
|
7749
9004
|
params: { path: {
|
|
@@ -7979,6 +9234,15 @@ const sources = {
|
|
|
7979
9234
|
}
|
|
7980
9235
|
};
|
|
7981
9236
|
const observabilityTools = [
|
|
9237
|
+
defineTool({
|
|
9238
|
+
name: "agent_overview",
|
|
9239
|
+
title: "Show the overview of an agent",
|
|
9240
|
+
description: "Show what an agent works on, what needs attention, its recent work grouped by the source of each task, and its counts and costs over 24 hours and 7 days.",
|
|
9241
|
+
operations: ["getAgentOverview"],
|
|
9242
|
+
safety: "read",
|
|
9243
|
+
input: z.object({ ...agentFields }),
|
|
9244
|
+
run: async (reference, run) => agentOverviewOf(run.api, await agentIdOf(run, reference))
|
|
9245
|
+
}),
|
|
7982
9246
|
defineTool({
|
|
7983
9247
|
name: "agent_sessions",
|
|
7984
9248
|
title: "List the sessions of an agent",
|
|
@@ -8528,6 +9792,12 @@ const costsText = (costs, { from, to }, slugs) => [
|
|
|
8528
9792
|
"",
|
|
8529
9793
|
`${costs.totalCostUsd.toFixed(2)} over ${costs.totalRuns} runs and ${costs.sessions} sessions.`
|
|
8530
9794
|
].join("\n");
|
|
9795
|
+
const switchThresholdOf = (given) => {
|
|
9796
|
+
if (given === void 0) return void 0;
|
|
9797
|
+
const threshold = Number(given);
|
|
9798
|
+
if (!/^[0-9]+$/u.test(given) || threshold < 1 || threshold > 100) throw new UsageError(`--switch-threshold ${given} is not a whole percent from 1 to 100.`);
|
|
9799
|
+
return threshold;
|
|
9800
|
+
};
|
|
8531
9801
|
const organizationChangeHints = {
|
|
8532
9802
|
...organizationHints,
|
|
8533
9803
|
"organization.slug_taken": "Another organization already uses that slug.",
|
|
@@ -8548,12 +9818,14 @@ const updatedOrganization = async (api, current, fields) => {
|
|
|
8548
9818
|
if ([
|
|
8549
9819
|
fields.name,
|
|
8550
9820
|
fields.slug,
|
|
8551
|
-
fields.color
|
|
9821
|
+
fields.color,
|
|
9822
|
+
fields.switchThreshold
|
|
8552
9823
|
].every((field) => field === void 0)) return current;
|
|
8553
9824
|
const body = {
|
|
8554
9825
|
name: fields.name ?? current.name,
|
|
8555
9826
|
slug: fields.slug ?? current.slug,
|
|
8556
|
-
color: fields.color ?? current.color
|
|
9827
|
+
color: fields.color ?? current.color,
|
|
9828
|
+
...fields.switchThreshold === void 0 ? {} : { claudeSwitchThreshold: fields.switchThreshold }
|
|
8557
9829
|
};
|
|
8558
9830
|
validated(updateOrganizationBodySchema, body, "a change to an organization");
|
|
8559
9831
|
return dataOf("change the organization", await api.PATCH("/organizations/{organizationId}", {
|
|
@@ -8656,6 +9928,7 @@ const createOrgsCommand = (adaptersOf) => defineGroup({
|
|
|
8656
9928
|
["slug", shown.slug],
|
|
8657
9929
|
["color", shown.color],
|
|
8658
9930
|
["logo", shown.logoHash ?? "-"],
|
|
9931
|
+
["switch threshold", `${String(shown.claudeSwitchThreshold)}%`],
|
|
8659
9932
|
["created", shown.createdAt],
|
|
8660
9933
|
["id", shown.id]
|
|
8661
9934
|
]));
|
|
@@ -8663,18 +9936,20 @@ const createOrgsCommand = (adaptersOf) => defineGroup({
|
|
|
8663
9936
|
}),
|
|
8664
9937
|
defineCommand({
|
|
8665
9938
|
name: "set",
|
|
8666
|
-
summary: "Change the name, slug, color or
|
|
8667
|
-
usage: "orgs set [<organization>] [--name <name>] [--slug <slug>] [--color <#rrggbb>] [--logo <path> | --no-logo]",
|
|
9939
|
+
summary: "Change the name, slug, color, logo or Claude switch threshold of an organization.",
|
|
9940
|
+
usage: "orgs set [<organization>] [--name <name>] [--slug <slug>] [--color <#rrggbb>] [--logo <path> | --no-logo] [--switch-threshold <percent>]",
|
|
8668
9941
|
optionsUsage: [
|
|
8669
9942
|
["--name <name>", "the new name"],
|
|
8670
9943
|
["--slug <slug>", "the new slug; scripts and --org that name the old one stop finding it"],
|
|
8671
9944
|
["--color <#rrggbb>", "the color of the organization in the panel"],
|
|
8672
|
-
...logoUsage
|
|
9945
|
+
...logoUsage,
|
|
9946
|
+
["--switch-threshold <percent>", `the usage at which a project moves to its next Claude account, 1 to 100`]
|
|
8673
9947
|
],
|
|
8674
9948
|
options: {
|
|
8675
9949
|
name: { type: "string" },
|
|
8676
9950
|
slug: { type: "string" },
|
|
8677
9951
|
color: { type: "string" },
|
|
9952
|
+
"switch-threshold": { type: "string" },
|
|
8678
9953
|
...logoOptions
|
|
8679
9954
|
},
|
|
8680
9955
|
positionals: {
|
|
@@ -8683,15 +9958,20 @@ const createOrgsCommand = (adaptersOf) => defineGroup({
|
|
|
8683
9958
|
},
|
|
8684
9959
|
run: async (invocation) => {
|
|
8685
9960
|
const { values } = invocation;
|
|
9961
|
+
const switchThreshold = switchThresholdOf(values["switch-threshold"]);
|
|
8686
9962
|
if ([
|
|
8687
9963
|
values.name,
|
|
8688
9964
|
values.slug,
|
|
8689
|
-
values.color
|
|
8690
|
-
|
|
9965
|
+
values.color,
|
|
9966
|
+
switchThreshold
|
|
9967
|
+
].every((value) => value === void 0) && !isLogoChange(values)) throw new UsageError("orgs set needs --name, --slug, --color, --logo, --no-logo or --switch-threshold.");
|
|
8691
9968
|
const logo = await logoActionOf(invocation.context.workingDirectory, values);
|
|
8692
9969
|
const run = await runOf(adaptersOf, invocation);
|
|
8693
9970
|
const current = await organizationOrInUse(run, invocation.positionals[0]);
|
|
8694
|
-
const updated = await updatedOrganization(run.api, current,
|
|
9971
|
+
const updated = await updatedOrganization(run.api, current, {
|
|
9972
|
+
...values,
|
|
9973
|
+
switchThreshold
|
|
9974
|
+
});
|
|
8695
9975
|
if (updated.slug !== current.slug) {
|
|
8696
9976
|
await followSlug(run.context.homeDirectory, run.origin, current, updated);
|
|
8697
9977
|
run.context.terminal.error(run.context.terminal.errorStyles.warning(`warning Scripts that pass --org ${current.slug} or set ${organizationVariable}=${current.slug} need ${updated.slug} now.`));
|
|
@@ -8767,14 +10047,15 @@ const organizationTools = [
|
|
|
8767
10047
|
defineTool({
|
|
8768
10048
|
name: "organization_update",
|
|
8769
10049
|
title: "Change an organization",
|
|
8770
|
-
description: "Change the name
|
|
10050
|
+
description: "Change the name, the color or the Claude switch threshold of an organization, by default the one this server acts in. A field left out keeps its value. A new slug is set with kcloud orgs set or in the panel.",
|
|
8771
10051
|
operations: ["listOrganizations", "updateOrganization"],
|
|
8772
10052
|
safety: "write",
|
|
8773
10053
|
idempotent: true,
|
|
8774
10054
|
input: z.object({
|
|
8775
10055
|
organization: z.string().trim().min(1).optional().describe("The organization, by its slug, its name or its id."),
|
|
8776
10056
|
name: organizationNameSchema.optional(),
|
|
8777
|
-
color: colorSchema.optional().describe("The color in the panel, as #rrggbb.")
|
|
10057
|
+
color: colorSchema.optional().describe("The color in the panel, as #rrggbb."),
|
|
10058
|
+
switchThreshold: switchThresholdSchema.optional().describe("The used share of a subscription, in percent, at which a project moves to its next Claude account; 100 moves only at the limit.")
|
|
8778
10059
|
}),
|
|
8779
10060
|
run: async ({ organization, ...change }, run) => updatedOrganization(run.api, await organizationOrInUse(run, organization), change)
|
|
8780
10061
|
}),
|
|
@@ -9484,7 +10765,8 @@ const createProjectsCommand = (adaptersOf) => defineGroup({
|
|
|
9484
10765
|
return said(run, `Deleted the project ${project.name}.`);
|
|
9485
10766
|
}
|
|
9486
10767
|
}),
|
|
9487
|
-
projectMembersCommand(adaptersOf)
|
|
10768
|
+
projectMembersCommand(adaptersOf),
|
|
10769
|
+
projectAccountsCommand(adaptersOf)
|
|
9488
10770
|
]
|
|
9489
10771
|
});
|
|
9490
10772
|
//#endregion
|
|
@@ -9673,24 +10955,33 @@ const providerTools = [
|
|
|
9673
10955
|
const releaseTools = [defineTool({
|
|
9674
10956
|
name: "releases_list",
|
|
9675
10957
|
title: "List releases",
|
|
9676
|
-
description: "List the Kikimora releases an agent can run, with the Claude Code and Playwright MCP versions each one ships.",
|
|
10958
|
+
description: "List the Kikimora releases an agent can run, with the Claude Code and Playwright MCP versions each one ships and whether it is stable, and the version \"latest\" points at now, null when no release is stable.",
|
|
9677
10959
|
operations: ["listReleases"],
|
|
9678
10960
|
safety: "read",
|
|
9679
10961
|
input: z.object({}),
|
|
9680
|
-
run: async (_, run) =>
|
|
10962
|
+
run: async (_, run) => {
|
|
10963
|
+
const { latest, items } = dataOf("list the releases", await run.api.GET("/releases"));
|
|
10964
|
+
return {
|
|
10965
|
+
latest,
|
|
10966
|
+
items
|
|
10967
|
+
};
|
|
10968
|
+
}
|
|
9681
10969
|
})];
|
|
9682
10970
|
//#endregion
|
|
9683
10971
|
//#region src/commands/repositories.ts
|
|
9684
10972
|
const projectOption = { project: { type: "string" } };
|
|
9685
10973
|
const projectUsage = [["--project <name>", "the project the repository belongs to, by slug, name or id"]];
|
|
10974
|
+
const providerOf = (details) => providerNameOf(details.provider);
|
|
9686
10975
|
const repositoryHints = {
|
|
9687
10976
|
"project.not_found": "kcloud projects list shows the projects you are a member of.",
|
|
9688
10977
|
"repository.not_found": "kcloud repositories list --project <name> shows them.",
|
|
9689
10978
|
"repository.already_registered": "The project already has that repository.",
|
|
9690
10979
|
"repository.in_use": "Take it off its agents first.",
|
|
9691
|
-
"repository.unreachable":
|
|
9692
|
-
"
|
|
9693
|
-
"credential.
|
|
10980
|
+
"repository.unreachable": (details) => `The credential cannot see it on ${providerOf(details)}; kcloud repositories available lists what it sees.`,
|
|
10981
|
+
"repository.catalog_unavailable": ({ provider, serverUrl }) => `${providerNameOf(provider)} at ${String(serverUrl)} did not return the repositories of the credential. kcloud repositories add <owner>/<name> --credential <credential> registers one by its path.`,
|
|
10982
|
+
"credential.kind_mismatch": "A repository needs the credential of a git provider: a github_pat, gitlab_token or bitbucket_token.",
|
|
10983
|
+
"credential.rejected": (details) => `${providerOf(details)} refused that credential; kcloud credentials rotate <credential> --project <name> replaces it.`,
|
|
10984
|
+
"credential.server_mismatch": ({ provider, serverUrl }) => `The repository is on ${providerNameOf(provider)} at ${String(serverUrl)}; the credential belongs to another provider or server. The SERVER column of kcloud credentials list shows where each one acts.`,
|
|
9694
10985
|
"credential.not_found": "kcloud credentials list --project <name> shows them."
|
|
9695
10986
|
};
|
|
9696
10987
|
const projectFrom = async (run, given) => {
|
|
@@ -9707,13 +10998,15 @@ const resolveRepositoryId = async (api, projectId, reference) => {
|
|
|
9707
10998
|
return first.id;
|
|
9708
10999
|
};
|
|
9709
11000
|
const ownerAndName = (reference) => {
|
|
9710
|
-
const
|
|
9711
|
-
|
|
11001
|
+
const separator = reference.lastIndexOf("/");
|
|
11002
|
+
const owner = reference.slice(0, separator);
|
|
11003
|
+
const name = reference.slice(separator + 1);
|
|
11004
|
+
if (separator < 1 || !isRepositoryOwner(owner) || !repositoryNamePattern.test(name)) throw new UsageError(`${reference} is not a repository path.`, "Name it as owner/name, or group/subgroup/name on GitLab, with up to 20 names before the last.");
|
|
9712
11005
|
return [owner, name];
|
|
9713
11006
|
};
|
|
9714
11007
|
const createRepositoriesCommand = (adaptersOf) => defineGroup({
|
|
9715
11008
|
name: "repositories",
|
|
9716
|
-
summary: "The GitHub repositories of a project.",
|
|
11009
|
+
summary: "The GitHub, GitLab and Bitbucket repositories of a project.",
|
|
9717
11010
|
usage: "repositories <command>",
|
|
9718
11011
|
subcommands: [
|
|
9719
11012
|
defineCommand({
|
|
@@ -9732,6 +11025,8 @@ const createRepositoriesCommand = (adaptersOf) => defineGroup({
|
|
|
9732
11025
|
const items = await repositoriesOf(run.api, projectId);
|
|
9733
11026
|
return printed(run, items, (repositories) => repositories.length === 0 ? "The project has no repository yet." : renderTable([
|
|
9734
11027
|
{ header: "REPOSITORY" },
|
|
11028
|
+
{ header: "PROVIDER" },
|
|
11029
|
+
{ header: "SERVER" },
|
|
9735
11030
|
{ header: "BRANCH" },
|
|
9736
11031
|
{ header: "VISIBILITY" },
|
|
9737
11032
|
{ header: "STATUS" },
|
|
@@ -9739,6 +11034,8 @@ const createRepositoriesCommand = (adaptersOf) => defineGroup({
|
|
|
9739
11034
|
{ header: "ID" }
|
|
9740
11035
|
], repositories.map((repository) => [
|
|
9741
11036
|
`${repository.owner}/${repository.name}`,
|
|
11037
|
+
repository.provider,
|
|
11038
|
+
repository.serverUrl,
|
|
9742
11039
|
repository.defaultBranch,
|
|
9743
11040
|
repository.isPrivate ? "private" : "public",
|
|
9744
11041
|
repository.status,
|
|
@@ -9749,9 +11046,9 @@ const createRepositoriesCommand = (adaptersOf) => defineGroup({
|
|
|
9749
11046
|
}),
|
|
9750
11047
|
defineCommand({
|
|
9751
11048
|
name: "available",
|
|
9752
|
-
summary: "List the
|
|
11049
|
+
summary: "List the repositories one git provider credential can see.",
|
|
9753
11050
|
usage: "repositories available --project <name> --credential <label>",
|
|
9754
|
-
optionsUsage: [...projectUsage, ["--credential <label>", "the
|
|
11051
|
+
optionsUsage: [...projectUsage, ["--credential <label>", "the git provider credential to look through"]],
|
|
9755
11052
|
options: {
|
|
9756
11053
|
...projectOption,
|
|
9757
11054
|
credential: { type: "string" }
|
|
@@ -9766,7 +11063,7 @@ const createRepositoriesCommand = (adaptersOf) => defineGroup({
|
|
|
9766
11063
|
const credential = invocation.values.credential;
|
|
9767
11064
|
if (credential === void 0) throw new UsageError("repositories available needs --credential <label>.", "kcloud credentials list --project <name> shows them.");
|
|
9768
11065
|
const credentialId = await resolveCredentialId(run.api, projectId, credential);
|
|
9769
|
-
const listed = dataOf("list the repositories of the credential", await run.api.GET("/projects/{id}/repositories/
|
|
11066
|
+
const listed = dataOf("list the repositories of the credential", await run.api.GET("/projects/{id}/repositories/available", { params: {
|
|
9770
11067
|
path: { id: projectId },
|
|
9771
11068
|
query: { credentialId }
|
|
9772
11069
|
} }), repositoryHints);
|
|
@@ -9776,16 +11073,20 @@ const createRepositoriesCommand = (adaptersOf) => defineGroup({
|
|
|
9776
11073
|
{ header: "VISIBILITY" }
|
|
9777
11074
|
], page.items.map((repository) => [
|
|
9778
11075
|
`${repository.owner}/${repository.name}`,
|
|
9779
|
-
repository.defaultBranch,
|
|
11076
|
+
repository.defaultBranch ?? "-",
|
|
9780
11077
|
repository.isPrivate ? "private" : "public"
|
|
9781
|
-
])), ...page.truncated ? ["",
|
|
11078
|
+
])), ...page.truncated ? ["", `${providerNameOf(page.provider)} returned only the first pages.`] : []].join("\n"));
|
|
9782
11079
|
}
|
|
9783
11080
|
}),
|
|
9784
11081
|
defineCommand({
|
|
9785
11082
|
name: "add",
|
|
9786
|
-
summary: "Register a
|
|
11083
|
+
summary: "Register a repository of a git provider in a project.",
|
|
9787
11084
|
usage: "repositories add <owner/name> --project <name> --credential <label>",
|
|
9788
|
-
optionsUsage: [
|
|
11085
|
+
optionsUsage: [
|
|
11086
|
+
...projectUsage,
|
|
11087
|
+
["--credential <label>", "the git provider credential that can see it; it sets the provider and the server"],
|
|
11088
|
+
["", "a GitLab subgroup belongs to the owner: group/subgroup/name; Bitbucket Data Center takes KEY/name"]
|
|
11089
|
+
],
|
|
9789
11090
|
options: {
|
|
9790
11091
|
...projectOption,
|
|
9791
11092
|
credential: { type: "string" }
|
|
@@ -9816,7 +11117,7 @@ const createRepositoriesCommand = (adaptersOf) => defineGroup({
|
|
|
9816
11117
|
name: "set-credential",
|
|
9817
11118
|
summary: "Point a repository at another credential.",
|
|
9818
11119
|
usage: "repositories set-credential <owner/name> --project <name> --credential <label>",
|
|
9819
|
-
optionsUsage: [...projectUsage, ["--credential <label>", "the
|
|
11120
|
+
optionsUsage: [...projectUsage, ["--credential <label>", "the credential to use from now on, of the provider and server of the repository"]],
|
|
9820
11121
|
options: {
|
|
9821
11122
|
...projectOption,
|
|
9822
11123
|
credential: { type: "string" }
|
|
@@ -9844,7 +11145,7 @@ const createRepositoriesCommand = (adaptersOf) => defineGroup({
|
|
|
9844
11145
|
}),
|
|
9845
11146
|
defineCommand({
|
|
9846
11147
|
name: "refresh",
|
|
9847
|
-
summary: "Check a repository against
|
|
11148
|
+
summary: "Check a repository against its provider again.",
|
|
9848
11149
|
usage: "repositories refresh <owner/name> --project <name>",
|
|
9849
11150
|
optionsUsage: projectUsage,
|
|
9850
11151
|
options: projectOption,
|
|
@@ -9894,26 +11195,26 @@ const createRepositoriesCommand = (adaptersOf) => defineGroup({
|
|
|
9894
11195
|
});
|
|
9895
11196
|
//#endregion
|
|
9896
11197
|
//#region src/mcp/tools/repositories.ts
|
|
9897
|
-
const repositoryField = z.string().trim().min(1).describe("The repository, as owner/name, by its name or by its id.");
|
|
9898
|
-
const credentialField = z.string().trim().min(1).describe("A
|
|
11198
|
+
const repositoryField = z.string().trim().min(1).describe("The repository, as owner/name (group/subgroup/name on GitLab), by its name or by its id.");
|
|
11199
|
+
const credentialField = z.string().trim().min(1).describe("A git provider credential of the project (github_pat, gitlab_token or bitbucket_token), by its label or its id.");
|
|
9899
11200
|
const repositoryTools = [
|
|
9900
11201
|
defineTool({
|
|
9901
11202
|
name: "repositories_list",
|
|
9902
11203
|
title: "List repositories",
|
|
9903
|
-
description: "registered lists the
|
|
9904
|
-
operations: ["listRepositories", "
|
|
11204
|
+
description: "registered lists the repositories of a project, each with its provider and server; available lists what one git provider credential can see on its server, to pick one for repository_add.",
|
|
11205
|
+
operations: ["listRepositories", "listAvailableRepositories"],
|
|
9905
11206
|
safety: "read",
|
|
9906
11207
|
input: z.object({
|
|
9907
11208
|
project: projectField,
|
|
9908
|
-
source: z.enum(["registered", "
|
|
9909
|
-
credential: credentialField.optional().describe("The credential to look through, for
|
|
11209
|
+
source: z.enum(["registered", "available"]).default("registered"),
|
|
11210
|
+
credential: credentialField.optional().describe("The credential to look through, for available.")
|
|
9910
11211
|
}),
|
|
9911
11212
|
run: async ({ project, source, credential }, run) => {
|
|
9912
11213
|
const projectId = await projectIdOf(run, project);
|
|
9913
11214
|
if (source === "registered") return repositoriesOf(run.api, projectId);
|
|
9914
|
-
if (credential === void 0) throw new UsageError("
|
|
11215
|
+
if (credential === void 0) throw new UsageError("available needs a credential.");
|
|
9915
11216
|
const credentialId = await resolveCredentialId(run.api, projectId, credential);
|
|
9916
|
-
return dataOf("list the repositories of the credential", await run.api.GET("/projects/{id}/repositories/
|
|
11217
|
+
return dataOf("list the repositories of the credential", await run.api.GET("/projects/{id}/repositories/available", { params: {
|
|
9917
11218
|
path: { id: projectId },
|
|
9918
11219
|
query: { credentialId }
|
|
9919
11220
|
} }), repositoryHints);
|
|
@@ -9922,12 +11223,12 @@ const repositoryTools = [
|
|
|
9922
11223
|
defineTool({
|
|
9923
11224
|
name: "repository_add",
|
|
9924
11225
|
title: "Register a repository",
|
|
9925
|
-
description: "Register a
|
|
11226
|
+
description: "Register a repository of a git provider in a project, so agents can clone it. The credential decides the provider and the server.",
|
|
9926
11227
|
operations: ["registerRepository"],
|
|
9927
11228
|
safety: "write",
|
|
9928
11229
|
input: z.object({
|
|
9929
11230
|
project: projectField,
|
|
9930
|
-
repository: z.string().trim().min(1).describe("The
|
|
11231
|
+
repository: z.string().trim().min(1).describe("The repository, as owner/name, or group/subgroup/name for a GitLab subgroup."),
|
|
9931
11232
|
credential: credentialField
|
|
9932
11233
|
}),
|
|
9933
11234
|
run: async ({ project, repository, credential }, run) => {
|
|
@@ -9947,7 +11248,7 @@ const repositoryTools = [
|
|
|
9947
11248
|
defineTool({
|
|
9948
11249
|
name: "repository_update",
|
|
9949
11250
|
title: "Change or refresh a repository",
|
|
9950
|
-
description: "set_credential points a repository at another
|
|
11251
|
+
description: "set_credential points a repository at another credential of its provider and server; refresh checks it against its provider again.",
|
|
9951
11252
|
operations: ["updateRepository", "refreshRepository"],
|
|
9952
11253
|
safety: "write",
|
|
9953
11254
|
input: z.object({
|
|
@@ -10123,6 +11424,7 @@ const kcloudTools = [
|
|
|
10123
11424
|
...accountTools,
|
|
10124
11425
|
...organizationTools,
|
|
10125
11426
|
...projectTools,
|
|
11427
|
+
...claudeAccountTools,
|
|
10126
11428
|
...credentialTools,
|
|
10127
11429
|
...repositoryTools,
|
|
10128
11430
|
...projectToolTools,
|
|
@@ -10864,13 +12166,31 @@ const createProvidersCommand = (adaptersOf) => defineGroup({
|
|
|
10864
12166
|
});
|
|
10865
12167
|
//#endregion
|
|
10866
12168
|
//#region src/commands/releases.ts
|
|
12169
|
+
const latestLineOf = ({ latest }) => latest === null ? "No release is stable yet." : `Latest points at ${latest}.`;
|
|
12170
|
+
const releasesOf = (listed) => listed.items.length === 0 ? "The catalogue holds no release an agent can run." : [
|
|
12171
|
+
renderTable([
|
|
12172
|
+
{ header: "VERSION" },
|
|
12173
|
+
{ header: "STABLE" },
|
|
12174
|
+
{ header: "CLAUDE CODE" },
|
|
12175
|
+
{ header: "PLAYWRIGHT MCP" },
|
|
12176
|
+
{ header: "PUBLISHED" }
|
|
12177
|
+
], listed.items.map((release) => [
|
|
12178
|
+
release.version,
|
|
12179
|
+
release.stable ? "yes" : "no",
|
|
12180
|
+
release.claudeCodeVersion ?? "-",
|
|
12181
|
+
release.playwrightMcpVersion ?? "-",
|
|
12182
|
+
release.imageCreatedAt ?? "-"
|
|
12183
|
+
])),
|
|
12184
|
+
"",
|
|
12185
|
+
latestLineOf(listed)
|
|
12186
|
+
].join("\n");
|
|
10867
12187
|
const createReleasesCommand = (adaptersOf) => defineGroup({
|
|
10868
12188
|
name: "releases",
|
|
10869
12189
|
summary: "The Kikimora releases an agent can run.",
|
|
10870
12190
|
usage: "releases <command>",
|
|
10871
12191
|
subcommands: [defineCommand({
|
|
10872
12192
|
name: "list",
|
|
10873
|
-
summary: "List the releases you can give an agent.",
|
|
12193
|
+
summary: "List the releases you can give an agent, and the one latest points at.",
|
|
10874
12194
|
usage: "releases list",
|
|
10875
12195
|
options: {},
|
|
10876
12196
|
positionals: {
|
|
@@ -10880,17 +12200,7 @@ const createReleasesCommand = (adaptersOf) => defineGroup({
|
|
|
10880
12200
|
run: async (invocation) => {
|
|
10881
12201
|
const run = await runOf(adaptersOf, invocation);
|
|
10882
12202
|
const releases = dataOf("list the releases", await run.api.GET("/releases"));
|
|
10883
|
-
return printed(run, releases,
|
|
10884
|
-
{ header: "VERSION" },
|
|
10885
|
-
{ header: "CLAUDE CODE" },
|
|
10886
|
-
{ header: "PLAYWRIGHT MCP" },
|
|
10887
|
-
{ header: "PUBLISHED" }
|
|
10888
|
-
], listed.items.map((release) => [
|
|
10889
|
-
release.version,
|
|
10890
|
-
release.claudeCodeVersion ?? "-",
|
|
10891
|
-
release.playwrightMcpVersion ?? "-",
|
|
10892
|
-
release.imageCreatedAt ?? "-"
|
|
10893
|
-
])));
|
|
12203
|
+
return printed(run, releases, releasesOf);
|
|
10894
12204
|
}
|
|
10895
12205
|
})]
|
|
10896
12206
|
});
|
|
@@ -10906,6 +12216,7 @@ const kcloudCommands = () => [
|
|
|
10906
12216
|
createTokensCommand(createAdapters),
|
|
10907
12217
|
createAuthCommand(createAdapters),
|
|
10908
12218
|
createProjectsCommand(createAdapters),
|
|
12219
|
+
createClaudeAccountsCommand(createAdapters),
|
|
10909
12220
|
createCredentialsCommand(createAdapters),
|
|
10910
12221
|
createRepositoriesCommand(createAdapters),
|
|
10911
12222
|
createToolsCommand(createAdapters),
|