@kikimoradev/kcloud 1.10.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 +1143 -162
  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",
@@ -1124,7 +1278,8 @@ agentEventSchema.options.map((option) => option.shape.event.value);
1124
1278
  provisioned: "platform.provisioned",
1125
1279
  sandboxStarted: "platform.sandboxStarted",
1126
1280
  sandboxStopped: "platform.sandboxStopped",
1127
- restartedByNode: "platform.restartedByNode"
1281
+ restartedByNode: "platform.restartedByNode",
1282
+ claudeAccountChanged: "platform.claudeAccountChanged"
1128
1283
  }).rawLog;
1129
1284
  const agentStateSchema = z.enum([
1130
1285
  "created",
@@ -2052,6 +2207,7 @@ const organizationSchema = z.object({
2052
2207
  slug: organizationSlugSchema,
2053
2208
  color: colorSchema,
2054
2209
  logoHash: logoHashSchema.nullable(),
2210
+ claudeSwitchThreshold: switchThresholdSchema,
2055
2211
  createdAt: z.iso.datetime()
2056
2212
  }).meta({ id: "Organization" });
2057
2213
  z.object({ items: z.array(organizationSchema) }).meta({ id: "OrganizationList" });
@@ -2112,7 +2268,8 @@ z.object({
2112
2268
  const updateOrganizationBodySchema = z.object({
2113
2269
  name: organizationNameSchema,
2114
2270
  slug: organizationSlugSchema,
2115
- color: colorSchema
2271
+ color: colorSchema,
2272
+ claudeSwitchThreshold: switchThresholdSchema.optional()
2116
2273
  }).meta({ id: "UpdateOrganizationBody" });
2117
2274
  z.object({ temporaryPassword: z.string() }).meta({ id: "TemporaryPassword" });
2118
2275
  z.object({ items: z.array(userSchema) }).meta({ id: "UserList" });
@@ -2212,6 +2369,8 @@ const auditActionSchema = z.enum([
2212
2369
  "credential.rotated",
2213
2370
  "credential.deleted",
2214
2371
  "credential.verified",
2372
+ "credential.identity_set",
2373
+ "project.claude_accounts_changed",
2215
2374
  "repository.registered",
2216
2375
  "repository.deleted",
2217
2376
  "tool.created",
@@ -2389,14 +2548,21 @@ const repositoryStatusSchema = z.enum([
2389
2548
  const repositoryNamePattern = /^[A-Za-z0-9](?:[A-Za-z0-9._-]{0,98}[A-Za-z0-9])?$/;
2390
2549
  const forbiddenBranchCharacters = /[\s\\~^:?*[\p{Cc}]/u;
2391
2550
  const isSafeBranchName = (value) => value.length > 0 && value.length <= 255 && !value.startsWith("-") && !forbiddenBranchCharacters.test(value);
2392
- 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" });
2393
2556
  const repositoryNameSchema = z.string().regex(repositoryNamePattern);
2394
2557
  const defaultBranchSchema = z.string().refine(isSafeBranchName, { message: "expected a branch name git and the TSV accept" });
2395
2558
  const repositorySchema = z.object({
2396
2559
  id: idSchema,
2397
2560
  projectId: idSchema,
2398
2561
  version: z.int().nonnegative(),
2399
- githubId: z.int().positive(),
2562
+ githubId: z.int().positive().nullable(),
2563
+ provider: gitProviderSchema,
2564
+ serverUrl: z.string(),
2565
+ providerRepositoryId: z.string(),
2400
2566
  owner: repositoryOwnerSchema,
2401
2567
  name: repositoryNameSchema,
2402
2568
  defaultBranch: defaultBranchSchema,
@@ -2418,16 +2584,18 @@ z.object({
2418
2584
  id: idSchema,
2419
2585
  repositoryId: idSchema
2420
2586
  });
2421
- const gitHubRepositorySchema = z.object({
2587
+ const availableRepositorySchema = z.object({
2422
2588
  owner: repositoryOwnerSchema,
2423
2589
  name: repositoryNameSchema,
2424
- defaultBranch: defaultBranchSchema,
2590
+ defaultBranch: defaultBranchSchema.nullable(),
2425
2591
  isPrivate: z.boolean()
2426
- }).meta({ id: "GitHubRepository" });
2592
+ }).meta({ id: "AvailableRepository" });
2427
2593
  z.object({
2428
- items: z.array(gitHubRepositorySchema),
2429
- truncated: z.boolean()
2430
- }).meta({ id: "GitHubRepositoryList" });
2594
+ items: z.array(availableRepositorySchema),
2595
+ truncated: z.boolean(),
2596
+ provider: gitProviderSchema,
2597
+ serverUrl: z.string()
2598
+ }).meta({ id: "AvailableRepositoryList" });
2431
2599
  z.object({ credentialId: idSchema });
2432
2600
  const toolKindSchema = z.enum([
2433
2601
  "http_api",
@@ -2462,7 +2630,8 @@ const reservedEnvironmentPrefixes = [
2462
2630
  "ANTHROPIC_",
2463
2631
  "GH_",
2464
2632
  "DOCKER_",
2465
- "BC_"
2633
+ "BC_",
2634
+ "KC_GIT_"
2466
2635
  ];
2467
2636
  const reservedEnvironmentNames = /* @__PURE__ */ new Set([
2468
2637
  "PATH",
@@ -2742,7 +2911,9 @@ const agentRepositorySchema = z.object({
2742
2911
  owner: repositoryOwnerSchema,
2743
2912
  name: repositoryNameSchema,
2744
2913
  defaultBranch: defaultBranchSchema,
2745
- status: repositoryStatusSchema
2914
+ status: repositoryStatusSchema,
2915
+ provider: gitProviderSchema,
2916
+ serverUrl: z.string()
2746
2917
  }).meta({ id: "AgentRepository" });
2747
2918
  const agentRuntimeSchema = z.object({
2748
2919
  available: z.boolean(),
@@ -2786,6 +2957,17 @@ const agentNodeSchema = z.object({
2786
2957
  name: nodeNameSchema,
2787
2958
  state: nodeStateSchema
2788
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" });
2789
2971
  agentListItemSchema.extend({
2790
2972
  version: z.int().nonnegative(),
2791
2973
  description: z.string(),
@@ -2794,8 +2976,10 @@ agentListItemSchema.extend({
2794
2976
  settings: kikimoraSettingsSchema,
2795
2977
  effectiveSettings: workerSettingsSchema,
2796
2978
  prompts: agentPromptsSchema,
2797
- claudeCredential: credentialSummarySchema,
2798
- githubCredential: credentialSummarySchema,
2979
+ claudeCredential: credentialSummarySchema.nullable(),
2980
+ claudeAccount: agentClaudeAccountSchema,
2981
+ githubCredential: credentialSummarySchema.nullable(),
2982
+ gitCredentials: z.array(gitCredentialSummarySchema),
2799
2983
  repositories: z.array(agentRepositorySchema),
2800
2984
  toolAssignments: z.array(agentToolSchema),
2801
2985
  release: releaseSummarySchema.nullable(),
@@ -2812,10 +2996,7 @@ const uniqueDirectories = {
2812
2996
  message: "expected every repository to use its own directory",
2813
2997
  path: ["repositories"]
2814
2998
  };
2815
- const distinctCredentials = {
2816
- message: "expected the Claude and the GitHub credential to be two credentials",
2817
- path: ["githubCredentialId"]
2818
- };
2999
+ const agentGitCredentialIdsSchema = z.array(idSchema).max(8).refine((ids) => new Set(ids).size === ids.length, { message: "expected every credential once" });
2819
3000
  const hasUniqueTools = (assignments) => new Set(assignments.map((assignment) => assignment.toolId)).size === assignments.length;
2820
3001
  const agentRepositoryLinksSchema = z.array(agentRepositoryLinkSchema).min(1).max(32);
2821
3002
  const agentToolAssignmentsSchema = z.array(toolAssignmentSchema).max(32).refine(hasUniqueTools, { message: "expected every tool to be assigned once" });
@@ -2828,15 +3009,16 @@ const createAgentBodySchema = z.object({
2828
3009
  kikimoraVersion: kikimoraVersionSchema,
2829
3010
  nodeId: idSchema,
2830
3011
  requiredCapabilities: requiredCapabilitiesSchema.optional(),
2831
- claudeCredentialId: idSchema,
2832
- githubCredentialId: idSchema,
3012
+ claudeCredentialId: idSchema.optional(),
3013
+ githubCredentialId: idSchema.optional(),
3014
+ gitCredentialIds: agentGitCredentialIdsSchema.default([]),
2833
3015
  repositories: agentRepositoryLinksSchema,
2834
3016
  toolAssignments: agentToolAssignmentsSchema,
2835
3017
  prompts: agentPromptsSchema,
2836
3018
  settings: kikimoraSettingsSchema,
2837
3019
  timezone: timezoneSchema,
2838
3020
  machine: agentMachineInputSchema
2839
- }).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" });
2840
3022
  const updateAgentSectionsSchema = z.object({
2841
3023
  general: z.object({
2842
3024
  name: agentNameSchema,
@@ -2845,9 +3027,10 @@ const updateAgentSectionsSchema = z.object({
2845
3027
  }).optional(),
2846
3028
  version: kikimoraVersionSchema.optional(),
2847
3029
  credentials: z.object({
2848
- claudeCredentialId: idSchema,
2849
- githubCredentialId: idSchema
2850
- }).refine((section) => section.claudeCredentialId !== section.githubCredentialId, distinctCredentials).optional(),
3030
+ claudeCredentialId: idSchema.optional(),
3031
+ githubCredentialId: idSchema.optional(),
3032
+ gitCredentialIds: agentGitCredentialIdsSchema.default([])
3033
+ }).optional(),
2851
3034
  repositories: agentRepositoryLinksSchema.optional(),
2852
3035
  tools: agentToolAssignmentsSchema.optional(),
2853
3036
  prompts: agentPromptsSchema.optional(),
@@ -3410,7 +3593,8 @@ const notificationKindSchema = z.enum([
3410
3593
  "backup.overdue",
3411
3594
  "host_operation.finished",
3412
3595
  "dead_letter.queued",
3413
- "node.resumed"
3596
+ "node.resumed",
3597
+ "project.claude_accounts_exhausted"
3414
3598
  ]);
3415
3599
  const notificationSeveritySchema = z.enum([
3416
3600
  "info",
@@ -3424,7 +3608,8 @@ const notificationTargetTypeSchema = z.enum([
3424
3608
  "queue",
3425
3609
  "volume",
3426
3610
  "platform",
3427
- "node"
3611
+ "node",
3612
+ "project"
3428
3613
  ]);
3429
3614
  const hostOperationOutcomes = [
3430
3615
  "succeeded",
@@ -3458,7 +3643,10 @@ z.discriminatedUnion("operation", [
3458
3643
  role: volumeRoleSchema
3459
3644
  })
3460
3645
  ]);
3461
- z.strictObject({ label: textParam }), z.strictObject({
3646
+ z.strictObject({
3647
+ label: textParam,
3648
+ kind: credentialKindSchema
3649
+ }), z.strictObject({
3462
3650
  label: textParam,
3463
3651
  expiresAt: instantParam
3464
3652
  }), z.strictObject({
@@ -3491,7 +3679,10 @@ z.strictObject({ label: textParam }), z.strictObject({
3491
3679
  }).refine((params) => params.pausedAgents === 0 === (params.pausedAgentNames === ""), {
3492
3680
  message: "expected the names of the paused agents exactly when there are some",
3493
3681
  path: ["pausedAgentNames"]
3494
- });
3682
+ }), z.union([z.strictObject({
3683
+ blocked: z.literal(false),
3684
+ nearestResetAt: instantParam
3685
+ }), z.strictObject({ blocked: z.literal(true) })]);
3495
3686
  const notificationParamsSchema = z.record(z.string(), z.union([
3496
3687
  z.string(),
3497
3688
  z.number(),
@@ -4434,6 +4625,22 @@ const runOf = async (adaptersOf, { settings, context }) => {
4434
4625
  };
4435
4626
  };
4436
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
4437
4644
  //#region src/commands/agents/shared.ts
4438
4645
  const agentOption = { project: { type: "string" } };
4439
4646
  const agentUsage = [["--project <name>", "the project of the agent, when the agent is named alone"]];
@@ -4442,7 +4649,11 @@ const actionsAfterPause = /* @__PURE__ */ new Set([
4442
4649
  "restart",
4443
4650
  "resume"
4444
4651
  ]);
4652
+ const serverOf = (serverUrl) => typeof serverUrl === "string" ? serverUrl : "its server";
4445
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.",
4446
4657
  "agent.not_found": "kcloud agents list --project <name> shows the agents.",
4447
4658
  "project.not_found": "kcloud projects list shows the projects you are a member of.",
4448
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.",
@@ -4450,6 +4661,7 @@ const agentHints = {
4450
4661
  "concurrency.conflict": "Someone changed the agent meanwhile; read it again and retry.",
4451
4662
  "agent.node_unsuitable": "The node lacks a capability the agent requires; kcloud nodes show <node> lists what it offers.",
4452
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.",
4453
4665
  "node.outdated": "The node of the agent runs an older release; kcloud nodes show <node> names the release it runs and its update."
4454
4666
  };
4455
4667
  const agentFrom = async (run, reference, project) => {
@@ -4802,8 +5014,7 @@ const agentPlanOf = (agent) => ({
4802
5014
  kikimoraVersion: agent.kikimoraVersion,
4803
5015
  ...agent.node === null ? {} : { nodeId: agent.node.id },
4804
5016
  requiredCapabilities: agent.requiredCapabilities,
4805
- claudeCredentialId: agent.claudeCredential.id,
4806
- githubCredentialId: agent.githubCredential.id,
5017
+ gitCredentialIds: agent.gitCredentials.map(({ id }) => id),
4807
5018
  repositories: agent.repositories.map(({ repositoryId, directory }) => ({
4808
5019
  repositoryId,
4809
5020
  directory
@@ -4823,9 +5034,12 @@ const agentPlanOf = (agent) => ({
4823
5034
  });
4824
5035
  const machineLimitKeys = /* @__PURE__ */ new Set(["cpus", "memoryMb"]);
4825
5036
  const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
4826
- 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) => {
4827
5041
  const named = keys.map((key) => `machine.${key}`).join(" and ");
4828
- 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.`);
4829
5043
  };
4830
5044
  const machineWithoutLimits = (run, machine) => {
4831
5045
  if (!isRecord(machine)) return machine;
@@ -4834,17 +5048,40 @@ const machineWithoutLimits = (run, machine) => {
4834
5048
  warnIgnored(run, ignored);
4835
5049
  return Object.fromEntries(Object.entries(machine).filter(([key]) => !machineLimitKeys.has(key)));
4836
5050
  };
4837
- const agentFileWithoutLimits = (run, document) => isRecord(document) && "machine" in document ? {
4838
- ...document,
4839
- machine: machineWithoutLimits(run, document.machine)
4840
- } : document;
4841
- const agentChangeWithoutLimits = (run, document) => isRecord(document) && isRecord(document.sections) && "machine" in document.sections ? {
4842
- ...document,
4843
- sections: {
4844
- ...document.sections,
4845
- machine: machineWithoutLimits(run, document.sections.machine)
4846
- }
4847
- } : 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
+ };
4848
5085
  //#endregion
4849
5086
  //#region src/commands/agents/config.ts
4850
5087
  const agentsOf = async (scope, project) => {
@@ -4960,7 +5197,7 @@ const configCommands = (adaptersOf) => [
4960
5197
  const agentId = await agentFrom(run, invocation.positionals[0], invocation.values.project);
4961
5198
  const file = invocation.values.file;
4962
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.");
4963
- 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)));
4964
5201
  return printed(run, agent, (changed) => changed.restartRequired ? `Changed ${changed.name}; it needs ${changeTakerOf(changed)} to take the change.` : `Changed ${changed.name}.`);
4965
5202
  }
4966
5203
  }),
@@ -5013,13 +5250,13 @@ const waitTexts = {
5013
5250
  };
5014
5251
  const reasonOf$1 = ({ reason }) => reason ?? "slot";
5015
5252
  const isKnownReason = (reason) => Object.hasOwn(waitTexts, reason);
5016
- 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})`;
5017
5254
  const waitRowsOf = ({ runtime }) => {
5018
5255
  const { admission } = runtime;
5019
5256
  if (admission === null) return [];
5020
5257
  const waits = ["monitor", "executor"].flatMap((role) => {
5021
5258
  const state = admission[role];
5022
- return state?.state === "waiting" ? [`${role} ${waitTextOf(reasonOf$1(state))}`] : [];
5259
+ return state?.state === "waiting" ? [`${role} ${waitTextOf$1(reasonOf$1(state))}`] : [];
5023
5260
  });
5024
5261
  return waits.length === 0 ? [] : [["admissions", waits.join("; ")]];
5025
5262
  };
@@ -5038,10 +5275,33 @@ const claudeCodeOf = ({ runtime, release }) => {
5038
5275
  if (running?.source === "host") return "host";
5039
5276
  return running?.version ?? release?.claudeCodeVersion ?? "unknown";
5040
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
+ };
5041
5292
  const failureRowsOf = ({ failureReason, retryAt }) => {
5042
5293
  if (failureReason === null) return [];
5043
5294
  return [["failure", retryAt === null ? failureReason : `${failureReason}, retry at ${retryAt}`]];
5044
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
+ ]))];
5045
5305
  const detailsOf = (agent) => [
5046
5306
  renderTable([{ header: "FIELD" }, { header: "VALUE" }], [
5047
5307
  ["name", agent.name],
@@ -5051,6 +5311,7 @@ const detailsOf = (agent) => [
5051
5311
  ["observed", agent.observedState],
5052
5312
  ["release", agent.kikimoraVersion],
5053
5313
  ["claude code", claudeCodeOf(agent)],
5314
+ ["claude account", claudeAccountOf(agent)],
5054
5315
  ["node", agent.node === null ? "none" : `${agent.node.name} (${agent.node.state})`],
5055
5316
  ["requires", agent.requiredCapabilities.length === 0 ? "nothing" : agent.requiredCapabilities.join(", ")],
5056
5317
  ["timezone", agent.timezone],
@@ -5071,13 +5332,19 @@ const detailsOf = (agent) => [
5071
5332
  "",
5072
5333
  agent.repositories.length === 0 ? "No repository is cloned into the workspace." : renderTable([
5073
5334
  { header: "REPOSITORY" },
5335
+ { header: "PROVIDER" },
5336
+ { header: "SERVER" },
5074
5337
  { header: "DIRECTORY" },
5075
5338
  { header: "STATUS" }
5076
5339
  ], agent.repositories.map((repository) => [
5077
5340
  `${repository.owner}/${repository.name}`,
5341
+ repository.provider,
5342
+ repository.serverUrl,
5078
5343
  repository.directory,
5079
5344
  repository.status
5080
- ]))
5345
+ ])),
5346
+ "",
5347
+ ...gitCredentialLinesOf(agent)
5081
5348
  ].join("\n");
5082
5349
  //#endregion
5083
5350
  //#region src/commands/agents/placement.ts
@@ -5104,7 +5371,6 @@ const placedNodeId = async (run, { projectId, given, ask }) => {
5104
5371
  };
5105
5372
  //#endregion
5106
5373
  //#region src/commands/agents/create.ts
5107
- const claudeKinds = /* @__PURE__ */ new Set(["anthropic_api_key", "claude_oauth_token"]);
5108
5374
  const catalogueOf = async (api, projectId) => {
5109
5375
  const path = { params: { path: { id: projectId } } };
5110
5376
  return {
@@ -5114,17 +5380,35 @@ const catalogueOf = async (api, projectId) => {
5114
5380
  releases: dataOf("list the releases", await api.GET("/releases")).items.map(({ version }) => version)
5115
5381
  };
5116
5382
  };
5117
- const chosenCredential = async (prompt, catalogue, kinds, question) => {
5118
- const choices = catalogue.credentials.filter((credential) => kinds(credential.kind)).map((credential) => ({
5119
- value: credential.id,
5120
- label: credential.label,
5121
- hint: credential.kind
5122
- }));
5123
- if (choices.length === 0) throw new CliError(`The project holds no credential for ${question}.`, "kcloud credentials add --project <name> stores one.");
5124
- return prompt.select({
5125
- message: question,
5126
- choices
5127
- });
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;
5128
5412
  };
5129
5413
  const chosenRepositories = async (prompt, catalogue) => {
5130
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.");
@@ -5137,7 +5421,7 @@ const chosenRepositories = async (prompt, catalogue) => {
5137
5421
  choices: [...left.map((repository) => ({
5138
5422
  value: repository.id,
5139
5423
  label: `${repository.owner}/${repository.name}`,
5140
- hint: repository.defaultBranch
5424
+ hint: `${repository.provider} ${repository.defaultBranch}`
5141
5425
  })), ...chosen.length === 0 ? [] : [{
5142
5426
  value: "",
5143
5427
  label: "no more repositories",
@@ -5232,15 +5516,16 @@ const askedPlan = async (run, projectId, values, required) => {
5232
5516
  }))
5233
5517
  });
5234
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);
5235
5521
  return {
5236
5522
  name,
5237
5523
  description: "",
5238
5524
  kikimoraVersion: release,
5239
5525
  nodeId,
5240
5526
  ...requirementOf(required),
5241
- claudeCredentialId: await chosenCredential(run.prompt, catalogue, (kind) => claudeKinds.has(kind), "The Claude credential it works with"),
5242
- githubCredentialId: await chosenCredential(run.prompt, catalogue, (kind) => kind === "github_pat", "The GitHub credential it clones with"),
5243
- repositories: [...await chosenRepositories(run.prompt, catalogue)],
5527
+ gitCredentialIds: [...gitCredentialIds],
5528
+ repositories: [...repositories],
5244
5529
  toolAssignments: [...await chosenTools(run.prompt, catalogue)],
5245
5530
  prompts: {
5246
5531
  monitor: await promptText(run, values["monitor-prompt-file"], "What the monitor should watch for"),
@@ -5256,7 +5541,7 @@ const askedPlan = async (run, projectId, values, required) => {
5256
5541
  };
5257
5542
  const isObject = (document) => typeof document === "object" && document !== null && !Array.isArray(document);
5258
5543
  const fileOf = async (run, projectId, { file, node }, required) => {
5259
- const document = agentFileWithoutLimits(run, await documentOf(run, file));
5544
+ const document = acceptedAgentFile(run, await documentOf(run, file));
5260
5545
  const placed = !isObject(document) || node === void 0 && "nodeId" in document ? document : {
5261
5546
  ...document,
5262
5547
  nodeId: await placedNodeId(run, {
@@ -5289,6 +5574,7 @@ const createCommand = (adaptersOf) => defineCommand({
5289
5574
  ["--docker", "give the agent a Docker sidecar, when the project allows it"],
5290
5575
  ["--browser", "give the agent a browser"],
5291
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"],
5292
5578
  ["", "--node and --require replace the nodeId and requiredCapabilities of the file"]
5293
5579
  ],
5294
5580
  options: {
@@ -5498,7 +5784,7 @@ const workKinds = /* @__PURE__ */ new Set([
5498
5784
  "summarising"
5499
5785
  ]);
5500
5786
  const organizationDashboardOf = async (api, organizationId) => dataOf("show the dashboard of the organization", await api.GET("/organizations/{organizationId}/dashboard", { params: { path: { organizationId } } }), organizationHints);
5501
- const statusOf = (agent) => agent.status === "transitioning" ? `${agent.status} (${agent.state})` : agent.status;
5787
+ const statusOf$1 = (agent) => agent.status === "transitioning" ? `${agent.status} (${agent.state})` : agent.status;
5502
5788
  const taskOf = (agent) => {
5503
5789
  const { activity } = agent;
5504
5790
  if (activity === null) return noTask;
@@ -5511,7 +5797,7 @@ const dashboardText = (dashboard) => {
5511
5797
  const rows = dashboard.projects.flatMap((project) => project.agents.map((agent) => [
5512
5798
  project.slug,
5513
5799
  agent.slug,
5514
- statusOf(agent),
5800
+ statusOf$1(agent),
5515
5801
  agent.since,
5516
5802
  taskOf(agent),
5517
5803
  agent.costTodayUsd.toFixed(2)
@@ -5544,7 +5830,7 @@ const frameLineOf = (names, frame, at) => {
5544
5830
  slug: agent.slug
5545
5831
  });
5546
5832
  const project = names.projects.get(agent.projectId) ?? agent.projectId;
5547
- return `${at.toISOString()} ${project}/${agent.slug} ${statusOf(agent)} ${taskOf(agent)}`;
5833
+ return `${at.toISOString()} ${project}/${agent.slug} ${statusOf$1(agent)} ${taskOf(agent)}`;
5548
5834
  }
5549
5835
  const removed = dashboardRemovalSchema.safeParse(frame.data);
5550
5836
  if (frame.event === "dashboard.removed" && removed.success) {
@@ -5711,10 +5997,10 @@ const pageOptions = {
5711
5997
  };
5712
5998
  const allUsage = ["--all", "follow the cursor to the end instead of one page"];
5713
5999
  const pageUsage = [["--limit <n>", `how many rows (default: 50)`], allUsage];
5714
- const dayOf$1 = (date) => date.toISOString().slice(0, 10);
6000
+ const dayOf$2 = (date) => date.toISOString().slice(0, 10);
5715
6001
  const costWindowOf = (days, now) => ({
5716
- from: dayOf$1(/* @__PURE__ */ new Date(now.getTime() - (days - 1) * 24 * 60 * 6e4)),
5717
- to: dayOf$1(now)
6002
+ from: dayOf$2(/* @__PURE__ */ new Date(now.getTime() - (days - 1) * 24 * 60 * 6e4)),
6003
+ to: dayOf$2(now)
5718
6004
  });
5719
6005
  const windowFrom = (days, now) => {
5720
6006
  const span = days === void 0 ? 7 : Number(days);
@@ -6438,16 +6724,57 @@ const secretUsage = [
6438
6724
  ];
6439
6725
  //#endregion
6440
6726
  //#region src/commands/credentials.ts
6441
- const projectOption$2 = { project: { type: "string" } };
6442
- const projectUsage$2 = [["--project <name>", "the project the credential belongs to, by slug, name or id"]];
6443
- 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
+ };
6444
6735
  const credentialHints = {
6445
6736
  "project.not_found": "kcloud projects list shows the projects you are a member of.",
6446
6737
  "credential.not_found": "kcloud credentials list --project <name> shows them.",
6447
6738
  "credential.label_taken": "Another credential of the project already has that label.",
6448
- "credential.in_use": "Point the agents at another credential first.",
6449
- "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
+ };
6450
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("'", `'\\''`)}'`;
6451
6778
  const percent = 100;
6452
6779
  const notApplicable = "-";
6453
6780
  const usedOf = (window) => window === null ? notApplicable : `${String(Math.round(window.utilization * percent))}%`;
@@ -6474,12 +6801,13 @@ const usageReportOf = (credential) => {
6474
6801
  window?.resetsAt ?? notApplicable
6475
6802
  ]))}\n${usage.stale ? `Read at ${usage.readAt}; the latest reading failed.` : `Read at ${usage.readAt}.`}`;
6476
6803
  };
6804
+ const accountOf = ({ metadata }) => metadata.accountName ?? metadata.githubLogin ?? notApplicable;
6477
6805
  const isoDateLength = 10;
6478
6806
  const expiryOf = ({ expiresAt, expiresAtComputed }) => {
6479
6807
  if (expiresAt === null) return "never";
6480
6808
  return expiresAtComputed ? `about ${expiresAt.slice(0, isoDateLength)}` : expiresAt;
6481
6809
  };
6482
- const projectFrom$2 = async (run, given) => {
6810
+ const projectFrom$3 = async (run, given) => {
6483
6811
  if (given === void 0) throw new UsageError("This command needs a project.", "Pass --project <name>; kcloud projects list shows them.");
6484
6812
  return resolveProjectId(run, given);
6485
6813
  };
@@ -6495,55 +6823,60 @@ const resolveCredential = async (api, projectId, reference) => {
6495
6823
  const resolveCredentialId = async (api, projectId, reference) => (await resolveCredential(api, projectId, reference)).id;
6496
6824
  const createCredentialsCommand = (adaptersOf) => defineGroup({
6497
6825
  name: "credentials",
6498
- summary: "The Claude and GitHub credentials of a project.",
6826
+ summary: "The GitHub, GitLab and Bitbucket tokens of a project.",
6499
6827
  usage: "credentials <command>",
6500
6828
  subcommands: [
6501
6829
  defineCommand({
6502
6830
  name: "list",
6503
6831
  summary: "List the credentials of a project.",
6504
6832
  usage: "credentials list --project <name>",
6505
- optionsUsage: projectUsage$2,
6506
- options: projectOption$2,
6833
+ optionsUsage: projectUsage$3,
6834
+ options: projectOption$3,
6507
6835
  positionals: {
6508
6836
  min: 0,
6509
6837
  max: 0
6510
6838
  },
6511
6839
  run: async (invocation) => {
6512
6840
  const run = await runOf(adaptersOf, invocation);
6513
- const projectId = await projectFrom$2(run, invocation.values.project);
6841
+ const projectId = await projectFrom$3(run, invocation.values.project);
6514
6842
  const items = await credentialsOf(run.api, projectId);
6515
- 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([
6516
6844
  { header: "LABEL" },
6517
6845
  { header: "KIND" },
6846
+ { header: "SERVER" },
6847
+ { header: "ACCOUNT" },
6518
6848
  { header: "FINGERPRINT" },
6519
6849
  { header: "VERIFIED" },
6520
6850
  { header: "EXPIRES" },
6521
- { header: "USAGE" },
6522
6851
  { header: "ID" }
6523
6852
  ], credentials.map((credential) => [
6524
6853
  credential.label,
6525
6854
  credential.kind,
6855
+ credentialServerOf(credential) ?? notApplicable,
6856
+ accountOf(credential),
6526
6857
  credential.fingerprint,
6527
6858
  credential.verification,
6528
6859
  expiryOf(credential),
6529
- usageSummaryOf(credential),
6530
6860
  credential.id
6531
- ])));
6861
+ ])), ...missingIdentityNotes(credentials, invocation.values.project).flatMap((note, index) => index === 0 ? ["", note] : [note])].join("\n"));
6532
6862
  }
6533
6863
  }),
6534
6864
  defineCommand({
6535
6865
  name: "add",
6536
6866
  summary: "Store a credential in a project, asking for the secret.",
6537
- 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>]",
6538
6868
  optionsUsage: [
6539
- ...projectUsage$2,
6540
- ["--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"],
6541
6872
  ["--label <label>", "how the panel and the agents name it"],
6873
+ ...gitOptionsUsage,
6542
6874
  ...secretUsage
6543
6875
  ],
6544
6876
  options: {
6545
- ...projectOption$2,
6877
+ ...projectOption$3,
6546
6878
  ...secretOptions,
6879
+ ...gitOptions,
6547
6880
  kind: { type: "string" },
6548
6881
  label: { type: "string" }
6549
6882
  },
@@ -6554,11 +6887,13 @@ const createCredentialsCommand = (adaptersOf) => defineGroup({
6554
6887
  run: async (invocation) => {
6555
6888
  const { values } = invocation;
6556
6889
  const run = await runOf(adaptersOf, invocation);
6557
- const projectId = await projectFrom$2(run, values.project);
6558
- const kind = credentialKinds.find((candidate) => candidate === values.kind);
6559
- 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(", ")}.`);
6560
6894
  const label = values.label ?? "";
6561
6895
  if (label.trim() === "") throw new UsageError("credentials add needs --label <label>.");
6896
+ const gitFields = gitFieldsOf(kind, values);
6562
6897
  const secret = await secretOf({
6563
6898
  run,
6564
6899
  file: values["secret-file"],
@@ -6570,19 +6905,60 @@ const createCredentialsCommand = (adaptersOf) => defineGroup({
6570
6905
  body: {
6571
6906
  kind,
6572
6907
  label,
6573
- secret
6908
+ secret,
6909
+ ...gitFields
6574
6910
  }
6575
- }), credentialHints);
6911
+ }), addHints);
6576
6912
  return printed(run, credential, (stored) => `Stored ${stored.label} (${stored.fingerprint}) as ${stored.id}.`);
6577
6913
  }
6578
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
+ }),
6579
6955
  defineCommand({
6580
6956
  name: "rename",
6581
6957
  summary: "Change the label of a credential.",
6582
6958
  usage: "credentials rename <credential> --project <name> --label <label>",
6583
- optionsUsage: [...projectUsage$2, ["--label <label>", "the new label"]],
6959
+ optionsUsage: [...projectUsage$3, ["--label <label>", "the new label"]],
6584
6960
  options: {
6585
- ...projectOption$2,
6961
+ ...projectOption$3,
6586
6962
  label: { type: "string" }
6587
6963
  },
6588
6964
  positionals: {
@@ -6592,7 +6968,7 @@ const createCredentialsCommand = (adaptersOf) => defineGroup({
6592
6968
  run: async (invocation) => {
6593
6969
  const { values } = invocation;
6594
6970
  const run = await runOf(adaptersOf, invocation);
6595
- const projectId = await projectFrom$2(run, values.project);
6971
+ const projectId = await projectFrom$3(run, values.project);
6596
6972
  const credentialId = await resolveCredentialId(run.api, projectId, invocation.positionals[0] ?? "");
6597
6973
  const label = values.label ?? "";
6598
6974
  if (label.trim() === "") throw new UsageError("credentials rename needs --label <label>.");
@@ -6611,12 +6987,12 @@ const createCredentialsCommand = (adaptersOf) => defineGroup({
6611
6987
  summary: "Replace the secret of a credential.",
6612
6988
  usage: "credentials rotate <credential> --project <name> [--restart-agents] [--secret-file <path>]",
6613
6989
  optionsUsage: [
6614
- ...projectUsage$2,
6990
+ ...projectUsage$3,
6615
6991
  ["--restart-agents", "restart the agents that use it once the secret is in"],
6616
6992
  ...secretUsage
6617
6993
  ],
6618
6994
  options: {
6619
- ...projectOption$2,
6995
+ ...projectOption$3,
6620
6996
  ...secretOptions,
6621
6997
  "restart-agents": {
6622
6998
  type: "boolean",
@@ -6630,7 +7006,7 @@ const createCredentialsCommand = (adaptersOf) => defineGroup({
6630
7006
  run: async (invocation) => {
6631
7007
  const { values } = invocation;
6632
7008
  const run = await runOf(adaptersOf, invocation);
6633
- const projectId = await projectFrom$2(run, values.project);
7009
+ const projectId = await projectFrom$3(run, values.project);
6634
7010
  const credentialId = await resolveCredentialId(run.api, projectId, invocation.positionals[0] ?? "");
6635
7011
  const secret = await secretOf({
6636
7012
  run,
@@ -6655,15 +7031,15 @@ const createCredentialsCommand = (adaptersOf) => defineGroup({
6655
7031
  name: "verify",
6656
7032
  summary: "Ask the instance to verify a credential against its service.",
6657
7033
  usage: "credentials verify <credential> --project <name>",
6658
- optionsUsage: projectUsage$2,
6659
- options: projectOption$2,
7034
+ optionsUsage: projectUsage$3,
7035
+ options: projectOption$3,
6660
7036
  positionals: {
6661
7037
  min: 1,
6662
7038
  max: 1
6663
7039
  },
6664
7040
  run: async (invocation) => {
6665
7041
  const run = await runOf(adaptersOf, invocation);
6666
- const projectId = await projectFrom$2(run, invocation.values.project);
7042
+ const projectId = await projectFrom$3(run, invocation.values.project);
6667
7043
  const credentialId = await resolveCredentialId(run.api, projectId, invocation.positionals[0] ?? "");
6668
7044
  doneWith("verify the credential", await run.api.POST("/projects/{id}/credentials/{credentialId}/verify", { params: { path: {
6669
7045
  id: projectId,
@@ -6676,9 +7052,9 @@ const createCredentialsCommand = (adaptersOf) => defineGroup({
6676
7052
  name: "usage",
6677
7053
  summary: "Show the usage of the Claude subscription behind a Claude Code OAuth token.",
6678
7054
  usage: "credentials usage <credential> --project <name> [--refresh]",
6679
- 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"]],
6680
7056
  options: {
6681
- ...projectOption$2,
7057
+ ...projectOption$3,
6682
7058
  refresh: {
6683
7059
  type: "boolean",
6684
7060
  default: false
@@ -6690,9 +7066,9 @@ const createCredentialsCommand = (adaptersOf) => defineGroup({
6690
7066
  },
6691
7067
  run: async (invocation) => {
6692
7068
  const run = await runOf(adaptersOf, invocation);
6693
- const projectId = await projectFrom$2(run, invocation.values.project);
7069
+ const projectId = await projectFrom$3(run, invocation.values.project);
6694
7070
  const stored = await resolveCredential(run.api, projectId, invocation.positionals[0] ?? "");
6695
- if (stored.kind !== "claude_oauth_token") throw new CliError(`${stored.label} is a ${stored.kind}, which reports no usage.`, usageUnavailable);
7071
+ if (stored.kind !== "claude_oauth_token") throw new CliError(`${stored.label} is a ${stored.kind}, which reports no usage.`, usageUnavailable$1);
6696
7072
  const credential = invocation.values.refresh ? dataOf("read the usage of the credential", await run.api.POST("/projects/{id}/credentials/{credentialId}/usage", { params: { path: {
6697
7073
  id: projectId,
6698
7074
  credentialId: stored.id
@@ -6704,15 +7080,15 @@ const createCredentialsCommand = (adaptersOf) => defineGroup({
6704
7080
  name: "delete",
6705
7081
  summary: "Delete a credential no agent uses.",
6706
7082
  usage: "credentials delete <credential> --project <name>",
6707
- optionsUsage: projectUsage$2,
6708
- options: projectOption$2,
7083
+ optionsUsage: projectUsage$3,
7084
+ options: projectOption$3,
6709
7085
  positionals: {
6710
7086
  min: 1,
6711
7087
  max: 1
6712
7088
  },
6713
7089
  run: async (invocation) => {
6714
7090
  const run = await runOf(adaptersOf, invocation);
6715
- const projectId = await projectFrom$2(run, invocation.values.project);
7091
+ const projectId = await projectFrom$3(run, invocation.values.project);
6716
7092
  const reference = invocation.positionals[0] ?? "";
6717
7093
  const credentialId = await resolveCredentialId(run.api, projectId, reference);
6718
7094
  await confirmed({
@@ -6730,6 +7106,263 @@ const createCredentialsCommand = (adaptersOf) => defineGroup({
6730
7106
  ]
6731
7107
  });
6732
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
6733
7366
  //#region src/mcp/tool.ts
6734
7367
  const defineTool = (definition) => ({
6735
7368
  ...definition,
@@ -6990,7 +7623,7 @@ const agentTools = [
6990
7623
  defineTool({
6991
7624
  name: "agent_create",
6992
7625
  title: "Create an agent",
6993
- description: "Create an agent in a project. The node it runs on, the credentials, repositories and tools are named by the ids that nodes_list, credentials_list, repositories_list and tools_list return; agent_show with asPlan shows a complete example. requiredCapabilities names what the node has to offer, from the offered list of nodes_list; the instance refuses a node that lacks one. The agent stays stopped until agent_action starts it.",
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.",
6994
7627
  operations: ["createAgent"],
6995
7628
  safety: "write",
6996
7629
  input: z.object({
@@ -7005,7 +7638,7 @@ const agentTools = [
7005
7638
  defineTool({
7006
7639
  name: "agent_update",
7007
7640
  title: "Change an agent",
7008
- description: "Change sections of an agent. Without version the change applies to the version read just before it. When restartRequired comes back true, agent_action start or update applies the change, whichever availableActions offers.",
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.",
7009
7642
  operations: ["updateAgent"],
7010
7643
  safety: "write",
7011
7644
  input: z.object({
@@ -7695,13 +8328,311 @@ const agentBackupTools = [
7695
8328
  })
7696
8329
  ];
7697
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
7698
8629
  //#region src/mcp/tools/credentials.ts
7699
8630
  const credentialField$1 = z.string().trim().min(1).describe("The credential, by its label or its id.");
7700
8631
  const credentialTools = [
7701
8632
  defineTool({
7702
8633
  name: "credentials_list",
7703
8634
  title: "List credentials",
7704
- 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.",
7705
8636
  operations: ["listCredentials"],
7706
8637
  safety: "read",
7707
8638
  input: z.object({ project: projectField }),
@@ -7709,11 +8640,12 @@ const credentialTools = [
7709
8640
  }),
7710
8641
  defineTool({
7711
8642
  name: "credential_update",
7712
- title: "Rename, verify or read the usage of a credential",
7713
- description: "rename gives a credential a new label; verify asks the instance to check it against its service, and credentials_list shows the answer; refresh_usage reads the usage of the Claude subscription behind a Claude Code OAuth token now, or answers the last reading within 30 seconds of the previous one.",
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.",
7714
8645
  operations: [
7715
8646
  "renameCredential",
7716
8647
  "verifyCredential",
8648
+ "setCredentialCommitIdentity",
7717
8649
  "refreshCredentialUsage"
7718
8650
  ],
7719
8651
  safety: "write",
@@ -7723,11 +8655,14 @@ const credentialTools = [
7723
8655
  action: z.enum([
7724
8656
  "rename",
7725
8657
  "verify",
8658
+ "set_identity",
7726
8659
  "refresh_usage"
7727
8660
  ]),
7728
- 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.")
7729
8664
  }),
7730
- run: async ({ project, credential, action, label }, run) => {
8665
+ run: async ({ project, credential, action, label, commitName, commitEmail }, run) => {
7731
8666
  const projectId = await projectIdOf(run, project);
7732
8667
  const credentialId = await resolveCredentialId(run.api, projectId, credential);
7733
8668
  if (action === "refresh_usage") return dataOf("read the usage of the credential", await run.api.POST("/projects/{id}/credentials/{credentialId}/usage", { params: { path: {
@@ -7744,6 +8679,19 @@ const credentialTools = [
7744
8679
  verifying: true
7745
8680
  };
7746
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
+ }
7747
8695
  if (label === void 0) throw new UsageError("rename needs a label.");
7748
8696
  return dataOf("rename the credential", await run.api.PATCH("/projects/{id}/credentials/{credentialId}", {
7749
8697
  params: { path: {
@@ -8528,6 +9476,12 @@ const costsText = (costs, { from, to }, slugs) => [
8528
9476
  "",
8529
9477
  `${costs.totalCostUsd.toFixed(2)} over ${costs.totalRuns} runs and ${costs.sessions} sessions.`
8530
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
+ };
8531
9485
  const organizationChangeHints = {
8532
9486
  ...organizationHints,
8533
9487
  "organization.slug_taken": "Another organization already uses that slug.",
@@ -8548,12 +9502,14 @@ const updatedOrganization = async (api, current, fields) => {
8548
9502
  if ([
8549
9503
  fields.name,
8550
9504
  fields.slug,
8551
- fields.color
9505
+ fields.color,
9506
+ fields.switchThreshold
8552
9507
  ].every((field) => field === void 0)) return current;
8553
9508
  const body = {
8554
9509
  name: fields.name ?? current.name,
8555
9510
  slug: fields.slug ?? current.slug,
8556
- color: fields.color ?? current.color
9511
+ color: fields.color ?? current.color,
9512
+ ...fields.switchThreshold === void 0 ? {} : { claudeSwitchThreshold: fields.switchThreshold }
8557
9513
  };
8558
9514
  validated(updateOrganizationBodySchema, body, "a change to an organization");
8559
9515
  return dataOf("change the organization", await api.PATCH("/organizations/{organizationId}", {
@@ -8656,6 +9612,7 @@ const createOrgsCommand = (adaptersOf) => defineGroup({
8656
9612
  ["slug", shown.slug],
8657
9613
  ["color", shown.color],
8658
9614
  ["logo", shown.logoHash ?? "-"],
9615
+ ["switch threshold", `${String(shown.claudeSwitchThreshold)}%`],
8659
9616
  ["created", shown.createdAt],
8660
9617
  ["id", shown.id]
8661
9618
  ]));
@@ -8663,18 +9620,20 @@ const createOrgsCommand = (adaptersOf) => defineGroup({
8663
9620
  }),
8664
9621
  defineCommand({
8665
9622
  name: "set",
8666
- summary: "Change the name, slug, color or logo of an organization.",
8667
- 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>]",
8668
9625
  optionsUsage: [
8669
9626
  ["--name <name>", "the new name"],
8670
9627
  ["--slug <slug>", "the new slug; scripts and --org that name the old one stop finding it"],
8671
9628
  ["--color <#rrggbb>", "the color of the organization in the panel"],
8672
- ...logoUsage
9629
+ ...logoUsage,
9630
+ ["--switch-threshold <percent>", `the usage at which a project moves to its next Claude account, 1 to 100`]
8673
9631
  ],
8674
9632
  options: {
8675
9633
  name: { type: "string" },
8676
9634
  slug: { type: "string" },
8677
9635
  color: { type: "string" },
9636
+ "switch-threshold": { type: "string" },
8678
9637
  ...logoOptions
8679
9638
  },
8680
9639
  positionals: {
@@ -8683,15 +9642,20 @@ const createOrgsCommand = (adaptersOf) => defineGroup({
8683
9642
  },
8684
9643
  run: async (invocation) => {
8685
9644
  const { values } = invocation;
9645
+ const switchThreshold = switchThresholdOf(values["switch-threshold"]);
8686
9646
  if ([
8687
9647
  values.name,
8688
9648
  values.slug,
8689
- values.color
8690
- ].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.");
8691
9652
  const logo = await logoActionOf(invocation.context.workingDirectory, values);
8692
9653
  const run = await runOf(adaptersOf, invocation);
8693
9654
  const current = await organizationOrInUse(run, invocation.positionals[0]);
8694
- const updated = await updatedOrganization(run.api, current, values);
9655
+ const updated = await updatedOrganization(run.api, current, {
9656
+ ...values,
9657
+ switchThreshold
9658
+ });
8695
9659
  if (updated.slug !== current.slug) {
8696
9660
  await followSlug(run.context.homeDirectory, run.origin, current, updated);
8697
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.`));
@@ -8767,14 +9731,15 @@ const organizationTools = [
8767
9731
  defineTool({
8768
9732
  name: "organization_update",
8769
9733
  title: "Change an organization",
8770
- 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.",
8771
9735
  operations: ["listOrganizations", "updateOrganization"],
8772
9736
  safety: "write",
8773
9737
  idempotent: true,
8774
9738
  input: z.object({
8775
9739
  organization: z.string().trim().min(1).optional().describe("The organization, by its slug, its name or its id."),
8776
9740
  name: organizationNameSchema.optional(),
8777
- 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.")
8778
9743
  }),
8779
9744
  run: async ({ organization, ...change }, run) => updatedOrganization(run.api, await organizationOrInUse(run, organization), change)
8780
9745
  }),
@@ -9484,7 +10449,8 @@ const createProjectsCommand = (adaptersOf) => defineGroup({
9484
10449
  return said(run, `Deleted the project ${project.name}.`);
9485
10450
  }
9486
10451
  }),
9487
- projectMembersCommand(adaptersOf)
10452
+ projectMembersCommand(adaptersOf),
10453
+ projectAccountsCommand(adaptersOf)
9488
10454
  ]
9489
10455
  });
9490
10456
  //#endregion
@@ -9683,14 +10649,17 @@ const releaseTools = [defineTool({
9683
10649
  //#region src/commands/repositories.ts
9684
10650
  const projectOption = { project: { type: "string" } };
9685
10651
  const projectUsage = [["--project <name>", "the project the repository belongs to, by slug, name or id"]];
10652
+ const providerOf = (details) => providerNameOf(details.provider);
9686
10653
  const repositoryHints = {
9687
10654
  "project.not_found": "kcloud projects list shows the projects you are a member of.",
9688
10655
  "repository.not_found": "kcloud repositories list --project <name> shows them.",
9689
10656
  "repository.already_registered": "The project already has that repository.",
9690
10657
  "repository.in_use": "Take it off its agents first.",
9691
- "repository.unreachable": "The credential cannot see it on GitHub.",
9692
- "credential.kind_mismatch": "A repository needs a github_pat credential.",
9693
- "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.`,
9694
10663
  "credential.not_found": "kcloud credentials list --project <name> shows them."
9695
10664
  };
9696
10665
  const projectFrom = async (run, given) => {
@@ -9707,13 +10676,15 @@ const resolveRepositoryId = async (api, projectId, reference) => {
9707
10676
  return first.id;
9708
10677
  };
9709
10678
  const ownerAndName = (reference) => {
9710
- const [owner, name, ...rest] = reference.split("/");
9711
- 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.");
9712
10683
  return [owner, name];
9713
10684
  };
9714
10685
  const createRepositoriesCommand = (adaptersOf) => defineGroup({
9715
10686
  name: "repositories",
9716
- summary: "The GitHub repositories of a project.",
10687
+ summary: "The GitHub, GitLab and Bitbucket repositories of a project.",
9717
10688
  usage: "repositories <command>",
9718
10689
  subcommands: [
9719
10690
  defineCommand({
@@ -9732,6 +10703,8 @@ const createRepositoriesCommand = (adaptersOf) => defineGroup({
9732
10703
  const items = await repositoriesOf(run.api, projectId);
9733
10704
  return printed(run, items, (repositories) => repositories.length === 0 ? "The project has no repository yet." : renderTable([
9734
10705
  { header: "REPOSITORY" },
10706
+ { header: "PROVIDER" },
10707
+ { header: "SERVER" },
9735
10708
  { header: "BRANCH" },
9736
10709
  { header: "VISIBILITY" },
9737
10710
  { header: "STATUS" },
@@ -9739,6 +10712,8 @@ const createRepositoriesCommand = (adaptersOf) => defineGroup({
9739
10712
  { header: "ID" }
9740
10713
  ], repositories.map((repository) => [
9741
10714
  `${repository.owner}/${repository.name}`,
10715
+ repository.provider,
10716
+ repository.serverUrl,
9742
10717
  repository.defaultBranch,
9743
10718
  repository.isPrivate ? "private" : "public",
9744
10719
  repository.status,
@@ -9749,9 +10724,9 @@ const createRepositoriesCommand = (adaptersOf) => defineGroup({
9749
10724
  }),
9750
10725
  defineCommand({
9751
10726
  name: "available",
9752
- summary: "List the GitHub repositories one credential can see.",
10727
+ summary: "List the repositories one git provider credential can see.",
9753
10728
  usage: "repositories available --project <name> --credential <label>",
9754
- optionsUsage: [...projectUsage, ["--credential <label>", "the github_pat credential to look through"]],
10729
+ optionsUsage: [...projectUsage, ["--credential <label>", "the git provider credential to look through"]],
9755
10730
  options: {
9756
10731
  ...projectOption,
9757
10732
  credential: { type: "string" }
@@ -9766,7 +10741,7 @@ const createRepositoriesCommand = (adaptersOf) => defineGroup({
9766
10741
  const credential = invocation.values.credential;
9767
10742
  if (credential === void 0) throw new UsageError("repositories available needs --credential <label>.", "kcloud credentials list --project <name> shows them.");
9768
10743
  const credentialId = await resolveCredentialId(run.api, projectId, credential);
9769
- const listed = dataOf("list the repositories of the credential", await run.api.GET("/projects/{id}/repositories/github", { params: {
10744
+ const listed = dataOf("list the repositories of the credential", await run.api.GET("/projects/{id}/repositories/available", { params: {
9770
10745
  path: { id: projectId },
9771
10746
  query: { credentialId }
9772
10747
  } }), repositoryHints);
@@ -9776,16 +10751,20 @@ const createRepositoriesCommand = (adaptersOf) => defineGroup({
9776
10751
  { header: "VISIBILITY" }
9777
10752
  ], page.items.map((repository) => [
9778
10753
  `${repository.owner}/${repository.name}`,
9779
- repository.defaultBranch,
10754
+ repository.defaultBranch ?? "-",
9780
10755
  repository.isPrivate ? "private" : "public"
9781
- ])), ...page.truncated ? ["", "GitHub returned only the first page."] : []].join("\n"));
10756
+ ])), ...page.truncated ? ["", `${providerNameOf(page.provider)} returned only the first pages.`] : []].join("\n"));
9782
10757
  }
9783
10758
  }),
9784
10759
  defineCommand({
9785
10760
  name: "add",
9786
- summary: "Register a GitHub repository in a project.",
10761
+ summary: "Register a repository of a git provider in a project.",
9787
10762
  usage: "repositories add <owner/name> --project <name> --credential <label>",
9788
- 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
+ ],
9789
10768
  options: {
9790
10769
  ...projectOption,
9791
10770
  credential: { type: "string" }
@@ -9816,7 +10795,7 @@ const createRepositoriesCommand = (adaptersOf) => defineGroup({
9816
10795
  name: "set-credential",
9817
10796
  summary: "Point a repository at another credential.",
9818
10797
  usage: "repositories set-credential <owner/name> --project <name> --credential <label>",
9819
- 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"]],
9820
10799
  options: {
9821
10800
  ...projectOption,
9822
10801
  credential: { type: "string" }
@@ -9844,7 +10823,7 @@ const createRepositoriesCommand = (adaptersOf) => defineGroup({
9844
10823
  }),
9845
10824
  defineCommand({
9846
10825
  name: "refresh",
9847
- summary: "Check a repository against GitHub again.",
10826
+ summary: "Check a repository against its provider again.",
9848
10827
  usage: "repositories refresh <owner/name> --project <name>",
9849
10828
  optionsUsage: projectUsage,
9850
10829
  options: projectOption,
@@ -9894,26 +10873,26 @@ const createRepositoriesCommand = (adaptersOf) => defineGroup({
9894
10873
  });
9895
10874
  //#endregion
9896
10875
  //#region src/mcp/tools/repositories.ts
9897
- const repositoryField = z.string().trim().min(1).describe("The repository, as owner/name, by its name or by its id.");
9898
- const credentialField = z.string().trim().min(1).describe("A 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.");
9899
10878
  const repositoryTools = [
9900
10879
  defineTool({
9901
10880
  name: "repositories_list",
9902
10881
  title: "List repositories",
9903
- 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.",
9904
- 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"],
9905
10884
  safety: "read",
9906
10885
  input: z.object({
9907
10886
  project: projectField,
9908
- source: z.enum(["registered", "github"]).default("registered"),
9909
- 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.")
9910
10889
  }),
9911
10890
  run: async ({ project, source, credential }, run) => {
9912
10891
  const projectId = await projectIdOf(run, project);
9913
10892
  if (source === "registered") return repositoriesOf(run.api, projectId);
9914
- if (credential === void 0) throw new UsageError("github needs a credential.");
10893
+ if (credential === void 0) throw new UsageError("available needs a credential.");
9915
10894
  const credentialId = await resolveCredentialId(run.api, projectId, credential);
9916
- 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: {
9917
10896
  path: { id: projectId },
9918
10897
  query: { credentialId }
9919
10898
  } }), repositoryHints);
@@ -9922,12 +10901,12 @@ const repositoryTools = [
9922
10901
  defineTool({
9923
10902
  name: "repository_add",
9924
10903
  title: "Register a repository",
9925
- 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.",
9926
10905
  operations: ["registerRepository"],
9927
10906
  safety: "write",
9928
10907
  input: z.object({
9929
10908
  project: projectField,
9930
- 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."),
9931
10910
  credential: credentialField
9932
10911
  }),
9933
10912
  run: async ({ project, repository, credential }, run) => {
@@ -9947,7 +10926,7 @@ const repositoryTools = [
9947
10926
  defineTool({
9948
10927
  name: "repository_update",
9949
10928
  title: "Change or refresh a repository",
9950
- 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.",
9951
10930
  operations: ["updateRepository", "refreshRepository"],
9952
10931
  safety: "write",
9953
10932
  input: z.object({
@@ -10123,6 +11102,7 @@ const kcloudTools = [
10123
11102
  ...accountTools,
10124
11103
  ...organizationTools,
10125
11104
  ...projectTools,
11105
+ ...claudeAccountTools,
10126
11106
  ...credentialTools,
10127
11107
  ...repositoryTools,
10128
11108
  ...projectToolTools,
@@ -10906,6 +11886,7 @@ const kcloudCommands = () => [
10906
11886
  createTokensCommand(createAdapters),
10907
11887
  createAuthCommand(createAdapters),
10908
11888
  createProjectsCommand(createAdapters),
11889
+ createClaudeAccountsCommand(createAdapters),
10909
11890
  createCredentialsCommand(createAdapters),
10910
11891
  createRepositoriesCommand(createAdapters),
10911
11892
  createToolsCommand(createAdapters),