@k2b/cloud 0.6.0-rc.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (315) hide show
  1. package/README.md +2 -2
  2. package/package.json +30 -17
  3. package/scripts/browser-performance.ts +14 -0
  4. package/scripts/build-pdf-renderer.ts +39 -0
  5. package/scripts/build.ts +15 -35
  6. package/scripts/preload.ts +9 -12
  7. package/scripts/sync-recovery-smoke.ts +2 -1
  8. package/src/_internal/app-presentation.ts +14 -2
  9. package/src/_internal/capabilities.ts +108 -7
  10. package/src/_internal/capability-streams.ts +81 -0
  11. package/src/_internal/define-app.ts +62 -36
  12. package/src/_internal/heartbeat.ts +4 -0
  13. package/src/_internal/help-catalog.ts +27 -172
  14. package/src/_internal/help.ts +13 -76
  15. package/src/_internal/page-responses.ts +2 -0
  16. package/src/_internal/postgres-application-name.ts +30 -0
  17. package/src/_internal/process-identity.ts +15 -0
  18. package/src/_internal/process-sync.ts +1 -1
  19. package/src/_internal/registry-validation.ts +29 -25
  20. package/src/_internal/registry.ts +2 -53
  21. package/src/_internal/runtime-context.ts +5 -16
  22. package/src/_internal/server-timing.ts +13 -0
  23. package/src/_internal/status-preserving-ssr.ts +18 -6
  24. package/src/_internal/web-vitals-asset.ts +10 -0
  25. package/src/access/PermissionEditor.tsx +14 -153
  26. package/src/access/PrincipalPicker.tsx +104 -0
  27. package/src/access/ui.ts +2 -0
  28. package/src/ai/admin.ts +14 -13
  29. package/src/ai/assistant-models.ts +12 -1
  30. package/src/ai/audio-format.ts +34 -0
  31. package/src/ai/audio-tool.ts +90 -0
  32. package/src/ai/browser-code-contracts.ts +79 -0
  33. package/src/ai/browser.ts +2 -0
  34. package/src/ai/capabilities.ts +55 -36
  35. package/src/ai/capability-execution.ts +47 -12
  36. package/src/ai/chat/blocks.tsx +157 -95
  37. package/src/ai/chat/capability-result.ts +15 -0
  38. package/src/ai/chat/capability-table.tsx +2 -10
  39. package/src/ai/chat/composer-adapter.ts +48 -3
  40. package/src/ai/chat/message-actions.tsx +8 -7
  41. package/src/ai/chat/message-utils.ts +12 -1
  42. package/src/ai/chat/messages.ts +2 -0
  43. package/src/ai/chat/presentation.tsx +19 -48
  44. package/src/ai/chat/primitives.tsx +43 -1
  45. package/src/ai/chat/tool-disclosure.tsx +17 -2
  46. package/src/ai/chat/tool-groups.ts +52 -0
  47. package/src/ai/chat-quotas.ts +25 -0
  48. package/src/ai/chat-task-contracts.ts +9 -1
  49. package/src/ai/chat-tasks.ts +276 -43
  50. package/src/ai/client/controller.ts +110 -33
  51. package/src/ai/client/file-source.ts +12 -6
  52. package/src/ai/client/live-connection.ts +1 -0
  53. package/src/ai/client/projection.ts +22 -11
  54. package/src/ai/client/transport.ts +4 -0
  55. package/src/ai/code-capability-routes.ts +110 -0
  56. package/src/ai/code-capability-transport.ts +38 -0
  57. package/src/ai/code-mode-skill.ts +114 -0
  58. package/src/ai/code-runtime-tools.ts +166 -0
  59. package/src/ai/code-source-contracts.ts +202 -0
  60. package/src/ai/code-source-tools.ts +145 -0
  61. package/src/ai/compaction.ts +27 -6
  62. package/src/ai/data-analysis-skill.ts +10 -0
  63. package/src/ai/default-tools.ts +25 -2
  64. package/src/ai/dictation-runtime.ts +171 -0
  65. package/src/ai/dictations.ts +188 -0
  66. package/src/ai/draft-content.ts +34 -0
  67. package/src/ai/enrich.ts +6 -0
  68. package/src/ai/executor.ts +191 -31
  69. package/src/ai/file-content-version.ts +22 -0
  70. package/src/ai/file-context.ts +2 -1
  71. package/src/ai/file-media-type.ts +12 -0
  72. package/src/ai/file-reference-contracts.ts +15 -0
  73. package/src/ai/file-tools.ts +2 -1
  74. package/src/ai/files-store.ts +174 -71
  75. package/src/ai/fixtures/vision-encrypted.pdf +0 -0
  76. package/src/ai/grids-skill.ts +40 -10
  77. package/src/ai/http.ts +15 -8
  78. package/src/ai/index.ts +26 -2
  79. package/src/ai/inference-calls.ts +198 -0
  80. package/src/ai/live-events.ts +5 -1
  81. package/src/ai/memory-learning.ts +60 -28
  82. package/src/ai/message-queue.ts +167 -0
  83. package/src/ai/migrate.ts +187 -11
  84. package/src/ai/model-pricing.ts +43 -0
  85. package/src/ai/pdf-render-worker.ts +74 -0
  86. package/src/ai/pdf-render.fixture.ts +28 -0
  87. package/src/ai/pdf-render.ts +77 -0
  88. package/src/ai/projects-routes.ts +3 -3
  89. package/src/ai/projects.ts +20 -6
  90. package/src/ai/protocol.ts +3 -0
  91. package/src/ai/provider.ts +1 -8
  92. package/src/ai/quota-provider.ts +191 -0
  93. package/src/ai/quota-report.ts +152 -0
  94. package/src/ai/quotas-migrate.ts +45 -0
  95. package/src/ai/quotas.ts +168 -0
  96. package/src/ai/routes.ts +215 -11
  97. package/src/ai/run-timeout.ts +10 -0
  98. package/src/ai/runtime-tools.ts +3 -0
  99. package/src/ai/runtime.ts +55 -16
  100. package/src/ai/settings.ts +36 -8
  101. package/src/ai/short-id.ts +2 -2
  102. package/src/ai/skill-catalog.ts +0 -10
  103. package/src/ai/skill-search.ts +47 -0
  104. package/src/ai/skill-seeds.ts +105 -20
  105. package/src/ai/skill-tool.ts +16 -5
  106. package/src/ai/skills-routes.ts +32 -4
  107. package/src/ai/skills.ts +324 -155
  108. package/src/ai/store.ts +373 -191
  109. package/src/ai/stream.ts +5 -0
  110. package/src/ai/structured-runs.ts +4 -3
  111. package/src/ai/structured.ts +8 -1
  112. package/src/ai/system-prompt.ts +12 -2
  113. package/src/ai/timeline.ts +2 -5
  114. package/src/ai/todo-contracts.ts +20 -0
  115. package/src/ai/todo-tool.ts +16 -0
  116. package/src/ai/tool-audit.ts +30 -40
  117. package/src/ai/tools.ts +6 -0
  118. package/src/ai/transcription.ts +202 -0
  119. package/src/ai/turn-timing.ts +114 -0
  120. package/src/ai/types.ts +58 -10
  121. package/src/ai/ui.tsx +2 -0
  122. package/src/ai/usage.ts +118 -118
  123. package/src/ai/vision-tool.ts +39 -10
  124. package/src/api/admin-ai-projects.ts +2 -2
  125. package/src/api/admin-ai-quotas.ts +74 -0
  126. package/src/api/admin-ai-skills.ts +48 -5
  127. package/src/api/admin-ai-usage.ts +2 -2
  128. package/src/api/admin-core-settings.ts +25 -0
  129. package/src/api/admin-identity.ts +91 -51
  130. package/src/api/admin-rail.ts +58 -0
  131. package/src/api/announcements.ts +14 -0
  132. package/src/api/capabilities.ts +292 -43
  133. package/src/api/capability-streams.ts +262 -0
  134. package/src/api/help.ts +8 -44
  135. package/src/api/index.ts +6 -0
  136. package/src/api/mcp.ts +44 -26
  137. package/src/api/me-web-vitals.ts +35 -0
  138. package/src/api/me.ts +2 -0
  139. package/src/api/search/schemas.ts +26 -15
  140. package/src/api/search.bench.ts +1 -1
  141. package/src/api/search.ts +54 -17
  142. package/src/browser/CloudResourceSearch.tsx +640 -702
  143. package/src/browser/app-approval-vault.ts +19 -7
  144. package/src/browser/command-bridge.ts +109 -0
  145. package/src/browser/command-shortcuts.ts +76 -0
  146. package/src/browser/commands.ts +66 -0
  147. package/src/browser/navigation-search.ts +48 -0
  148. package/src/browser/notifications.ts +1 -1
  149. package/src/browser/resource-picker.tsx +5 -18
  150. package/src/browser/resource-search-dialog.ts +9 -0
  151. package/src/browser/resource-search-input.ts +46 -0
  152. package/src/browser/resource-search-messages.ts +76 -40
  153. package/src/browser/resource-search.ts +7 -0
  154. package/src/browser/search-bridge.ts +102 -0
  155. package/src/browser/search-commands.ts +59 -0
  156. package/src/browser/search.ts +13 -0
  157. package/src/browser/spotlight-position.ts +208 -0
  158. package/src/browser/testing.ts +5 -0
  159. package/src/browser/web-vitals.ts +25 -0
  160. package/src/capabilities/claims.ts +179 -0
  161. package/src/capabilities/client.ts +34 -0
  162. package/src/capabilities/command-link.ts +16 -0
  163. package/src/capabilities/executions.ts +298 -0
  164. package/src/capabilities/migrate.ts +81 -0
  165. package/src/capabilities/server.ts +114 -34
  166. package/src/capabilities/stream-body.ts +16 -0
  167. package/src/capabilities/streams.ts +28 -0
  168. package/src/cli/admin/ai-quotas.ts +161 -0
  169. package/src/cli/admin/ai-skills.ts +64 -0
  170. package/src/cli/admin/ai-usage.ts +53 -30
  171. package/src/cli/admin/app-credentials.ts +79 -0
  172. package/src/cli/admin/data.ts +17 -0
  173. package/src/cli/admin/index.ts +9 -0
  174. package/src/cli/capabilities.ts +29 -0
  175. package/src/cli/capability-streams.ts +25 -0
  176. package/src/cli/index.ts +3 -2
  177. package/src/config/env.ts +11 -8
  178. package/src/contracts/app.ts +23 -3
  179. package/src/contracts/capabilities.ts +68 -5
  180. package/src/contracts/capability-streams.ts +46 -0
  181. package/src/contracts/commands.ts +49 -0
  182. package/src/contracts/index.ts +6 -1
  183. package/src/contracts/rail-admin.ts +38 -0
  184. package/src/contracts/rail-preferences.ts +16 -0
  185. package/src/contracts/registry.ts +3 -27
  186. package/src/contracts/settings-types.ts +1 -1
  187. package/src/contracts/shared.ts +9 -5
  188. package/src/contracts/web-vitals.ts +27 -0
  189. package/src/desktop/solid.tsx +3 -3
  190. package/src/index.ts +0 -4
  191. package/src/server/index.ts +3 -0
  192. package/src/server/middleware/auth.ts +3 -2
  193. package/src/server/middleware/route-template.ts +9 -3
  194. package/src/server/middleware/runtime.ts +2 -1
  195. package/src/server/middleware/settings.ts +17 -11
  196. package/src/server/services/access-revision.ts +10 -0
  197. package/src/server/services/access.ts +1 -1
  198. package/src/server/services/index.ts +1 -0
  199. package/src/services/account-category-policy.ts +9 -0
  200. package/src/services/account-lifecycle/audit.ts +1 -1
  201. package/src/services/accounts/app.ts +12 -2
  202. package/src/services/accounts/email-write.ts +23 -0
  203. package/src/services/accounts/groups.ts +15 -4
  204. package/src/services/accounts/identities.ts +249 -0
  205. package/src/services/accounts/identity-reconciliation.ts +87 -0
  206. package/src/services/accounts/ipa-data.ts +2 -2
  207. package/src/services/accounts/local-groups.ts +2 -2
  208. package/src/services/accounts/posix.ts +45 -29
  209. package/src/services/accounts/users.ts +2 -2
  210. package/src/services/announcements/index.ts +69 -8
  211. package/src/services/audit/index.ts +1 -1
  212. package/src/services/cache-fill.ts +35 -0
  213. package/src/services/help/index.ts +109 -0
  214. package/src/services/help/maintenance.ts +25 -0
  215. package/src/services/help/store.ts +180 -0
  216. package/src/services/help/types.ts +46 -0
  217. package/src/services/identity/invocation-actor.ts +2 -2
  218. package/src/services/identity/key-config.ts +2 -1
  219. package/src/services/identity/key-ring.ts +1 -1
  220. package/src/services/index.ts +15 -0
  221. package/src/services/ipa/users.ts +268 -221
  222. package/src/services/legal-consent.ts +1 -1
  223. package/src/services/logging/index.ts +15 -13
  224. package/src/services/logging/trace.ts +2 -1
  225. package/src/services/logging/web-vitals.ts +81 -0
  226. package/src/services/mandates/index.ts +18 -4
  227. package/src/services/mandates/policy.ts +65 -2
  228. package/src/services/notifications/batches.ts +124 -57
  229. package/src/services/notifications/browser.ts +12 -0
  230. package/src/services/notifications/index.ts +0 -2
  231. package/src/services/notifications/platform.ts +0 -4
  232. package/src/services/oauth-tokens.ts +3 -3
  233. package/src/services/pdf/gotenberg.ts +107 -84
  234. package/src/services/pdf/index.ts +3 -0
  235. package/src/services/providers/local/users.ts +25 -16
  236. package/src/services/public-http.ts +101 -0
  237. package/src/services/rail-shortcuts.ts +95 -0
  238. package/src/services/rail-snapshot.ts +53 -0
  239. package/src/services/request-cache-redis.ts +28 -0
  240. package/src/services/session/index.ts +15 -2
  241. package/src/services/session/user.ts +62 -37
  242. package/src/services/settings/core-settings.ts +21 -28
  243. package/src/services/settings/defaults.ts +5 -0
  244. package/src/services/settings/store.ts +58 -84
  245. package/src/shared/ai-costs.ts +42 -0
  246. package/src/shared/ai-platform-prompt.ts +1 -1
  247. package/src/shared/ai-quotas.ts +136 -0
  248. package/src/shared/ai-usage.ts +6 -16
  249. package/src/shared/app-presentation.ts +21 -15
  250. package/src/shared/capability-messages.ts +14 -0
  251. package/src/shared/help.ts +6 -42
  252. package/src/shared/index.ts +6 -0
  253. package/src/ssr/AdminLayout.tsx +7 -5
  254. package/src/ssr/AdminSidebar.tsx +29 -15
  255. package/src/ssr/AppLaunchpad.island.tsx +24 -137
  256. package/src/ssr/AppLaunchpadPanel.tsx +181 -0
  257. package/src/ssr/BrowserPushRegistration.island.tsx +12 -0
  258. package/src/ssr/GlobalSearchDialog.tsx +167 -20
  259. package/src/ssr/GlobalSearchTrigger.island.tsx +33 -22
  260. package/src/ssr/Layout.tsx +17 -6
  261. package/src/ssr/LayoutBreadcrumbs.island.tsx +3 -5
  262. package/src/ssr/LayoutFooter.tsx +1 -1
  263. package/src/ssr/LayoutHeader.tsx +46 -14
  264. package/src/ssr/LayoutHelp.tsx +23 -22
  265. package/src/ssr/{HotkeysHelpRail.island.tsx → LayoutHelpTrigger.island.tsx} +18 -12
  266. package/src/ssr/LayoutRail.tsx +17 -9
  267. package/src/ssr/MobileNavigation.tsx +71 -0
  268. package/src/ssr/MobileProfileActions.tsx +49 -0
  269. package/src/ssr/ProfilePreferences.island.tsx +3 -36
  270. package/src/ssr/RailApps.island.tsx +2 -2
  271. package/src/ssr/RailEditor.tsx +17 -2
  272. package/src/ssr/WorkspaceNavigation.island.tsx +7 -0
  273. package/src/ssr/WorkspaceNavigationProvider.tsx +16 -0
  274. package/src/ssr/admin-navigation.ts +7 -1
  275. package/src/ssr/help.ts +29 -0
  276. package/src/ssr/islands/SearchBar.tsx +67 -0
  277. package/src/ssr/islands/index.ts +3 -1
  278. package/src/ssr/layout-context.ts +2 -2
  279. package/src/ssr/layout-help-search.ts +15 -0
  280. package/src/ssr/mobile-menu-history.ts +70 -0
  281. package/src/ssr/platform-messages.ts +16 -8
  282. package/src/ssr/profile-actions.ts +42 -0
  283. package/src/ssr/rail-context.ts +8 -4
  284. package/src/ssr/rail-messages.ts +2 -0
  285. package/src/ssr/rail-navigation.ts +3 -3
  286. package/src/ssr/workspace-navigation.ts +75 -0
  287. package/src/styles/effects.css +6 -6
  288. package/src/styles/global.css +54 -0
  289. package/src/styles/input.css +43 -41
  290. package/src/styles/resource-search.css +505 -0
  291. package/src/styles/utilities-feedback.css +13 -0
  292. package/src/styles/utilities-layout.css +1 -5
  293. package/src/styles/utilities-navigation.css +3 -39
  294. package/src/types/ambient.d.ts +5 -0
  295. package/src/workflows/ai/runtime.ts +5 -1
  296. package/src/workflows/ai/store.ts +2 -0
  297. package/src/workflows/ai-actions.ts +16 -13
  298. package/src/workflows/definition.ts +10 -5
  299. package/src/workflows/store/actions.ts +44 -20
  300. package/src/workflows/store/index.ts +2 -0
  301. package/src/workflows/store/runs.ts +36 -0
  302. package/src/workflows/store/worker-pool.ts +55 -0
  303. package/src/workflows/store/worker-runtime.ts +90 -0
  304. package/scripts/app-favicon.test.ts +0 -49
  305. package/src/ai/chat/blocks.render.test.tsx +0 -1108
  306. package/src/ai/kit-skill.ts +0 -22
  307. package/src/ai/usage-migrate.ts +0 -81
  308. package/src/browser/CloudResourceSearch.behavior.test.tsx +0 -74
  309. package/src/browser/notification-target.ts +0 -27
  310. package/src/contracts/notification-live.ts +0 -66
  311. package/src/services/notifications/live.ts +0 -50
  312. package/src/ssr/BrowserNotifications.island.tsx +0 -135
  313. package/src/ssr/GlobalSearchDialog.behavior.test.tsx +0 -96
  314. package/src/ssr/Layout.render.test.tsx +0 -148
  315. package/src/ssr/islands/SearchBar.island.tsx +0 -80
@@ -0,0 +1,161 @@
1
+ import { AiModelPricingSchema, type AiModelPricing } from "../../shared/ai-costs";
2
+ import {
3
+ AiQuotaConfigSchema,
4
+ AiQuotaReportQuerySchema,
5
+ AiQuotaIdentitySchema,
6
+ AiQuotaResetSchema,
7
+ type AiQuotaConfig,
8
+ type AiQuotaIdentity,
9
+ } from "../../shared/ai-quotas";
10
+ import { cliText, command, confirmFlag, flag, printRows, printStructured, type CloudCliContext } from "../index";
11
+ import { apiGet, apiJson, queryString, readJsonInput } from "./shared";
12
+
13
+ const path = "/api/admin/core/ai-quotas";
14
+ const identity = {
15
+ type: flag.enum(["user", "service_account"] as const, { default: "user", description: "Identity type" }),
16
+ id: flag.string({ required: true, description: "Identity UUID from users" }),
17
+ };
18
+ const print = (ctx: CloudCliContext, value: unknown) => {
19
+ if (!printStructured(ctx, value)) ctx.print(JSON.stringify(value, null, 2));
20
+ };
21
+ const confirm = (ctx: CloudCliContext, yes: boolean) => {
22
+ if (!yes) throw new Error(cliText(ctx, { en: "Quota changes require --yes.", de: "Kontingentänderungen erfordern --yes." }));
23
+ };
24
+
25
+ export const aiQuotaCommands = [
26
+ command("ai models pricing get", {
27
+ summary: "List configured model input/output reference prices per million tokens",
28
+ async run({ ctx }) {
29
+ print(ctx, await apiGet(ctx, `${path}/models`));
30
+ },
31
+ }),
32
+ command("ai models pricing set", {
33
+ summary: "Set one model's reference prices; JSON null removes prices and makes it unlimited",
34
+ flags: {
35
+ id: flag.string({ required: true, description: "Model profile ID" }),
36
+ pricing: flag.input({
37
+ required: true,
38
+ description: "{inputPerMillion,outputPerMillion} or null: --pricing, --pricing-file or --stdin",
39
+ }),
40
+ yes: confirmFlag("Confirm model price change"),
41
+ },
42
+ async run({ ctx, flags }) {
43
+ confirm(ctx, flags.yes);
44
+ const pricing = AiModelPricingSchema.nullable().parse(await readJsonInput<unknown>(flags.pricing, "model pricing"));
45
+ const current = await apiGet<{ models: { id: string; pricing?: AiModelPricing }[] }>(ctx, `${path}/models`);
46
+ const model = current.models.find((model) => model.id === flags.id);
47
+ if (!model) throw new Error("Model not found.");
48
+ print(
49
+ ctx,
50
+ await apiJson(ctx, "PUT", `${path}/models/${encodeURIComponent(flags.id!)}/pricing`, { pricing, expected: model.pricing ?? null }),
51
+ );
52
+ },
53
+ }),
54
+ command("ai quotas background status", {
55
+ summary: "Show rolling 24-hour background costs and emergency-stop state",
56
+ async run({ ctx }) {
57
+ print(ctx, await apiGet(ctx, `${path}/background`));
58
+ },
59
+ }),
60
+ command("ai quotas background release", {
61
+ summary: "Release the background emergency stop after reviewing costs",
62
+ flags: { yes: confirmFlag("Confirm release of the background AI emergency stop") },
63
+ async run({ ctx, flags }) {
64
+ confirm(ctx, flags.yes);
65
+ print(ctx, await apiJson(ctx, "POST", `${path}/background/release`, {}));
66
+ },
67
+ }),
68
+ command("ai quotas config get", {
69
+ summary: "Export Assistant limits, rules and revision (platform admin)",
70
+ async run({ ctx }) {
71
+ print(ctx, await apiGet<AiQuotaConfig>(ctx, path));
72
+ },
73
+ }),
74
+ command("ai quotas config set", {
75
+ summary: "Replace Assistant quota configuration using its current revision",
76
+ flags: {
77
+ config: flag.input({ required: true, description: "Complete configuration JSON: --config, --config-file or --stdin" }),
78
+ yes: confirmFlag("Confirm replacement of Assistant quota configuration"),
79
+ },
80
+ async run({ ctx, flags }) {
81
+ confirm(ctx, flags.yes);
82
+ const config = AiQuotaConfigSchema.parse(await readJsonInput<unknown>(flags.config, "quota configuration"));
83
+ print(ctx, await apiJson<AiQuotaConfig>(ctx, "PUT", path, config));
84
+ },
85
+ }),
86
+ command("ai quotas models", {
87
+ summary: "List configured model profiles; only priced chat models support cost rules",
88
+ async run({ ctx }) {
89
+ const result = await apiGet<{ models: { id: string; label: string; pricing?: AiModelPricing }[] }>(ctx, `${path}/models`);
90
+ printRows(
91
+ ctx,
92
+ result,
93
+ result.models.map((model) => ({ ...model, pricing: model.pricing ? JSON.stringify(model.pricing) : "unpriced" })),
94
+ [{ key: "id" }, { key: "label" }, { key: "pricing" }],
95
+ );
96
+ },
97
+ }),
98
+ command("ai quotas report", {
99
+ summary: "Report chat costs and current allowances with the same filters as the admin page",
100
+ flags: {
101
+ range: flag.enum(["24h", "7d", "30d", "90d"] as const, { default: "30d" }),
102
+ until: flag.string({ description: "ISO period end; reuse query.until for consistent pagination" }),
103
+ search: flag.string({ description: "Search identity labels" }),
104
+ model: flag.string({ description: "Model profile ID" }),
105
+ status: flag.enum(["all", "available", "exhausted", "unknown", "unlimited", "disabled"] as const, { default: "all" }),
106
+ sort: flag.enum(["label", "cost", "lastUsed"] as const, { default: "lastUsed" }),
107
+ direction: flag.enum(["asc", "desc"] as const, { default: "desc" }),
108
+ page: flag.int({ min: 1, max: 1_000_000, default: 1 }),
109
+ identity: flag.string({ description: "Optional selected identity UUID" }),
110
+ identityType: flag.enum(["user", "service_account"] as const, { default: "user" }),
111
+ },
112
+ async run({ ctx, flags }) {
113
+ const query = AiQuotaReportQuerySchema.parse(flags);
114
+ print(ctx, await apiGet(ctx, `${path}/report${queryString(query)}`));
115
+ },
116
+ }),
117
+ command("ai quotas users", {
118
+ summary: "List chat users; search also finds identities without prior usage",
119
+ flags: {
120
+ search: flag.string({ description: "Search identity labels" }),
121
+ page: flag.int({ min: 1, default: 1 }),
122
+ },
123
+ async run({ ctx, flags }) {
124
+ const result = await apiGet<{ items: AiQuotaIdentity[]; total: number; page: number; perPage: number }>(
125
+ ctx,
126
+ `${path}/users${queryString(flags)}`,
127
+ );
128
+ printRows(ctx, result, result.items, [{ key: "id" }, { key: "type" }, { key: "label" }, { key: "lastUsed" }]);
129
+ if (ctx.options.output === "text")
130
+ ctx.print(
131
+ cliText(ctx, {
132
+ en: `Page ${result.page}; ${result.total} identities.`,
133
+ de: `Seite ${result.page}; ${result.total} Identitäten.`,
134
+ }),
135
+ );
136
+ },
137
+ }),
138
+ command("ai quotas balance", {
139
+ summary: "Show one identity's effective limits, usage and reset times",
140
+ flags: identity,
141
+ async run({ ctx, flags }) {
142
+ const subject = AiQuotaIdentitySchema.parse(flags);
143
+ print(ctx, await apiGet(ctx, `${path}/balance${queryString(subject)}`));
144
+ },
145
+ }),
146
+ command("ai quotas reset", {
147
+ summary: "Reset one identity and quota scope; reuse request ID when retrying",
148
+ flags: {
149
+ ...identity,
150
+ scope: flag.string({ required: true, description: "Model profile ID or * (quote the wildcard)" }),
151
+ requestId: flag.string({ required: true, description: "UUID for this reset; reuse for retries, use a new UUID for a new reset" }),
152
+ yes: confirmFlag("Confirm reset of this identity and quota scope"),
153
+ },
154
+ async run({ ctx, flags }) {
155
+ confirm(ctx, flags.yes);
156
+ const { yes: _yes, ...input } = flags;
157
+ const reset = AiQuotaResetSchema.parse(input);
158
+ print(ctx, await apiJson(ctx, "POST", `${path}/reset`, reset));
159
+ },
160
+ }),
161
+ ];
@@ -0,0 +1,64 @@
1
+ import type { AiSkillAdminListItem } from "../../ai/skills";
2
+ import { arg, command, confirmFlag, flag, printRows, printStructured } from "../index";
3
+ import { apiGet, apiJson, queryString } from "./shared";
4
+
5
+ export const aiSkillCommands = [
6
+ command("ai skills list", {
7
+ summary: "List all Skills with template origin, version, status, and revision (platform admin)",
8
+ flags: {
9
+ search: flag.string(),
10
+ page: flag.int({ min: 1, default: 1 }),
11
+ perPage: flag.int({ name: "per-page", min: 1, max: 500, default: 100 }),
12
+ },
13
+ run: async ({ ctx, flags }) => {
14
+ const result = await apiGet<{ items: AiSkillAdminListItem[]; total: number; page: number; perPage: number }>(
15
+ ctx,
16
+ `/api/admin/core/ai-skills${queryString(flags)}`,
17
+ );
18
+ printRows(ctx, result, result.items, [
19
+ { key: "shortId" },
20
+ { key: "name" },
21
+ { key: "revision" },
22
+ { key: "templateId" },
23
+ { key: "templateVersion" },
24
+ { key: "currentTemplateVersion" },
25
+ { key: "templateStatus" },
26
+ ]);
27
+ },
28
+ }),
29
+ command("ai skills templates", {
30
+ summary: "List current trusted Skill template IDs and versions",
31
+ run: async ({ ctx }) => {
32
+ const result = await apiGet<{ templates: { templateId: string; name: string; version: number }[] }>(
33
+ ctx,
34
+ "/api/admin/core/ai-skills/templates",
35
+ );
36
+ printRows(ctx, result, result.templates, [{ key: "templateId" }, { key: "name" }, { key: "version" }]);
37
+ },
38
+ }),
39
+ ...(["associate", "reset"] as const).map((mode) =>
40
+ command(`ai skills ${mode}`, {
41
+ summary:
42
+ mode === "associate"
43
+ ? "Associate an existing Skill with a template, preserving all content"
44
+ : "Replace all linked Skill content and references with the current template",
45
+ args: { skillId: arg.required() },
46
+ flags: {
47
+ templateId: flag.string({ name: "template", required: true }),
48
+ templateVersion: flag.int({ name: "template-version", required: true, min: 1 }),
49
+ expectedRevision: flag.int({ name: "revision", required: true, min: 1 }),
50
+ yes: confirmFlag("Confirm the exact Skill and template; export customized content before resetting"),
51
+ },
52
+ run: async ({ ctx, args, flags }) => {
53
+ if (!flags.yes) throw new Error("Review the Skill and template first, then confirm with --yes.");
54
+ const { yes: _yes, ...input } = flags;
55
+ const result = await apiJson(ctx, "POST", `/api/admin/core/ai-skills/${encodeURIComponent(args.skillId)}/template`, {
56
+ ...input,
57
+ mode,
58
+ confirmed: true,
59
+ });
60
+ if (!printStructured(ctx, result)) ctx.print("Skill template updated.");
61
+ },
62
+ }),
63
+ ),
64
+ ];
@@ -1,25 +1,29 @@
1
+ import type { AiUsageReport, AiUsageRun } from "../../ai/usage";
2
+ import { AI_USAGE_RANGES, AI_USAGE_REASONS } from "../../shared/ai-usage";
1
3
  import { arg, command, flag } from "../index";
2
4
  import { apiGet, printJsonOrTable } from "./shared";
3
- import { AI_USAGE_RANGES, AI_USAGE_REASONS } from "../../shared/ai-usage";
4
- import type { AiUsageReport, AiUsageRun } from "../../ai/usage";
5
+
5
6
  const filters = {
6
7
  range: flag.enum(AI_USAGE_RANGES, { default: "30d", description: "Response/run period" }),
7
8
  until: flag.string({ description: "Fixed ISO end time; reuse report.query.until across pages" }),
8
9
  userId: flag.string({ name: "user", description: "User UUID or unassigned" }),
9
10
  modelProfileId: flag.string({ name: "model", description: "Model profile ID" }),
10
11
  providerModel: flag.string({ name: "provider-model", description: "Actual provider model" }),
12
+ workflowId: flag.string({ name: "workflow", description: "Workflow definition UUID" }),
13
+ workflowRunId: flag.string({ name: "workflow-run", description: "Workflow run UUID" }),
11
14
  appId: flag.string({ name: "app", description: "Source application ID" }),
12
- kind: flag.enum(["chat", "background", "tool"] as const, { description: "Run kind (runs only)" }),
15
+ kind: flag.enum(["chat", "background"] as const, { description: "Run kind (runs only)" }),
13
16
  status: flag.string({ description: "Run status, e.g. failed (runs only)" }),
14
- task: flag.string({ description: "Exact task or tool name (runs only)" }),
17
+ task: flag.string({ description: "Exact background task name (runs only)" }),
15
18
  errorCode: flag.string({ name: "error-code", description: "Stored error code (runs only)" }),
16
19
  search: flag.string({ description: "Search task and error text (runs/facets only)" }),
17
20
  rating: flag.enum(["up", "down"] as const, { description: "Rating (feedback only)" }),
18
21
  reason: flag.enum(AI_USAGE_REASONS, { description: "Feedback reason (feedback only)" }),
19
- sort: flag.enum(["runs", "tokens", "credits", "errors", "negative", "negativeRate"] as const, {
22
+ sort: flag.enum(["runs", "tokens", "cost", "errors", "negative", "negativeRate"] as const, {
20
23
  default: "runs",
21
24
  description: "Comparison sort order",
22
25
  }),
26
+ direction: flag.enum(["asc", "desc"] as const, { default: "desc", description: "Comparison sort direction" }),
23
27
  page: flag.int({ default: 1, min: 1 }),
24
28
  perPage: flag.int({ name: "per-page", default: 50, min: 1, max: 100 }),
25
29
  };
@@ -30,7 +34,7 @@ const params = (values: Record<string, string | number | undefined>) => {
30
34
  return q;
31
35
  };
32
36
  export const aiUsageCommands = [
33
- ...(["report", "users", "models", "tasks", "apps", "launches", "capabilities", "feedback", "runs"] as const).map((section) =>
37
+ ...(["report", "users", "models", "tasks", "apps", "workflows", "launches", "feedback", "runs"] as const).map((section) =>
34
38
  command(`ai usage ${section}`, {
35
39
  summary:
36
40
  section === "report"
@@ -44,25 +48,39 @@ export const aiUsageCommands = [
44
48
  return printJsonOrTable(
45
49
  ctx,
46
50
  report,
47
- [report.overview],
48
- [{ key: "runs" }, { key: "tokens" }, { key: "credits" }, { key: "failed" }, { key: "positive" }, { key: "negative" }],
51
+ [{ ...report.overview, unit: report.unit }],
52
+ [
53
+ { key: "runs" },
54
+ { key: "tokens" },
55
+ { key: "cost" },
56
+ { key: "unit" },
57
+ { key: "failed" },
58
+ { key: "positive" },
59
+ { key: "negative" },
60
+ ],
49
61
  );
50
62
  const page = report[section];
51
63
  if (ctx.options.output === "jsonl") {
52
- for (const item of page.items) ctx.jsonLine(item);
64
+ for (const item of page.items) ctx.jsonLine({ ...item, unit: report.unit });
53
65
  return;
54
66
  }
55
- if (ctx.options.output === "json") return ctx.json({ ...page, query: report.query, since: report.since, until: report.until });
67
+ if (ctx.options.output === "json")
68
+ return ctx.json({ ...page, query: report.query, since: report.since, until: report.until, unit: report.unit });
56
69
  if (section === "runs")
57
- ctx.table(report.runs.items, [
58
- { key: "id" },
59
- { key: "kind" },
60
- { key: "task" },
61
- { key: "status" },
62
- { key: "userLabel" },
63
- { key: "modelProfileId" },
64
- { key: "error" },
65
- ]);
70
+ ctx.table(
71
+ report.runs.items.map((row) => ({ ...row, unit: report.unit })),
72
+ [
73
+ { key: "id" },
74
+ { key: "kind" },
75
+ { key: "task" },
76
+ { key: "status" },
77
+ { key: "userLabel" },
78
+ { key: "modelProfileId" },
79
+ { key: "cost" },
80
+ { key: "unit" },
81
+ { key: "error" },
82
+ ],
83
+ );
66
84
  else if (section === "feedback")
67
85
  ctx.table(report.feedback.items, [
68
86
  { key: "id" },
@@ -73,17 +91,22 @@ export const aiUsageCommands = [
73
91
  ]);
74
92
  else if (section === "launches") ctx.table(report.launches.items, [{ key: "appId" }, { key: "chats" }, { key: "users" }]);
75
93
  else
76
- ctx.table(report[section].items, [
77
- { key: "id" },
78
- { key: "label" },
79
- { key: "providerModel" },
80
- { key: "runs" },
81
- { key: "tokens" },
82
- { key: "failed" },
83
- { key: "positive" },
84
- { key: "negative" },
85
- { key: "rated" },
86
- ]);
94
+ ctx.table(
95
+ report[section].items.map((row) => ({ ...row, unit: report.unit })),
96
+ [
97
+ { key: "id" },
98
+ { key: "label" },
99
+ { key: "providerModel" },
100
+ { key: "runs" },
101
+ { key: "tokens" },
102
+ { key: "cost" },
103
+ { key: "unit" },
104
+ { key: "failed" },
105
+ { key: "positive" },
106
+ { key: "negative" },
107
+ { key: "rated" },
108
+ ],
109
+ );
87
110
  ctx.print(`Page ${page.page}; ${page.total} total. Snapshot until ${report.until}`);
88
111
  },
89
112
  }),
@@ -0,0 +1,79 @@
1
+ import { z } from "zod";
2
+ import { arg, cliText, command, confirmFlag, flag, printStructured } from "../index";
3
+ import { apiGet, apiJson } from "./shared";
4
+
5
+ const root = "/api/admin/identity/workloads";
6
+ const path = (appId: string) => `${root}/${encodeURIComponent(appId)}/credentials`;
7
+ const metadata = z.object({
8
+ id: z.string(),
9
+ name: z.string(),
10
+ status: z.string(),
11
+ tokenPrefix: z.string(),
12
+ expiresAt: z.string().nullable(),
13
+ });
14
+
15
+ export const appCredentialCommands = [
16
+ command("app-credentials apps", {
17
+ summary: "List registered applications for workload credentials",
18
+ async run({ ctx }) {
19
+ const apps = z.array(z.object({ id: z.string(), name: z.string() })).parse(await apiGet(ctx, root));
20
+ if (!printStructured(ctx, apps))
21
+ ctx.table(apps, [
22
+ { key: "id", label: "App" },
23
+ { key: "name", label: "Name" },
24
+ ]);
25
+ },
26
+ }),
27
+ command("app-credentials list", {
28
+ summary: "List one app's credential metadata without secrets",
29
+ args: { app: arg.required({ valueLabel: "app" }) },
30
+ flags: { page: flag.int({ min: 1, default: 1 }), perPage: flag.int({ min: 1, max: 500, default: 20 }) },
31
+ async run({ ctx, args, flags }) {
32
+ const listing = z
33
+ .object({ items: z.array(metadata), page: z.number(), perPage: z.number(), total: z.number(), hasNext: z.boolean() })
34
+ .parse(await apiGet(ctx, `${path(args.app)}?page=${flags.page}&perPage=${flags.perPage}`));
35
+ if (!printStructured(ctx, listing))
36
+ ctx.table(listing.items, [
37
+ { key: "id", label: "ID" },
38
+ { key: "name", label: "Name" },
39
+ { key: "status", label: "Status" },
40
+ { key: "expiresAt", label: "Expires" },
41
+ ]);
42
+ },
43
+ }),
44
+ command("app-credentials create", {
45
+ summary: "Create an app credential; returns the token once (store stdout securely)",
46
+ args: { app: arg.required({ valueLabel: "app" }) },
47
+ flags: {
48
+ name: flag.string({ required: true }),
49
+ expiresAt: flag.string({ description: "Optional future ISO 8601 expiry" }),
50
+ yes: confirmFlag("Create this app credential"),
51
+ },
52
+ async run({ ctx, args, flags }) {
53
+ if (!flags.yes)
54
+ throw new Error(cliText(ctx, { en: "Creating an app credential requires --yes.", de: "App-Zugang erstellen erfordert --yes." }));
55
+ const result = z
56
+ .object({ credential: metadata, token: z.string() })
57
+ .parse(
58
+ await apiJson(ctx, "POST", path(args.app), {
59
+ name: flags.name,
60
+ scopes: ["identity:invoke"],
61
+ ...(flags.expiresAt ? { expiresAt: flags.expiresAt } : {}),
62
+ }),
63
+ );
64
+ if (!printStructured(ctx, result)) ctx.print(result.token);
65
+ },
66
+ }),
67
+ command("app-credentials revoke", {
68
+ summary: "Revoke one exact app credential; its background calls will stop",
69
+ args: { app: arg.required({ valueLabel: "app" }), credential: arg.required({ valueLabel: "credential" }) },
70
+ flags: { yes: confirmFlag("Revoke this app credential") },
71
+ async run({ ctx, args, flags }) {
72
+ if (!flags.yes)
73
+ throw new Error(cliText(ctx, { en: "Revoking an app credential requires --yes.", de: "App-Zugang widerrufen erfordert --yes." }));
74
+ const credentialId = z.uuid().parse(args.credential);
75
+ const result = await apiJson(ctx, "DELETE", `${path(args.app)}/${credentialId}`);
76
+ if (!printStructured(ctx, result)) ctx.print(cliText(ctx, { en: "Credential revoked.", de: "Zugang widerrufen." }));
77
+ },
78
+ }),
79
+ ];
@@ -18,11 +18,18 @@ export type PostgresDiagnostics = {
18
18
  totalBytes: number;
19
19
  installedExtensions: number;
20
20
  availableExtensions: number;
21
+ runtime: {
22
+ deadlocks: number;
23
+ oldestTransactionSeconds: number;
24
+ oldestQuerySeconds: number;
25
+ };
21
26
  tableRows: {
22
27
  schema: string;
23
28
  name: string;
24
29
  estimatedRows: number;
25
30
  deadRows: number;
31
+ seqScans: number;
32
+ indexScans: number;
26
33
  totalBytes: number;
27
34
  tableBytes: number;
28
35
  indexBytes: number;
@@ -117,6 +124,9 @@ export const dataCommands = [
117
124
  tables: data.tables,
118
125
  storage: formatBytes(data.totalBytes),
119
126
  extensions: `${data.installedExtensions}/${data.availableExtensions}`,
127
+ deadlocks: data.available ? data.runtime.deadlocks : "-",
128
+ oldestTransaction: data.available ? formatSeconds(data.runtime.oldestTransactionSeconds) : "-",
129
+ oldestQuery: data.available ? formatSeconds(data.runtime.oldestQuerySeconds) : "-",
120
130
  warnings: data.warnings.length,
121
131
  error: data.error ?? "",
122
132
  },
@@ -127,6 +137,9 @@ export const dataCommands = [
127
137
  { key: "tables" },
128
138
  { key: "storage" },
129
139
  { key: "extensions" },
140
+ { key: "deadlocks", label: "Deadlocks since reset" },
141
+ { key: "oldestTransaction", label: "Oldest transaction" },
142
+ { key: "oldestQuery", label: "Oldest active query" },
130
143
  { key: "warnings" },
131
144
  { key: "error" },
132
145
  ]);
@@ -161,6 +174,8 @@ export const dataCommands = [
161
174
  heap: formatBytes(table.tableBytes),
162
175
  indexes: formatBytes(table.indexBytes),
163
176
  deadRows: table.deadRows,
177
+ seqScans: table.seqScans,
178
+ indexScans: table.indexScans,
164
179
  warnings: table.warnings.join(", "),
165
180
  }));
166
181
  printJsonOrTable(ctx, { ...data, tableRows: tables }, rows, [
@@ -170,6 +185,8 @@ export const dataCommands = [
170
185
  { key: "heap" },
171
186
  { key: "indexes" },
172
187
  { key: "deadRows" },
188
+ { key: "seqScans", label: "Seq scans since reset" },
189
+ { key: "indexScans", label: "Index scans since reset" },
173
190
  { key: "warnings" },
174
191
  ]);
175
192
  },
@@ -1,3 +1,4 @@
1
+ import { appCredentialCommands } from "./app-credentials";
1
2
  /**
2
3
  * `cld admin` — Cloud administration surfaces.
3
4
  *
@@ -9,6 +10,8 @@ import { defineCliCommands } from "../commands";
9
10
  import { accountAdministrationCommands } from "./account-administration";
10
11
  import { accountCategoryCommands } from "./account-categories";
11
12
  import { appSignInCommands } from "./app-sign-in";
13
+ import { aiSkillCommands } from "./ai-skills";
14
+ import { aiQuotaCommands } from "./ai-quotas";
12
15
  import { aiUsageCommands } from "./ai-usage";
13
16
  import { dataCommands } from "./data";
14
17
  import { documentationCommands } from "./documentation";
@@ -30,19 +33,25 @@ export default defineCliCommands({
30
33
  name: "admin",
31
34
  summary: "Inspect and operate Cloud administration surfaces.",
32
35
  groupSummaries: {
36
+ "app-credentials": "Manage per-application credentials for background work.",
33
37
  "app-sign-in config": "Manage the trusted app website and pairing policy.",
34
38
  "accounts config": "Manage account types and sign-in visibility.",
35
39
  "accounts administration": "Manage account requests and account/group follow-up notices.",
40
+ "ai quotas": "Manage direct Assistant chat limits and consumption.",
41
+ "ai quotas config": "Export and replace quota policies with revision checks.",
36
42
  legal: "Manage Terms, Privacy, and Imprint.",
37
43
  linux: "Prepare Linux identities.",
38
44
  "linux config": "Manage Linux identity defaults and allocation range.",
39
45
  },
40
46
  commands: [
47
+ ...appCredentialCommands,
41
48
  ...documentationCommands,
42
49
  ...appSignInCommands,
43
50
  ...accountCategoryCommands,
44
51
  ...accountAdministrationCommands,
45
52
  ...aiUsageCommands,
53
+ ...aiQuotaCommands,
54
+ ...aiSkillCommands,
46
55
  ...instanceCommands,
47
56
  ...gatewayCommands,
48
57
  ...logCommands,
@@ -1,3 +1,5 @@
1
+ import { saveCapabilityStream } from "./capability-streams";
2
+ import { CapabilityStreamSchema } from "../contracts/capabilities";
1
3
  import { z } from "zod";
2
4
  import { readBoundedJson } from "../_internal/bounded-json";
3
5
  import {
@@ -118,6 +120,33 @@ export default defineCliCommands({
118
120
  name: "capabilities",
119
121
  summary: "Discover and invoke versioned Cloud app capabilities.",
120
122
  commands: [
123
+ ...(["read", "write", "status", "abort"] as const).map(verb => command(`stream-${verb}`, {
124
+ summary: `${verb} an authenticated binary capability stream`,
125
+ flags: {
126
+ input: flag.input({ required: true, description: "Stream descriptor JSON; use --input-file or --stdin" }),
127
+ ...(verb === "read" ? { out: flag.string({ required: true, description: "New output file; never overwritten" }) } : {}),
128
+ ...(verb === "write" ? { file: flag.string({ required: true, description: "Local binary file to upload" }) } : {}),
129
+ },
130
+ run: async ({ctx, flags}) => {
131
+ const stream = CapabilityStreamSchema.parse(await parseInput(flags.input));
132
+ if ((verb === "read") !== (stream.direction === "read")) throw new Error("Wrong stream direction.");
133
+ const body = verb === "write" ? Bun.file(String(flags.file)) : undefined;
134
+ if (body && (!(await body.exists()) || body.size !== stream.size)) throw new Error("Input file must match the authorized byte size.");
135
+ const controller = new AbortController();
136
+ const abort = () => controller.abort();
137
+ process.once("SIGINT", abort); process.once("SIGTERM", abort);
138
+ try {
139
+ const signal = AbortSignal.any([controller.signal, AbortSignal.timeout(300_000)]);
140
+ const response = await ctx.fetch(`/api/capabilities/v1/streams/${verb}`, {
141
+ method: "POST", headers: {"x-cloud-stream-id": stream.id, "content-type": "application/octet-stream"},
142
+ body, signal, redirect: "error",
143
+ });
144
+ printGenericResult(ctx, verb === "read"
145
+ ? await saveCapabilityStream(response, stream, String(flags.out), signal)
146
+ : await readCapabilityJson(response, CAPABILITY_MAX_RESULT_BYTES));
147
+ } finally { process.off("SIGINT", abort); process.off("SIGTERM", abort); }
148
+ },
149
+ })),
121
150
  command("catalog", {
122
151
  summary: "List live capability manifests",
123
152
  flags: {
@@ -0,0 +1,25 @@
1
+ import { createWriteStream } from "node:fs";
2
+ import { link, mkdtemp, rm } from "node:fs/promises";
3
+ import { dirname, join, resolve } from "node:path";
4
+ import { pipeline } from "node:stream/promises";
5
+ import { exactStream } from "../capabilities/stream-body";
6
+ import type { CapabilityStream } from "../contracts/capabilities";
7
+
8
+ /** Publish only a complete download; never overwrite an existing local file. */
9
+ export async function saveCapabilityStream(response: Response, stream: CapabilityStream, output: string, signal: AbortSignal) {
10
+ if (!response.ok || !response.body) {
11
+ await response.body?.cancel();
12
+ throw new Error(`Stream read failed (HTTP ${response.status}).`);
13
+ }
14
+ const path = resolve(output);
15
+ const temporary = await mkdtemp(join(dirname(path), ".cloud-stream-"));
16
+ try {
17
+ const part = join(temporary, "content");
18
+ await pipeline(exactStream(response.body, stream.size), createWriteStream(part, { flags: "wx", mode: 0o600 }), { signal });
19
+ signal.throwIfAborted();
20
+ await link(part, path);
21
+ return { path, bytes: stream.size };
22
+ } finally {
23
+ await rm(temporary, { recursive: true, force: true });
24
+ }
25
+ }
package/src/cli/index.ts CHANGED
@@ -32,7 +32,7 @@ export type CloudCliContext = {
32
32
  setDefault: (key: string, value: string | undefined) => Promise<void>;
33
33
  createApiClient: <TApi extends Hono<any, any, any>>(basePath: string) => CloudApiClient<TApi>;
34
34
  fetch: (path: string, init?: RequestInit) => Promise<Response>;
35
- readJson: <T>(response: Response) => Promise<T>;
35
+ readJson: <T>(response: Pick<Response, "json" | "text" | "ok" | "status" | "statusText">) => Promise<T>;
36
36
  print: (value?: string) => void;
37
37
  /** Write a stdout chunk without adding a newline. */
38
38
  /** Resolves once the value is flushed; await it when writing large output. */
@@ -51,7 +51,8 @@ export type CloudCliModule = {
51
51
  booleanFlags?: readonly string[];
52
52
  requiresCloud?: boolean;
53
53
  requiresCloudFor?: (args: string[], flags: CloudCliFlags) => boolean;
54
- help?: () => string;
54
+ /** Render module help in the request locale; command syntax stays unchanged. */
55
+ help?: (locale?: string) => string;
55
56
  run: (context: CloudCliContext) => Promise<number | void> | number | void;
56
57
  };
57
58