@kikimoradev/kcloud 1.10.0 → 1.11.1

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