@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.
- package/README.md +2 -2
- package/package.json +30 -17
- package/scripts/browser-performance.ts +14 -0
- package/scripts/build-pdf-renderer.ts +39 -0
- package/scripts/build.ts +15 -35
- package/scripts/preload.ts +9 -12
- package/scripts/sync-recovery-smoke.ts +2 -1
- package/src/_internal/app-presentation.ts +14 -2
- package/src/_internal/capabilities.ts +108 -7
- package/src/_internal/capability-streams.ts +81 -0
- package/src/_internal/define-app.ts +62 -36
- package/src/_internal/heartbeat.ts +4 -0
- package/src/_internal/help-catalog.ts +27 -172
- package/src/_internal/help.ts +13 -76
- package/src/_internal/page-responses.ts +2 -0
- package/src/_internal/postgres-application-name.ts +30 -0
- package/src/_internal/process-identity.ts +15 -0
- package/src/_internal/process-sync.ts +1 -1
- package/src/_internal/registry-validation.ts +29 -25
- package/src/_internal/registry.ts +2 -53
- package/src/_internal/runtime-context.ts +5 -16
- package/src/_internal/server-timing.ts +13 -0
- package/src/_internal/status-preserving-ssr.ts +18 -6
- package/src/_internal/web-vitals-asset.ts +10 -0
- package/src/access/PermissionEditor.tsx +14 -153
- package/src/access/PrincipalPicker.tsx +104 -0
- package/src/access/ui.ts +2 -0
- package/src/ai/admin.ts +14 -13
- package/src/ai/assistant-models.ts +12 -1
- package/src/ai/audio-format.ts +34 -0
- package/src/ai/audio-tool.ts +90 -0
- package/src/ai/browser-code-contracts.ts +79 -0
- package/src/ai/browser.ts +2 -0
- package/src/ai/capabilities.ts +55 -36
- package/src/ai/capability-execution.ts +47 -12
- package/src/ai/chat/blocks.tsx +157 -95
- package/src/ai/chat/capability-result.ts +15 -0
- package/src/ai/chat/capability-table.tsx +2 -10
- package/src/ai/chat/composer-adapter.ts +48 -3
- package/src/ai/chat/message-actions.tsx +8 -7
- package/src/ai/chat/message-utils.ts +12 -1
- package/src/ai/chat/messages.ts +2 -0
- package/src/ai/chat/presentation.tsx +19 -48
- package/src/ai/chat/primitives.tsx +43 -1
- package/src/ai/chat/tool-disclosure.tsx +17 -2
- package/src/ai/chat/tool-groups.ts +52 -0
- package/src/ai/chat-quotas.ts +25 -0
- package/src/ai/chat-task-contracts.ts +9 -1
- package/src/ai/chat-tasks.ts +276 -43
- package/src/ai/client/controller.ts +110 -33
- package/src/ai/client/file-source.ts +12 -6
- package/src/ai/client/live-connection.ts +1 -0
- package/src/ai/client/projection.ts +22 -11
- package/src/ai/client/transport.ts +4 -0
- package/src/ai/code-capability-routes.ts +110 -0
- package/src/ai/code-capability-transport.ts +38 -0
- package/src/ai/code-mode-skill.ts +114 -0
- package/src/ai/code-runtime-tools.ts +166 -0
- package/src/ai/code-source-contracts.ts +202 -0
- package/src/ai/code-source-tools.ts +145 -0
- package/src/ai/compaction.ts +27 -6
- package/src/ai/data-analysis-skill.ts +10 -0
- package/src/ai/default-tools.ts +25 -2
- package/src/ai/dictation-runtime.ts +171 -0
- package/src/ai/dictations.ts +188 -0
- package/src/ai/draft-content.ts +34 -0
- package/src/ai/enrich.ts +6 -0
- package/src/ai/executor.ts +191 -31
- package/src/ai/file-content-version.ts +22 -0
- package/src/ai/file-context.ts +2 -1
- package/src/ai/file-media-type.ts +12 -0
- package/src/ai/file-reference-contracts.ts +15 -0
- package/src/ai/file-tools.ts +2 -1
- package/src/ai/files-store.ts +174 -71
- package/src/ai/fixtures/vision-encrypted.pdf +0 -0
- package/src/ai/grids-skill.ts +40 -10
- package/src/ai/http.ts +15 -8
- package/src/ai/index.ts +26 -2
- package/src/ai/inference-calls.ts +198 -0
- package/src/ai/live-events.ts +5 -1
- package/src/ai/memory-learning.ts +60 -28
- package/src/ai/message-queue.ts +167 -0
- package/src/ai/migrate.ts +187 -11
- package/src/ai/model-pricing.ts +43 -0
- package/src/ai/pdf-render-worker.ts +74 -0
- package/src/ai/pdf-render.fixture.ts +28 -0
- package/src/ai/pdf-render.ts +77 -0
- package/src/ai/projects-routes.ts +3 -3
- package/src/ai/projects.ts +20 -6
- package/src/ai/protocol.ts +3 -0
- package/src/ai/provider.ts +1 -8
- package/src/ai/quota-provider.ts +191 -0
- package/src/ai/quota-report.ts +152 -0
- package/src/ai/quotas-migrate.ts +45 -0
- package/src/ai/quotas.ts +168 -0
- package/src/ai/routes.ts +215 -11
- package/src/ai/run-timeout.ts +10 -0
- package/src/ai/runtime-tools.ts +3 -0
- package/src/ai/runtime.ts +55 -16
- package/src/ai/settings.ts +36 -8
- package/src/ai/short-id.ts +2 -2
- package/src/ai/skill-catalog.ts +0 -10
- package/src/ai/skill-search.ts +47 -0
- package/src/ai/skill-seeds.ts +105 -20
- package/src/ai/skill-tool.ts +16 -5
- package/src/ai/skills-routes.ts +32 -4
- package/src/ai/skills.ts +324 -155
- package/src/ai/store.ts +373 -191
- package/src/ai/stream.ts +5 -0
- package/src/ai/structured-runs.ts +4 -3
- package/src/ai/structured.ts +8 -1
- package/src/ai/system-prompt.ts +12 -2
- package/src/ai/timeline.ts +2 -5
- package/src/ai/todo-contracts.ts +20 -0
- package/src/ai/todo-tool.ts +16 -0
- package/src/ai/tool-audit.ts +30 -40
- package/src/ai/tools.ts +6 -0
- package/src/ai/transcription.ts +202 -0
- package/src/ai/turn-timing.ts +114 -0
- package/src/ai/types.ts +58 -10
- package/src/ai/ui.tsx +2 -0
- package/src/ai/usage.ts +118 -118
- package/src/ai/vision-tool.ts +39 -10
- package/src/api/admin-ai-projects.ts +2 -2
- package/src/api/admin-ai-quotas.ts +74 -0
- package/src/api/admin-ai-skills.ts +48 -5
- package/src/api/admin-ai-usage.ts +2 -2
- package/src/api/admin-core-settings.ts +25 -0
- package/src/api/admin-identity.ts +91 -51
- package/src/api/admin-rail.ts +58 -0
- package/src/api/announcements.ts +14 -0
- package/src/api/capabilities.ts +292 -43
- package/src/api/capability-streams.ts +262 -0
- package/src/api/help.ts +8 -44
- package/src/api/index.ts +6 -0
- package/src/api/mcp.ts +44 -26
- package/src/api/me-web-vitals.ts +35 -0
- package/src/api/me.ts +2 -0
- package/src/api/search/schemas.ts +26 -15
- package/src/api/search.bench.ts +1 -1
- package/src/api/search.ts +54 -17
- package/src/browser/CloudResourceSearch.tsx +640 -702
- package/src/browser/app-approval-vault.ts +19 -7
- package/src/browser/command-bridge.ts +109 -0
- package/src/browser/command-shortcuts.ts +76 -0
- package/src/browser/commands.ts +66 -0
- package/src/browser/navigation-search.ts +48 -0
- package/src/browser/notifications.ts +1 -1
- package/src/browser/resource-picker.tsx +5 -18
- package/src/browser/resource-search-dialog.ts +9 -0
- package/src/browser/resource-search-input.ts +46 -0
- package/src/browser/resource-search-messages.ts +76 -40
- package/src/browser/resource-search.ts +7 -0
- package/src/browser/search-bridge.ts +102 -0
- package/src/browser/search-commands.ts +59 -0
- package/src/browser/search.ts +13 -0
- package/src/browser/spotlight-position.ts +208 -0
- package/src/browser/testing.ts +5 -0
- package/src/browser/web-vitals.ts +25 -0
- package/src/capabilities/claims.ts +179 -0
- package/src/capabilities/client.ts +34 -0
- package/src/capabilities/command-link.ts +16 -0
- package/src/capabilities/executions.ts +298 -0
- package/src/capabilities/migrate.ts +81 -0
- package/src/capabilities/server.ts +114 -34
- package/src/capabilities/stream-body.ts +16 -0
- package/src/capabilities/streams.ts +28 -0
- package/src/cli/admin/ai-quotas.ts +161 -0
- package/src/cli/admin/ai-skills.ts +64 -0
- package/src/cli/admin/ai-usage.ts +53 -30
- package/src/cli/admin/app-credentials.ts +79 -0
- package/src/cli/admin/data.ts +17 -0
- package/src/cli/admin/index.ts +9 -0
- package/src/cli/capabilities.ts +29 -0
- package/src/cli/capability-streams.ts +25 -0
- package/src/cli/index.ts +3 -2
- package/src/config/env.ts +11 -8
- package/src/contracts/app.ts +23 -3
- package/src/contracts/capabilities.ts +68 -5
- package/src/contracts/capability-streams.ts +46 -0
- package/src/contracts/commands.ts +49 -0
- package/src/contracts/index.ts +6 -1
- package/src/contracts/rail-admin.ts +38 -0
- package/src/contracts/rail-preferences.ts +16 -0
- package/src/contracts/registry.ts +3 -27
- package/src/contracts/settings-types.ts +1 -1
- package/src/contracts/shared.ts +9 -5
- package/src/contracts/web-vitals.ts +27 -0
- package/src/desktop/solid.tsx +3 -3
- package/src/index.ts +0 -4
- package/src/server/index.ts +3 -0
- package/src/server/middleware/auth.ts +3 -2
- package/src/server/middleware/route-template.ts +9 -3
- package/src/server/middleware/runtime.ts +2 -1
- package/src/server/middleware/settings.ts +17 -11
- package/src/server/services/access-revision.ts +10 -0
- package/src/server/services/access.ts +1 -1
- package/src/server/services/index.ts +1 -0
- package/src/services/account-category-policy.ts +9 -0
- package/src/services/account-lifecycle/audit.ts +1 -1
- package/src/services/accounts/app.ts +12 -2
- package/src/services/accounts/email-write.ts +23 -0
- package/src/services/accounts/groups.ts +15 -4
- package/src/services/accounts/identities.ts +249 -0
- package/src/services/accounts/identity-reconciliation.ts +87 -0
- package/src/services/accounts/ipa-data.ts +2 -2
- package/src/services/accounts/local-groups.ts +2 -2
- package/src/services/accounts/posix.ts +45 -29
- package/src/services/accounts/users.ts +2 -2
- package/src/services/announcements/index.ts +69 -8
- package/src/services/audit/index.ts +1 -1
- package/src/services/cache-fill.ts +35 -0
- package/src/services/help/index.ts +109 -0
- package/src/services/help/maintenance.ts +25 -0
- package/src/services/help/store.ts +180 -0
- package/src/services/help/types.ts +46 -0
- package/src/services/identity/invocation-actor.ts +2 -2
- package/src/services/identity/key-config.ts +2 -1
- package/src/services/identity/key-ring.ts +1 -1
- package/src/services/index.ts +15 -0
- package/src/services/ipa/users.ts +268 -221
- package/src/services/legal-consent.ts +1 -1
- package/src/services/logging/index.ts +15 -13
- package/src/services/logging/trace.ts +2 -1
- package/src/services/logging/web-vitals.ts +81 -0
- package/src/services/mandates/index.ts +18 -4
- package/src/services/mandates/policy.ts +65 -2
- package/src/services/notifications/batches.ts +124 -57
- package/src/services/notifications/browser.ts +12 -0
- package/src/services/notifications/index.ts +0 -2
- package/src/services/notifications/platform.ts +0 -4
- package/src/services/oauth-tokens.ts +3 -3
- package/src/services/pdf/gotenberg.ts +107 -84
- package/src/services/pdf/index.ts +3 -0
- package/src/services/providers/local/users.ts +25 -16
- package/src/services/public-http.ts +101 -0
- package/src/services/rail-shortcuts.ts +95 -0
- package/src/services/rail-snapshot.ts +53 -0
- package/src/services/request-cache-redis.ts +28 -0
- package/src/services/session/index.ts +15 -2
- package/src/services/session/user.ts +62 -37
- package/src/services/settings/core-settings.ts +21 -28
- package/src/services/settings/defaults.ts +5 -0
- package/src/services/settings/store.ts +58 -84
- package/src/shared/ai-costs.ts +42 -0
- package/src/shared/ai-platform-prompt.ts +1 -1
- package/src/shared/ai-quotas.ts +136 -0
- package/src/shared/ai-usage.ts +6 -16
- package/src/shared/app-presentation.ts +21 -15
- package/src/shared/capability-messages.ts +14 -0
- package/src/shared/help.ts +6 -42
- package/src/shared/index.ts +6 -0
- package/src/ssr/AdminLayout.tsx +7 -5
- package/src/ssr/AdminSidebar.tsx +29 -15
- package/src/ssr/AppLaunchpad.island.tsx +24 -137
- package/src/ssr/AppLaunchpadPanel.tsx +181 -0
- package/src/ssr/BrowserPushRegistration.island.tsx +12 -0
- package/src/ssr/GlobalSearchDialog.tsx +167 -20
- package/src/ssr/GlobalSearchTrigger.island.tsx +33 -22
- package/src/ssr/Layout.tsx +17 -6
- package/src/ssr/LayoutBreadcrumbs.island.tsx +3 -5
- package/src/ssr/LayoutFooter.tsx +1 -1
- package/src/ssr/LayoutHeader.tsx +46 -14
- package/src/ssr/LayoutHelp.tsx +23 -22
- package/src/ssr/{HotkeysHelpRail.island.tsx → LayoutHelpTrigger.island.tsx} +18 -12
- package/src/ssr/LayoutRail.tsx +17 -9
- package/src/ssr/MobileNavigation.tsx +71 -0
- package/src/ssr/MobileProfileActions.tsx +49 -0
- package/src/ssr/ProfilePreferences.island.tsx +3 -36
- package/src/ssr/RailApps.island.tsx +2 -2
- package/src/ssr/RailEditor.tsx +17 -2
- package/src/ssr/WorkspaceNavigation.island.tsx +7 -0
- package/src/ssr/WorkspaceNavigationProvider.tsx +16 -0
- package/src/ssr/admin-navigation.ts +7 -1
- package/src/ssr/help.ts +29 -0
- package/src/ssr/islands/SearchBar.tsx +67 -0
- package/src/ssr/islands/index.ts +3 -1
- package/src/ssr/layout-context.ts +2 -2
- package/src/ssr/layout-help-search.ts +15 -0
- package/src/ssr/mobile-menu-history.ts +70 -0
- package/src/ssr/platform-messages.ts +16 -8
- package/src/ssr/profile-actions.ts +42 -0
- package/src/ssr/rail-context.ts +8 -4
- package/src/ssr/rail-messages.ts +2 -0
- package/src/ssr/rail-navigation.ts +3 -3
- package/src/ssr/workspace-navigation.ts +75 -0
- package/src/styles/effects.css +6 -6
- package/src/styles/global.css +54 -0
- package/src/styles/input.css +43 -41
- package/src/styles/resource-search.css +505 -0
- package/src/styles/utilities-feedback.css +13 -0
- package/src/styles/utilities-layout.css +1 -5
- package/src/styles/utilities-navigation.css +3 -39
- package/src/types/ambient.d.ts +5 -0
- package/src/workflows/ai/runtime.ts +5 -1
- package/src/workflows/ai/store.ts +2 -0
- package/src/workflows/ai-actions.ts +16 -13
- package/src/workflows/definition.ts +10 -5
- package/src/workflows/store/actions.ts +44 -20
- package/src/workflows/store/index.ts +2 -0
- package/src/workflows/store/runs.ts +36 -0
- package/src/workflows/store/worker-pool.ts +55 -0
- package/src/workflows/store/worker-runtime.ts +90 -0
- package/scripts/app-favicon.test.ts +0 -49
- package/src/ai/chat/blocks.render.test.tsx +0 -1108
- package/src/ai/kit-skill.ts +0 -22
- package/src/ai/usage-migrate.ts +0 -81
- package/src/browser/CloudResourceSearch.behavior.test.tsx +0 -74
- package/src/browser/notification-target.ts +0 -27
- package/src/contracts/notification-live.ts +0 -66
- package/src/services/notifications/live.ts +0 -50
- package/src/ssr/BrowserNotifications.island.tsx +0 -135
- package/src/ssr/GlobalSearchDialog.behavior.test.tsx +0 -96
- package/src/ssr/Layout.render.test.tsx +0 -148
- 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
|
-
|
|
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"
|
|
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
|
|
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", "
|
|
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", "
|
|
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
|
-
[
|
|
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")
|
|
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(
|
|
58
|
-
{
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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(
|
|
77
|
-
{
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
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
|
+
];
|
package/src/cli/admin/data.ts
CHANGED
|
@@ -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
|
},
|
package/src/cli/admin/index.ts
CHANGED
|
@@ -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,
|
package/src/cli/capabilities.ts
CHANGED
|
@@ -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
|
|
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
|
|