@cosmicdrift/kumiko-bundled-features 0.256.0 → 0.258.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 (59) hide show
  1. package/package.json +9 -10
  2. package/src/auth-mfa/__tests__/enable-flow.integration.test.ts +78 -6
  3. package/src/auth-mfa/__tests__/screen.test.ts +58 -0
  4. package/src/auth-mfa/feature.ts +6 -16
  5. package/src/auth-mfa/handlers/enable-start.write.ts +21 -12
  6. package/src/auth-mfa/i18n.ts +19 -0
  7. package/src/auth-mfa/screens.ts +52 -0
  8. package/src/auth-mfa/web/client-plugin.ts +4 -5
  9. package/src/auth-mfa/web/i18n.ts +0 -7
  10. package/src/auth-mfa/web/index.ts +0 -2
  11. package/src/auth-mfa/web/mfa-disable-dialog.tsx +2 -2
  12. package/src/auth-mfa/web/mfa-recovery-codes-reveal.tsx +4 -4
  13. package/src/auth-mfa/web/mfa-setup-preauth-screen.tsx +5 -5
  14. package/src/auth-mfa/web/qrcode-browser.d.ts +2 -2
  15. package/src/folders/__tests__/folders-read-gate.integration.test.ts +179 -0
  16. package/src/folders/entity.ts +47 -28
  17. package/src/folders/feature.ts +58 -4
  18. package/src/folders/handlers/clear-folder.write.ts +4 -4
  19. package/src/folders/handlers/set-folder.write.ts +4 -4
  20. package/src/ledger/__tests__/ledger.integration.test.ts +111 -4
  21. package/src/ledger/entity.ts +30 -0
  22. package/src/ledger/handlers/confirm-schedule-period.write.ts +7 -0
  23. package/src/ledger/handlers/create-transaction.write.ts +2 -0
  24. package/src/ledger/schemas.ts +3 -0
  25. package/src/notes-history/__tests__/add-note-author-name-kms.integration.test.ts +2 -1
  26. package/src/notes-history/__tests__/notes-history-ownership.integration.test.ts +4 -1
  27. package/src/notes-history/__tests__/notes-history-parent-visibility.integration.test.ts +3 -1
  28. package/src/notes-history/__tests__/notes-history.integration.test.ts +2 -1
  29. package/src/notes-history/__tests__/notes-read-gate.integration.test.ts +243 -0
  30. package/src/notes-history/entity.ts +11 -1
  31. package/src/notes-history/feature.ts +12 -11
  32. package/src/notes-history/handlers/add-note.write.ts +8 -12
  33. package/src/personal-access-tokens/__tests__/expiry.test.ts +19 -0
  34. package/src/personal-access-tokens/__tests__/feature.test.ts +93 -0
  35. package/src/personal-access-tokens/__tests__/pat.integration.test.ts +143 -13
  36. package/src/personal-access-tokens/constants.ts +5 -2
  37. package/src/personal-access-tokens/expiry.ts +7 -0
  38. package/src/personal-access-tokens/feature.ts +8 -17
  39. package/src/personal-access-tokens/handlers/list.query.ts +75 -16
  40. package/src/personal-access-tokens/i18n.ts +54 -0
  41. package/src/personal-access-tokens/resolver.ts +2 -7
  42. package/src/personal-access-tokens/screens.ts +107 -0
  43. package/src/shared/index.ts +1 -1
  44. package/src/shared/parent-visibility.ts +30 -0
  45. package/src/tags/__tests__/tags-read-gate.integration.test.ts +366 -0
  46. package/src/tags/__tests__/tags.integration.test.ts +1 -0
  47. package/src/tags/entity.ts +11 -1
  48. package/src/tags/feature.ts +25 -8
  49. package/src/tags/handlers/assign-tag.write.ts +2 -8
  50. package/src/tags/handlers/remove-tag.write.ts +2 -8
  51. package/src/tags/web/__tests__/entity-tags.test.tsx +13 -4
  52. package/src/tags/web/entity-tags.tsx +9 -2
  53. package/src/tags/web/tag-section.tsx +9 -4
  54. package/src/auth-mfa/web/__tests__/mfa-enable-screen.test.tsx +0 -182
  55. package/src/auth-mfa/web/mfa-enable-screen.tsx +0 -224
  56. package/src/personal-access-tokens/web/client-plugin.tsx +0 -29
  57. package/src/personal-access-tokens/web/i18n.ts +0 -40
  58. package/src/personal-access-tokens/web/index.ts +0 -7
  59. package/src/personal-access-tokens/web/pat-tokens-screen.tsx +0 -296
@@ -1,182 +0,0 @@
1
- import { afterAll, beforeEach, describe, expect, mock, test } from "bun:test";
2
- import {
3
- createStaticLocaleResolver,
4
- LocaleProvider,
5
- PrimitivesProvider,
6
- } from "@cosmicdrift/kumiko-renderer";
7
- import { defaultPrimitives } from "@cosmicdrift/kumiko-renderer-web";
8
- import { fireEvent, render, screen, waitFor } from "@testing-library/react";
9
- import type { ReactNode } from "react";
10
- import { makeSessionApi } from "../../../auth-email-password/web/__tests__/test-utils";
11
- import { SessionContext } from "../../../auth-email-password/web/session";
12
- import { defaultTranslations } from "../i18n";
13
-
14
- type WriteResult =
15
- | { readonly isSuccess: true; readonly data: unknown }
16
- | { readonly isSuccess: false; readonly error: { readonly i18nKey: string } };
17
-
18
- const dispatchSpy = mock<() => Promise<WriteResult>>(async () => ({
19
- isSuccess: true,
20
- data: {
21
- setupToken: "setup-token-1",
22
- otpauthUri: "otpauth://totp/App:user@example.com?secret=ABCD1234&issuer=App",
23
- recoveryCodes: ["code-1", "code-2"],
24
- },
25
- }));
26
-
27
- const actual_renderer = await import("@cosmicdrift/kumiko-renderer");
28
- mock.module("@cosmicdrift/kumiko-renderer", () => ({
29
- ...actual_renderer,
30
- useDispatcher: mock(() => ({ write: dispatchSpy, query: mock(), batch: mock() })),
31
- }));
32
-
33
- mock.module("qrcode/lib/browser", () => ({
34
- default: { toString: mock(async () => "<svg></svg>") },
35
- }));
36
-
37
- // bun's mock.module is process-global and outlives this file — without
38
- // restoring it, any later suite in the same test run that needs the real
39
- // useDispatcher from @cosmicdrift/kumiko-renderer silently gets this mock
40
- // instead (green-by-accident or a confusing foreign-suite failure,
41
- // depending on test order).
42
- afterAll(() => {
43
- mock.module("@cosmicdrift/kumiko-renderer", () => actual_renderer);
44
- });
45
-
46
- const { MfaEnableScreen } = await import("../mfa-enable-screen");
47
-
48
- const session = makeSessionApi();
49
-
50
- function Wrapper({ children }: { readonly children: ReactNode }): ReactNode {
51
- return (
52
- <PrimitivesProvider value={defaultPrimitives}>
53
- <LocaleProvider
54
- resolver={createStaticLocaleResolver({ locale: "en" })}
55
- fallbackBundles={[defaultTranslations]}
56
- >
57
- <SessionContext.Provider value={session}>{children}</SessionContext.Provider>
58
- </LocaleProvider>
59
- </PrimitivesProvider>
60
- );
61
- }
62
-
63
- beforeEach(() => {
64
- dispatchSpy.mockClear();
65
- });
66
-
67
- describe("MfaEnableScreen", () => {
68
- test("rendert Intro + Start-Button, kein Setup-Bereich vor dem Start", () => {
69
- render(
70
- <Wrapper>
71
- <MfaEnableScreen />
72
- </Wrapper>,
73
- );
74
- expect(screen.getByText("Two-factor authentication")).toBeTruthy();
75
- expect(screen.getByRole("button", { name: "Start setup" })).toBeTruthy();
76
- expect(screen.queryByLabelText("Code aus der Authenticator-App")).toBeNull();
77
- });
78
-
79
- test("Start-Klick → dispatcher.write ruft enable-start, danach Setup-Bereich mit Secret + Recovery-Codes", async () => {
80
- render(
81
- <Wrapper>
82
- <MfaEnableScreen />
83
- </Wrapper>,
84
- );
85
-
86
- fireEvent.click(screen.getByRole("button", { name: "Start setup" }));
87
-
88
- await waitFor(() => {
89
- expect(dispatchSpy).toHaveBeenCalledWith(
90
- "auth-mfa:write:enable-start",
91
- expect.objectContaining({ accountLabel: "user@example.com" }),
92
- );
93
- });
94
- await waitFor(() => {
95
- expect(screen.getByText("ABCD1234")).toBeTruthy();
96
- });
97
- expect(screen.getByText(/code-1/)).toBeTruthy();
98
- expect(screen.getByText(/code-2/)).toBeTruthy();
99
- });
100
-
101
- test("Bestätigen-Button bleibt disabled bis Recovery-Codes bestätigt + 6-stelliger Code eingegeben", async () => {
102
- render(
103
- <Wrapper>
104
- <MfaEnableScreen />
105
- </Wrapper>,
106
- );
107
- fireEvent.click(screen.getByRole("button", { name: "Start setup" }));
108
- await waitFor(() => {
109
- expect(screen.getByText("ABCD1234")).toBeTruthy();
110
- });
111
-
112
- const confirm = screen.getByRole("button", { name: "Enable" }) as HTMLButtonElement;
113
- expect(confirm.disabled).toBe(true);
114
-
115
- fireEvent.click(screen.getByLabelText("I've saved my recovery codes."));
116
- expect(confirm.disabled).toBe(true);
117
-
118
- fireEvent.change(screen.getByLabelText(/^Code from your authenticator app/), {
119
- target: { value: "123456" },
120
- });
121
- expect(confirm.disabled).toBe(false);
122
- });
123
-
124
- test("Bestätigen → dispatcher.write ruft enable-confirm, danach Success-Banner", async () => {
125
- dispatchSpy
126
- .mockImplementationOnce(async () => ({
127
- isSuccess: true,
128
- data: {
129
- setupToken: "setup-token-1",
130
- otpauthUri: "otpauth://totp/App:user@example.com?secret=ABCD1234&issuer=App",
131
- recoveryCodes: ["code-1", "code-2"],
132
- },
133
- }))
134
- .mockImplementationOnce(async () => ({ isSuccess: true, data: undefined }));
135
- const onEnabled = mock(() => {});
136
- render(
137
- <Wrapper>
138
- <MfaEnableScreen onEnabled={onEnabled} />
139
- </Wrapper>,
140
- );
141
- fireEvent.click(screen.getByRole("button", { name: "Start setup" }));
142
- await waitFor(() => {
143
- expect(screen.getByText("ABCD1234")).toBeTruthy();
144
- });
145
- fireEvent.click(screen.getByLabelText("I've saved my recovery codes."));
146
- fireEvent.change(screen.getByLabelText(/^Code from your authenticator app/), {
147
- target: { value: "123456" },
148
- });
149
-
150
- fireEvent.click(screen.getByRole("button", { name: "Enable" }));
151
-
152
- await waitFor(() => {
153
- expect(dispatchSpy).toHaveBeenCalledWith(
154
- "auth-mfa:write:enable-confirm",
155
- expect.objectContaining({ setupToken: "setup-token-1", code: "123456" }),
156
- );
157
- });
158
- await waitFor(() => {
159
- expect(screen.getByText("Two-factor authentication is now enabled.")).toBeTruthy();
160
- expect(onEnabled).toHaveBeenCalled();
161
- });
162
- });
163
-
164
- test("enable-start schlägt fehl → übersetztes Fehler-Banner, kein Setup-Bereich", async () => {
165
- dispatchSpy.mockImplementationOnce(async () => ({
166
- isSuccess: false,
167
- error: { i18nKey: "auth.mfa.errors.mfaAlreadyEnabled" },
168
- }));
169
- render(
170
- <Wrapper>
171
- <MfaEnableScreen />
172
- </Wrapper>,
173
- );
174
-
175
- fireEvent.click(screen.getByRole("button", { name: "Start setup" }));
176
-
177
- await waitFor(() => {
178
- expect(screen.getByText("Two-factor authentication is already enabled.")).toBeTruthy();
179
- });
180
- expect(screen.queryByText("ABCD1234")).toBeNull();
181
- });
182
- });
@@ -1,224 +0,0 @@
1
- // @runtime client
2
- // MfaEnableScreen — logged-in self-service TOTP-Enrollment. Kein Dialog:
3
- // DefaultDialog schließt nach JEDEM onConfirm (siehe renderer-web/src/
4
- // primitives/dialog.tsx — onOpenChange(false) im finally-Block), das passt
5
- // nicht zu einem Mehrschritt-Flow (Secret zeigen → Recovery-Codes zeigen →
6
- // Code bestätigen). Folgt stattdessen pat-tokens-screen.tsx's embedded-
7
- // Screen-Konvention: Feature registriert das dormant via r.screen, App
8
- // platziert es via r.nav.
9
-
10
- import { useDispatcher, usePrimitives, useTranslation } from "@cosmicdrift/kumiko-renderer";
11
- import { FormScreenShell } from "@cosmicdrift/kumiko-renderer-web";
12
- // qrcode's package.json#browser remap avoids Node-only deps (yargs/pngjs)
13
- // for bundlers that honor it — Metro doesn't, hence the explicit subpath.
14
- // @types/qrcode only covers the main "qrcode" entry, not this subpath, and
15
- // TypeScript can't auto-discover an ambient .d.ts sibling from inside a
16
- // node_modules package (consuming apps typecheck this raw .tsx source).
17
- // Apps mounting MfaEnableScreen need their own local ambient shim — see
18
- // qrcode-browser.d.ts in this directory for the declaration to copy.
19
- import QRCode from "qrcode/lib/browser";
20
- import { type ReactNode, useState } from "react";
21
- // kumiko-lint-ignore cross-feature-import client-only hook, the feature's server barrel has no web/ re-export
22
- import { useSession } from "../../auth-email-password/web";
23
- import { AuthMfaHandlers } from "../constants";
24
-
25
- type EnableStartResponse = {
26
- readonly setupToken: string;
27
- readonly otpauthUri: string;
28
- readonly recoveryCodes: readonly string[];
29
- };
30
-
31
- type SetupState = {
32
- readonly setupToken: string;
33
- readonly secretParam: string;
34
- readonly recoveryCodes: readonly string[];
35
- readonly qrSvg: string;
36
- };
37
-
38
- // otpauth:// URIs put the secret in the query string — extract it for the
39
- // manual-entry fallback (authenticator apps without a camera / desktop use).
40
- function extractSecret(otpauthUri: string): string {
41
- const query = otpauthUri.split("?")[1] ?? "";
42
- return new URLSearchParams(query).get("secret") ?? "";
43
- }
44
-
45
- export type MfaEnableScreenProps = {
46
- readonly embedded?: boolean;
47
- // Fired once enable-confirm succeeds — MfaEnableScreen has no query of its
48
- // own to invalidate, so a host screen composing it alongside other MFA
49
- // state (e.g. a status query gating which section renders) needs this to
50
- // know when to refetch/swap views instead of leaving the success banner
51
- // as a dead end.
52
- readonly onEnabled?: () => void;
53
- };
54
-
55
- export function MfaEnableScreen({
56
- embedded = false,
57
- onEnabled,
58
- }: MfaEnableScreenProps = {}): ReactNode {
59
- const t = useTranslation();
60
- const { Button, Banner, Field, Input, Section, Heading } = usePrimitives();
61
- const dispatcher = useDispatcher();
62
- const session = useSession();
63
-
64
- const [setup, setSetup] = useState<SetupState | null>(null);
65
- const [acknowledged, setAcknowledged] = useState(false);
66
- const [code, setCode] = useState("");
67
- const [busy, setBusy] = useState(false);
68
- const [error, setError] = useState<string | null>(null);
69
- const [enabled, setEnabled] = useState(false);
70
-
71
- const startSetup = async (): Promise<void> => {
72
- setBusy(true);
73
- setError(null);
74
- try {
75
- const res = await dispatcher.write<EnableStartResponse>(AuthMfaHandlers.enableStart, {
76
- accountLabel: session.user?.email ?? "",
77
- });
78
- if (!res.isSuccess) {
79
- setError(res.error.i18nKey);
80
- return;
81
- }
82
- // errorCorrectionLevel "H" (~30% redundancy) — more resilient to
83
- // camera/lighting issues than the default, no downside for a code
84
- // this short-lived.
85
- const qrSvg = await QRCode.toString(res.data.otpauthUri, {
86
- type: "svg",
87
- errorCorrectionLevel: "H",
88
- });
89
- setSetup({
90
- setupToken: res.data.setupToken,
91
- secretParam: extractSecret(res.data.otpauthUri),
92
- recoveryCodes: res.data.recoveryCodes,
93
- qrSvg,
94
- });
95
- setAcknowledged(false);
96
- setCode("");
97
- } catch {
98
- setError("auth.mfa.errors.setupFailed");
99
- } finally {
100
- setBusy(false);
101
- }
102
- };
103
-
104
- const confirmSetup = async (): Promise<void> => {
105
- if (!setup) return;
106
- setBusy(true);
107
- setError(null);
108
- try {
109
- const res = await dispatcher.write(AuthMfaHandlers.enableConfirm, {
110
- setupToken: setup.setupToken,
111
- code,
112
- });
113
- if (!res.isSuccess) {
114
- setError(res.error.i18nKey);
115
- return;
116
- }
117
- setEnabled(true);
118
- setSetup(null);
119
- onEnabled?.();
120
- } catch {
121
- setError("auth.mfa.errors.setupFailed");
122
- } finally {
123
- setBusy(false);
124
- }
125
- };
126
-
127
- const content = (
128
- <div className="flex flex-col gap-6">
129
- <Heading>{t("auth.mfa.enable.title")}</Heading>
130
-
131
- {enabled && <Banner variant="info">{t("auth.mfa.enable.success")}</Banner>}
132
- {error !== null && <Banner variant="error">{t(error)}</Banner>}
133
-
134
- {!setup && !enabled && (
135
- <Section
136
- testId="mfa-enable-intro"
137
- actions={
138
- <Button
139
- variant="primary"
140
- onClick={() => void startSetup()}
141
- loading={busy}
142
- disabled={busy}
143
- >
144
- {t("auth.mfa.enable.start")}
145
- </Button>
146
- }
147
- >
148
- <span className="text-sm text-muted-foreground">{t("auth.mfa.enable.intro")}</span>
149
- </Section>
150
- )}
151
-
152
- {setup && (
153
- <Section
154
- testId="mfa-enable-setup"
155
- actions={
156
- <>
157
- <Button variant="secondary" onClick={() => setSetup(null)} disabled={busy}>
158
- {t("auth.mfa.enable.cancel")}
159
- </Button>
160
- <Button
161
- variant="primary"
162
- onClick={() => void confirmSetup()}
163
- loading={busy}
164
- disabled={busy || !acknowledged || code.length !== 6}
165
- >
166
- {t("auth.mfa.enable.confirm")}
167
- </Button>
168
- </>
169
- }
170
- >
171
- <div className="flex flex-col items-center gap-2 text-center">
172
- <span className="text-sm font-semibold">{t("auth.mfa.enable.scanTitle")}</span>
173
- {/* qrcode's own SVG string output, not user input — safe to inline */}
174
- <div
175
- className="h-40 w-40"
176
- // biome-ignore lint/security/noDangerouslySetInnerHtml: qrcode-generated SVG, no user input
177
- dangerouslySetInnerHTML={{ __html: setup.qrSvg }}
178
- />
179
- <span className="text-xs text-muted-foreground">
180
- {t("auth.mfa.enable.manualEntry")}
181
- </span>
182
- <code className="inline-block break-all rounded bg-muted px-3 py-2 font-mono text-sm">
183
- {setup.secretParam}
184
- </code>
185
- </div>
186
-
187
- <div className="flex flex-col gap-2">
188
- <span className="text-sm font-semibold">{t("auth.mfa.enable.recoveryTitle")}</span>
189
- <span className="text-xs text-muted-foreground">
190
- {t("auth.mfa.enable.recoveryHint")}
191
- </span>
192
- <code className="block whitespace-pre-wrap break-all rounded bg-muted px-3 py-2 font-mono text-sm">
193
- {setup.recoveryCodes.join("\n")}
194
- </code>
195
- <Field id="mfa-enable-ack" label={t("auth.mfa.enable.acknowledge")}>
196
- <Input
197
- kind="boolean"
198
- id="mfa-enable-ack"
199
- name="mfa-enable-ack"
200
- value={acknowledged}
201
- onChange={setAcknowledged}
202
- />
203
- </Field>
204
- </div>
205
-
206
- <Field id="mfa-enable-code" label={t("auth.mfa.enable.code")} required>
207
- <Input
208
- kind="text"
209
- id="mfa-enable-code"
210
- name="mfa-enable-code"
211
- value={code}
212
- onChange={setCode}
213
- disabled={busy || !acknowledged}
214
- autoComplete="one-time-code"
215
- />
216
- </Field>
217
- </Section>
218
- )}
219
- </div>
220
- );
221
-
222
- if (embedded) return content;
223
- return <FormScreenShell testId="mfa-enable-screen">{content}</FormScreenShell>;
224
- }
@@ -1,29 +0,0 @@
1
- // @runtime client
2
- // Client-feature factory for personal-access-tokens. Ships the PatTokensScreen
3
- // (mapped to the "api-tokens" screen id) + default translations. Apps mount it
4
- // in createKumikoApp({ clientFeatures: [personalAccessTokensClient()] }); the
5
- // server registers the screen dormant (r.screen) and the app places it via
6
- // r.nav.
7
-
8
- import { mergeTranslations, type TranslationsByLocale } from "@cosmicdrift/kumiko-renderer";
9
- import type { ClientFeatureDefinition } from "@cosmicdrift/kumiko-renderer-web";
10
- import { PAT_FEATURE, PAT_SCREEN_ID } from "../constants";
11
- import { defaultTranslations } from "./i18n";
12
- import { PatTokensScreen } from "./pat-tokens-screen";
13
-
14
- export type PersonalAccessTokensClientOptions = {
15
- /** Key-wise overrides over the default bundles (de/en). */
16
- readonly translations?: TranslationsByLocale;
17
- };
18
-
19
- export function personalAccessTokensClient(
20
- options?: PersonalAccessTokensClientOptions,
21
- ): ClientFeatureDefinition {
22
- return {
23
- name: PAT_FEATURE,
24
- translations: mergeTranslations(defaultTranslations, options?.translations ?? {}),
25
- components: {
26
- [PAT_SCREEN_ID]: PatTokensScreen,
27
- },
28
- };
29
- }
@@ -1,40 +0,0 @@
1
- // @runtime client
2
- import type { TranslationsByLocale } from "@cosmicdrift/kumiko-renderer";
3
-
4
- export const defaultTranslations: TranslationsByLocale = {
5
- en: {
6
- "pat.title": "Personal Access Tokens",
7
- "pat.create.title": "Create a new token",
8
- "pat.create.subtitle": "Pick permissions and an expiration. The token is shown only once.",
9
- "pat.create.name": "Name",
10
- "pat.create.namePlaceholder": "e.g. CLI automation",
11
- "pat.create.scopes": "Per-API access",
12
- "pat.level.none": "No access",
13
- "pat.level.read": "Read",
14
- "pat.level.write": "Read & write",
15
- "pat.create.expiry": "Expiration",
16
- "pat.create.submit": "Create token",
17
- "pat.create.needName": "Please enter a name.",
18
- "pat.create.needScope": "Please select at least one permission.",
19
- "pat.create.needPassword": "Please enter your password to confirm.",
20
- "pat.create.currentPassword": "Password (to confirm)",
21
- "pat.create.mfaCode": "2FA code (if enabled)",
22
- "pat.create.mfaCodePlaceholder": "6-digit code",
23
- "pat.expiry.30d": "30 days",
24
- "pat.expiry.90d": "90 days",
25
- "pat.expiry.1y": "1 year",
26
- "pat.expiry.never": "No expiration",
27
- "pat.created.hint": "Copy this token now — for security it is shown only this once.",
28
- "pat.created.copy": "Copy",
29
- "pat.created.copied": "Copied ✓",
30
- "pat.created.dismiss": "Done",
31
- "pat.list.title": "Your tokens",
32
- "pat.list.empty": "No tokens yet.",
33
- "pat.list.revoke": "Revoke",
34
- "pat.list.revoked": "Revoked",
35
- "pat.list.validUntil": "Valid until {date}",
36
- "pat.list.neverExpires": "Never expires",
37
- "pat.list.created": "Created {date}",
38
- "pat.error.generic": "Action failed.",
39
- },
40
- };
@@ -1,7 +0,0 @@
1
- // @runtime client
2
- export {
3
- type PersonalAccessTokensClientOptions,
4
- personalAccessTokensClient,
5
- } from "./client-plugin";
6
- export { defaultTranslations } from "./i18n";
7
- export { PatTokensScreen } from "./pat-tokens-screen";