@kikimoradev/kcloud 1.9.0 → 1.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/main.js +1166 -164
  2. 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
- z.enum(["anthropic_api_key", "claude_oauth_token"]);
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",
@@ -782,6 +934,8 @@ const errorCodes = Object.keys({
782
934
  "agent.slug_taken": "conflict",
783
935
  "agent.directory_conflict": "rule_violation",
784
936
  "agent.repository_required": "rule_violation",
937
+ "agent.provider_token_required": "rule_violation",
938
+ "agent.provider_token_conflict": "rule_violation",
785
939
  "agent.tool_duplicate": "rule_violation",
786
940
  "agent.context_too_long": "rule_violation",
787
941
  "agent.docker_unavailable": "rule_violation",
@@ -1075,16 +1229,37 @@ const agentEventSchema = z.discriminatedUnion("event", [
1075
1229
  }),
1076
1230
  z.looseObject({
1077
1231
  ...roleEnvelope("session.text"),
1232
+ sessionId: z.string().optional(),
1078
1233
  text: z.string()
1079
1234
  }),
1235
+ z.looseObject({
1236
+ ...roleEnvelope("session.thinking"),
1237
+ sessionId: z.string().optional(),
1238
+ text: z.string(),
1239
+ truncated: z.boolean().optional()
1240
+ }),
1080
1241
  z.looseObject({
1081
1242
  ...roleEnvelope("session.tool"),
1243
+ sessionId: z.string().optional(),
1082
1244
  tool: z.string(),
1083
- detail: z.string()
1245
+ detail: z.string(),
1246
+ toolUseId: z.string().optional(),
1247
+ input: z.unknown().optional(),
1248
+ inputTruncated: z.boolean().optional()
1249
+ }),
1250
+ z.looseObject({
1251
+ ...roleEnvelope("session.toolResult"),
1252
+ sessionId: z.string().optional(),
1253
+ toolUseId: z.string(),
1254
+ output: z.string(),
1255
+ truncated: z.boolean().optional()
1084
1256
  }),
1085
1257
  z.looseObject({
1086
1258
  ...roleEnvelope("session.toolError"),
1087
- output: z.string()
1259
+ sessionId: z.string().optional(),
1260
+ toolUseId: z.string().optional(),
1261
+ output: z.string(),
1262
+ truncated: z.boolean().optional()
1088
1263
  })
1089
1264
  ]);
1090
1265
  z.looseObject({
@@ -1103,7 +1278,8 @@ agentEventSchema.options.map((option) => option.shape.event.value);
1103
1278
  provisioned: "platform.provisioned",
1104
1279
  sandboxStarted: "platform.sandboxStarted",
1105
1280
  sandboxStopped: "platform.sandboxStopped",
1106
- restartedByNode: "platform.restartedByNode"
1281
+ restartedByNode: "platform.restartedByNode",
1282
+ claudeAccountChanged: "platform.claudeAccountChanged"
1107
1283
  }).rawLog;
1108
1284
  const agentStateSchema = z.enum([
1109
1285
  "created",
@@ -2031,6 +2207,7 @@ const organizationSchema = z.object({
2031
2207
  slug: organizationSlugSchema,
2032
2208
  color: colorSchema,
2033
2209
  logoHash: logoHashSchema.nullable(),
2210
+ claudeSwitchThreshold: switchThresholdSchema,
2034
2211
  createdAt: z.iso.datetime()
2035
2212
  }).meta({ id: "Organization" });
2036
2213
  z.object({ items: z.array(organizationSchema) }).meta({ id: "OrganizationList" });
@@ -2091,7 +2268,8 @@ z.object({
2091
2268
  const updateOrganizationBodySchema = z.object({
2092
2269
  name: organizationNameSchema,
2093
2270
  slug: organizationSlugSchema,
2094
- color: colorSchema
2271
+ color: colorSchema,
2272
+ claudeSwitchThreshold: switchThresholdSchema.optional()
2095
2273
  }).meta({ id: "UpdateOrganizationBody" });
2096
2274
  z.object({ temporaryPassword: z.string() }).meta({ id: "TemporaryPassword" });
2097
2275
  z.object({ items: z.array(userSchema) }).meta({ id: "UserList" });
@@ -2191,6 +2369,8 @@ const auditActionSchema = z.enum([
2191
2369
  "credential.rotated",
2192
2370
  "credential.deleted",
2193
2371
  "credential.verified",
2372
+ "credential.identity_set",
2373
+ "project.claude_accounts_changed",
2194
2374
  "repository.registered",
2195
2375
  "repository.deleted",
2196
2376
  "tool.created",
@@ -2368,14 +2548,21 @@ const repositoryStatusSchema = z.enum([
2368
2548
  const repositoryNamePattern = /^[A-Za-z0-9](?:[A-Za-z0-9._-]{0,98}[A-Za-z0-9])?$/;
2369
2549
  const forbiddenBranchCharacters = /[\s\\~^:?*[\p{Cc}]/u;
2370
2550
  const isSafeBranchName = (value) => value.length > 0 && value.length <= 255 && !value.startsWith("-") && !forbiddenBranchCharacters.test(value);
2371
- const repositoryOwnerSchema = z.string().regex(repositoryNamePattern);
2551
+ const isRepositoryOwner = (value) => {
2552
+ const segments = value.split("/");
2553
+ return value.length <= 255 && segments.length <= 20 && segments.every((segment) => repositoryNamePattern.test(segment));
2554
+ };
2555
+ const repositoryOwnerSchema = z.string().refine(isRepositoryOwner, { message: "expected one to twenty names separated by a slash" });
2372
2556
  const repositoryNameSchema = z.string().regex(repositoryNamePattern);
2373
2557
  const defaultBranchSchema = z.string().refine(isSafeBranchName, { message: "expected a branch name git and the TSV accept" });
2374
2558
  const repositorySchema = z.object({
2375
2559
  id: idSchema,
2376
2560
  projectId: idSchema,
2377
2561
  version: z.int().nonnegative(),
2378
- githubId: z.int().positive(),
2562
+ githubId: z.int().positive().nullable(),
2563
+ provider: gitProviderSchema,
2564
+ serverUrl: z.string(),
2565
+ providerRepositoryId: z.string(),
2379
2566
  owner: repositoryOwnerSchema,
2380
2567
  name: repositoryNameSchema,
2381
2568
  defaultBranch: defaultBranchSchema,
@@ -2397,16 +2584,18 @@ z.object({
2397
2584
  id: idSchema,
2398
2585
  repositoryId: idSchema
2399
2586
  });
2400
- const gitHubRepositorySchema = z.object({
2587
+ const availableRepositorySchema = z.object({
2401
2588
  owner: repositoryOwnerSchema,
2402
2589
  name: repositoryNameSchema,
2403
- defaultBranch: defaultBranchSchema,
2590
+ defaultBranch: defaultBranchSchema.nullable(),
2404
2591
  isPrivate: z.boolean()
2405
- }).meta({ id: "GitHubRepository" });
2592
+ }).meta({ id: "AvailableRepository" });
2406
2593
  z.object({
2407
- items: z.array(gitHubRepositorySchema),
2408
- truncated: z.boolean()
2409
- }).meta({ id: "GitHubRepositoryList" });
2594
+ items: z.array(availableRepositorySchema),
2595
+ truncated: z.boolean(),
2596
+ provider: gitProviderSchema,
2597
+ serverUrl: z.string()
2598
+ }).meta({ id: "AvailableRepositoryList" });
2410
2599
  z.object({ credentialId: idSchema });
2411
2600
  const toolKindSchema = z.enum([
2412
2601
  "http_api",
@@ -2441,7 +2630,8 @@ const reservedEnvironmentPrefixes = [
2441
2630
  "ANTHROPIC_",
2442
2631
  "GH_",
2443
2632
  "DOCKER_",
2444
- "BC_"
2633
+ "BC_",
2634
+ "KC_GIT_"
2445
2635
  ];
2446
2636
  const reservedEnvironmentNames = /* @__PURE__ */ new Set([
2447
2637
  "PATH",
@@ -2721,7 +2911,9 @@ const agentRepositorySchema = z.object({
2721
2911
  owner: repositoryOwnerSchema,
2722
2912
  name: repositoryNameSchema,
2723
2913
  defaultBranch: defaultBranchSchema,
2724
- status: repositoryStatusSchema
2914
+ status: repositoryStatusSchema,
2915
+ provider: gitProviderSchema,
2916
+ serverUrl: z.string()
2725
2917
  }).meta({ id: "AgentRepository" });
2726
2918
  const agentRuntimeSchema = z.object({
2727
2919
  available: z.boolean(),
@@ -2765,6 +2957,17 @@ const agentNodeSchema = z.object({
2765
2957
  name: nodeNameSchema,
2766
2958
  state: nodeStateSchema
2767
2959
  }).meta({ id: "AgentNode" });
2960
+ const agentClaudeAccountSchema = z.object({
2961
+ current: z.object({
2962
+ id: idSchema,
2963
+ label: z.string(),
2964
+ kind: claudeCredentialKindSchema,
2965
+ position: z.int().positive().nullable()
2966
+ }).nullable(),
2967
+ queueSize: z.int().nonnegative(),
2968
+ waitReason: claudeAccountWaitReasonSchema.nullable(),
2969
+ waitUntil: z.iso.datetime().nullable()
2970
+ }).meta({ id: "AgentClaudeAccount" });
2768
2971
  agentListItemSchema.extend({
2769
2972
  version: z.int().nonnegative(),
2770
2973
  description: z.string(),
@@ -2773,8 +2976,10 @@ agentListItemSchema.extend({
2773
2976
  settings: kikimoraSettingsSchema,
2774
2977
  effectiveSettings: workerSettingsSchema,
2775
2978
  prompts: agentPromptsSchema,
2776
- claudeCredential: credentialSummarySchema,
2777
- githubCredential: credentialSummarySchema,
2979
+ claudeCredential: credentialSummarySchema.nullable(),
2980
+ claudeAccount: agentClaudeAccountSchema,
2981
+ githubCredential: credentialSummarySchema.nullable(),
2982
+ gitCredentials: z.array(gitCredentialSummarySchema),
2778
2983
  repositories: z.array(agentRepositorySchema),
2779
2984
  toolAssignments: z.array(agentToolSchema),
2780
2985
  release: releaseSummarySchema.nullable(),
@@ -2791,10 +2996,7 @@ const uniqueDirectories = {
2791
2996
  message: "expected every repository to use its own directory",
2792
2997
  path: ["repositories"]
2793
2998
  };
2794
- const distinctCredentials = {
2795
- message: "expected the Claude and the GitHub credential to be two credentials",
2796
- path: ["githubCredentialId"]
2797
- };
2999
+ const agentGitCredentialIdsSchema = z.array(idSchema).max(8).refine((ids) => new Set(ids).size === ids.length, { message: "expected every credential once" });
2798
3000
  const hasUniqueTools = (assignments) => new Set(assignments.map((assignment) => assignment.toolId)).size === assignments.length;
2799
3001
  const agentRepositoryLinksSchema = z.array(agentRepositoryLinkSchema).min(1).max(32);
2800
3002
  const agentToolAssignmentsSchema = z.array(toolAssignmentSchema).max(32).refine(hasUniqueTools, { message: "expected every tool to be assigned once" });
@@ -2807,15 +3009,16 @@ const createAgentBodySchema = z.object({
2807
3009
  kikimoraVersion: kikimoraVersionSchema,
2808
3010
  nodeId: idSchema,
2809
3011
  requiredCapabilities: requiredCapabilitiesSchema.optional(),
2810
- claudeCredentialId: idSchema,
2811
- githubCredentialId: idSchema,
3012
+ claudeCredentialId: idSchema.optional(),
3013
+ githubCredentialId: idSchema.optional(),
3014
+ gitCredentialIds: agentGitCredentialIdsSchema.default([]),
2812
3015
  repositories: agentRepositoryLinksSchema,
2813
3016
  toolAssignments: agentToolAssignmentsSchema,
2814
3017
  prompts: agentPromptsSchema,
2815
3018
  settings: kikimoraSettingsSchema,
2816
3019
  timezone: timezoneSchema,
2817
3020
  machine: agentMachineInputSchema
2818
- }).refine((body) => hasUniqueDirectories(body.repositories), uniqueDirectories).refine((body) => body.claudeCredentialId !== body.githubCredentialId, distinctCredentials).meta({ id: "CreateAgentBody" });
3021
+ }).refine((body) => hasUniqueDirectories(body.repositories), uniqueDirectories).meta({ id: "CreateAgentBody" });
2819
3022
  const updateAgentSectionsSchema = z.object({
2820
3023
  general: z.object({
2821
3024
  name: agentNameSchema,
@@ -2824,9 +3027,10 @@ const updateAgentSectionsSchema = z.object({
2824
3027
  }).optional(),
2825
3028
  version: kikimoraVersionSchema.optional(),
2826
3029
  credentials: z.object({
2827
- claudeCredentialId: idSchema,
2828
- githubCredentialId: idSchema
2829
- }).refine((section) => section.claudeCredentialId !== section.githubCredentialId, distinctCredentials).optional(),
3030
+ claudeCredentialId: idSchema.optional(),
3031
+ githubCredentialId: idSchema.optional(),
3032
+ gitCredentialIds: agentGitCredentialIdsSchema.default([])
3033
+ }).optional(),
2830
3034
  repositories: agentRepositoryLinksSchema.optional(),
2831
3035
  tools: agentToolAssignmentsSchema.optional(),
2832
3036
  prompts: agentPromptsSchema.optional(),
@@ -3389,7 +3593,8 @@ const notificationKindSchema = z.enum([
3389
3593
  "backup.overdue",
3390
3594
  "host_operation.finished",
3391
3595
  "dead_letter.queued",
3392
- "node.resumed"
3596
+ "node.resumed",
3597
+ "project.claude_accounts_exhausted"
3393
3598
  ]);
3394
3599
  const notificationSeveritySchema = z.enum([
3395
3600
  "info",
@@ -3403,7 +3608,8 @@ const notificationTargetTypeSchema = z.enum([
3403
3608
  "queue",
3404
3609
  "volume",
3405
3610
  "platform",
3406
- "node"
3611
+ "node",
3612
+ "project"
3407
3613
  ]);
3408
3614
  const hostOperationOutcomes = [
3409
3615
  "succeeded",
@@ -3437,7 +3643,10 @@ z.discriminatedUnion("operation", [
3437
3643
  role: volumeRoleSchema
3438
3644
  })
3439
3645
  ]);
3440
- z.strictObject({ label: textParam }), z.strictObject({
3646
+ z.strictObject({
3647
+ label: textParam,
3648
+ kind: credentialKindSchema
3649
+ }), z.strictObject({
3441
3650
  label: textParam,
3442
3651
  expiresAt: instantParam
3443
3652
  }), z.strictObject({
@@ -3470,7 +3679,10 @@ z.strictObject({ label: textParam }), z.strictObject({
3470
3679
  }).refine((params) => params.pausedAgents === 0 === (params.pausedAgentNames === ""), {
3471
3680
  message: "expected the names of the paused agents exactly when there are some",
3472
3681
  path: ["pausedAgentNames"]
3473
- });
3682
+ }), z.union([z.strictObject({
3683
+ blocked: z.literal(false),
3684
+ nearestResetAt: instantParam
3685
+ }), z.strictObject({ blocked: z.literal(true) })]);
3474
3686
  const notificationParamsSchema = z.record(z.string(), z.union([
3475
3687
  z.string(),
3476
3688
  z.number(),
@@ -4413,6 +4625,22 @@ const runOf = async (adaptersOf, { settings, context }) => {
4413
4625
  };
4414
4626
  };
4415
4627
  //#endregion
4628
+ //#region src/git-providers.ts
4629
+ const providerNames = {
4630
+ github: "GitHub",
4631
+ gitlab: "GitLab",
4632
+ bitbucket: "Bitbucket"
4633
+ };
4634
+ const isGitProvider = (value) => gitProviders.some((provider) => provider === value);
4635
+ const providerNameOf = (provider) => isGitProvider(provider) ? providerNames[provider] : "the provider";
4636
+ const gitProviderOf = (credential) => providerOfKind(credential.kind);
4637
+ const credentialServerOf = (credential) => {
4638
+ const provider = gitProviderOf(credential);
4639
+ if (provider === null) return null;
4640
+ return credential.metadata.serverUrl ?? defaultServerUrls[provider];
4641
+ };
4642
+ const kindOfProvider = (provider) => isGitProvider(provider) ? gitCredentialKindOf[provider] : "<kind>";
4643
+ //#endregion
4416
4644
  //#region src/commands/agents/shared.ts
4417
4645
  const agentOption = { project: { type: "string" } };
4418
4646
  const agentUsage = [["--project <name>", "the project of the agent, when the agent is named alone"]];
@@ -4421,7 +4649,11 @@ const actionsAfterPause = /* @__PURE__ */ new Set([
4421
4649
  "restart",
4422
4650
  "resume"
4423
4651
  ]);
4652
+ const serverOf = (serverUrl) => typeof serverUrl === "string" ? serverUrl : "its server";
4424
4653
  const agentHints = {
4654
+ "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.`,
4655
+ "agent.provider_token_conflict": ({ provider, serverUrl }) => `Two credentials of the agent are for ${providerNameOf(provider)} at ${serverOf(serverUrl)}; gitCredentialIds takes one for each server.`,
4656
+ "credential.kind_mismatch": "claudeCredentialId takes a Claude credential, and gitCredentialIds the github_pat, gitlab_token or bitbucket_token credentials of the project.",
4425
4657
  "agent.not_found": "kcloud agents list --project <name> shows the agents.",
4426
4658
  "project.not_found": "kcloud projects list shows the projects you are a member of.",
4427
4659
  "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.",
@@ -4429,6 +4661,7 @@ const agentHints = {
4429
4661
  "concurrency.conflict": "Someone changed the agent meanwhile; read it again and retry.",
4430
4662
  "agent.node_unsuitable": "The node lacks a capability the agent requires; kcloud nodes show <node> lists what it offers.",
4431
4663
  "node.offline": "The node of the agent is not connected; kcloud nodes show <node> tells when it was last seen.",
4664
+ "account.queue_empty": "The queue of Claude accounts of the project is empty; kcloud projects accounts add <account> --project <name> puts one in it.",
4432
4665
  "node.outdated": "The node of the agent runs an older release; kcloud nodes show <node> names the release it runs and its update."
4433
4666
  };
4434
4667
  const agentFrom = async (run, reference, project) => {
@@ -4781,8 +5014,7 @@ const agentPlanOf = (agent) => ({
4781
5014
  kikimoraVersion: agent.kikimoraVersion,
4782
5015
  ...agent.node === null ? {} : { nodeId: agent.node.id },
4783
5016
  requiredCapabilities: agent.requiredCapabilities,
4784
- claudeCredentialId: agent.claudeCredential.id,
4785
- githubCredentialId: agent.githubCredential.id,
5017
+ gitCredentialIds: agent.gitCredentials.map(({ id }) => id),
4786
5018
  repositories: agent.repositories.map(({ repositoryId, directory }) => ({
4787
5019
  repositoryId,
4788
5020
  directory
@@ -4802,9 +5034,12 @@ const agentPlanOf = (agent) => ({
4802
5034
  });
4803
5035
  const machineLimitKeys = /* @__PURE__ */ new Set(["cpus", "memoryMb"]);
4804
5036
  const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
4805
- const warnIgnored = ({ context }, keys) => {
5037
+ const warned = ({ context }, text) => {
5038
+ context.terminal.error(context.terminal.errorStyles.warning(`warning ${text}`));
5039
+ };
5040
+ const warnIgnored = (run, keys) => {
4806
5041
  const named = keys.map((key) => `machine.${key}`).join(" and ");
4807
- context.terminal.error(context.terminal.errorStyles.warning(`warning ${named} ${keys.length === 1 ? "is" : "are"} ignored: an agent has no limits of its own, and the pools of its node limit its sessions.`));
5042
+ 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.`);
4808
5043
  };
4809
5044
  const machineWithoutLimits = (run, machine) => {
4810
5045
  if (!isRecord(machine)) return machine;
@@ -4813,17 +5048,40 @@ const machineWithoutLimits = (run, machine) => {
4813
5048
  warnIgnored(run, ignored);
4814
5049
  return Object.fromEntries(Object.entries(machine).filter(([key]) => !machineLimitKeys.has(key)));
4815
5050
  };
4816
- const agentFileWithoutLimits = (run, document) => isRecord(document) && "machine" in document ? {
4817
- ...document,
4818
- machine: machineWithoutLimits(run, document.machine)
4819
- } : document;
4820
- const agentChangeWithoutLimits = (run, document) => isRecord(document) && isRecord(document.sections) && "machine" in document.sections ? {
4821
- ...document,
4822
- sections: {
4823
- ...document.sections,
4824
- machine: machineWithoutLimits(run, document.sections.machine)
4825
- }
4826
- } : document;
5051
+ const claudeCredentialKey = "claudeCredentialId";
5052
+ const withoutClaudeCredential = (run, fields) => {
5053
+ if (!(claudeCredentialKey in fields)) return fields;
5054
+ warned(run, `${claudeCredentialKey} is ignored: an agent works on the queue of Claude accounts of its project, which kcloud projects accounts list shows.`);
5055
+ return Object.fromEntries(Object.entries(fields).filter(([key]) => key !== claudeCredentialKey));
5056
+ };
5057
+ const acceptedAgentFile = (run, document) => {
5058
+ if (!isRecord(document)) return document;
5059
+ const kept = withoutClaudeCredential(run, document);
5060
+ return "machine" in kept ? {
5061
+ ...kept,
5062
+ machine: machineWithoutLimits(run, kept.machine)
5063
+ } : kept;
5064
+ };
5065
+ const credentialsSectionOf = (run, sections) => {
5066
+ if (!isRecord(sections.credentials)) return sections;
5067
+ const credentials = withoutClaudeCredential(run, sections.credentials);
5068
+ if (Object.keys(credentials).length > 0) return {
5069
+ ...sections,
5070
+ credentials
5071
+ };
5072
+ return Object.fromEntries(Object.entries(sections).filter(([key]) => key !== "credentials"));
5073
+ };
5074
+ const acceptedAgentChange = (run, document) => {
5075
+ if (!isRecord(document) || !isRecord(document.sections)) return document;
5076
+ const sections = credentialsSectionOf(run, document.sections);
5077
+ return {
5078
+ ...document,
5079
+ sections: "machine" in sections ? {
5080
+ ...sections,
5081
+ machine: machineWithoutLimits(run, sections.machine)
5082
+ } : sections
5083
+ };
5084
+ };
4827
5085
  //#endregion
4828
5086
  //#region src/commands/agents/config.ts
4829
5087
  const agentsOf = async (scope, project) => {
@@ -4939,7 +5197,7 @@ const configCommands = (adaptersOf) => [
4939
5197
  const agentId = await agentFrom(run, invocation.positionals[0], invocation.values.project);
4940
5198
  const file = invocation.values.file;
4941
5199
  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.");
4942
- const agent = await changedAgent(run.api, agentId, agentChangeWithoutLimits(run, await documentOf(run, file)));
5200
+ const agent = await changedAgent(run.api, agentId, acceptedAgentChange(run, await documentOf(run, file)));
4943
5201
  return printed(run, agent, (changed) => changed.restartRequired ? `Changed ${changed.name}; it needs ${changeTakerOf(changed)} to take the change.` : `Changed ${changed.name}.`);
4944
5202
  }
4945
5203
  }),
@@ -4992,13 +5250,13 @@ const waitTexts = {
4992
5250
  };
4993
5251
  const reasonOf$1 = ({ reason }) => reason ?? "slot";
4994
5252
  const isKnownReason = (reason) => Object.hasOwn(waitTexts, reason);
4995
- const waitTextOf = (reason) => isKnownReason(reason) ? waitTexts[reason] : `held by the node (${reason})`;
5253
+ const waitTextOf$1 = (reason) => isKnownReason(reason) ? waitTexts[reason] : `held by the node (${reason})`;
4996
5254
  const waitRowsOf = ({ runtime }) => {
4997
5255
  const { admission } = runtime;
4998
5256
  if (admission === null) return [];
4999
5257
  const waits = ["monitor", "executor"].flatMap((role) => {
5000
5258
  const state = admission[role];
5001
- return state?.state === "waiting" ? [`${role} ${waitTextOf(reasonOf$1(state))}`] : [];
5259
+ return state?.state === "waiting" ? [`${role} ${waitTextOf$1(reasonOf$1(state))}`] : [];
5002
5260
  });
5003
5261
  return waits.length === 0 ? [] : [["admissions", waits.join("; ")]];
5004
5262
  };
@@ -5017,10 +5275,33 @@ const claudeCodeOf = ({ runtime, release }) => {
5017
5275
  if (running?.source === "host") return "host";
5018
5276
  return running?.version ?? release?.claudeCodeVersion ?? "unknown";
5019
5277
  };
5278
+ const claudeAccountWaitOf = ({ waitReason, waitUntil }) => {
5279
+ switch (waitReason) {
5280
+ case "exhausted": return waitUntil === null ? "every account is exhausted" : `every account is exhausted until ${waitUntil}`;
5281
+ case "blocked": return "no account of the queue can work";
5282
+ case "empty": return "the queue of the project is empty";
5283
+ default: return null;
5284
+ }
5285
+ };
5286
+ const claudeAccountOf = ({ claudeAccount }) => {
5287
+ const { current, queueSize } = claudeAccount;
5288
+ const account = current === null ? "none" : `${current.label} (${current.kind}), ${current.position === null ? "outside the queue" : `${String(current.position)} of ${String(queueSize)}`}`;
5289
+ const wait = claudeAccountWaitOf(claudeAccount);
5290
+ return wait === null ? account : `${account}; ${wait}`;
5291
+ };
5020
5292
  const failureRowsOf = ({ failureReason, retryAt }) => {
5021
5293
  if (failureReason === null) return [];
5022
5294
  return [["failure", retryAt === null ? failureReason : `${failureReason}, retry at ${retryAt}`]];
5023
5295
  };
5296
+ const gitCredentialLinesOf = ({ gitCredentials }) => gitCredentials.length === 0 ? ["No git credential is set."] : ["GIT CREDENTIALS", renderTable([
5297
+ { header: "PROVIDER" },
5298
+ { header: "SERVER" },
5299
+ { header: "LABEL" }
5300
+ ], gitCredentials.map(({ provider, serverUrl, label }) => [
5301
+ provider,
5302
+ serverUrl,
5303
+ label
5304
+ ]))];
5024
5305
  const detailsOf = (agent) => [
5025
5306
  renderTable([{ header: "FIELD" }, { header: "VALUE" }], [
5026
5307
  ["name", agent.name],
@@ -5030,6 +5311,7 @@ const detailsOf = (agent) => [
5030
5311
  ["observed", agent.observedState],
5031
5312
  ["release", agent.kikimoraVersion],
5032
5313
  ["claude code", claudeCodeOf(agent)],
5314
+ ["claude account", claudeAccountOf(agent)],
5033
5315
  ["node", agent.node === null ? "none" : `${agent.node.name} (${agent.node.state})`],
5034
5316
  ["requires", agent.requiredCapabilities.length === 0 ? "nothing" : agent.requiredCapabilities.join(", ")],
5035
5317
  ["timezone", agent.timezone],
@@ -5050,13 +5332,19 @@ const detailsOf = (agent) => [
5050
5332
  "",
5051
5333
  agent.repositories.length === 0 ? "No repository is cloned into the workspace." : renderTable([
5052
5334
  { header: "REPOSITORY" },
5335
+ { header: "PROVIDER" },
5336
+ { header: "SERVER" },
5053
5337
  { header: "DIRECTORY" },
5054
5338
  { header: "STATUS" }
5055
5339
  ], agent.repositories.map((repository) => [
5056
5340
  `${repository.owner}/${repository.name}`,
5341
+ repository.provider,
5342
+ repository.serverUrl,
5057
5343
  repository.directory,
5058
5344
  repository.status
5059
- ]))
5345
+ ])),
5346
+ "",
5347
+ ...gitCredentialLinesOf(agent)
5060
5348
  ].join("\n");
5061
5349
  //#endregion
5062
5350
  //#region src/commands/agents/placement.ts
@@ -5083,7 +5371,6 @@ const placedNodeId = async (run, { projectId, given, ask }) => {
5083
5371
  };
5084
5372
  //#endregion
5085
5373
  //#region src/commands/agents/create.ts
5086
- const claudeKinds = /* @__PURE__ */ new Set(["anthropic_api_key", "claude_oauth_token"]);
5087
5374
  const catalogueOf = async (api, projectId) => {
5088
5375
  const path = { params: { path: { id: projectId } } };
5089
5376
  return {
@@ -5093,17 +5380,35 @@ const catalogueOf = async (api, projectId) => {
5093
5380
  releases: dataOf("list the releases", await api.GET("/releases")).items.map(({ version }) => version)
5094
5381
  };
5095
5382
  };
5096
- const chosenCredential = async (prompt, catalogue, kinds, question) => {
5097
- const choices = catalogue.credentials.filter((credential) => kinds(credential.kind)).map((credential) => ({
5098
- value: credential.id,
5099
- label: credential.label,
5100
- hint: credential.kind
5101
- }));
5102
- if (choices.length === 0) throw new CliError(`The project holds no credential for ${question}.`, "kcloud credentials add --project <name> stores one.");
5103
- return prompt.select({
5104
- message: question,
5105
- choices
5106
- });
5383
+ const serversOf = (catalogue, chosen) => {
5384
+ const servers = /* @__PURE__ */ new Map();
5385
+ for (const { repositoryId } of chosen) {
5386
+ const repository = catalogue.repositories.find(({ id }) => id === repositoryId);
5387
+ if (repository === void 0) continue;
5388
+ const { provider, serverUrl } = repository;
5389
+ servers.set(`${provider} ${serverUrl}`, {
5390
+ provider,
5391
+ serverUrl
5392
+ });
5393
+ }
5394
+ return [...servers.values()].sort((first, second) => gitProviders.indexOf(first.provider) - gitProviders.indexOf(second.provider) || first.serverUrl.localeCompare(second.serverUrl));
5395
+ };
5396
+ const gitCredentialQuestionOf = ({ provider, serverUrl }) => provider === "github" ? "The GitHub credential it clones with" : `The ${providerNameOf(provider)} credential for ${serverUrl} it clones with`;
5397
+ const chosenGitCredentials = async (prompt, catalogue, chosen) => {
5398
+ const ids = [];
5399
+ for (const server of serversOf(catalogue, chosen)) {
5400
+ const choices = catalogue.credentials.filter((credential) => gitProviderOf(credential) === server.provider && credentialServerOf(credential) === server.serverUrl).map((credential) => ({
5401
+ value: credential.id,
5402
+ label: credential.label,
5403
+ hint: credential.kind
5404
+ }));
5405
+ 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.`);
5406
+ ids.push(await prompt.select({
5407
+ message: gitCredentialQuestionOf(server),
5408
+ choices
5409
+ }));
5410
+ }
5411
+ return ids;
5107
5412
  };
5108
5413
  const chosenRepositories = async (prompt, catalogue) => {
5109
5414
  if (catalogue.repositories.length === 0) throw new CliError("The project has no repository to clone.", "kcloud repositories add <owner/name> --project <name> registers one.");
@@ -5116,7 +5421,7 @@ const chosenRepositories = async (prompt, catalogue) => {
5116
5421
  choices: [...left.map((repository) => ({
5117
5422
  value: repository.id,
5118
5423
  label: `${repository.owner}/${repository.name}`,
5119
- hint: repository.defaultBranch
5424
+ hint: `${repository.provider} ${repository.defaultBranch}`
5120
5425
  })), ...chosen.length === 0 ? [] : [{
5121
5426
  value: "",
5122
5427
  label: "no more repositories",
@@ -5211,15 +5516,16 @@ const askedPlan = async (run, projectId, values, required) => {
5211
5516
  }))
5212
5517
  });
5213
5518
  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(", ")}.`);
5519
+ const repositories = await chosenRepositories(run.prompt, catalogue);
5520
+ const gitCredentialIds = await chosenGitCredentials(run.prompt, catalogue, repositories);
5214
5521
  return {
5215
5522
  name,
5216
5523
  description: "",
5217
5524
  kikimoraVersion: release,
5218
5525
  nodeId,
5219
5526
  ...requirementOf(required),
5220
- claudeCredentialId: await chosenCredential(run.prompt, catalogue, (kind) => claudeKinds.has(kind), "The Claude credential it works with"),
5221
- githubCredentialId: await chosenCredential(run.prompt, catalogue, (kind) => kind === "github_pat", "The GitHub credential it clones with"),
5222
- repositories: [...await chosenRepositories(run.prompt, catalogue)],
5527
+ gitCredentialIds: [...gitCredentialIds],
5528
+ repositories: [...repositories],
5223
5529
  toolAssignments: [...await chosenTools(run.prompt, catalogue)],
5224
5530
  prompts: {
5225
5531
  monitor: await promptText(run, values["monitor-prompt-file"], "What the monitor should watch for"),
@@ -5235,7 +5541,7 @@ const askedPlan = async (run, projectId, values, required) => {
5235
5541
  };
5236
5542
  const isObject = (document) => typeof document === "object" && document !== null && !Array.isArray(document);
5237
5543
  const fileOf = async (run, projectId, { file, node }, required) => {
5238
- const document = agentFileWithoutLimits(run, await documentOf(run, file));
5544
+ const document = acceptedAgentFile(run, await documentOf(run, file));
5239
5545
  const placed = !isObject(document) || node === void 0 && "nodeId" in document ? document : {
5240
5546
  ...document,
5241
5547
  nodeId: await placedNodeId(run, {
@@ -5268,6 +5574,7 @@ const createCommand = (adaptersOf) => defineCommand({
5268
5574
  ["--docker", "give the agent a Docker sidecar, when the project allows it"],
5269
5575
  ["--browser", "give the agent a browser"],
5270
5576
  ["", "kcloud agents show <agent> --as-file prints what --file takes"],
5577
+ ["", "the file names the git tokens as gitCredentialIds, one for each server of its repositories"],
5271
5578
  ["", "--node and --require replace the nodeId and requiredCapabilities of the file"]
5272
5579
  ],
5273
5580
  options: {
@@ -5477,7 +5784,7 @@ const workKinds = /* @__PURE__ */ new Set([
5477
5784
  "summarising"
5478
5785
  ]);
5479
5786
  const organizationDashboardOf = async (api, organizationId) => dataOf("show the dashboard of the organization", await api.GET("/organizations/{organizationId}/dashboard", { params: { path: { organizationId } } }), organizationHints);
5480
- const statusOf = (agent) => agent.status === "transitioning" ? `${agent.status} (${agent.state})` : agent.status;
5787
+ const statusOf$1 = (agent) => agent.status === "transitioning" ? `${agent.status} (${agent.state})` : agent.status;
5481
5788
  const taskOf = (agent) => {
5482
5789
  const { activity } = agent;
5483
5790
  if (activity === null) return noTask;
@@ -5490,7 +5797,7 @@ const dashboardText = (dashboard) => {
5490
5797
  const rows = dashboard.projects.flatMap((project) => project.agents.map((agent) => [
5491
5798
  project.slug,
5492
5799
  agent.slug,
5493
- statusOf(agent),
5800
+ statusOf$1(agent),
5494
5801
  agent.since,
5495
5802
  taskOf(agent),
5496
5803
  agent.costTodayUsd.toFixed(2)
@@ -5523,7 +5830,7 @@ const frameLineOf = (names, frame, at) => {
5523
5830
  slug: agent.slug
5524
5831
  });
5525
5832
  const project = names.projects.get(agent.projectId) ?? agent.projectId;
5526
- return `${at.toISOString()} ${project}/${agent.slug} ${statusOf(agent)} ${taskOf(agent)}`;
5833
+ return `${at.toISOString()} ${project}/${agent.slug} ${statusOf$1(agent)} ${taskOf(agent)}`;
5527
5834
  }
5528
5835
  const removed = dashboardRemovalSchema.safeParse(frame.data);
5529
5836
  if (frame.event === "dashboard.removed" && removed.success) {
@@ -5690,10 +5997,10 @@ const pageOptions = {
5690
5997
  };
5691
5998
  const allUsage = ["--all", "follow the cursor to the end instead of one page"];
5692
5999
  const pageUsage = [["--limit <n>", `how many rows (default: 50)`], allUsage];
5693
- const dayOf$1 = (date) => date.toISOString().slice(0, 10);
6000
+ const dayOf$2 = (date) => date.toISOString().slice(0, 10);
5694
6001
  const costWindowOf = (days, now) => ({
5695
- from: dayOf$1(/* @__PURE__ */ new Date(now.getTime() - (days - 1) * 24 * 60 * 6e4)),
5696
- to: dayOf$1(now)
6002
+ from: dayOf$2(/* @__PURE__ */ new Date(now.getTime() - (days - 1) * 24 * 60 * 6e4)),
6003
+ to: dayOf$2(now)
5697
6004
  });
5698
6005
  const windowFrom = (days, now) => {
5699
6006
  const span = days === void 0 ? 7 : Number(days);
@@ -6417,16 +6724,57 @@ const secretUsage = [
6417
6724
  ];
6418
6725
  //#endregion
6419
6726
  //#region src/commands/credentials.ts
6420
- const projectOption$2 = { project: { type: "string" } };
6421
- const projectUsage$2 = [["--project <name>", "the project the credential belongs to, by slug, name or id"]];
6422
- const usageUnavailable = "Only a Claude Code OAuth token reports the usage of a subscription.";
6727
+ const projectOption$3 = { project: { type: "string" } };
6728
+ const projectUsage$3 = [["--project <name>", "the project the credential belongs to, by slug, name or id"]];
6729
+ const usageUnavailable$1 = "Only a Claude Code OAuth token reports the usage of a subscription; kcloud claude-accounts usage shows it.";
6730
+ const defaultKind = "github_pat";
6731
+ const countOf = (value) => typeof value === "number" ? value : 0;
6732
+ const inUseHint = ({ agents, repositories }) => {
6733
+ return `Point the ${[...countOf(agents) > 0 || countOf(repositories) === 0 ? ["agents"] : [], ...countOf(repositories) > 0 ? ["repositories"] : []].join(" and the ")} at another credential first.`;
6734
+ };
6423
6735
  const credentialHints = {
6424
6736
  "project.not_found": "kcloud projects list shows the projects you are a member of.",
6425
6737
  "credential.not_found": "kcloud credentials list --project <name> shows them.",
6426
6738
  "credential.label_taken": "Another credential of the project already has that label.",
6427
- "credential.in_use": "Point the agents at another credential first.",
6428
- "credential.usage_unavailable": usageUnavailable
6739
+ "credential.in_use": inUseHint,
6740
+ "credential.usage_unavailable": usageUnavailable$1,
6741
+ "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."
6742
+ };
6743
+ const addHints = {
6744
+ ...credentialHints,
6745
+ "credential.server_refused": "Give the https address of a public host, without credentials, query or fragment, as --server-url.",
6746
+ "validation.failed": "Check the options: --server-url takes an http or https address, and a Bitbucket Cloud API token (ATATT...) needs --account-email."
6747
+ };
6748
+ const gitOptionsUsage = [
6749
+ ["--server-url <url>", "the server of a GitLab or Bitbucket token (default: gitlab.com or bitbucket.org)"],
6750
+ ["--account-email <email>", "the Atlassian account of a Bitbucket Cloud API token"],
6751
+ ["--commit-name <name>", "the name its commits carry, with --commit-email"],
6752
+ ["--commit-email <email>", "the e-mail its commits carry, with --commit-name"]
6753
+ ];
6754
+ const gitOptions = {
6755
+ "server-url": { type: "string" },
6756
+ "account-email": { type: "string" },
6757
+ "commit-name": { type: "string" },
6758
+ "commit-email": { type: "string" }
6759
+ };
6760
+ const gitFieldsOf = (kind, values) => {
6761
+ const given = Object.entries(values).filter(([name, value]) => name in gitOptions && value !== void 0).map(([name]) => `--${name}`);
6762
+ if (!isGitCredentialKind(kind)) {
6763
+ if (given.length === 0) return {};
6764
+ throw new UsageError(`${given.join(", ")} only suits a ${gitCredentialKinds.join(", ")} credential, not ${kind}.`);
6765
+ }
6766
+ 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.");
6767
+ 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.");
6768
+ return {
6769
+ ...values["server-url"] === void 0 ? {} : { serverUrl: values["server-url"] },
6770
+ ...values["account-email"] === void 0 ? {} : { accountEmail: values["account-email"] },
6771
+ ...values["commit-name"] === void 0 ? {} : { commitName: values["commit-name"] },
6772
+ ...values["commit-email"] === void 0 ? {} : { commitEmail: values["commit-email"] }
6773
+ };
6429
6774
  };
6775
+ 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>`);
6776
+ const plainShellWord = /^[A-Za-z0-9_./@:-]+$/;
6777
+ const shellWord = (value) => plainShellWord.test(value) ? value : `'${value.replaceAll("'", `'\\''`)}'`;
6430
6778
  const percent = 100;
6431
6779
  const notApplicable = "-";
6432
6780
  const usedOf = (window) => window === null ? notApplicable : `${String(Math.round(window.utilization * percent))}%`;
@@ -6453,12 +6801,13 @@ const usageReportOf = (credential) => {
6453
6801
  window?.resetsAt ?? notApplicable
6454
6802
  ]))}\n${usage.stale ? `Read at ${usage.readAt}; the latest reading failed.` : `Read at ${usage.readAt}.`}`;
6455
6803
  };
6804
+ const accountOf = ({ metadata }) => metadata.accountName ?? metadata.githubLogin ?? notApplicable;
6456
6805
  const isoDateLength = 10;
6457
6806
  const expiryOf = ({ expiresAt, expiresAtComputed }) => {
6458
6807
  if (expiresAt === null) return "never";
6459
6808
  return expiresAtComputed ? `about ${expiresAt.slice(0, isoDateLength)}` : expiresAt;
6460
6809
  };
6461
- const projectFrom$2 = async (run, given) => {
6810
+ const projectFrom$3 = async (run, given) => {
6462
6811
  if (given === void 0) throw new UsageError("This command needs a project.", "Pass --project <name>; kcloud projects list shows them.");
6463
6812
  return resolveProjectId(run, given);
6464
6813
  };
@@ -6474,55 +6823,60 @@ const resolveCredential = async (api, projectId, reference) => {
6474
6823
  const resolveCredentialId = async (api, projectId, reference) => (await resolveCredential(api, projectId, reference)).id;
6475
6824
  const createCredentialsCommand = (adaptersOf) => defineGroup({
6476
6825
  name: "credentials",
6477
- summary: "The Claude and GitHub credentials of a project.",
6826
+ summary: "The GitHub, GitLab and Bitbucket tokens of a project.",
6478
6827
  usage: "credentials <command>",
6479
6828
  subcommands: [
6480
6829
  defineCommand({
6481
6830
  name: "list",
6482
6831
  summary: "List the credentials of a project.",
6483
6832
  usage: "credentials list --project <name>",
6484
- optionsUsage: projectUsage$2,
6485
- options: projectOption$2,
6833
+ optionsUsage: projectUsage$3,
6834
+ options: projectOption$3,
6486
6835
  positionals: {
6487
6836
  min: 0,
6488
6837
  max: 0
6489
6838
  },
6490
6839
  run: async (invocation) => {
6491
6840
  const run = await runOf(adaptersOf, invocation);
6492
- const projectId = await projectFrom$2(run, invocation.values.project);
6841
+ const projectId = await projectFrom$3(run, invocation.values.project);
6493
6842
  const items = await credentialsOf(run.api, projectId);
6494
- return printed(run, items, (credentials) => credentials.length === 0 ? "The project holds no credential yet." : renderTable([
6843
+ return printed(run, items, (credentials) => credentials.length === 0 ? "The project holds no credential yet." : [renderTable([
6495
6844
  { header: "LABEL" },
6496
6845
  { header: "KIND" },
6846
+ { header: "SERVER" },
6847
+ { header: "ACCOUNT" },
6497
6848
  { header: "FINGERPRINT" },
6498
6849
  { header: "VERIFIED" },
6499
6850
  { header: "EXPIRES" },
6500
- { header: "USAGE" },
6501
6851
  { header: "ID" }
6502
6852
  ], credentials.map((credential) => [
6503
6853
  credential.label,
6504
6854
  credential.kind,
6855
+ credentialServerOf(credential) ?? notApplicable,
6856
+ accountOf(credential),
6505
6857
  credential.fingerprint,
6506
6858
  credential.verification,
6507
6859
  expiryOf(credential),
6508
- usageSummaryOf(credential),
6509
6860
  credential.id
6510
- ])));
6861
+ ])), ...missingIdentityNotes(credentials, invocation.values.project).flatMap((note, index) => index === 0 ? ["", note] : [note])].join("\n"));
6511
6862
  }
6512
6863
  }),
6513
6864
  defineCommand({
6514
6865
  name: "add",
6515
6866
  summary: "Store a credential in a project, asking for the secret.",
6516
- usage: "credentials add --project <name> --kind <kind> --label <label> [--secret-file <path>]",
6867
+ usage: "credentials add --project <name> [--kind <kind>] --label <label> [--server-url <url>] [--secret-file <path>]",
6517
6868
  optionsUsage: [
6518
- ...projectUsage$2,
6519
- ["--kind <kind>", `one of ${credentialKinds.join(", ")}`],
6869
+ ...projectUsage$3,
6870
+ ["--kind <kind>", `the kind of the credential (default: ${defaultKind})`],
6871
+ ["", "kcloud claude-accounts add stores a Claude account in the organization"],
6520
6872
  ["--label <label>", "how the panel and the agents name it"],
6873
+ ...gitOptionsUsage,
6521
6874
  ...secretUsage
6522
6875
  ],
6523
6876
  options: {
6524
- ...projectOption$2,
6877
+ ...projectOption$3,
6525
6878
  ...secretOptions,
6879
+ ...gitOptions,
6526
6880
  kind: { type: "string" },
6527
6881
  label: { type: "string" }
6528
6882
  },
@@ -6533,11 +6887,13 @@ const createCredentialsCommand = (adaptersOf) => defineGroup({
6533
6887
  run: async (invocation) => {
6534
6888
  const { values } = invocation;
6535
6889
  const run = await runOf(adaptersOf, invocation);
6536
- const projectId = await projectFrom$2(run, values.project);
6537
- const kind = credentialKinds.find((candidate) => candidate === values.kind);
6538
- if (kind === void 0) throw new UsageError(`--kind ${values.kind ?? ""} is not a kind of credential.`, `The kinds are ${credentialKinds.join(", ")}.`);
6890
+ const projectId = await projectFrom$3(run, values.project);
6891
+ const given = values.kind ?? defaultKind;
6892
+ const kind = credentialKinds.find((candidate) => candidate === given);
6893
+ if (kind === void 0) throw new UsageError(`--kind ${given} is not a kind of credential.`, `The kinds are ${credentialKinds.join(", ")}.`);
6539
6894
  const label = values.label ?? "";
6540
6895
  if (label.trim() === "") throw new UsageError("credentials add needs --label <label>.");
6896
+ const gitFields = gitFieldsOf(kind, values);
6541
6897
  const secret = await secretOf({
6542
6898
  run,
6543
6899
  file: values["secret-file"],
@@ -6549,19 +6905,60 @@ const createCredentialsCommand = (adaptersOf) => defineGroup({
6549
6905
  body: {
6550
6906
  kind,
6551
6907
  label,
6552
- secret
6908
+ secret,
6909
+ ...gitFields
6553
6910
  }
6554
- }), credentialHints);
6911
+ }), addHints);
6555
6912
  return printed(run, credential, (stored) => `Stored ${stored.label} (${stored.fingerprint}) as ${stored.id}.`);
6556
6913
  }
6557
6914
  }),
6915
+ defineCommand({
6916
+ name: "set-identity",
6917
+ summary: "Set the name and e-mail the commits of a git token carry.",
6918
+ usage: "credentials set-identity <credential> --project <name> --commit-name <name> --commit-email <email>",
6919
+ optionsUsage: [
6920
+ ...projectUsage$3,
6921
+ ["--commit-name <name>", "the name its commits carry"],
6922
+ ["--commit-email <email>", "the e-mail its commits carry"],
6923
+ ["", "the agents that use the token need a restart to sign with it"]
6924
+ ],
6925
+ options: {
6926
+ ...projectOption$3,
6927
+ "commit-name": { type: "string" },
6928
+ "commit-email": { type: "string" }
6929
+ },
6930
+ positionals: {
6931
+ min: 1,
6932
+ max: 1
6933
+ },
6934
+ run: async (invocation) => {
6935
+ const { values } = invocation;
6936
+ const commitName = values["commit-name"] ?? "";
6937
+ const commitEmail = values["commit-email"] ?? "";
6938
+ if (commitName.trim() === "" || commitEmail.trim() === "") throw new UsageError("credentials set-identity needs --commit-name <name> and --commit-email <email>.");
6939
+ const run = await runOf(adaptersOf, invocation);
6940
+ const projectId = await projectFrom$3(run, values.project);
6941
+ const credentialId = await resolveCredentialId(run.api, projectId, invocation.positionals[0] ?? "");
6942
+ const credential = dataOf("set the commit identity of the credential", await run.api.PUT("/projects/{id}/credentials/{credentialId}/commit-identity", {
6943
+ params: { path: {
6944
+ id: projectId,
6945
+ credentialId
6946
+ } },
6947
+ body: {
6948
+ commitName,
6949
+ commitEmail
6950
+ }
6951
+ }), credentialHints);
6952
+ return printed(run, credential, (changed) => `${changed.label} signs commits as ${commitName} <${commitEmail}> now.`);
6953
+ }
6954
+ }),
6558
6955
  defineCommand({
6559
6956
  name: "rename",
6560
6957
  summary: "Change the label of a credential.",
6561
6958
  usage: "credentials rename <credential> --project <name> --label <label>",
6562
- optionsUsage: [...projectUsage$2, ["--label <label>", "the new label"]],
6959
+ optionsUsage: [...projectUsage$3, ["--label <label>", "the new label"]],
6563
6960
  options: {
6564
- ...projectOption$2,
6961
+ ...projectOption$3,
6565
6962
  label: { type: "string" }
6566
6963
  },
6567
6964
  positionals: {
@@ -6571,7 +6968,7 @@ const createCredentialsCommand = (adaptersOf) => defineGroup({
6571
6968
  run: async (invocation) => {
6572
6969
  const { values } = invocation;
6573
6970
  const run = await runOf(adaptersOf, invocation);
6574
- const projectId = await projectFrom$2(run, values.project);
6971
+ const projectId = await projectFrom$3(run, values.project);
6575
6972
  const credentialId = await resolveCredentialId(run.api, projectId, invocation.positionals[0] ?? "");
6576
6973
  const label = values.label ?? "";
6577
6974
  if (label.trim() === "") throw new UsageError("credentials rename needs --label <label>.");
@@ -6590,12 +6987,12 @@ const createCredentialsCommand = (adaptersOf) => defineGroup({
6590
6987
  summary: "Replace the secret of a credential.",
6591
6988
  usage: "credentials rotate <credential> --project <name> [--restart-agents] [--secret-file <path>]",
6592
6989
  optionsUsage: [
6593
- ...projectUsage$2,
6990
+ ...projectUsage$3,
6594
6991
  ["--restart-agents", "restart the agents that use it once the secret is in"],
6595
6992
  ...secretUsage
6596
6993
  ],
6597
6994
  options: {
6598
- ...projectOption$2,
6995
+ ...projectOption$3,
6599
6996
  ...secretOptions,
6600
6997
  "restart-agents": {
6601
6998
  type: "boolean",
@@ -6609,7 +7006,7 @@ const createCredentialsCommand = (adaptersOf) => defineGroup({
6609
7006
  run: async (invocation) => {
6610
7007
  const { values } = invocation;
6611
7008
  const run = await runOf(adaptersOf, invocation);
6612
- const projectId = await projectFrom$2(run, values.project);
7009
+ const projectId = await projectFrom$3(run, values.project);
6613
7010
  const credentialId = await resolveCredentialId(run.api, projectId, invocation.positionals[0] ?? "");
6614
7011
  const secret = await secretOf({
6615
7012
  run,
@@ -6634,15 +7031,15 @@ const createCredentialsCommand = (adaptersOf) => defineGroup({
6634
7031
  name: "verify",
6635
7032
  summary: "Ask the instance to verify a credential against its service.",
6636
7033
  usage: "credentials verify <credential> --project <name>",
6637
- optionsUsage: projectUsage$2,
6638
- options: projectOption$2,
7034
+ optionsUsage: projectUsage$3,
7035
+ options: projectOption$3,
6639
7036
  positionals: {
6640
7037
  min: 1,
6641
7038
  max: 1
6642
7039
  },
6643
7040
  run: async (invocation) => {
6644
7041
  const run = await runOf(adaptersOf, invocation);
6645
- const projectId = await projectFrom$2(run, invocation.values.project);
7042
+ const projectId = await projectFrom$3(run, invocation.values.project);
6646
7043
  const credentialId = await resolveCredentialId(run.api, projectId, invocation.positionals[0] ?? "");
6647
7044
  doneWith("verify the credential", await run.api.POST("/projects/{id}/credentials/{credentialId}/verify", { params: { path: {
6648
7045
  id: projectId,
@@ -6655,9 +7052,9 @@ const createCredentialsCommand = (adaptersOf) => defineGroup({
6655
7052
  name: "usage",
6656
7053
  summary: "Show the usage of the Claude subscription behind a Claude Code OAuth token.",
6657
7054
  usage: "credentials usage <credential> --project <name> [--refresh]",
6658
- optionsUsage: [...projectUsage$2, ["--refresh", "read it from Anthropic now; within 30 seconds the last reading"]],
7055
+ optionsUsage: [...projectUsage$3, ["--refresh", "read it from Anthropic now; within 30 seconds the last reading"]],
6659
7056
  options: {
6660
- ...projectOption$2,
7057
+ ...projectOption$3,
6661
7058
  refresh: {
6662
7059
  type: "boolean",
6663
7060
  default: false
@@ -6669,9 +7066,9 @@ const createCredentialsCommand = (adaptersOf) => defineGroup({
6669
7066
  },
6670
7067
  run: async (invocation) => {
6671
7068
  const run = await runOf(adaptersOf, invocation);
6672
- const projectId = await projectFrom$2(run, invocation.values.project);
7069
+ const projectId = await projectFrom$3(run, invocation.values.project);
6673
7070
  const stored = await resolveCredential(run.api, projectId, invocation.positionals[0] ?? "");
6674
- if (stored.kind !== "claude_oauth_token") throw new CliError(`${stored.label} is a ${stored.kind}, which reports no usage.`, usageUnavailable);
7071
+ if (stored.kind !== "claude_oauth_token") throw new CliError(`${stored.label} is a ${stored.kind}, which reports no usage.`, usageUnavailable$1);
6675
7072
  const credential = invocation.values.refresh ? dataOf("read the usage of the credential", await run.api.POST("/projects/{id}/credentials/{credentialId}/usage", { params: { path: {
6676
7073
  id: projectId,
6677
7074
  credentialId: stored.id
@@ -6683,15 +7080,15 @@ const createCredentialsCommand = (adaptersOf) => defineGroup({
6683
7080
  name: "delete",
6684
7081
  summary: "Delete a credential no agent uses.",
6685
7082
  usage: "credentials delete <credential> --project <name>",
6686
- optionsUsage: projectUsage$2,
6687
- options: projectOption$2,
7083
+ optionsUsage: projectUsage$3,
7084
+ options: projectOption$3,
6688
7085
  positionals: {
6689
7086
  min: 1,
6690
7087
  max: 1
6691
7088
  },
6692
7089
  run: async (invocation) => {
6693
7090
  const run = await runOf(adaptersOf, invocation);
6694
- const projectId = await projectFrom$2(run, invocation.values.project);
7091
+ const projectId = await projectFrom$3(run, invocation.values.project);
6695
7092
  const reference = invocation.positionals[0] ?? "";
6696
7093
  const credentialId = await resolveCredentialId(run.api, projectId, reference);
6697
7094
  await confirmed({
@@ -6709,6 +7106,263 @@ const createCredentialsCommand = (adaptersOf) => defineGroup({
6709
7106
  ]
6710
7107
  });
6711
7108
  //#endregion
7109
+ //#region src/commands/claude-accounts.ts
7110
+ const accountUsage$1 = [["", "an account is named by its label or its id"]];
7111
+ const usageUnavailable = "Only a Claude subscription reports its usage.";
7112
+ const queuesText = (projects) => typeof projects === "string" && projects !== "" ? projects : "some projects";
7113
+ const claudeAccountHints = {
7114
+ ...organizationHints,
7115
+ "auth.forbidden": "Changing a Claude account takes an admin of the organization.",
7116
+ "account.not_found": "kcloud claude-accounts list shows the Claude accounts of the organization.",
7117
+ "account.label_taken": "Another Claude account of the organization already has that label.",
7118
+ "account.in_queue": ({ projects }) => `It stands in the queue of ${queuesText(projects)}; kcloud projects accounts remove <account> --project <project> takes it out.`,
7119
+ "credential.usage_unavailable": usageUnavailable
7120
+ };
7121
+ const queuesOf = (queues) => queues.length === 0 ? "-" : queues.map(({ projectName, position }) => `${projectName} #${String(position)}`).join(", ");
7122
+ const statusOf = ({ verification, creditsExhaustedAt }) => creditsExhaustedAt === null ? verification : "no credits";
7123
+ 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;
7124
+ const claudeAccountIn = (accounts, reference) => {
7125
+ const found = accounts.filter((account) => account.id === reference.toLowerCase() || account.label === reference);
7126
+ const [first] = found;
7127
+ 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(", ")}.`);
7128
+ if (found.length > 1) throw new CliError(`${reference} names ${found.length} Claude accounts.`, "Name it by its id instead.");
7129
+ return first;
7130
+ };
7131
+ const resolveClaudeAccount = async (scope, reference) => {
7132
+ const { id } = await scope.organization();
7133
+ return {
7134
+ organizationId: id,
7135
+ account: claudeAccountIn(await claudeAccountsOf(scope.api, id), reference)
7136
+ };
7137
+ };
7138
+ const renamedClaudeAccount = async (api, organizationId, credentialId, label) => dataOf("rename the Claude account", await api.PATCH("/organizations/{organizationId}/claude-accounts/{credentialId}", {
7139
+ params: { path: {
7140
+ organizationId,
7141
+ credentialId
7142
+ } },
7143
+ body: { label }
7144
+ }), claudeAccountHints);
7145
+ const verifyClaudeAccount = async (api, organizationId, credentialId) => {
7146
+ doneWith("verify the Claude account", await api.POST("/organizations/{organizationId}/claude-accounts/{credentialId}/verify", { params: { path: {
7147
+ organizationId,
7148
+ credentialId
7149
+ } } }), claudeAccountHints);
7150
+ };
7151
+ 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: {
7152
+ organizationId,
7153
+ credentialId
7154
+ } } }), claudeAccountHints);
7155
+ const deleteClaudeAccount = async (api, organizationId, credentialId) => {
7156
+ doneWith("delete the Claude account", await api.DELETE("/organizations/{organizationId}/claude-accounts/{credentialId}", { params: { path: {
7157
+ organizationId,
7158
+ credentialId
7159
+ } } }), claudeAccountHints);
7160
+ };
7161
+ const createClaudeAccountsCommand = (adaptersOf) => defineGroup({
7162
+ name: "claude-accounts",
7163
+ summary: "The Claude subscriptions and Anthropic API keys of the organization.",
7164
+ usage: "claude-accounts <command>",
7165
+ subcommands: [
7166
+ defineCommand({
7167
+ name: "list",
7168
+ summary: "List the Claude accounts of the organization and the queues they are in.",
7169
+ usage: "claude-accounts list",
7170
+ options: {},
7171
+ positionals: {
7172
+ min: 0,
7173
+ max: 0
7174
+ },
7175
+ run: async (invocation) => {
7176
+ const run = await runOf(adaptersOf, invocation);
7177
+ const organization = await run.organization();
7178
+ const items = await claudeAccountsOf(run.api, organization.id);
7179
+ return printed(run, items, (accounts) => accounts.length === 0 ? `${organization.slug} has no Claude account yet; kcloud claude-accounts add stores one.` : renderTable([
7180
+ { header: "KIND" },
7181
+ { header: "LABEL" },
7182
+ { header: "STATUS" },
7183
+ { header: "USAGE" },
7184
+ { header: "QUEUES" },
7185
+ { header: "ID" }
7186
+ ], accounts.map((account) => [
7187
+ account.kind,
7188
+ account.label,
7189
+ statusOf(account),
7190
+ usageSummaryOf(account),
7191
+ queuesOf(account.queues),
7192
+ account.id
7193
+ ])));
7194
+ }
7195
+ }),
7196
+ defineCommand({
7197
+ name: "add",
7198
+ summary: "Store a Claude subscription or an Anthropic API key, asking for it.",
7199
+ usage: "claude-accounts add --kind <kind> --label <label> [--secret-file <path>]",
7200
+ optionsUsage: [
7201
+ ["--kind <kind>", "claude_oauth_token for a subscription, anthropic_api_key for an API key"],
7202
+ ["--label <label>", "how the panel and the queues name it"],
7203
+ ...secretUsage,
7204
+ ["", "kcloud projects accounts add puts it in the queue of a project"]
7205
+ ],
7206
+ options: {
7207
+ ...secretOptions,
7208
+ kind: { type: "string" },
7209
+ label: { type: "string" }
7210
+ },
7211
+ positionals: {
7212
+ min: 0,
7213
+ max: 0
7214
+ },
7215
+ run: async (invocation) => {
7216
+ const { values } = invocation;
7217
+ const kind = claudeCredentialKinds.find((candidate) => candidate === values.kind);
7218
+ if (kind === void 0) throw new UsageError(`--kind ${values.kind ?? ""} is not a kind of Claude account.`, `The kinds are ${claudeCredentialKinds.join(", ")}.`);
7219
+ const label = values.label ?? "";
7220
+ if (label.trim() === "") throw new UsageError("claude-accounts add needs --label <label>.");
7221
+ const run = await runOf(adaptersOf, invocation);
7222
+ const organization = await run.organization();
7223
+ const secret = await secretOf({
7224
+ run,
7225
+ file: values["secret-file"],
7226
+ fromStdin: values["secret-stdin"],
7227
+ question: `The secret of ${label}`
7228
+ });
7229
+ const account = dataOf("store the Claude account", await run.api.POST("/organizations/{organizationId}/claude-accounts", {
7230
+ params: { path: { organizationId: organization.id } },
7231
+ body: {
7232
+ kind,
7233
+ label,
7234
+ secret
7235
+ }
7236
+ }), claudeAccountHints);
7237
+ return printed(run, account, (stored) => `Stored ${stored.label} (${stored.fingerprint}) as ${stored.id}; the instance is verifying it.`);
7238
+ }
7239
+ }),
7240
+ defineCommand({
7241
+ name: "rename",
7242
+ summary: "Change the label of a Claude account.",
7243
+ usage: "claude-accounts rename <account> --label <label>",
7244
+ optionsUsage: [...accountUsage$1, ["--label <label>", "the new label"]],
7245
+ options: { label: { type: "string" } },
7246
+ positionals: {
7247
+ min: 1,
7248
+ max: 1
7249
+ },
7250
+ run: async (invocation) => {
7251
+ const label = invocation.values.label ?? "";
7252
+ if (label.trim() === "") throw new UsageError("claude-accounts rename needs --label <label>.");
7253
+ const run = await runOf(adaptersOf, invocation);
7254
+ const { organizationId, account } = await resolveClaudeAccount(run, invocation.positionals[0] ?? "");
7255
+ const renamed = await renamedClaudeAccount(run.api, organizationId, account.id, label);
7256
+ return printed(run, renamed, (shown) => `The Claude account is ${shown.label} now.`);
7257
+ }
7258
+ }),
7259
+ defineCommand({
7260
+ name: "rotate",
7261
+ summary: "Replace the secret of a Claude account.",
7262
+ usage: "claude-accounts rotate <account> [--restart-agents] [--secret-file <path>]",
7263
+ optionsUsage: [
7264
+ ...accountUsage$1,
7265
+ ["--restart-agents", "restart the agents that work on it once the secret is in"],
7266
+ ...secretUsage
7267
+ ],
7268
+ options: {
7269
+ ...secretOptions,
7270
+ "restart-agents": {
7271
+ type: "boolean",
7272
+ default: false
7273
+ }
7274
+ },
7275
+ positionals: {
7276
+ min: 1,
7277
+ max: 1
7278
+ },
7279
+ run: async (invocation) => {
7280
+ const { values } = invocation;
7281
+ const run = await runOf(adaptersOf, invocation);
7282
+ const { organizationId, account } = await resolveClaudeAccount(run, invocation.positionals[0] ?? "");
7283
+ const secret = await secretOf({
7284
+ run,
7285
+ file: values["secret-file"],
7286
+ fromStdin: values["secret-stdin"],
7287
+ question: `The new secret of ${account.label}`
7288
+ });
7289
+ const rotated = dataOf("rotate the Claude account", await run.api.POST("/organizations/{organizationId}/claude-accounts/{credentialId}/rotate", {
7290
+ params: { path: {
7291
+ organizationId,
7292
+ credentialId: account.id
7293
+ } },
7294
+ body: {
7295
+ secret,
7296
+ restartAgents: values["restart-agents"]
7297
+ }
7298
+ }), claudeAccountHints);
7299
+ return printed(run, rotated, (shown) => `Rotated ${shown.label} to ${shown.fingerprint}${values["restart-agents"] ? "; its agents restart" : ""}.`);
7300
+ }
7301
+ }),
7302
+ defineCommand({
7303
+ name: "verify",
7304
+ summary: "Ask the instance to verify a Claude account against Anthropic.",
7305
+ usage: "claude-accounts verify <account>",
7306
+ optionsUsage: accountUsage$1,
7307
+ options: {},
7308
+ positionals: {
7309
+ min: 1,
7310
+ max: 1
7311
+ },
7312
+ run: async (invocation) => {
7313
+ const run = await runOf(adaptersOf, invocation);
7314
+ const { organizationId, account } = await resolveClaudeAccount(run, invocation.positionals[0] ?? "");
7315
+ await verifyClaudeAccount(run.api, organizationId, account.id);
7316
+ return said(run, `The instance is verifying ${account.label}; kcloud claude-accounts list shows the answer.`);
7317
+ }
7318
+ }),
7319
+ defineCommand({
7320
+ name: "usage",
7321
+ summary: "Show the usage of a Claude subscription.",
7322
+ usage: "claude-accounts usage <account> [--refresh]",
7323
+ optionsUsage: [...accountUsage$1, ["--refresh", "read it from Anthropic now; within 30 seconds the last reading"]],
7324
+ options: { refresh: {
7325
+ type: "boolean",
7326
+ default: false
7327
+ } },
7328
+ positionals: {
7329
+ min: 1,
7330
+ max: 1
7331
+ },
7332
+ run: async (invocation) => {
7333
+ const run = await runOf(adaptersOf, invocation);
7334
+ const { organizationId, account } = await resolveClaudeAccount(run, invocation.positionals[0] ?? "");
7335
+ if (account.kind !== "claude_oauth_token") throw new CliError(`${account.label} is an ${account.kind}, which reports no usage.`, usageUnavailable);
7336
+ const read = invocation.values.refresh ? await refreshedClaudeAccount(run.api, organizationId, account.id) : account;
7337
+ return printed(run, read, usageReportOf);
7338
+ }
7339
+ }),
7340
+ defineCommand({
7341
+ name: "delete",
7342
+ summary: "Delete a Claude account that stands in no queue.",
7343
+ usage: "claude-accounts delete <account>",
7344
+ optionsUsage: accountUsage$1,
7345
+ options: {},
7346
+ positionals: {
7347
+ min: 1,
7348
+ max: 1
7349
+ },
7350
+ run: async (invocation) => {
7351
+ const run = await runOf(adaptersOf, invocation);
7352
+ const reference = invocation.positionals[0] ?? "";
7353
+ const { organizationId, account } = await resolveClaudeAccount(run, reference);
7354
+ await confirmed({
7355
+ prompt: run.prompt,
7356
+ settings: run.settings,
7357
+ question: `Delete the Claude account ${account.label}? The platform erases its secret.`
7358
+ });
7359
+ await deleteClaudeAccount(run.api, organizationId, account.id);
7360
+ return said(run, `Deleted the Claude account ${account.label}.`);
7361
+ }
7362
+ })
7363
+ ]
7364
+ });
7365
+ //#endregion
6712
7366
  //#region src/mcp/tool.ts
6713
7367
  const defineTool = (definition) => ({
6714
7368
  ...definition,
@@ -6969,7 +7623,7 @@ const agentTools = [
6969
7623
  defineTool({
6970
7624
  name: "agent_create",
6971
7625
  title: "Create an agent",
6972
- 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.",
7626
+ 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. The agent stays stopped until agent_action starts it.",
6973
7627
  operations: ["createAgent"],
6974
7628
  safety: "write",
6975
7629
  input: z.object({
@@ -6984,7 +7638,7 @@ const agentTools = [
6984
7638
  defineTool({
6985
7639
  name: "agent_update",
6986
7640
  title: "Change an agent",
6987
- 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.",
7641
+ 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. 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.",
6988
7642
  operations: ["updateAgent"],
6989
7643
  safety: "write",
6990
7644
  input: z.object({
@@ -7674,13 +8328,311 @@ const agentBackupTools = [
7674
8328
  })
7675
8329
  ];
7676
8330
  //#endregion
8331
+ //#region src/commands/projects/accounts.ts
8332
+ const projectAccountsHints = {
8333
+ ...projectHint,
8334
+ "auth.forbidden": "Changing the queue of a project takes an admin of the project.",
8335
+ "account.not_found": "kcloud claude-accounts list shows the Claude accounts of the organization.",
8336
+ "account.kind_mismatch": "A queue holds Claude subscriptions and Anthropic API keys alone.",
8337
+ "concurrency.conflict": "Someone changed the queue meanwhile; kcloud projects accounts list shows it now."
8338
+ };
8339
+ const projectOption$2 = { project: { type: "string" } };
8340
+ 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"]];
8341
+ const pad = (value) => String(value).padStart(2, "0");
8342
+ const dayOf$1 = (moment) => `${String(moment.getFullYear())}-${pad(moment.getMonth() + 1)}-${pad(moment.getDate())}`;
8343
+ const clockOf = (at, now) => {
8344
+ const moment = new Date(at);
8345
+ const time = `${pad(moment.getHours())}:${pad(moment.getMinutes())}`;
8346
+ return dayOf$1(moment) === dayOf$1(now) ? time : `${dayOf$1(moment)} ${time}`;
8347
+ };
8348
+ const stateOf$1 = ({ state, until }, now) => {
8349
+ switch (state) {
8350
+ case "inUse": return "in use";
8351
+ case "exhausted": return until === null ? "exhausted" : `exhausted until ${clockOf(until, now)}`;
8352
+ case "noCredits": return "no credits";
8353
+ default: return state;
8354
+ }
8355
+ };
8356
+ const waitTextOf = ({ waitReason, nearestResetAt }, now) => {
8357
+ switch (waitReason) {
8358
+ 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)}.`;
8359
+ case "blocked": return "No account of the queue can work: top up the credits of a key, or fix or replace an invalid account.";
8360
+ case "empty": return "The queue is empty, so the agents of the project cannot start; kcloud projects accounts add <account> --project <name> adds one.";
8361
+ default: return null;
8362
+ }
8363
+ };
8364
+ const queueText = (queue, now) => {
8365
+ const wait = waitTextOf(queue, now);
8366
+ if (queue.entries.length === 0) return wait ?? "The queue is empty.";
8367
+ const table = renderTable([
8368
+ {
8369
+ header: "#",
8370
+ align: "right"
8371
+ },
8372
+ { header: "LABEL" },
8373
+ { header: "KIND" },
8374
+ { header: "STATE" },
8375
+ { header: "USAGE" },
8376
+ {
8377
+ header: "AGENTS",
8378
+ align: "right"
8379
+ },
8380
+ { header: "ID" }
8381
+ ], queue.entries.map((entry) => [
8382
+ String(entry.position),
8383
+ entry.credential.label,
8384
+ entry.credential.kind,
8385
+ stateOf$1(entry, now),
8386
+ usageSummaryOf(entry.credential),
8387
+ String(entry.agents),
8388
+ entry.credential.id
8389
+ ]));
8390
+ return wait === null ? table : `${table}\n\n${wait}`;
8391
+ };
8392
+ 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);
8393
+ const queueSet = async (api, projectId, credentialIds, version) => dataOf("set the queue of Claude accounts of the project", await api.PUT("/projects/{id}/claude-accounts", {
8394
+ params: { path: { id: projectId } },
8395
+ body: {
8396
+ credentialIds: [...credentialIds],
8397
+ version
8398
+ }
8399
+ }), projectAccountsHints);
8400
+ const organizationAccountIds = async (api, projectId, references) => {
8401
+ if (references.length === 0) return [];
8402
+ const { organizationId } = await projectOf(api, projectId);
8403
+ const accounts = await claudeAccountsOf(api, organizationId);
8404
+ const ids = references.map((reference) => claudeAccountIn(accounts, reference).id);
8405
+ const twice = references.filter((_, index) => ids.indexOf(ids[index] ?? "") !== index);
8406
+ 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.");
8407
+ return ids;
8408
+ };
8409
+ const queuedIndexOf = (queue, reference) => {
8410
+ const found = queue.entries.findIndex(({ credential }) => credential.id === reference.toLowerCase() || credential.label === reference);
8411
+ 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(", ")}.`);
8412
+ return found;
8413
+ };
8414
+ const idsOf = (queue) => queue.entries.map(({ credential }) => credential.id);
8415
+ const queueWith = async (api, projectId, reference) => {
8416
+ const queue = await projectClaudeAccountsOf(api, projectId);
8417
+ const [id = ""] = await organizationAccountIds(api, projectId, [reference]);
8418
+ const place = queue.entries.find(({ credential }) => credential.id === id);
8419
+ 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.");
8420
+ return queueSet(api, projectId, [...idsOf(queue), id], queue.version);
8421
+ };
8422
+ const queueWithout = async (api, projectId, reference) => {
8423
+ const queue = await projectClaudeAccountsOf(api, projectId);
8424
+ const index = queuedIndexOf(queue, reference);
8425
+ const ids = idsOf(queue);
8426
+ ids.splice(index, 1);
8427
+ return queueSet(api, projectId, ids, queue.version);
8428
+ };
8429
+ const queueMoved = async (api, projectId, reference, place) => {
8430
+ const queue = await projectClaudeAccountsOf(api, projectId);
8431
+ 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"}.`);
8432
+ const ids = idsOf(queue);
8433
+ const [moved = ""] = ids.splice(queuedIndexOf(queue, reference), 1);
8434
+ ids.splice(place - 1, 0, moved);
8435
+ return queueSet(api, projectId, ids, queue.version);
8436
+ };
8437
+ const placeOf = (given) => {
8438
+ if (given === void 0) throw new UsageError("projects accounts move needs --to <n>.");
8439
+ if (!/^[0-9]+$/u.test(given) || Number(given) < 1) throw new UsageError(`--to ${given} is not a place in the queue, counted from 1.`);
8440
+ return Number(given);
8441
+ };
8442
+ const projectFrom$2 = async (scope, given) => {
8443
+ if (given === void 0) throw new UsageError("This command needs a project.", "Pass --project <name>; kcloud projects list shows them.");
8444
+ return resolveProjectId(scope, given);
8445
+ };
8446
+ const printedQueue = (run, queue) => printed(run, queue, (shown) => queueText(shown, run.adapters.now()));
8447
+ const projectAccountsCommand = (adaptersOf) => defineGroup({
8448
+ name: "accounts",
8449
+ summary: "The queue of Claude accounts the agents of a project work on.",
8450
+ usage: "projects accounts <command>",
8451
+ subcommands: [
8452
+ defineCommand({
8453
+ name: "list",
8454
+ summary: "List the queue of Claude accounts of a project with their state.",
8455
+ usage: "projects accounts list --project <name>",
8456
+ optionsUsage: projectUsage$2,
8457
+ options: projectOption$2,
8458
+ positionals: {
8459
+ min: 0,
8460
+ max: 0
8461
+ },
8462
+ run: async (invocation) => {
8463
+ const run = await runOf(adaptersOf, invocation);
8464
+ const projectId = await projectFrom$2(run, invocation.values.project);
8465
+ return printedQueue(run, await projectClaudeAccountsOf(run.api, projectId));
8466
+ }
8467
+ }),
8468
+ defineCommand({
8469
+ name: "set",
8470
+ summary: "Set the whole queue of Claude accounts of a project, in order.",
8471
+ usage: "projects accounts set [<account>...] --project <name>",
8472
+ optionsUsage: [...projectUsage$2, ["", "the first account named comes first; none empties the queue"]],
8473
+ options: projectOption$2,
8474
+ positionals: {
8475
+ min: 0,
8476
+ max: 50
8477
+ },
8478
+ run: async (invocation) => {
8479
+ const run = await runOf(adaptersOf, invocation);
8480
+ const projectId = await projectFrom$2(run, invocation.values.project);
8481
+ const ids = await organizationAccountIds(run.api, projectId, invocation.positionals);
8482
+ const { version } = await projectClaudeAccountsOf(run.api, projectId);
8483
+ return printedQueue(run, await queueSet(run.api, projectId, ids, version));
8484
+ }
8485
+ }),
8486
+ defineCommand({
8487
+ name: "add",
8488
+ summary: "Put a Claude account of the organization at the end of the queue.",
8489
+ usage: "projects accounts add <account> --project <name>",
8490
+ optionsUsage: projectUsage$2,
8491
+ options: projectOption$2,
8492
+ positionals: {
8493
+ min: 1,
8494
+ max: 1
8495
+ },
8496
+ run: async (invocation) => {
8497
+ const run = await runOf(adaptersOf, invocation);
8498
+ const projectId = await projectFrom$2(run, invocation.values.project);
8499
+ return printedQueue(run, await queueWith(run.api, projectId, invocation.positionals[0] ?? ""));
8500
+ }
8501
+ }),
8502
+ defineCommand({
8503
+ name: "remove",
8504
+ summary: "Take a Claude account out of the queue; it stays in the organization.",
8505
+ usage: "projects accounts remove <account> --project <name>",
8506
+ optionsUsage: projectUsage$2,
8507
+ options: projectOption$2,
8508
+ positionals: {
8509
+ min: 1,
8510
+ max: 1
8511
+ },
8512
+ run: async (invocation) => {
8513
+ const run = await runOf(adaptersOf, invocation);
8514
+ const projectId = await projectFrom$2(run, invocation.values.project);
8515
+ return printedQueue(run, await queueWithout(run.api, projectId, invocation.positionals[0] ?? ""));
8516
+ }
8517
+ }),
8518
+ defineCommand({
8519
+ name: "move",
8520
+ summary: "Move a Claude account to another place of the queue.",
8521
+ usage: "projects accounts move <account> --to <n> --project <name>",
8522
+ optionsUsage: [...projectUsage$2, ["--to <n>", "the new place, counted from 1"]],
8523
+ options: {
8524
+ ...projectOption$2,
8525
+ to: { type: "string" }
8526
+ },
8527
+ positionals: {
8528
+ min: 1,
8529
+ max: 1
8530
+ },
8531
+ run: async (invocation) => {
8532
+ const place = placeOf(invocation.values.to);
8533
+ const run = await runOf(adaptersOf, invocation);
8534
+ const projectId = await projectFrom$2(run, invocation.values.project);
8535
+ return printedQueue(run, await queueMoved(run.api, projectId, invocation.positionals[0] ?? "", place));
8536
+ }
8537
+ })
8538
+ ]
8539
+ });
8540
+ //#endregion
8541
+ //#region src/mcp/tools/claude-accounts.ts
8542
+ const accountField$1 = z.string().trim().min(1).describe("The Claude account, by its label or its id, from claude_accounts_list.");
8543
+ const claudeAccountTools = [
8544
+ defineTool({
8545
+ name: "claude_accounts_list",
8546
+ title: "List the Claude accounts of the organization",
8547
+ 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.",
8548
+ operations: ["listClaudeAccounts"],
8549
+ safety: "read",
8550
+ input: z.object({}),
8551
+ run: async (_, run) => claudeAccountsOf(run.api, (await run.organization()).id)
8552
+ }),
8553
+ defineTool({
8554
+ name: "claude_account_update",
8555
+ title: "Rename, verify or read the usage of a Claude account",
8556
+ 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.",
8557
+ operations: [
8558
+ "renameClaudeAccount",
8559
+ "verifyClaudeAccount",
8560
+ "refreshClaudeAccountUsage"
8561
+ ],
8562
+ safety: "write",
8563
+ input: z.object({
8564
+ account: accountField$1,
8565
+ action: z.enum([
8566
+ "rename",
8567
+ "verify",
8568
+ "refresh_usage"
8569
+ ]),
8570
+ label: z.string().trim().min(1).optional().describe("The new label, for rename.")
8571
+ }),
8572
+ run: async ({ account, action, label }, run) => {
8573
+ if (action === "rename" && label === void 0) throw new UsageError("rename needs a label.");
8574
+ const { organizationId, account: found } = await resolveClaudeAccount(run, account);
8575
+ if (action === "refresh_usage") return refreshedClaudeAccount(run.api, organizationId, found.id);
8576
+ if (action === "verify") {
8577
+ await verifyClaudeAccount(run.api, organizationId, found.id);
8578
+ return {
8579
+ id: found.id,
8580
+ verifying: true
8581
+ };
8582
+ }
8583
+ return renamedClaudeAccount(run.api, organizationId, found.id, label ?? "");
8584
+ }
8585
+ }),
8586
+ defineTool({
8587
+ name: "claude_account_delete",
8588
+ title: "Delete a Claude account",
8589
+ 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.",
8590
+ operations: ["deleteClaudeAccount"],
8591
+ safety: "destructive",
8592
+ input: z.object({ account: accountField$1 }),
8593
+ run: async ({ account }, run) => {
8594
+ const { organizationId, account: found } = await resolveClaudeAccount(run, account);
8595
+ await deleteClaudeAccount(run.api, organizationId, found.id);
8596
+ return { deleted: found.id };
8597
+ }
8598
+ }),
8599
+ defineTool({
8600
+ name: "project_accounts_list",
8601
+ title: "List the queue of Claude accounts of a project",
8602
+ 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.",
8603
+ operations: ["listProjectClaudeAccounts"],
8604
+ safety: "read",
8605
+ input: z.object({ project: projectField }),
8606
+ run: async ({ project }, run) => projectClaudeAccountsOf(run.api, await projectIdOf(run, project))
8607
+ }),
8608
+ defineTool({
8609
+ name: "project_accounts_set",
8610
+ title: "Set the queue of Claude accounts of a project",
8611
+ 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.",
8612
+ operations: ["listProjectClaudeAccounts", "setProjectClaudeAccounts"],
8613
+ safety: "write",
8614
+ idempotent: true,
8615
+ input: z.object({
8616
+ project: projectField,
8617
+ accounts: z.array(accountField$1).max(50).describe("The Claude accounts of the queue, by label or id, first one first."),
8618
+ version: z.int().nonnegative().optional().describe("The version of the queue the change is based on, to refuse a change made meanwhile.")
8619
+ }),
8620
+ run: async ({ project, accounts, version }, run) => {
8621
+ const projectId = await projectIdOf(run, project);
8622
+ const ids = await organizationAccountIds(run.api, projectId, accounts);
8623
+ const based = version ?? (await projectClaudeAccountsOf(run.api, projectId)).version;
8624
+ return queueSet(run.api, projectId, ids, based);
8625
+ }
8626
+ })
8627
+ ];
8628
+ //#endregion
7677
8629
  //#region src/mcp/tools/credentials.ts
7678
8630
  const credentialField$1 = z.string().trim().min(1).describe("The credential, by its label or its id.");
7679
8631
  const credentialTools = [
7680
8632
  defineTool({
7681
8633
  name: "credentials_list",
7682
8634
  title: "List credentials",
7683
- description: "List the Claude and GitHub credentials of a project, with their fingerprints and verification, never their secrets.",
8635
+ 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.",
7684
8636
  operations: ["listCredentials"],
7685
8637
  safety: "read",
7686
8638
  input: z.object({ project: projectField }),
@@ -7688,11 +8640,12 @@ const credentialTools = [
7688
8640
  }),
7689
8641
  defineTool({
7690
8642
  name: "credential_update",
7691
- title: "Rename, verify or read the usage of a credential",
7692
- 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.",
8643
+ title: "Rename, verify, sign commits as or read the usage of a credential",
8644
+ 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.",
7693
8645
  operations: [
7694
8646
  "renameCredential",
7695
8647
  "verifyCredential",
8648
+ "setCredentialCommitIdentity",
7696
8649
  "refreshCredentialUsage"
7697
8650
  ],
7698
8651
  safety: "write",
@@ -7702,11 +8655,14 @@ const credentialTools = [
7702
8655
  action: z.enum([
7703
8656
  "rename",
7704
8657
  "verify",
8658
+ "set_identity",
7705
8659
  "refresh_usage"
7706
8660
  ]),
7707
- label: z.string().trim().min(1).optional().describe("The new label, for rename.")
8661
+ label: z.string().trim().min(1).optional().describe("The new label, for rename."),
8662
+ commitName: commitNameSchema.optional().describe("The name its commits carry, for set_identity."),
8663
+ commitEmail: commitEmailSchema.optional().describe("The e-mail its commits carry, for set_identity.")
7708
8664
  }),
7709
- run: async ({ project, credential, action, label }, run) => {
8665
+ run: async ({ project, credential, action, label, commitName, commitEmail }, run) => {
7710
8666
  const projectId = await projectIdOf(run, project);
7711
8667
  const credentialId = await resolveCredentialId(run.api, projectId, credential);
7712
8668
  if (action === "refresh_usage") return dataOf("read the usage of the credential", await run.api.POST("/projects/{id}/credentials/{credentialId}/usage", { params: { path: {
@@ -7723,6 +8679,19 @@ const credentialTools = [
7723
8679
  verifying: true
7724
8680
  };
7725
8681
  }
8682
+ if (action === "set_identity") {
8683
+ if (commitName === void 0 || commitEmail === void 0) throw new UsageError("set_identity needs a commitName and a commitEmail.");
8684
+ return dataOf("set the commit identity of the credential", await run.api.PUT("/projects/{id}/credentials/{credentialId}/commit-identity", {
8685
+ params: { path: {
8686
+ id: projectId,
8687
+ credentialId
8688
+ } },
8689
+ body: {
8690
+ commitName,
8691
+ commitEmail
8692
+ }
8693
+ }), credentialHints);
8694
+ }
7726
8695
  if (label === void 0) throw new UsageError("rename needs a label.");
7727
8696
  return dataOf("rename the credential", await run.api.PATCH("/projects/{id}/credentials/{credentialId}", {
7728
8697
  params: { path: {
@@ -8507,6 +9476,12 @@ const costsText = (costs, { from, to }, slugs) => [
8507
9476
  "",
8508
9477
  `${costs.totalCostUsd.toFixed(2)} over ${costs.totalRuns} runs and ${costs.sessions} sessions.`
8509
9478
  ].join("\n");
9479
+ const switchThresholdOf = (given) => {
9480
+ if (given === void 0) return void 0;
9481
+ const threshold = Number(given);
9482
+ 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.`);
9483
+ return threshold;
9484
+ };
8510
9485
  const organizationChangeHints = {
8511
9486
  ...organizationHints,
8512
9487
  "organization.slug_taken": "Another organization already uses that slug.",
@@ -8527,12 +9502,14 @@ const updatedOrganization = async (api, current, fields) => {
8527
9502
  if ([
8528
9503
  fields.name,
8529
9504
  fields.slug,
8530
- fields.color
9505
+ fields.color,
9506
+ fields.switchThreshold
8531
9507
  ].every((field) => field === void 0)) return current;
8532
9508
  const body = {
8533
9509
  name: fields.name ?? current.name,
8534
9510
  slug: fields.slug ?? current.slug,
8535
- color: fields.color ?? current.color
9511
+ color: fields.color ?? current.color,
9512
+ ...fields.switchThreshold === void 0 ? {} : { claudeSwitchThreshold: fields.switchThreshold }
8536
9513
  };
8537
9514
  validated(updateOrganizationBodySchema, body, "a change to an organization");
8538
9515
  return dataOf("change the organization", await api.PATCH("/organizations/{organizationId}", {
@@ -8635,6 +9612,7 @@ const createOrgsCommand = (adaptersOf) => defineGroup({
8635
9612
  ["slug", shown.slug],
8636
9613
  ["color", shown.color],
8637
9614
  ["logo", shown.logoHash ?? "-"],
9615
+ ["switch threshold", `${String(shown.claudeSwitchThreshold)}%`],
8638
9616
  ["created", shown.createdAt],
8639
9617
  ["id", shown.id]
8640
9618
  ]));
@@ -8642,18 +9620,20 @@ const createOrgsCommand = (adaptersOf) => defineGroup({
8642
9620
  }),
8643
9621
  defineCommand({
8644
9622
  name: "set",
8645
- summary: "Change the name, slug, color or logo of an organization.",
8646
- usage: "orgs set [<organization>] [--name <name>] [--slug <slug>] [--color <#rrggbb>] [--logo <path> | --no-logo]",
9623
+ summary: "Change the name, slug, color, logo or Claude switch threshold of an organization.",
9624
+ usage: "orgs set [<organization>] [--name <name>] [--slug <slug>] [--color <#rrggbb>] [--logo <path> | --no-logo] [--switch-threshold <percent>]",
8647
9625
  optionsUsage: [
8648
9626
  ["--name <name>", "the new name"],
8649
9627
  ["--slug <slug>", "the new slug; scripts and --org that name the old one stop finding it"],
8650
9628
  ["--color <#rrggbb>", "the color of the organization in the panel"],
8651
- ...logoUsage
9629
+ ...logoUsage,
9630
+ ["--switch-threshold <percent>", `the usage at which a project moves to its next Claude account, 1 to 100`]
8652
9631
  ],
8653
9632
  options: {
8654
9633
  name: { type: "string" },
8655
9634
  slug: { type: "string" },
8656
9635
  color: { type: "string" },
9636
+ "switch-threshold": { type: "string" },
8657
9637
  ...logoOptions
8658
9638
  },
8659
9639
  positionals: {
@@ -8662,15 +9642,20 @@ const createOrgsCommand = (adaptersOf) => defineGroup({
8662
9642
  },
8663
9643
  run: async (invocation) => {
8664
9644
  const { values } = invocation;
9645
+ const switchThreshold = switchThresholdOf(values["switch-threshold"]);
8665
9646
  if ([
8666
9647
  values.name,
8667
9648
  values.slug,
8668
- values.color
8669
- ].every((value) => value === void 0) && !isLogoChange(values)) throw new UsageError("orgs set needs --name, --slug, --color, --logo or --no-logo.");
9649
+ values.color,
9650
+ switchThreshold
9651
+ ].every((value) => value === void 0) && !isLogoChange(values)) throw new UsageError("orgs set needs --name, --slug, --color, --logo, --no-logo or --switch-threshold.");
8670
9652
  const logo = await logoActionOf(invocation.context.workingDirectory, values);
8671
9653
  const run = await runOf(adaptersOf, invocation);
8672
9654
  const current = await organizationOrInUse(run, invocation.positionals[0]);
8673
- const updated = await updatedOrganization(run.api, current, values);
9655
+ const updated = await updatedOrganization(run.api, current, {
9656
+ ...values,
9657
+ switchThreshold
9658
+ });
8674
9659
  if (updated.slug !== current.slug) {
8675
9660
  await followSlug(run.context.homeDirectory, run.origin, current, updated);
8676
9661
  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.`));
@@ -8746,14 +9731,15 @@ const organizationTools = [
8746
9731
  defineTool({
8747
9732
  name: "organization_update",
8748
9733
  title: "Change an organization",
8749
- description: "Change the name or color 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.",
9734
+ 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.",
8750
9735
  operations: ["listOrganizations", "updateOrganization"],
8751
9736
  safety: "write",
8752
9737
  idempotent: true,
8753
9738
  input: z.object({
8754
9739
  organization: z.string().trim().min(1).optional().describe("The organization, by its slug, its name or its id."),
8755
9740
  name: organizationNameSchema.optional(),
8756
- color: colorSchema.optional().describe("The color in the panel, as #rrggbb.")
9741
+ color: colorSchema.optional().describe("The color in the panel, as #rrggbb."),
9742
+ 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.")
8757
9743
  }),
8758
9744
  run: async ({ organization, ...change }, run) => updatedOrganization(run.api, await organizationOrInUse(run, organization), change)
8759
9745
  }),
@@ -9463,7 +10449,8 @@ const createProjectsCommand = (adaptersOf) => defineGroup({
9463
10449
  return said(run, `Deleted the project ${project.name}.`);
9464
10450
  }
9465
10451
  }),
9466
- projectMembersCommand(adaptersOf)
10452
+ projectMembersCommand(adaptersOf),
10453
+ projectAccountsCommand(adaptersOf)
9467
10454
  ]
9468
10455
  });
9469
10456
  //#endregion
@@ -9662,14 +10649,17 @@ const releaseTools = [defineTool({
9662
10649
  //#region src/commands/repositories.ts
9663
10650
  const projectOption = { project: { type: "string" } };
9664
10651
  const projectUsage = [["--project <name>", "the project the repository belongs to, by slug, name or id"]];
10652
+ const providerOf = (details) => providerNameOf(details.provider);
9665
10653
  const repositoryHints = {
9666
10654
  "project.not_found": "kcloud projects list shows the projects you are a member of.",
9667
10655
  "repository.not_found": "kcloud repositories list --project <name> shows them.",
9668
10656
  "repository.already_registered": "The project already has that repository.",
9669
10657
  "repository.in_use": "Take it off its agents first.",
9670
- "repository.unreachable": "The credential cannot see it on GitHub.",
9671
- "credential.kind_mismatch": "A repository needs a github_pat credential.",
9672
- "credential.rejected": "GitHub refused that credential; kcloud credentials rotate <credential> --project <name> replaces it.",
10658
+ "repository.unreachable": (details) => `The credential cannot see it on ${providerOf(details)}; kcloud repositories available lists what it sees.`,
10659
+ "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.`,
10660
+ "credential.kind_mismatch": "A repository needs the credential of a git provider: a github_pat, gitlab_token or bitbucket_token.",
10661
+ "credential.rejected": (details) => `${providerOf(details)} refused that credential; kcloud credentials rotate <credential> --project <name> replaces it.`,
10662
+ "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.`,
9673
10663
  "credential.not_found": "kcloud credentials list --project <name> shows them."
9674
10664
  };
9675
10665
  const projectFrom = async (run, given) => {
@@ -9686,13 +10676,15 @@ const resolveRepositoryId = async (api, projectId, reference) => {
9686
10676
  return first.id;
9687
10677
  };
9688
10678
  const ownerAndName = (reference) => {
9689
- const [owner, name, ...rest] = reference.split("/");
9690
- if (owner === void 0 || name === void 0 || rest.length > 0 || name === "") throw new UsageError(`${reference} is not a GitHub repository.`, "Name it as owner/name.");
10679
+ const separator = reference.lastIndexOf("/");
10680
+ const owner = reference.slice(0, separator);
10681
+ const name = reference.slice(separator + 1);
10682
+ 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.");
9691
10683
  return [owner, name];
9692
10684
  };
9693
10685
  const createRepositoriesCommand = (adaptersOf) => defineGroup({
9694
10686
  name: "repositories",
9695
- summary: "The GitHub repositories of a project.",
10687
+ summary: "The GitHub, GitLab and Bitbucket repositories of a project.",
9696
10688
  usage: "repositories <command>",
9697
10689
  subcommands: [
9698
10690
  defineCommand({
@@ -9711,6 +10703,8 @@ const createRepositoriesCommand = (adaptersOf) => defineGroup({
9711
10703
  const items = await repositoriesOf(run.api, projectId);
9712
10704
  return printed(run, items, (repositories) => repositories.length === 0 ? "The project has no repository yet." : renderTable([
9713
10705
  { header: "REPOSITORY" },
10706
+ { header: "PROVIDER" },
10707
+ { header: "SERVER" },
9714
10708
  { header: "BRANCH" },
9715
10709
  { header: "VISIBILITY" },
9716
10710
  { header: "STATUS" },
@@ -9718,6 +10712,8 @@ const createRepositoriesCommand = (adaptersOf) => defineGroup({
9718
10712
  { header: "ID" }
9719
10713
  ], repositories.map((repository) => [
9720
10714
  `${repository.owner}/${repository.name}`,
10715
+ repository.provider,
10716
+ repository.serverUrl,
9721
10717
  repository.defaultBranch,
9722
10718
  repository.isPrivate ? "private" : "public",
9723
10719
  repository.status,
@@ -9728,9 +10724,9 @@ const createRepositoriesCommand = (adaptersOf) => defineGroup({
9728
10724
  }),
9729
10725
  defineCommand({
9730
10726
  name: "available",
9731
- summary: "List the GitHub repositories one credential can see.",
10727
+ summary: "List the repositories one git provider credential can see.",
9732
10728
  usage: "repositories available --project <name> --credential <label>",
9733
- optionsUsage: [...projectUsage, ["--credential <label>", "the github_pat credential to look through"]],
10729
+ optionsUsage: [...projectUsage, ["--credential <label>", "the git provider credential to look through"]],
9734
10730
  options: {
9735
10731
  ...projectOption,
9736
10732
  credential: { type: "string" }
@@ -9745,7 +10741,7 @@ const createRepositoriesCommand = (adaptersOf) => defineGroup({
9745
10741
  const credential = invocation.values.credential;
9746
10742
  if (credential === void 0) throw new UsageError("repositories available needs --credential <label>.", "kcloud credentials list --project <name> shows them.");
9747
10743
  const credentialId = await resolveCredentialId(run.api, projectId, credential);
9748
- const listed = dataOf("list the repositories of the credential", await run.api.GET("/projects/{id}/repositories/github", { params: {
10744
+ const listed = dataOf("list the repositories of the credential", await run.api.GET("/projects/{id}/repositories/available", { params: {
9749
10745
  path: { id: projectId },
9750
10746
  query: { credentialId }
9751
10747
  } }), repositoryHints);
@@ -9755,16 +10751,20 @@ const createRepositoriesCommand = (adaptersOf) => defineGroup({
9755
10751
  { header: "VISIBILITY" }
9756
10752
  ], page.items.map((repository) => [
9757
10753
  `${repository.owner}/${repository.name}`,
9758
- repository.defaultBranch,
10754
+ repository.defaultBranch ?? "-",
9759
10755
  repository.isPrivate ? "private" : "public"
9760
- ])), ...page.truncated ? ["", "GitHub returned only the first page."] : []].join("\n"));
10756
+ ])), ...page.truncated ? ["", `${providerNameOf(page.provider)} returned only the first pages.`] : []].join("\n"));
9761
10757
  }
9762
10758
  }),
9763
10759
  defineCommand({
9764
10760
  name: "add",
9765
- summary: "Register a GitHub repository in a project.",
10761
+ summary: "Register a repository of a git provider in a project.",
9766
10762
  usage: "repositories add <owner/name> --project <name> --credential <label>",
9767
- optionsUsage: [...projectUsage, ["--credential <label>", "the github_pat credential that can see it"]],
10763
+ optionsUsage: [
10764
+ ...projectUsage,
10765
+ ["--credential <label>", "the git provider credential that can see it; it sets the provider and the server"],
10766
+ ["", "a GitLab subgroup belongs to the owner: group/subgroup/name; Bitbucket Data Center takes KEY/name"]
10767
+ ],
9768
10768
  options: {
9769
10769
  ...projectOption,
9770
10770
  credential: { type: "string" }
@@ -9795,7 +10795,7 @@ const createRepositoriesCommand = (adaptersOf) => defineGroup({
9795
10795
  name: "set-credential",
9796
10796
  summary: "Point a repository at another credential.",
9797
10797
  usage: "repositories set-credential <owner/name> --project <name> --credential <label>",
9798
- optionsUsage: [...projectUsage, ["--credential <label>", "the github_pat credential to use from now on"]],
10798
+ optionsUsage: [...projectUsage, ["--credential <label>", "the credential to use from now on, of the provider and server of the repository"]],
9799
10799
  options: {
9800
10800
  ...projectOption,
9801
10801
  credential: { type: "string" }
@@ -9823,7 +10823,7 @@ const createRepositoriesCommand = (adaptersOf) => defineGroup({
9823
10823
  }),
9824
10824
  defineCommand({
9825
10825
  name: "refresh",
9826
- summary: "Check a repository against GitHub again.",
10826
+ summary: "Check a repository against its provider again.",
9827
10827
  usage: "repositories refresh <owner/name> --project <name>",
9828
10828
  optionsUsage: projectUsage,
9829
10829
  options: projectOption,
@@ -9873,26 +10873,26 @@ const createRepositoriesCommand = (adaptersOf) => defineGroup({
9873
10873
  });
9874
10874
  //#endregion
9875
10875
  //#region src/mcp/tools/repositories.ts
9876
- const repositoryField = z.string().trim().min(1).describe("The repository, as owner/name, by its name or by its id.");
9877
- const credentialField = z.string().trim().min(1).describe("A github_pat credential of the project, by its label or its id.");
10876
+ 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.");
10877
+ 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.");
9878
10878
  const repositoryTools = [
9879
10879
  defineTool({
9880
10880
  name: "repositories_list",
9881
10881
  title: "List repositories",
9882
- description: "registered lists the GitHub repositories of a project; github lists what one github_pat credential can see on GitHub, to pick one for repository_add.",
9883
- operations: ["listRepositories", "listGitHubRepositories"],
10882
+ 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.",
10883
+ operations: ["listRepositories", "listAvailableRepositories"],
9884
10884
  safety: "read",
9885
10885
  input: z.object({
9886
10886
  project: projectField,
9887
- source: z.enum(["registered", "github"]).default("registered"),
9888
- credential: credentialField.optional().describe("The credential to look through, for github.")
10887
+ source: z.enum(["registered", "available"]).default("registered"),
10888
+ credential: credentialField.optional().describe("The credential to look through, for available.")
9889
10889
  }),
9890
10890
  run: async ({ project, source, credential }, run) => {
9891
10891
  const projectId = await projectIdOf(run, project);
9892
10892
  if (source === "registered") return repositoriesOf(run.api, projectId);
9893
- if (credential === void 0) throw new UsageError("github needs a credential.");
10893
+ if (credential === void 0) throw new UsageError("available needs a credential.");
9894
10894
  const credentialId = await resolveCredentialId(run.api, projectId, credential);
9895
- return dataOf("list the repositories of the credential", await run.api.GET("/projects/{id}/repositories/github", { params: {
10895
+ return dataOf("list the repositories of the credential", await run.api.GET("/projects/{id}/repositories/available", { params: {
9896
10896
  path: { id: projectId },
9897
10897
  query: { credentialId }
9898
10898
  } }), repositoryHints);
@@ -9901,12 +10901,12 @@ const repositoryTools = [
9901
10901
  defineTool({
9902
10902
  name: "repository_add",
9903
10903
  title: "Register a repository",
9904
- description: "Register a GitHub repository in a project, so agents can clone it.",
10904
+ description: "Register a repository of a git provider in a project, so agents can clone it. The credential decides the provider and the server.",
9905
10905
  operations: ["registerRepository"],
9906
10906
  safety: "write",
9907
10907
  input: z.object({
9908
10908
  project: projectField,
9909
- repository: z.string().trim().min(1).describe("The GitHub repository, as owner/name."),
10909
+ repository: z.string().trim().min(1).describe("The repository, as owner/name, or group/subgroup/name for a GitLab subgroup."),
9910
10910
  credential: credentialField
9911
10911
  }),
9912
10912
  run: async ({ project, repository, credential }, run) => {
@@ -9926,7 +10926,7 @@ const repositoryTools = [
9926
10926
  defineTool({
9927
10927
  name: "repository_update",
9928
10928
  title: "Change or refresh a repository",
9929
- description: "set_credential points a repository at another github_pat credential; refresh checks it on GitHub again.",
10929
+ description: "set_credential points a repository at another credential of its provider and server; refresh checks it against its provider again.",
9930
10930
  operations: ["updateRepository", "refreshRepository"],
9931
10931
  safety: "write",
9932
10932
  input: z.object({
@@ -10102,6 +11102,7 @@ const kcloudTools = [
10102
11102
  ...accountTools,
10103
11103
  ...organizationTools,
10104
11104
  ...projectTools,
11105
+ ...claudeAccountTools,
10105
11106
  ...credentialTools,
10106
11107
  ...repositoryTools,
10107
11108
  ...projectToolTools,
@@ -10885,6 +11886,7 @@ const kcloudCommands = () => [
10885
11886
  createTokensCommand(createAdapters),
10886
11887
  createAuthCommand(createAdapters),
10887
11888
  createProjectsCommand(createAdapters),
11889
+ createClaudeAccountsCommand(createAdapters),
10888
11890
  createCredentialsCommand(createAdapters),
10889
11891
  createRepositoriesCommand(createAdapters),
10890
11892
  createToolsCommand(createAdapters),