@k2b/cloud 0.42.0 → 0.44.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 (352) hide show
  1. package/package.json +13 -5
  2. package/scripts/app-favicon.ts +7 -6
  3. package/scripts/build.ts +3 -0
  4. package/scripts/installable-assets.ts +61 -0
  5. package/scripts/preload.ts +3 -0
  6. package/src/_internal/app-access-path.ts +12 -0
  7. package/src/_internal/app-access-request.ts +13 -0
  8. package/src/_internal/app-access-resources.ts +141 -0
  9. package/src/_internal/app-event-catalog.ts +40 -0
  10. package/src/_internal/app-event-schemas.ts +127 -0
  11. package/src/_internal/application-package.ts +40 -0
  12. package/src/_internal/built-in-app-ids.json +27 -0
  13. package/src/_internal/capabilities.ts +185 -12
  14. package/src/_internal/capability-origin.ts +4 -0
  15. package/src/_internal/capability-streams.ts +10 -5
  16. package/src/_internal/define-app.ts +159 -45
  17. package/src/_internal/installable-assets.ts +90 -0
  18. package/src/_internal/installable-head.ts +22 -0
  19. package/src/_internal/object-store-area.ts +9 -0
  20. package/src/_internal/object-store-config.ts +101 -0
  21. package/src/_internal/object-store-key.ts +14 -0
  22. package/src/_internal/page-responses.ts +34 -3
  23. package/src/_internal/process-identity.ts +7 -2
  24. package/src/_internal/registry-validation.ts +15 -1
  25. package/src/_internal/registry.ts +44 -1
  26. package/src/_internal/runtime-context.ts +9 -1
  27. package/src/_internal/runtime-watcher.ts +8 -0
  28. package/src/_internal/status-preserving-ssr.ts +12 -1
  29. package/src/access/AppAccessEditor.browser-fixture.ts +429 -0
  30. package/src/access/AppAccessEditor.browser-frame.tsx +68 -0
  31. package/src/access/AppAccessEditor.browser-harness.tsx +65 -0
  32. package/src/access/AppAccessEditor.tsx +380 -0
  33. package/src/access/PermissionEditor.browser-harness.tsx +22 -2
  34. package/src/access/PermissionEditor.tsx +386 -77
  35. package/src/access/PrincipalAvatar.tsx +63 -0
  36. package/src/access/PrincipalPicker.tsx +29 -8
  37. package/src/access/ResourceApiKeys.tsx +3 -4
  38. package/src/access/app-access-adapter.ts +51 -0
  39. package/src/access/app-access-messages.ts +172 -0
  40. package/src/access/messages.ts +14 -0
  41. package/src/access/ui.ts +4 -1
  42. package/src/account/Avatar.tsx +21 -3
  43. package/src/account/ProfilePictureDialog.tsx +699 -0
  44. package/src/account/profile-picture-messages.ts +269 -0
  45. package/src/account/ui.ts +8 -1
  46. package/src/ai/admin.ts +3 -0
  47. package/src/ai/app-frame.ts +12 -0
  48. package/src/ai/application-tasks.ts +101 -0
  49. package/src/ai/assistant-models.ts +126 -64
  50. package/src/ai/capabilities.ts +19 -4
  51. package/src/ai/capability-execution.ts +5 -2
  52. package/src/ai/chart-block.ts +63 -30
  53. package/src/ai/chat/chart-block.tsx +12 -3
  54. package/src/ai/chat/composer-adapter.ts +8 -7
  55. package/src/ai/chat/message-actions.tsx +1 -1
  56. package/src/ai/chat/message-utils.ts +2 -2
  57. package/src/ai/chat/presentation.tsx +22 -1
  58. package/src/ai/chat-quotas.ts +5 -4
  59. package/src/ai/chat-tasks.ts +2 -2
  60. package/src/ai/client/controller.ts +11 -10
  61. package/src/ai/client/projection.ts +3 -3
  62. package/src/ai/code-capability-routes.ts +1 -0
  63. package/src/ai/compaction.ts +3 -3
  64. package/src/ai/credentials.ts +13 -114
  65. package/src/ai/default-tool-contracts.ts +4 -3
  66. package/src/ai/dictation-runtime.ts +4 -4
  67. package/src/ai/enrich.ts +2 -2
  68. package/src/ai/executor.ts +38 -27
  69. package/src/ai/http.ts +17 -29
  70. package/src/ai/index.ts +11 -5
  71. package/src/ai/inference-calls.ts +6 -4
  72. package/src/ai/maintenance.ts +1 -1
  73. package/src/ai/memory-learning-runs.ts +5 -5
  74. package/src/ai/memory-learning.ts +3 -3
  75. package/src/ai/message-queue.ts +1 -2
  76. package/src/ai/migrate.ts +75 -42
  77. package/src/ai/model-access-migrate.ts +3 -17
  78. package/src/ai/model-access.ts +44 -85
  79. package/src/ai/model-card.tsx +134 -0
  80. package/src/ai/model-catalog.ts +359 -0
  81. package/src/ai/model-chooser-messages.ts +88 -0
  82. package/src/ai/model-chooser.tsx +577 -0
  83. package/src/ai/model-delete.ts +234 -0
  84. package/src/ai/model-fixture.ts +39 -0
  85. package/src/ai/model-icon.tsx +36 -0
  86. package/src/ai/model-overview.ts +53 -0
  87. package/src/ai/model-usage.ts +45 -0
  88. package/src/ai/openai-api-messages.ts +0 -4
  89. package/src/ai/openai-api-slots.ts +10 -2
  90. package/src/ai/openai-api.ts +83 -16
  91. package/src/ai/personal-agent.ts +1 -1
  92. package/src/ai/prefs.ts +12 -10
  93. package/src/ai/projects-routes.ts +2 -1
  94. package/src/ai/projects.ts +13 -12
  95. package/src/ai/protocol.ts +2 -2
  96. package/src/ai/provider-connections.ts +161 -0
  97. package/src/ai/provider-errors.ts +138 -0
  98. package/src/ai/provider-fetch.ts +4 -0
  99. package/src/ai/provider-retry.ts +1 -1
  100. package/src/ai/provider.ts +2 -1
  101. package/src/ai/quota-limits.ts +36 -0
  102. package/src/ai/quota-provider.ts +11 -2
  103. package/src/ai/quota-report.ts +7 -4
  104. package/src/ai/quotas-migrate.ts +1 -0
  105. package/src/ai/quotas.ts +19 -8
  106. package/src/ai/recent-resources.ts +6 -0
  107. package/src/ai/request-headers.ts +14 -87
  108. package/src/ai/resource-refs.ts +6 -2
  109. package/src/ai/routes.ts +17 -18
  110. package/src/ai/runtime.ts +50 -22
  111. package/src/ai/settings.ts +93 -580
  112. package/src/ai/store.ts +45 -23
  113. package/src/ai/stream.ts +1 -1
  114. package/src/ai/structured-runs.ts +2 -2
  115. package/src/ai/structured.ts +29 -46
  116. package/src/ai/task-contracts.ts +25 -5
  117. package/src/ai/task-execution.ts +15 -6
  118. package/src/ai/task-model.ts +9 -0
  119. package/src/ai/transcription.ts +37 -21
  120. package/src/ai/turn-failure.ts +24 -2
  121. package/src/ai/types.ts +50 -62
  122. package/src/ai/ui.tsx +13 -0
  123. package/src/ai/usage.ts +8 -8
  124. package/src/ai/validate.ts +8 -7
  125. package/src/ai/view-block.ts +2 -2
  126. package/src/api/accounts-entities.ts +40 -16
  127. package/src/api/admin-ai-quotas.ts +34 -56
  128. package/src/api/admin-ai-usage.ts +1 -1
  129. package/src/api/admin-ai.ts +173 -0
  130. package/src/api/admin-core-settings.ts +68 -244
  131. package/src/api/admin-directory-shares.ts +55 -0
  132. package/src/api/admin-identity.ts +81 -2
  133. package/src/api/app-access.ts +85 -0
  134. package/src/api/app-approval.ts +3 -1
  135. package/src/api/app-event-delivery.ts +51 -0
  136. package/src/api/app-events.ts +123 -0
  137. package/src/api/app-resource-access.ts +48 -0
  138. package/src/api/auth/account-mismatch.ts +24 -0
  139. package/src/api/auth.ts +26 -3
  140. package/src/api/capabilities.ts +127 -15
  141. package/src/api/capability-streams.ts +2 -5
  142. package/src/api/identity-child-invocation.ts +145 -0
  143. package/src/api/index.ts +11 -2
  144. package/src/api/mcp.ts +180 -5
  145. package/src/api/me.ts +38 -17
  146. package/src/api/pwa-phone.ts +87 -48
  147. package/src/api/pwa.ts +34 -8
  148. package/src/api/resource-links.ts +83 -6
  149. package/src/api/resource-resolve.ts +50 -0
  150. package/src/api/search/schemas.ts +3 -1
  151. package/src/api/search.ts +9 -7
  152. package/src/api/widgets.ts +1 -0
  153. package/src/app-pwa/authority.ts +75 -0
  154. package/src/app-pwa/contracts.ts +177 -0
  155. package/src/app-pwa/core-query-error.ts +20 -0
  156. package/src/app-pwa/index.ts +23 -0
  157. package/src/app-pwa/protocol.ts +27 -0
  158. package/src/app-pwa/schema.ts +59 -0
  159. package/src/app-pwa/service.ts +861 -0
  160. package/src/app-pwa/transaction.ts +338 -0
  161. package/src/app-pwa/transport-input.ts +57 -0
  162. package/src/app-pwa/transport.ts +235 -0
  163. package/src/approvals/messages.ts +2 -2
  164. package/src/browser/app-access-recovery.ts +44 -0
  165. package/src/browser/app-session.ts +14 -3
  166. package/src/browser/cloud-id.ts +86 -0
  167. package/src/browser/live-websocket.ts +5 -0
  168. package/src/browser/pwa-drafts.ts +57 -0
  169. package/src/browser/resource-clipboard.ts +4 -0
  170. package/src/browser/resource-links.ts +52 -0
  171. package/src/browser/resource-picker.tsx +3 -2
  172. package/src/capabilities/calendar-sources.ts +302 -0
  173. package/src/capabilities/executions.ts +10 -3
  174. package/src/capabilities/migrate.ts +4 -0
  175. package/src/capabilities/nested.ts +43 -0
  176. package/src/capabilities/server-invoke.ts +202 -0
  177. package/src/capabilities/server.ts +26 -187
  178. package/src/capabilities/testing.ts +5 -2
  179. package/src/cli/access.ts +6 -5
  180. package/src/cli/account.ts +57 -0
  181. package/src/cli/admin/agents.ts +66 -0
  182. package/src/cli/admin/ai-models.ts +199 -0
  183. package/src/cli/admin/ai-quotas.ts +6 -102
  184. package/src/cli/admin/ai-usage.ts +4 -4
  185. package/src/cli/admin/app-credentials.ts +5 -3
  186. package/src/cli/admin/index.ts +7 -3
  187. package/src/cli/app-access.ts +60 -0
  188. package/src/cli/apps.ts +89 -26
  189. package/src/cli/avatar.ts +64 -0
  190. package/src/cli/capabilities.ts +22 -1
  191. package/src/cli/events.ts +320 -0
  192. package/src/cli/index.ts +1 -1
  193. package/src/config/env.ts +40 -0
  194. package/src/contracts/app-access.ts +52 -5
  195. package/src/contracts/app-approval.ts +8 -1
  196. package/src/contracts/app-details.ts +89 -0
  197. package/src/contracts/app-events.ts +51 -0
  198. package/src/contracts/app.ts +24 -5
  199. package/src/contracts/calendar-source.ts +274 -0
  200. package/src/contracts/calendar-view.ts +35 -0
  201. package/src/contracts/capabilities.ts +164 -3
  202. package/src/contracts/geo-admin.ts +2 -0
  203. package/src/contracts/geo-route.ts +0 -11
  204. package/src/contracts/geo.ts +14 -2
  205. package/src/contracts/index.ts +6 -0
  206. package/src/contracts/installable.ts +40 -0
  207. package/src/contracts/notification-types.ts +36 -9
  208. package/src/contracts/profile.ts +32 -0
  209. package/src/contracts/pwa-builds.ts +14 -0
  210. package/src/contracts/pwa-location.ts +38 -0
  211. package/src/contracts/pwa.ts +24 -3
  212. package/src/contracts/registry.ts +8 -0
  213. package/src/contracts/resource-clipboard.ts +35 -0
  214. package/src/contracts/resource-pages.ts +140 -0
  215. package/src/contracts/resource-resolution.ts +13 -0
  216. package/src/contracts/shared.ts +4 -0
  217. package/src/contracts/user-notifications.ts +2 -0
  218. package/src/events/app-event-delivery.ts +100 -0
  219. package/src/events/app-events.testing.ts +65 -0
  220. package/src/events/app-events.ts +134 -0
  221. package/src/events/core-user-created.ts +24 -0
  222. package/src/events/index.ts +9 -0
  223. package/src/events/testing.ts +2 -0
  224. package/src/index.ts +2 -1
  225. package/src/server/api/respond.ts +8 -3
  226. package/src/server/index.ts +1 -0
  227. package/src/server/middleware/app-access.ts +7 -6
  228. package/src/server/middleware/auth.ts +168 -66
  229. package/src/server/middleware/index.ts +1 -0
  230. package/src/server/middleware/invocation.ts +4 -2
  231. package/src/server/middleware/workload.ts +3 -3
  232. package/src/server/oauth-scope.ts +10 -0
  233. package/src/server/public-storage.ts +106 -0
  234. package/src/server/services/access.ts +8 -0
  235. package/src/services/accounts/app.ts +35 -3
  236. package/src/services/accounts/avatar.ts +342 -35
  237. package/src/services/accounts/base-group.ts +4 -5
  238. package/src/services/accounts/directory-shares.ts +157 -0
  239. package/src/services/accounts/entities.ts +50 -6
  240. package/src/services/accounts/groups.ts +3 -1
  241. package/src/services/accounts/identities.ts +64 -12
  242. package/src/services/accounts/local-groups.ts +1 -1
  243. package/src/services/accounts/users.ts +23 -15
  244. package/src/services/app-access-management.ts +519 -0
  245. package/src/services/app-access.ts +25 -7
  246. package/src/services/app-approval.ts +15 -4
  247. package/src/services/app-details.ts +224 -0
  248. package/src/services/app-discovery.ts +2 -1
  249. package/src/services/app-event-shapes.ts +139 -0
  250. package/src/services/app-events/accept.ts +116 -0
  251. package/src/services/app-events/admin.ts +250 -0
  252. package/src/services/app-events/declarations.ts +187 -0
  253. package/src/services/app-events/deliver.ts +218 -0
  254. package/src/services/app-events.ts +92 -0
  255. package/src/services/config-conflict.ts +11 -0
  256. package/src/services/core-user-events.ts +1 -0
  257. package/src/services/freeipa-config.ts +9 -0
  258. package/src/services/geo/admin.ts +4 -1
  259. package/src/services/geo/ledger.ts +47 -14
  260. package/src/services/geo/route-chain.ts +40 -18
  261. package/src/services/geo/route-estimate.ts +19 -0
  262. package/src/services/geo/route-plan.ts +0 -2
  263. package/src/services/geo/route-service.ts +57 -39
  264. package/src/services/geo/service.ts +51 -25
  265. package/src/services/identity/core-origin.ts +12 -0
  266. package/src/services/identity/index.ts +2 -1
  267. package/src/services/identity/invocation-actor.ts +50 -2
  268. package/src/services/identity/invocation-authority.ts +38 -1
  269. package/src/services/identity/invocation-token.ts +255 -50
  270. package/src/services/identity/issue-invocation.ts +9 -1
  271. package/src/services/identity/models-only-auth.ts +13 -4
  272. package/src/services/identity/nested-invocation.ts +128 -0
  273. package/src/services/image-metadata.ts +65 -1
  274. package/src/services/index.ts +24 -1
  275. package/src/services/ipa/sync.ts +6 -2
  276. package/src/services/ipa/users.ts +4 -1
  277. package/src/services/mandates/policy.ts +19 -12
  278. package/src/services/notifications/browser.ts +4 -0
  279. package/src/services/notifications/catalog.ts +5 -2
  280. package/src/services/notifications/platform.ts +7 -2
  281. package/src/services/notifications/user.ts +18 -7
  282. package/src/services/oauth-tokens.ts +28 -2
  283. package/src/services/object-store.ts +217 -0
  284. package/src/services/providers/local/users.ts +5 -0
  285. package/src/services/pwa-devices.ts +147 -37
  286. package/src/services/service-account-credentials.ts +5 -2
  287. package/src/services/session/index.ts +14 -0
  288. package/src/services/settings/core-settings.ts +91 -47
  289. package/src/services/settings/index.ts +8 -0
  290. package/src/services/settings/versions.ts +26 -0
  291. package/src/shared/ai-models.ts +274 -0
  292. package/src/shared/ai-quotas.ts +2 -2
  293. package/src/shared/ai-usage.ts +2 -2
  294. package/src/shared/index.ts +2 -0
  295. package/src/shared-text/documents.ts +25 -8
  296. package/src/shared-text/log.ts +1 -1
  297. package/src/shared-text/text-diff.ts +75 -0
  298. package/src/ssr/AdminArea.tsx +131 -0
  299. package/src/ssr/AdminLayout.tsx +11 -3
  300. package/src/ssr/AdminSidebar.tsx +7 -7
  301. package/src/ssr/AppAccessDenied.tsx +114 -0
  302. package/src/ssr/AppBadge.tsx +14 -0
  303. package/src/ssr/AppLaunchpadPanel.tsx +1 -1
  304. package/src/ssr/AppPwaRuntime.island.tsx +82 -0
  305. package/src/ssr/CloudShell.island.tsx +25 -4
  306. package/src/ssr/Layout.tsx +15 -7
  307. package/src/ssr/PwaLayout.tsx +73 -8
  308. package/src/ssr/PwaPartCount.island.tsx +23 -0
  309. package/src/ssr/PwaRuntime.island.tsx +85 -6
  310. package/src/ssr/SettingsSaveOutcome.tsx +58 -0
  311. package/src/ssr/SettingsSaveStatus.island.tsx +16 -0
  312. package/src/ssr/ShellRuntime.island.tsx +5 -0
  313. package/src/ssr/__snapshots__/Layout.unchanged.render.test.tsx.snap +3 -3
  314. package/src/ssr/__snapshots__/LayoutHelp.ssr.render.test.tsx.snap +1 -1
  315. package/src/ssr/admin-active-link.ts +16 -0
  316. package/src/ssr/admin-navigation.ts +26 -20
  317. package/src/ssr/app-badges.ts +31 -7
  318. package/src/ssr/app-pwa-document.tsx +16 -0
  319. package/src/ssr/app-pwa-messages.ts +17 -0
  320. package/src/ssr/index.ts +8 -0
  321. package/src/ssr/layout-context.ts +1 -1
  322. package/src/ssr/page-trail-runtime.ts +3 -1
  323. package/src/ssr/platform-messages.ts +9 -1
  324. package/src/ssr/pwa-messages.ts +10 -2
  325. package/src/ssr/rail-navigation.ts +5 -1
  326. package/src/ssr/settings-draft.ts +153 -0
  327. package/src/ssr/shell-data.tsx +2 -0
  328. package/src/styles/global.css +1 -0
  329. package/src/styles/profile-picture.css +270 -0
  330. package/src/styles/utilities-navigation.css +6 -0
  331. package/src/workflows/ai/policy.ts +49 -0
  332. package/src/workflows/ai/runtime.ts +131 -21
  333. package/src/workflows/ai/store.ts +283 -35
  334. package/src/workflows/ai/types.ts +8 -2
  335. package/src/workflows/ai-actions.ts +148 -120
  336. package/src/workflows/authoring.ts +31 -0
  337. package/src/workflows/contracts.ts +1 -0
  338. package/src/workflows/definition.ts +2 -0
  339. package/src/workflows/editor.ts +15 -0
  340. package/src/workflows/index.ts +2 -1
  341. package/src/workflows/language/compiler.ts +34 -3
  342. package/src/workflows/runtime/executor.ts +132 -33
  343. package/src/workflows/runtime/index.ts +2 -0
  344. package/src/workflows/runtime/ports.ts +5 -1
  345. package/src/workflows/store/actions.ts +1 -1
  346. package/src/workflows/store/definitions.ts +5 -0
  347. package/src/workflows/store/runs.ts +5 -3
  348. package/src/workflows/store/worker.ts +7 -2
  349. package/src/ai/model-pricing.ts +0 -43
  350. package/src/ai/model-request-settings.ts +0 -98
  351. package/src/ai/settings-messages.ts +0 -129
  352. package/src/shared/ai-model-request-settings.ts +0 -21
@@ -1,5 +1,7 @@
1
1
  import { readFile } from "node:fs/promises";
2
+ import { type PersonAvatar, randomizePersonAvatar, startPersonAvatar } from "../contracts/avatar";
2
3
  import { isModelsOnlyApiKey } from "../contracts/shared";
4
+ import { applyAvatarParts, avatarRows, type CliAvatarState, readAvatarPhoto } from "./avatar";
3
5
  import { arg, type CliInputFlagValue, type CloudCliContext, command, confirmFlag, defineCliCommands, flag, readCliInput } from "./index";
4
6
 
5
7
  type User = {
@@ -229,6 +231,7 @@ export default defineCliCommands({
229
231
  summary: "Manage the authenticated account, profile, personal API keys, and SSH keys.",
230
232
  groupSummaries: {
231
233
  profile: "Show and update the account profile",
234
+ avatar: "Show and change the profile picture: photo, built avatar, or initials",
232
235
  "api-keys": "Manage personal API keys",
233
236
  "ssh-keys": "Manage SSH public keys",
234
237
  password: "Change the account password",
@@ -264,6 +267,60 @@ export default defineCliCommands({
264
267
  await updateProfile(ctx, buildProfileUpdate(flags));
265
268
  },
266
269
  }),
270
+ command("avatar show", {
271
+ summary: "Show which profile picture others see and the parts of the built avatar",
272
+ run: async ({ ctx }) => {
273
+ const state = await readApi<CliAvatarState>(ctx, "/avatar");
274
+ printJsonOrTable(ctx, state, avatarRows(state), [{ key: "key" }, { key: "value" }]);
275
+ },
276
+ }),
277
+ command("avatar use", {
278
+ summary: "Show the stored photo, the built avatar, or initials",
279
+ args: { kind: arg.required({ valueLabel: "photo|avatar|initials", description: "Picture to show" }) },
280
+ run: async ({ ctx, args }) => {
281
+ const use = { photo: "photo", avatar: "built", initials: "initials" }[args.kind];
282
+ if (!use) throw new Error(`Choose photo, avatar, or initials, not "${args.kind}".`);
283
+ const state = await readApi<CliAvatarState>(ctx, "/avatar", jsonRequest("PUT", { use }));
284
+ printJsonOrTable(ctx, state, avatarRows(state), [{ key: "key" }, { key: "value" }]);
285
+ },
286
+ }),
287
+ command("avatar random", {
288
+ summary: "Build a random avatar, keep the skin tone, and show it",
289
+ run: async ({ ctx }) => {
290
+ const current = await readApi<CliAvatarState>(ctx, "/avatar");
291
+ const base = current.built?.kind === "person" ? current.built : startPersonAvatar();
292
+ const state = await readApi<CliAvatarState>(ctx, "/avatar", jsonRequest("PUT", { built: randomizePersonAvatar(base) }));
293
+ printJsonOrTable(ctx, state, avatarRows(state), [{ key: "key" }, { key: "value" }]);
294
+ },
295
+ }),
296
+ command("avatar set", {
297
+ summary: "Change parts of the built avatar, for example hair=curly hairColor=red, and show it",
298
+ args: { parts: arg.rest({ valueLabel: "part=value", description: "Parts to change; an unknown value lists the choices" }) },
299
+ run: async ({ ctx, args }) => {
300
+ const current = await readApi<CliAvatarState>(ctx, "/avatar");
301
+ const base: PersonAvatar = current.built?.kind === "person" ? current.built : startPersonAvatar();
302
+ const state = await readApi<CliAvatarState>(ctx, "/avatar", jsonRequest("PUT", { built: applyAvatarParts(base, args.parts) }));
303
+ printJsonOrTable(ctx, state, avatarRows(state), [{ key: "key" }, { key: "value" }]);
304
+ },
305
+ }),
306
+ command("avatar photo", {
307
+ summary: "Store a small square PNG, JPEG, or WebP (at most 48 KiB) and show it",
308
+ args: { file: arg.required({ valueLabel: "file", description: "Image file" }) },
309
+ run: async ({ ctx, args }) => {
310
+ const state = await readApi<CliAvatarState>(ctx, "/avatar", jsonRequest("PUT", { dataUrl: await readAvatarPhoto(args.file) }));
311
+ printJsonOrTable(ctx, state, avatarRows(state), [{ key: "key" }, { key: "value" }]);
312
+ },
313
+ }),
314
+ command("avatar delete-photo", {
315
+ summary: "Delete the stored photo; when it was showing, initials show",
316
+ flags: { yes: confirmFlag("Delete the stored photo") },
317
+ run: async ({ ctx, flags }) => {
318
+ if (!flags.yes) throw new Error("Pass --yes to delete the stored photo.");
319
+ await readApi(ctx, "/avatar", jsonRequest("DELETE"));
320
+ const state = await readApi<CliAvatarState>(ctx, "/avatar");
321
+ printJsonOrTable(ctx, state, avatarRows(state), [{ key: "key" }, { key: "value" }]);
322
+ },
323
+ }),
267
324
  command("activity", {
268
325
  summary: "List recent account activity",
269
326
  flags: {
@@ -5,6 +5,8 @@
5
5
  * `cld` profile through the host; the secret is never printed.
6
6
  */
7
7
  import { z } from "zod";
8
+ import { type RobotAvatar, randomRobotAvatar } from "../../contracts/avatar";
9
+ import { applyAvatarParts, avatarRows, type CliAvatarState, readAvatarPhoto } from "../avatar";
8
10
  import { arg, cliText, command, confirmFlag, flag, printStructured } from "../index";
9
11
  import { apiGet, apiJson, queryString } from "./shared";
10
12
 
@@ -62,6 +64,12 @@ const resolveAgent = async (ctx: Ctx, reference: string): Promise<Account> => {
62
64
  throw new Error(`Several agents match "${reference}": ${matches.map((item) => `${item.name} (${item.id})`).join(", ")}.`);
63
65
  };
64
66
 
67
+ const avatarPath = (id: string) => `${accountsRoot}/${encodeURIComponent(id)}/avatar`;
68
+
69
+ const printAvatar = (ctx: Ctx, state: CliAvatarState) => {
70
+ if (!printStructured(ctx, state)) ctx.table(avatarRows(state), [{ key: "key" }, { key: "value" }]);
71
+ };
72
+
65
73
  const disableAccount = (ctx: Ctx, id: string) => apiJson(ctx, "PATCH", `${accountsRoot}/${encodeURIComponent(id)}`, { status: "disabled" });
66
74
 
67
75
  const requireProfileName = (flags: { profile?: string }): string => {
@@ -216,4 +224,62 @@ export const agentCommands = [
216
224
  }
217
225
  },
218
226
  }),
227
+ command("agents avatar show", {
228
+ summary: "Show an agent's picture: its robot parts, and whether a photo is stored and showing",
229
+ args: { agent: arg.required({ valueLabel: "agent-id-or-name" }) },
230
+ async run({ ctx, args }) {
231
+ const agent = await resolveAgent(ctx, args.agent);
232
+ printAvatar(ctx, await apiGet<CliAvatarState>(ctx, avatarPath(agent.id)));
233
+ },
234
+ }),
235
+ command("agents avatar random", {
236
+ summary: "Give an agent a new random robot and show it",
237
+ args: { agent: arg.required({ valueLabel: "agent-id-or-name" }) },
238
+ async run({ ctx, args }) {
239
+ const agent = await resolveAgent(ctx, args.agent);
240
+ printAvatar(ctx, await apiJson<CliAvatarState>(ctx, "PUT", avatarPath(agent.id), { built: randomRobotAvatar() }));
241
+ },
242
+ }),
243
+ command("agents avatar set", {
244
+ summary: "Change parts of an agent's robot, for example head=round color=teal, and show it",
245
+ args: {
246
+ agent: arg.required({ valueLabel: "agent-id-or-name" }),
247
+ parts: arg.rest({ valueLabel: "part=value", description: "Parts to change; an unknown value lists the choices" }),
248
+ },
249
+ async run({ ctx, args }) {
250
+ const agent = await resolveAgent(ctx, args.agent);
251
+ const current = await apiGet<CliAvatarState>(ctx, avatarPath(agent.id));
252
+ if (current.built?.kind !== "robot") throw new Error(`Agent "${agent.name}" has no robot to change.`);
253
+ const built = applyAvatarParts<RobotAvatar>(current.built, args.parts);
254
+ printAvatar(ctx, await apiJson<CliAvatarState>(ctx, "PUT", avatarPath(agent.id), { built }));
255
+ },
256
+ }),
257
+ command("agents avatar photo", {
258
+ summary: "Store a small square PNG, JPEG, or WebP (at most 48 KiB) as an agent's picture and show it",
259
+ args: { agent: arg.required({ valueLabel: "agent-id-or-name" }), file: arg.required({ valueLabel: "file" }) },
260
+ async run({ ctx, args }) {
261
+ const agent = await resolveAgent(ctx, args.agent);
262
+ printAvatar(ctx, await apiJson<CliAvatarState>(ctx, "PUT", avatarPath(agent.id), { dataUrl: await readAvatarPhoto(args.file) }));
263
+ },
264
+ }),
265
+ command("agents avatar use", {
266
+ summary: "Show an agent's stored photo or its robot",
267
+ args: { agent: arg.required({ valueLabel: "agent-id-or-name" }), kind: arg.required({ valueLabel: "photo|robot" }) },
268
+ async run({ ctx, args }) {
269
+ const use = { photo: "photo", robot: "built" }[args.kind];
270
+ if (!use) throw new Error(`Choose photo or robot, not "${args.kind}".`);
271
+ const agent = await resolveAgent(ctx, args.agent);
272
+ printAvatar(ctx, await apiJson<CliAvatarState>(ctx, "PUT", avatarPath(agent.id), { use }));
273
+ },
274
+ }),
275
+ command("agents avatar delete-photo", {
276
+ summary: "Delete an agent's stored photo; its robot shows again",
277
+ args: { agent: arg.required({ valueLabel: "agent-id-or-name" }) },
278
+ flags: { yes: confirmFlag("Delete this agent's photo") },
279
+ async run({ ctx, args, flags }) {
280
+ requireYes(ctx, flags.yes, "Deleting a photo requires --yes.", "Foto löschen erfordert --yes.");
281
+ const agent = await resolveAgent(ctx, args.agent);
282
+ printAvatar(ctx, await apiJson<CliAvatarState>(ctx, "DELETE", avatarPath(agent.id)));
283
+ },
284
+ }),
219
285
  ];
@@ -0,0 +1,199 @@
1
+ import { z } from "zod";
2
+ import {
3
+ AI_MODEL_SLOTS,
4
+ AiDeleteModelSchema,
5
+ AiModelAccessDraftSchema,
6
+ AiModelInputSchema,
7
+ AiModelPatchSchema,
8
+ AiProviderInputSchema,
9
+ AiProviderPatchSchema,
10
+ AiVariantInputSchema,
11
+ AiVariantPatchSchema,
12
+ } from "../../shared/ai-models";
13
+ import { arg, type CloudCliContext, command, confirmFlag, flag, printStructured, readCliInput } from "../index";
14
+ import { apiGet, apiJson, readJsonInput } from "./shared";
15
+
16
+ const base = "/api/admin/core/ai";
17
+ const print = (ctx: CloudCliContext, result: unknown) => {
18
+ if (!printStructured(ctx, result)) ctx.print(JSON.stringify(result, null, 2));
19
+ };
20
+ const confirm = (yes: boolean) => {
21
+ if (!yes) throw new Error("AI configuration changes require --yes.");
22
+ };
23
+ const id = { id: arg.required({ valueLabel: "id" }) };
24
+ const input = flag.input({ required: true, description: "JSON input: --input, --input-file or --stdin" });
25
+ const yes = confirmFlag("Confirm AI configuration change");
26
+ const endpoint = (area: string, id: string) => `${base}/${area}/${encodeURIComponent(id)}`;
27
+ export const aiModelCommands = [
28
+ command("ai providers list", {
29
+ summary: "List provider connections and their status",
30
+ async run({ ctx }) {
31
+ print(ctx, await apiGet(ctx, `${base}/providers`));
32
+ },
33
+ }),
34
+ command("ai providers show", {
35
+ summary: "Show a provider; credential and header values stay private",
36
+ args: id,
37
+ async run({ ctx, args }) {
38
+ print(ctx, await apiGet(ctx, endpoint("providers", args.id)));
39
+ },
40
+ }),
41
+ command("ai providers add", {
42
+ summary: "Create a provider connection",
43
+ flags: { input, yes },
44
+ async run({ ctx, flags }) {
45
+ confirm(flags.yes);
46
+ print(
47
+ ctx,
48
+ await apiJson(ctx, "POST", `${base}/providers`, AiProviderInputSchema.parse(await readJsonInput<unknown>(flags.input, "provider"))),
49
+ );
50
+ },
51
+ }),
52
+ command("ai providers update", {
53
+ summary: "Update a provider with optimistic concurrency",
54
+ args: id,
55
+ flags: { input, yes },
56
+ async run({ ctx, args, flags }) {
57
+ confirm(flags.yes);
58
+ const patch = AiProviderPatchSchema.parse(await readJsonInput<unknown>(flags.input, "provider patch"));
59
+ const current = await apiGet<{ revision: number }>(ctx, endpoint("providers", args.id));
60
+ print(ctx, await apiJson(ctx, "PATCH", endpoint("providers", args.id), { ...patch, revision: current.revision }));
61
+ },
62
+ }),
63
+ command("ai providers test", {
64
+ summary: "Test a saved provider connection (10 second timeout)",
65
+ args: id,
66
+ async run({ ctx, args }) {
67
+ print(ctx, await apiJson(ctx, "POST", `${endpoint("providers", args.id)}/test`, {}));
68
+ },
69
+ }),
70
+ command("ai providers key", {
71
+ summary: "Replace a provider key from protected input",
72
+ args: id,
73
+ flags: { key: flag.input({ required: true, description: "API key: use --key-file or --stdin" }), yes },
74
+ async run({ ctx, args, flags }) {
75
+ confirm(flags.yes);
76
+ const apiKey = await readCliInput(flags.key, { required: true, label: "API key" });
77
+ const current = await apiGet<{ revision: number }>(ctx, endpoint("providers", args.id));
78
+ print(
79
+ ctx,
80
+ await apiJson(ctx, "PUT", `${endpoint("providers", args.id)}/credential`, {
81
+ apiKey: apiKey?.trim() ?? "",
82
+ revision: current.revision,
83
+ }),
84
+ );
85
+ },
86
+ }),
87
+ command("ai providers models", {
88
+ summary: "List the models advertised by a provider",
89
+ args: id,
90
+ async run({ ctx, args }) {
91
+ print(ctx, await apiGet(ctx, `${endpoint("providers", args.id)}/available-models`));
92
+ },
93
+ }),
94
+ command("ai models list", {
95
+ summary: "List configured models",
96
+ async run({ ctx }) {
97
+ print(ctx, await apiGet(ctx, `${base}/models`));
98
+ },
99
+ }),
100
+ command("ai models show", {
101
+ summary: "Show a model, variants and access",
102
+ args: id,
103
+ async run({ ctx, args }) {
104
+ print(ctx, await apiGet(ctx, endpoint("models", args.id)));
105
+ },
106
+ }),
107
+ command("ai models add", {
108
+ summary: "Add a model to a provider",
109
+ args: { provider: arg.required() },
110
+ flags: { input, yes },
111
+ async run({ ctx, args, flags }) {
112
+ confirm(flags.yes);
113
+ const data = z
114
+ .union([AiModelInputSchema, z.array(AiModelInputSchema).min(1)])
115
+ .parse(await readJsonInput<unknown>(flags.input, "models"));
116
+ print(ctx, await apiJson(ctx, "POST", `${endpoint("providers", args.provider)}/models`, data));
117
+ },
118
+ }),
119
+ command("ai models update", {
120
+ summary: "Update one model and its shared prices",
121
+ args: id,
122
+ flags: { input, yes },
123
+ async run({ ctx, args, flags }) {
124
+ confirm(flags.yes);
125
+ const data = AiModelPatchSchema.extend({ access: AiModelAccessDraftSchema.optional() }).parse(
126
+ await readJsonInput<unknown>(flags.input, "model patch"),
127
+ );
128
+ const current = await apiGet<{ revision: number }>(ctx, endpoint("models", args.id));
129
+ print(ctx, await apiJson(ctx, "PATCH", endpoint("models", args.id), { ...data, revision: current.revision }));
130
+ },
131
+ }),
132
+ command("ai variants add", {
133
+ summary: "Create a named model variant",
134
+ args: { model: arg.required() },
135
+ flags: { input, yes },
136
+ async run({ ctx, args, flags }) {
137
+ confirm(flags.yes);
138
+ print(
139
+ ctx,
140
+ await apiJson(
141
+ ctx,
142
+ "POST",
143
+ `${endpoint("models", args.model)}/variants`,
144
+ AiVariantInputSchema.parse(await readJsonInput<unknown>(flags.input, "variant")),
145
+ ),
146
+ );
147
+ },
148
+ }),
149
+ command("ai variants update", {
150
+ summary: "Update a variant's overrides",
151
+ args: id,
152
+ flags: { input, yes },
153
+ async run({ ctx, args, flags }) {
154
+ confirm(flags.yes);
155
+ const data = AiVariantPatchSchema.parse(await readJsonInput<unknown>(flags.input, "variant patch"));
156
+ const current = await apiGet<{ variants: { id: string; revision: number }[] }>(ctx, endpoint("models", args.id.split(":")[0]!));
157
+ const variant = current.variants.find((v) => v.id === args.id);
158
+ if (!variant) throw new Error("Variant not found.");
159
+ print(ctx, await apiJson(ctx, "PATCH", endpoint("variants", args.id), { ...data, revision: variant.revision }));
160
+ },
161
+ }),
162
+ ...(["providers", "models", "variants"] as const).map((area) =>
163
+ command(`ai ${area} delete`, {
164
+ summary: "Delete with explicit assignment replacements and retargeting choice",
165
+ args: id,
166
+ flags: { input, yes },
167
+ async run({ ctx, args, flags }) {
168
+ confirm(flags.yes);
169
+ const data = AiDeleteModelSchema.parse(await readJsonInput<unknown>(flags.input, "delete consequences"));
170
+ print(ctx, await apiJson(ctx, "DELETE", endpoint(area, args.id), data));
171
+ },
172
+ }),
173
+ ),
174
+ command("ai classes show", {
175
+ summary: "Show class and tool assignments, fallback chains and recent callers",
176
+ async run({ ctx }) {
177
+ print(ctx, await apiGet(ctx, `${base}/assignments`));
178
+ },
179
+ }),
180
+ command("ai classes set", {
181
+ summary: "Assign a class or tool slot to a variant or none",
182
+ args: { slot: arg.required(), variant: arg.required() },
183
+ flags: { yes },
184
+ async run({ ctx, args, flags }) {
185
+ confirm(flags.yes);
186
+ const slot = z.enum(AI_MODEL_SLOTS).parse(args.slot);
187
+ const current = await apiGet<{ assignments: { slot: string; revision: number }[] }>(ctx, `${base}/assignments`);
188
+ const assignment = current.assignments.find((a) => a.slot === slot);
189
+ if (!assignment) throw new Error("Assignment missing.");
190
+ print(
191
+ ctx,
192
+ await apiJson(ctx, "PUT", `${base}/assignments/${slot}`, {
193
+ variantId: args.variant === "none" ? null : args.variant,
194
+ revision: assignment.revision,
195
+ }),
196
+ );
197
+ },
198
+ }),
199
+ ];
@@ -1,9 +1,5 @@
1
- import { type AiModelPricing, AiModelPricingSchema } from "../../shared/ai-costs";
2
- import {
3
- type AiModelRequestSettings,
4
- AiModelRequestSettingsSchema,
5
- AiModelRequestSettingsUpdateSchema,
6
- } from "../../shared/ai-model-request-settings";
1
+ import type { AiModelPricing } from "../../shared/ai-costs";
2
+
7
3
  import {
8
4
  type AiQuotaConfig,
9
5
  AiQuotaConfigSchema,
@@ -12,7 +8,7 @@ import {
12
8
  AiQuotaReportQuerySchema,
13
9
  AiQuotaResetSchema,
14
10
  } from "../../shared/ai-quotas";
15
- import { type CloudCliContext, cliText, command, confirmFlag, flag, printRows, printStructured, readCliInput } from "../index";
11
+ import { type CloudCliContext, cliText, command, confirmFlag, flag, printRows, printStructured } from "../index";
16
12
  import { apiGet, apiJson, queryString, readJsonInput } from "./shared";
17
13
 
18
14
  const path = "/api/admin/core/ai-quotas";
@@ -28,98 +24,6 @@ const confirm = (ctx: CloudCliContext, yes: boolean) => {
28
24
  };
29
25
 
30
26
  export const aiQuotaCommands = [
31
- command("ai models settings get", {
32
- summary: "Read model thinking level, extra parameters and configured header names",
33
- flags: { id: flag.string({ required: true, description: "Model profile ID" }) },
34
- async run({ ctx, flags }) {
35
- print(ctx, AiModelRequestSettingsSchema.parse(await apiGet(ctx, `${path}/models/${encodeURIComponent(flags.id!)}/settings`)));
36
- },
37
- }),
38
- command("ai models settings set", {
39
- summary: "Patch one model's request settings; header values are write-only",
40
- flags: {
41
- id: flag.string({ required: true, description: "Model profile ID" }),
42
- thinkingLevel: flag.string({ name: "thinking-level", description: "Provider-specific thinking level" }),
43
- clearThinkingLevel: flag.boolean({ name: "clear-thinking-level", description: "Use the model's default thinking level" }),
44
- extraBody: flag.input({ name: "extra-body", description: "Extra parameters JSON: --extra-body, --extra-body-file or --stdin" }),
45
- clearExtraBody: flag.boolean({ name: "clear-extra-body", description: "Remove extra parameters" }),
46
- headers: flag.input({
47
- stdinName: "headers-stdin",
48
- description: "Header patch JSON; use --headers-file or --headers-stdin for secrets",
49
- }),
50
- clearHeaders: flag.boolean({ name: "clear-headers", description: "Remove all configured extra headers before applying the patch" }),
51
- yes: confirmFlag("Confirm model request settings change"),
52
- },
53
- async run({ ctx, flags }) {
54
- if (!flags.yes) throw new Error("Model request settings changes require --yes.");
55
- if (flags.thinkingLevel !== undefined && flags.clearThinkingLevel)
56
- throw new Error("Choose --thinking-level or --clear-thinking-level.");
57
- if (flags.extraBody.provided && flags.clearExtraBody) throw new Error("Choose --extra-body or --clear-extra-body.");
58
- if (
59
- !flags.extraBody.provided &&
60
- !flags.headers.provided &&
61
- flags.thinkingLevel === undefined &&
62
- !flags.clearThinkingLevel &&
63
- !flags.clearExtraBody &&
64
- !flags.clearHeaders
65
- )
66
- throw new Error("Select at least one model request setting to change.");
67
- if (flags.extraBody.source === "stdin" && flags.headers.source === "stdin")
68
- throw new Error("Read one JSON input from stdin and supply the other from a file.");
69
- const parseInput = async (input: typeof flags.headers, label: string): Promise<unknown> => {
70
- const raw = await readCliInput(input, { required: true, label });
71
- try {
72
- return JSON.parse(raw!);
73
- } catch {
74
- throw new Error(`Invalid ${label} JSON.`);
75
- }
76
- };
77
- const patch: Record<string, unknown> = {};
78
- if (flags.thinkingLevel !== undefined || flags.clearThinkingLevel)
79
- patch.reasoningEffort = flags.clearThinkingLevel ? null : flags.thinkingLevel;
80
- if (flags.extraBody.provided || flags.clearExtraBody)
81
- patch.extraBody = flags.clearExtraBody ? null : await parseInput(flags.extraBody, "extra parameters");
82
- if (flags.headers.provided) patch.requestHeaders = await parseInput(flags.headers, "extra headers");
83
- if (flags.clearHeaders) patch.clearRequestHeaders = true;
84
- // Validate locally before reading the current revision or sending secrets.
85
- const valid = AiModelRequestSettingsUpdateSchema.parse({ ...patch, expected: "pending" });
86
- const current = await apiGet<AiModelRequestSettings>(ctx, `${path}/models/${encodeURIComponent(flags.id!)}/settings`);
87
- print(
88
- ctx,
89
- AiModelRequestSettingsSchema.parse(
90
- await apiJson(ctx, "PUT", `${path}/models/${encodeURIComponent(flags.id!)}/settings`, { ...valid, expected: current.revision }),
91
- ),
92
- );
93
- },
94
- }),
95
- command("ai models pricing get", {
96
- summary: "List configured model input/output reference prices per million tokens",
97
- async run({ ctx }) {
98
- print(ctx, await apiGet(ctx, `${path}/models`));
99
- },
100
- }),
101
- command("ai models pricing set", {
102
- summary: "Set one model's reference prices; JSON null removes prices and makes it unlimited",
103
- flags: {
104
- id: flag.string({ required: true, description: "Model profile ID" }),
105
- pricing: flag.input({
106
- required: true,
107
- description: "{inputPerMillion,outputPerMillion} or null: --pricing, --pricing-file or --stdin",
108
- }),
109
- yes: confirmFlag("Confirm model price change"),
110
- },
111
- async run({ ctx, flags }) {
112
- confirm(ctx, flags.yes);
113
- const pricing = AiModelPricingSchema.nullable().parse(await readJsonInput<unknown>(flags.pricing, "model pricing"));
114
- const current = await apiGet<{ models: { id: string; pricing?: AiModelPricing }[] }>(ctx, `${path}/models`);
115
- const model = current.models.find((model) => model.id === flags.id);
116
- if (!model) throw new Error("Model not found.");
117
- print(
118
- ctx,
119
- await apiJson(ctx, "PUT", `${path}/models/${encodeURIComponent(flags.id!)}/pricing`, { pricing, expected: model.pricing ?? null }),
120
- );
121
- },
122
- }),
123
27
  command("ai quotas background status", {
124
28
  summary: "Show rolling 24-hour background costs and emergency-stop state",
125
29
  async run({ ctx }) {
@@ -153,7 +57,7 @@ export const aiQuotaCommands = [
153
57
  },
154
58
  }),
155
59
  command("ai quotas models", {
156
- summary: "List configured model profiles; only priced chat models support cost rules",
60
+ summary: "List configured models; only priced chat models support cost rules",
157
61
  async run({ ctx }) {
158
62
  const result = await apiGet<{ models: { id: string; label: string; pricing?: AiModelPricing }[] }>(ctx, `${path}/models`);
159
63
  printRows(
@@ -170,7 +74,7 @@ export const aiQuotaCommands = [
170
74
  range: flag.enum(["24h", "7d", "30d", "90d"] as const, { default: "30d" }),
171
75
  until: flag.string({ description: "ISO period end; reuse query.until for consistent pagination" }),
172
76
  search: flag.string({ description: "Search identity labels" }),
173
- model: flag.string({ description: "Model profile ID" }),
77
+ model: flag.string({ description: "Model ID" }),
174
78
  status: flag.enum(["all", "available", "exhausted", "unknown", "unlimited", "disabled"] as const, { default: "all" }),
175
79
  sort: flag.enum(["label", "cost", "lastUsed"] as const, { default: "lastUsed" }),
176
80
  direction: flag.enum(["asc", "desc"] as const, { default: "desc" }),
@@ -216,7 +120,7 @@ export const aiQuotaCommands = [
216
120
  summary: "Reset one identity and quota scope; reuse request ID when retrying",
217
121
  flags: {
218
122
  ...identity,
219
- scope: flag.string({ required: true, description: "Model profile ID or * (quote the wildcard)" }),
123
+ scope: flag.string({ required: true, description: "Model ID or * (quote the wildcard)" }),
220
124
  requestId: flag.string({ required: true, description: "UUID for this reset; reuse for retries, use a new UUID for a new reset" }),
221
125
  yes: confirmFlag("Confirm reset of this identity and quota scope"),
222
126
  },
@@ -7,7 +7,7 @@ const filters = {
7
7
  range: flag.enum(AI_USAGE_RANGES, { default: "30d", description: "Response/run period" }),
8
8
  until: flag.string({ description: "Fixed ISO end time; reuse report.query.until across pages" }),
9
9
  userId: flag.string({ name: "user", description: "User UUID or unassigned" }),
10
- modelProfileId: flag.string({ name: "model", description: "Model profile ID" }),
10
+ modelId: flag.string({ name: "model", description: "Model profile ID" }),
11
11
  providerModel: flag.string({ name: "provider-model", description: "Actual provider model" }),
12
12
  workflowId: flag.string({ name: "workflow", description: "Workflow definition UUID" }),
13
13
  workflowRunId: flag.string({ name: "workflow-run", description: "Workflow run UUID" }),
@@ -75,7 +75,7 @@ export const aiUsageCommands = [
75
75
  { key: "task" },
76
76
  { key: "status" },
77
77
  { key: "userLabel" },
78
- { key: "modelProfileId" },
78
+ { key: "modelId" },
79
79
  { key: "cost" },
80
80
  { key: "unit" },
81
81
  { key: "error" },
@@ -85,7 +85,7 @@ export const aiUsageCommands = [
85
85
  ctx.table(report.feedback.items, [
86
86
  { key: "id" },
87
87
  { key: "userLabel" },
88
- { key: "modelProfileId" },
88
+ { key: "modelId" },
89
89
  { key: "rating" },
90
90
  { key: "comment" },
91
91
  ]);
@@ -125,7 +125,7 @@ export const aiUsageCommands = [
125
125
  command("ai usage facets", {
126
126
  summary: "Find user IDs, model profiles, provider models, apps or tasks for filtering",
127
127
  flags: {
128
- field: flag.enum(["userId", "modelProfileId", "providerModel", "appId", "task"] as const, { required: true }),
128
+ field: flag.enum(["userId", "modelId", "providerModel", "appId", "task"] as const, { required: true }),
129
129
  search: flag.string(),
130
130
  range: filters.range,
131
131
  },
@@ -51,7 +51,7 @@ export const appCredentialCommands = [
51
51
  },
52
52
  async run({ ctx, args, flags }) {
53
53
  if (!flags.yes)
54
- throw new Error(cliText(ctx, { en: "Creating an app credential requires --yes.", de: "App-Zugang erstellen erfordert --yes." }));
54
+ throw new Error(cliText(ctx, { en: "Creating an app credential requires --yes.", de: "App-Schlüssel erstellen erfordert --yes." }));
55
55
  const result = z.object({ credential: metadata, token: z.string() }).parse(
56
56
  await apiJson(ctx, "POST", path(args.app), {
57
57
  name: flags.name,
@@ -68,10 +68,12 @@ export const appCredentialCommands = [
68
68
  flags: { yes: confirmFlag("Revoke this app credential") },
69
69
  async run({ ctx, args, flags }) {
70
70
  if (!flags.yes)
71
- throw new Error(cliText(ctx, { en: "Revoking an app credential requires --yes.", de: "App-Zugang widerrufen erfordert --yes." }));
71
+ throw new Error(
72
+ cliText(ctx, { en: "Revoking an app credential requires --yes.", de: "App-Schlüssel widerrufen erfordert --yes." }),
73
+ );
72
74
  const credentialId = z.uuid().parse(args.credential);
73
75
  const result = await apiJson(ctx, "DELETE", `${path(args.app)}/${credentialId}`);
74
- if (!printStructured(ctx, result)) ctx.print(cliText(ctx, { en: "Credential revoked.", de: "Zugang widerrufen." }));
76
+ if (!printStructured(ctx, result)) ctx.print(cliText(ctx, { en: "Credential revoked.", de: "Schlüssel widerrufen." }));
75
77
  },
76
78
  }),
77
79
  ];
@@ -9,6 +9,7 @@ import { defineCliCommands } from "../commands";
9
9
  import { accountAdministrationCommands } from "./account-administration";
10
10
  import { accountCategoryCommands } from "./account-categories";
11
11
  import { agentCommands } from "./agents";
12
+ import { aiModelCommands } from "./ai-models";
12
13
  import { aiQuotaCommands } from "./ai-quotas";
13
14
  import { aiSkillCommands } from "./ai-skills";
14
15
  import { aiUsageCommands } from "./ai-usage";
@@ -39,14 +40,16 @@ export default defineCliCommands({
39
40
  accounts: "Manage account policy and account administration.",
40
41
  ai: "Manage AI models, quotas, Skills, and usage.",
41
42
  "ai models": "Manage AI model configuration.",
42
- "ai models pricing": "Manage model reference prices.",
43
- "ai models settings": "Manage model thinking levels, extra parameters, and extra headers.",
43
+ "ai providers": "Manage AI provider connections.",
44
+ "ai variants": "Manage named model variants.",
45
+ "ai classes": "Manage model classes and tools.",
44
46
  "ai quotas background": "Inspect and release the background emergency stop.",
45
47
  "ai skills": "Inspect Assistant Skills and trusted templates.",
46
48
  "ai usage": "Inspect AI runs and usage facets.",
47
49
  announcements: "Manage platform announcements and banners.",
48
50
  "app-credentials": "Manage per-application credentials for background work.",
49
- agents: "Create, list, revoke, and re-key agent accounts with OAuth client credentials.",
51
+ agents: "Create, list, revoke, and re-key agent accounts with OAuth client credentials, and change their pictures.",
52
+ "agents avatar": "Show and change an agent's picture: its robot parts or a photo.",
50
53
  "app-sign-in": "Manage app sign-in and pairing.",
51
54
  apps: "Inspect and remove registered apps.",
52
55
  documentation: "Manage the administration documentation website.",
@@ -93,6 +96,7 @@ export default defineCliCommands({
93
96
  ...accountCategoryCommands,
94
97
  ...accountAdministrationCommands,
95
98
  ...aiUsageCommands,
99
+ ...aiModelCommands,
96
100
  ...aiQuotaCommands,
97
101
  ...aiSkillCommands,
98
102
  ...instanceCommands,
@@ -0,0 +1,60 @@
1
+ import type { AccessEntry } from "../contracts/shared";
2
+ import type { AppAccessChange, AppAccessSnapshot } from "../services/app-access-management";
3
+ import { createAccessCommands } from "./access";
4
+ import { flag } from "./commands";
5
+ import type { CloudCliContext } from "./index";
6
+
7
+ /** Add to an app's CLI module; the same audited access API serves the editor and these commands. */
8
+ export const createAppAccessCommands = (appId: string) => {
9
+ const path = `/_cloud/app-access/${encodeURIComponent(appId)}`;
10
+ const change = async (ctx: CloudCliContext, input: AppAccessChange) =>
11
+ ctx.readJson<{ entry?: AccessEntry; canUse: boolean }>(
12
+ await ctx.fetch(path, {
13
+ method: "POST",
14
+ headers: { "content-type": "application/json" },
15
+ body: JSON.stringify(input),
16
+ }),
17
+ );
18
+ const commands = createAccessCommands({
19
+ resourceLabel: "app",
20
+ allowedPermissions: ["read", "admin"],
21
+ allowAuthenticated: true,
22
+ allowPublic: false,
23
+ allowServiceAccounts: true,
24
+ resolveResource: async (_ctx, args) => {
25
+ if (args.length) throw new Error("App access takes no resource argument.");
26
+ return { id: appId, label: appId };
27
+ },
28
+ list: async (ctx) => (await ctx.readJson<AppAccessSnapshot>(await ctx.fetch(path))).entries,
29
+ grant: async (ctx, _resource, principal, permission) => {
30
+ if (permission !== "read" && permission !== "admin") throw new Error("App permissions are use or manage.");
31
+ const result = await change(ctx, { action: "grant", principal, permission });
32
+ if (!result.entry) throw new Error("The access change returned no entry. Read the list before retrying.");
33
+ return result.entry;
34
+ },
35
+ update: async (ctx, _resource, accessId, permission) => {
36
+ if (permission !== "read" && permission !== "admin") throw new Error("App permissions are use or manage.");
37
+ await change(ctx, { action: "update", accessId, permission });
38
+ },
39
+ revoke: async (ctx, _resource, accessId) => {
40
+ await change(ctx, { action: "revoke", accessId });
41
+ },
42
+ });
43
+ // Product vocabulary at the CLI boundary; stored permission levels remain read/admin.
44
+ return commands.map((definition) =>
45
+ !definition.flags?.permission
46
+ ? definition
47
+ : {
48
+ ...definition,
49
+ flags: {
50
+ ...definition.flags,
51
+ permission: flag.enum(["use", "manage"], {
52
+ required: true,
53
+ description: "use: open the app; manage: use it and manage access",
54
+ }),
55
+ },
56
+ run: (input: Parameters<typeof definition.run>[0]) =>
57
+ definition.run({ ...input, flags: { ...input.flags, permission: input.flags.permission === "use" ? "read" : "admin" } }),
58
+ },
59
+ );
60
+ };