primitive-admin 1.0.40 → 1.0.42

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 (36) hide show
  1. package/assets/skill/skills/primitive-platform/SKILL.md +1 -9
  2. package/dist/bin/primitive.js +132 -16
  3. package/dist/bin/primitive.js.map +1 -1
  4. package/dist/src/commands/admins.js +107 -0
  5. package/dist/src/commands/admins.js.map +1 -1
  6. package/dist/src/commands/blob-buckets.js +354 -0
  7. package/dist/src/commands/blob-buckets.js.map +1 -0
  8. package/dist/src/commands/collections.js +18 -4
  9. package/dist/src/commands/collections.js.map +1 -1
  10. package/dist/src/commands/cron-triggers.js +364 -0
  11. package/dist/src/commands/cron-triggers.js.map +1 -0
  12. package/dist/src/commands/email-templates.js +19 -5
  13. package/dist/src/commands/email-templates.js.map +1 -1
  14. package/dist/src/commands/env.js +260 -0
  15. package/dist/src/commands/env.js.map +1 -0
  16. package/dist/src/commands/init.js +90 -2
  17. package/dist/src/commands/init.js.map +1 -1
  18. package/dist/src/commands/sync.js +428 -7
  19. package/dist/src/commands/sync.js.map +1 -1
  20. package/dist/src/lib/api-client.js +137 -3
  21. package/dist/src/lib/api-client.js.map +1 -1
  22. package/dist/src/lib/config.js +51 -53
  23. package/dist/src/lib/config.js.map +1 -1
  24. package/dist/src/lib/credentials-store.js +307 -0
  25. package/dist/src/lib/credentials-store.js.map +1 -0
  26. package/dist/src/lib/env-resolver.js +121 -0
  27. package/dist/src/lib/env-resolver.js.map +1 -0
  28. package/dist/src/lib/paginate.js +42 -0
  29. package/dist/src/lib/paginate.js.map +1 -0
  30. package/dist/src/lib/project-config.js +209 -0
  31. package/dist/src/lib/project-config.js.map +1 -0
  32. package/dist/src/lib/sync-paths.js +102 -0
  33. package/dist/src/lib/sync-paths.js.map +1 -0
  34. package/dist/src/lib/version-check.js +5 -2
  35. package/dist/src/lib/version-check.js.map +1 -1
  36. package/package.json +1 -1
@@ -0,0 +1,307 @@
1
+ /**
2
+ * Credentials storage with dual-mode support.
3
+ *
4
+ * Mode A (project mode): credentials live at
5
+ * <projectRoot>/.primitive/credentials.json
6
+ * with per-environment slots:
7
+ * {
8
+ * "environments": {
9
+ * "dev": { "accessToken": "...", "refreshToken": "...", ... },
10
+ * "prod": { ... }
11
+ * }
12
+ * }
13
+ *
14
+ * Mode B (legacy mode): no .primitive/config.json in scope, so we fall back to
15
+ * ~/.primitive/credentials.json
16
+ * with the flat Credentials shape the CLI has always used.
17
+ *
18
+ * The legacy mode is still the "global default" for users who haven't
19
+ * run `primitive init` yet.
20
+ */
21
+ import { chmodSync, existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync, } from "fs";
22
+ import { homedir } from "os";
23
+ import { join } from "path";
24
+ import { getCurrentEnvironment, getProjectConfig } from "./env-resolver.js";
25
+ export const PROJECT_LOCAL_DIR = ".primitive";
26
+ export const PROJECT_CREDENTIALS_FILENAME = "credentials.json";
27
+ /**
28
+ * Resolves the legacy config dir on each call (rather than capturing it
29
+ * at module load) so tests and environment variables can override it
30
+ * after the module has already been imported.
31
+ */
32
+ function getLegacyConfigDir() {
33
+ return process.env.PRIMITIVE_CONFIG_DIR || join(homedir(), ".primitive");
34
+ }
35
+ function getLegacyCredentialsFile() {
36
+ return join(getLegacyConfigDir(), PROJECT_CREDENTIALS_FILENAME);
37
+ }
38
+ /** Returns true when a .primitive/config.json is in scope (project mode active). */
39
+ export function isProjectMode() {
40
+ return getProjectConfig() !== null;
41
+ }
42
+ /**
43
+ * Returns the path to the credentials file that will be used, based on
44
+ * whether we're in project mode or legacy mode.
45
+ */
46
+ export function getCredentialsFilePath() {
47
+ const env = resolveCurrentEnvironment();
48
+ if (env) {
49
+ return join(env.projectRoot, PROJECT_LOCAL_DIR, PROJECT_CREDENTIALS_FILENAME);
50
+ }
51
+ return getLegacyCredentialsFile();
52
+ }
53
+ /**
54
+ * Resolves the current environment. If an environment CAN'T be resolved
55
+ * (e.g. --env points at an unknown name), this throws — we never silently
56
+ * fall back to legacy credentials because a typo in the env name must not
57
+ * cause the CLI to operate against the wrong app/server.
58
+ *
59
+ * Returns null only in genuine legacy mode (no project config found at all).
60
+ */
61
+ function resolveCurrentEnvironment() {
62
+ return getCurrentEnvironment();
63
+ }
64
+ function ensureDir(dir) {
65
+ if (!existsSync(dir)) {
66
+ mkdirSync(dir, { recursive: true, mode: 0o700 });
67
+ }
68
+ }
69
+ function readJsonFile(path) {
70
+ try {
71
+ if (!existsSync(path))
72
+ return null;
73
+ const data = readFileSync(path, "utf-8");
74
+ return JSON.parse(data);
75
+ }
76
+ catch {
77
+ return null;
78
+ }
79
+ }
80
+ /**
81
+ * Loads a legacy (global) credentials file. Used for backwards compat.
82
+ */
83
+ function loadLegacyCredentials() {
84
+ return readJsonFile(getLegacyCredentialsFile());
85
+ }
86
+ /**
87
+ * Loads the project credentials file, if present. Never throws — returns
88
+ * an empty shape when the file is missing or unreadable.
89
+ */
90
+ function loadProjectCredentialsFile(projectRoot) {
91
+ const path = join(projectRoot, PROJECT_LOCAL_DIR, PROJECT_CREDENTIALS_FILENAME);
92
+ const data = readJsonFile(path);
93
+ if (!data || typeof data !== "object" || !data.environments) {
94
+ return { environments: {} };
95
+ }
96
+ return data;
97
+ }
98
+ function writeProjectCredentialsFile(projectRoot, file) {
99
+ const dir = join(projectRoot, PROJECT_LOCAL_DIR);
100
+ ensureDir(dir);
101
+ const path = join(dir, PROJECT_CREDENTIALS_FILENAME);
102
+ writeFileSync(path, JSON.stringify(file, null, 2), { mode: 0o600 });
103
+ try {
104
+ chmodSync(path, 0o600);
105
+ }
106
+ catch {
107
+ // Best-effort on Windows / weird filesystems
108
+ }
109
+ }
110
+ /**
111
+ * Reads credentials for the current environment (project mode) or the
112
+ * legacy global file. Returns the standard Credentials shape so existing
113
+ * callers don't need to change.
114
+ *
115
+ * In project mode, we blend the per-env stored creds with the serverUrl
116
+ * and appId from .primitive/config.json to produce the Credentials the rest of
117
+ * the CLI expects.
118
+ */
119
+ export function loadCredentialsStore() {
120
+ const env = resolveCurrentEnvironment();
121
+ if (!env) {
122
+ return loadLegacyCredentials();
123
+ }
124
+ const file = loadProjectCredentialsFile(env.projectRoot);
125
+ const stored = file.environments[env.name];
126
+ if (!stored)
127
+ return null;
128
+ return {
129
+ serverUrl: env.config.apiUrl,
130
+ accessToken: stored.accessToken,
131
+ refreshToken: stored.refreshToken,
132
+ expiresAt: stored.expiresAt,
133
+ adminId: stored.adminId,
134
+ email: stored.email,
135
+ role: stored.role,
136
+ name: stored.name,
137
+ // Prefer the project-level appId (canonical); fall back to the stored
138
+ // per-session current app for compatibility.
139
+ currentAppId: env.config.appId || stored.currentAppId,
140
+ currentAppName: env.config.appName || stored.currentAppName,
141
+ globalAdminAppId: stored.globalAdminAppId,
142
+ };
143
+ }
144
+ /**
145
+ * Saves credentials. In project mode, writes to the per-env slot in the
146
+ * project credentials file. In legacy mode, writes the global file.
147
+ *
148
+ * The serverUrl on the incoming Credentials is used as-is for legacy
149
+ * mode, and ignored in project mode (the env's apiUrl is canonical).
150
+ */
151
+ export function saveCredentialsStore(credentials) {
152
+ const env = resolveCurrentEnvironment();
153
+ if (!env) {
154
+ ensureDir(getLegacyConfigDir());
155
+ writeFileSync(getLegacyCredentialsFile(), JSON.stringify(credentials, null, 2), {
156
+ mode: 0o600,
157
+ });
158
+ try {
159
+ chmodSync(getLegacyCredentialsFile(), 0o600);
160
+ }
161
+ catch {
162
+ // Best-effort
163
+ }
164
+ return;
165
+ }
166
+ const file = loadProjectCredentialsFile(env.projectRoot);
167
+ file.environments[env.name] = {
168
+ accessToken: credentials.accessToken,
169
+ refreshToken: credentials.refreshToken,
170
+ expiresAt: credentials.expiresAt,
171
+ adminId: credentials.adminId,
172
+ email: credentials.email,
173
+ role: credentials.role,
174
+ name: credentials.name,
175
+ globalAdminAppId: credentials.globalAdminAppId,
176
+ // Only persist currentAppId/Name when they're NOT already declared
177
+ // in .primitive/config.json — otherwise the project config is the source of
178
+ // truth.
179
+ currentAppId: env.config.appId ? undefined : credentials.currentAppId,
180
+ currentAppName: env.config.appName ? undefined : credentials.currentAppName,
181
+ };
182
+ writeProjectCredentialsFile(env.projectRoot, file);
183
+ }
184
+ /**
185
+ * Clears credentials for the current environment (project mode) or
186
+ * deletes the legacy file (legacy mode).
187
+ */
188
+ export function clearCredentialsStore() {
189
+ const env = resolveCurrentEnvironment();
190
+ if (!env) {
191
+ try {
192
+ if (existsSync(getLegacyCredentialsFile())) {
193
+ unlinkSync(getLegacyCredentialsFile());
194
+ }
195
+ }
196
+ catch {
197
+ // Ignore
198
+ }
199
+ return;
200
+ }
201
+ const file = loadProjectCredentialsFile(env.projectRoot);
202
+ delete file.environments[env.name];
203
+ writeProjectCredentialsFile(env.projectRoot, file);
204
+ }
205
+ /**
206
+ * Removes the credentials slot for the named environment in a specific
207
+ * project's credentials file. Used by `env remove` so stale tokens from a
208
+ * previously-removed environment can never be silently re-used if the same
209
+ * name is later added back. Never throws.
210
+ */
211
+ export function clearCredentialsForEnvInProject(projectRoot, envName) {
212
+ try {
213
+ const file = loadProjectCredentialsFile(projectRoot);
214
+ if (file.environments[envName]) {
215
+ delete file.environments[envName];
216
+ writeProjectCredentialsFile(projectRoot, file);
217
+ }
218
+ }
219
+ catch {
220
+ // Best-effort — removal is not a hard requirement for env removal to succeed.
221
+ }
222
+ }
223
+ /**
224
+ * Lists all environments that have stored credentials in project mode.
225
+ * Returns an empty array in legacy mode.
226
+ */
227
+ export function listAuthenticatedEnvironments() {
228
+ const env = resolveCurrentEnvironment();
229
+ if (!env)
230
+ return [];
231
+ const file = loadProjectCredentialsFile(env.projectRoot);
232
+ return Object.keys(file.environments);
233
+ }
234
+ /**
235
+ * Updates the "current app" in credential storage. In project mode with a
236
+ * project-level appId this is a no-op (the project config is canonical).
237
+ */
238
+ export function setCurrentAppStore(appId, appName) {
239
+ const env = resolveCurrentEnvironment();
240
+ if (!env) {
241
+ // Legacy mode — mutate the global credentials file directly.
242
+ const creds = loadLegacyCredentials();
243
+ if (!creds) {
244
+ throw new Error("Not logged in. Run 'primitive login' first.");
245
+ }
246
+ creds.currentAppId = appId;
247
+ creds.currentAppName = appName;
248
+ ensureDir(getLegacyConfigDir());
249
+ writeFileSync(getLegacyCredentialsFile(), JSON.stringify(creds, null, 2), {
250
+ mode: 0o600,
251
+ });
252
+ return;
253
+ }
254
+ // Project mode: if the env already has an appId in .primitive/config.json, we
255
+ // don't overwrite it here (use `primitive env use` or edit .primitive/config.json
256
+ // to change the project binding). We *do* store a per-session override
257
+ // in credentials for environments without a declared appId.
258
+ if (env.config.appId) {
259
+ // No-op: env is bound to a specific appId in .primitive/config.json
260
+ return;
261
+ }
262
+ const file = loadProjectCredentialsFile(env.projectRoot);
263
+ const slot = file.environments[env.name];
264
+ if (!slot) {
265
+ throw new Error(`Not logged in to environment "${env.name}". Run 'primitive login --env ${env.name}' first.`);
266
+ }
267
+ slot.currentAppId = appId;
268
+ slot.currentAppName = appName;
269
+ writeProjectCredentialsFile(env.projectRoot, file);
270
+ }
271
+ /**
272
+ * Clears the "current app" for the current environment.
273
+ */
274
+ export function clearCurrentAppStore() {
275
+ const env = resolveCurrentEnvironment();
276
+ if (!env) {
277
+ const creds = loadLegacyCredentials();
278
+ if (!creds)
279
+ return;
280
+ delete creds.currentAppId;
281
+ delete creds.currentAppName;
282
+ ensureDir(getLegacyConfigDir());
283
+ writeFileSync(getLegacyCredentialsFile(), JSON.stringify(creds, null, 2), {
284
+ mode: 0o600,
285
+ });
286
+ return;
287
+ }
288
+ if (env.config.appId) {
289
+ // Can't clear a project-bound app; the user should edit .primitive/config.json.
290
+ return;
291
+ }
292
+ const file = loadProjectCredentialsFile(env.projectRoot);
293
+ const slot = file.environments[env.name];
294
+ if (!slot)
295
+ return;
296
+ delete slot.currentAppId;
297
+ delete slot.currentAppName;
298
+ writeProjectCredentialsFile(env.projectRoot, file);
299
+ }
300
+ /**
301
+ * Returns the legacy credentials file, if one exists. Used by migration
302
+ * prompts that want to detect a pre-project-config install.
303
+ */
304
+ export function peekLegacyCredentials() {
305
+ return loadLegacyCredentials();
306
+ }
307
+ //# sourceMappingURL=credentials-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"credentials-store.js","sourceRoot":"","sources":["../../../src/lib/credentials-store.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,121 @@
1
+ /**
2
+ * Environment resolution.
3
+ *
4
+ * Decides which named environment the CLI is currently targeting. The
5
+ * resolution order is:
6
+ *
7
+ * 1. Explicit --env <name> flag (set via setCurrentEnvName from bin/primitive.ts)
8
+ * 2. PRIMITIVE_ENV environment variable
9
+ * 3. The project config's defaultEnvironment
10
+ * 4. If the project has exactly one environment, that one
11
+ * 5. null (legacy mode — fall back to ~/.primitive/credentials.json)
12
+ *
13
+ * Commands read the resolved environment once via getCurrentEnvironment().
14
+ * The resolution is cached for the lifetime of the process.
15
+ */
16
+ import { loadProjectConfig, findProjectConfigPath, findProjectRoot, PROJECT_CONFIG_DISPLAY_NAME, } from "./project-config.js";
17
+ /** Process-lifetime cache for resolution results. */
18
+ let cachedResolution;
19
+ let explicitEnvName = null;
20
+ let cachedProjectConfig;
21
+ /**
22
+ * Called by bin/primitive.ts when the user passes --env <name> on the command
23
+ * line. Must be called before any command code runs so subsequent calls to
24
+ * getCurrentEnvironment() see the override.
25
+ */
26
+ export function setCurrentEnvName(name) {
27
+ explicitEnvName = name;
28
+ // Invalidate any cached resolution so the next getCurrentEnvironment()
29
+ // picks up the new override.
30
+ cachedResolution = undefined;
31
+ }
32
+ /**
33
+ * Forces the resolver to re-read the project config on the next call.
34
+ * Used by tests and by commands that mutate the project config.
35
+ */
36
+ export function resetEnvResolver() {
37
+ cachedResolution = undefined;
38
+ cachedProjectConfig = undefined;
39
+ }
40
+ /**
41
+ * Loads and caches the project config. Returns null if no
42
+ * .primitive/config.json is present. Throws ProjectConfigError on a broken config.
43
+ */
44
+ export function getProjectConfig() {
45
+ if (cachedProjectConfig === undefined) {
46
+ cachedProjectConfig = loadProjectConfig();
47
+ }
48
+ return cachedProjectConfig ?? null;
49
+ }
50
+ /**
51
+ * Resolves the current environment, or returns null if we're running in
52
+ * legacy mode (no .primitive/config.json present).
53
+ *
54
+ * This never throws for a missing project config — that's legitimate
55
+ * "legacy mode" usage. It DOES throw if the project config exists but the
56
+ * requested environment doesn't.
57
+ */
58
+ export function getCurrentEnvironment() {
59
+ if (cachedResolution !== undefined)
60
+ return cachedResolution;
61
+ const project = getProjectConfig();
62
+ if (!project) {
63
+ cachedResolution = null;
64
+ return null;
65
+ }
66
+ const configPath = findProjectConfigPath();
67
+ const projectRoot = findProjectRoot();
68
+ // Determine the environment name.
69
+ const requested = explicitEnvName ??
70
+ process.env.PRIMITIVE_ENV ??
71
+ project.defaultEnvironment ??
72
+ null;
73
+ let name;
74
+ if (requested) {
75
+ if (!project.environments[requested]) {
76
+ const available = Object.keys(project.environments).join(", ") || "(none)";
77
+ throw new Error(`Environment "${requested}" is not defined in ${PROJECT_CONFIG_DISPLAY_NAME}. Available: ${available}`);
78
+ }
79
+ name = requested;
80
+ }
81
+ else {
82
+ const names = Object.keys(project.environments);
83
+ if (names.length === 1) {
84
+ name = names[0];
85
+ }
86
+ else if (names.length === 0) {
87
+ throw new Error(`${PROJECT_CONFIG_DISPLAY_NAME} has no environments defined. Run 'primitive env add <name>' or 'primitive init'.`);
88
+ }
89
+ else {
90
+ throw new Error(`No environment selected. Set "defaultEnvironment" in ${PROJECT_CONFIG_DISPLAY_NAME}, pass --env <name>, or export PRIMITIVE_ENV. Available: ${names.join(", ")}`);
91
+ }
92
+ }
93
+ cachedResolution = {
94
+ name,
95
+ config: project.environments[name],
96
+ projectConfigPath: configPath,
97
+ projectRoot,
98
+ };
99
+ return cachedResolution;
100
+ }
101
+ /**
102
+ * Returns the current environment name for display purposes (e.g. in status
103
+ * headers). Returns null in legacy mode. Swallows resolution errors so the
104
+ * header never crashes the CLI.
105
+ */
106
+ export function getCurrentEnvNameSafe() {
107
+ try {
108
+ return getCurrentEnvironment()?.name ?? null;
109
+ }
110
+ catch {
111
+ return null;
112
+ }
113
+ }
114
+ /**
115
+ * Returns the explicit name set via setCurrentEnvName(), for commands that
116
+ * need to know whether the user passed --env vs. relying on defaults.
117
+ */
118
+ export function getExplicitEnvName() {
119
+ return explicitEnvName;
120
+ }
121
+ //# sourceMappingURL=env-resolver.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"env-resolver.js","sourceRoot":"","sources":["../../../src/lib/env-resolver.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Cursor-pagination helper used by the CLI's API client.
3
+ *
4
+ * The server exposes cursor-paginated endpoints (e.g. GET /admin/api/admins/me/apps)
5
+ * that return `{ items, nextCursor? }` pages. Callers historically only fetched
6
+ * the first page, silently truncating results (see issue #436). This helper
7
+ * walks every page and aggregates the items in order.
8
+ *
9
+ * Exported separately so the looping contract can be unit-tested without
10
+ * stubbing the full ApiClient.
11
+ */
12
+ /**
13
+ * Walk every cursor page from `fetchPage` and return the concatenated items.
14
+ *
15
+ * Detects two failure modes that would otherwise hang the CLI:
16
+ * - A cursor that repeats (server returned the same `nextCursor` twice).
17
+ * - A cursor chain longer than `maxPages` (default 1000).
18
+ * Both throw so the caller fails loudly rather than looping forever.
19
+ */
20
+ export async function paginateAll(fetchPage, options = {}) {
21
+ const maxPages = options.maxPages ?? 1000;
22
+ const all = [];
23
+ const seenCursors = new Set();
24
+ let cursor;
25
+ for (let page = 0; page < maxPages; page++) {
26
+ const resp = await fetchPage(cursor);
27
+ if (Array.isArray(resp.items)) {
28
+ all.push(...resp.items);
29
+ }
30
+ const next = resp.nextCursor ?? undefined;
31
+ if (!next) {
32
+ return all;
33
+ }
34
+ if (seenCursors.has(next)) {
35
+ throw new Error(`Pagination cursor did not advance (cursor ${next} repeated); aborting to avoid an infinite loop.`);
36
+ }
37
+ seenCursors.add(next);
38
+ cursor = next;
39
+ }
40
+ throw new Error(`Pagination exceeded ${maxPages} pages without terminating; server cursor may not be advancing.`);
41
+ }
42
+ //# sourceMappingURL=paginate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"paginate.js","sourceRoot":"","sources":["../../../src/lib/paginate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAkBH;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAC/B,SAAyB,EACzB,UAA2B,EAAE;IAE7B,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,IAAI,IAAI,CAAC;IAC1C,MAAM,GAAG,GAAQ,EAAE,CAAC;IACpB,MAAM,WAAW,GAAG,IAAI,GAAG,EAAU,CAAC;IACtC,IAAI,MAA0B,CAAC;IAE/B,KAAK,IAAI,IAAI,GAAG,CAAC,EAAE,IAAI,GAAG,QAAQ,EAAE,IAAI,EAAE,EAAE,CAAC;QAC3C,MAAM,IAAI,GAAG,MAAM,SAAS,CAAC,MAAM,CAAC,CAAC;QACrC,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;YAC9B,GAAG,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC;QAC1B,CAAC;QACD,MAAM,IAAI,GAAG,IAAI,CAAC,UAAU,IAAI,SAAS,CAAC;QAC1C,IAAI,CAAC,IAAI,EAAE,CAAC;YACV,OAAO,GAAG,CAAC;QACb,CAAC;QACD,IAAI,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;YAC1B,MAAM,IAAI,KAAK,CACb,6CAA6C,IAAI,iDAAiD,CACnG,CAAC;QACJ,CAAC;QACD,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACtB,MAAM,GAAG,IAAI,CAAC;IAChB,CAAC;IAED,MAAM,IAAI,KAAK,CACb,uBAAuB,QAAQ,iEAAiE,CACjG,CAAC;AACJ,CAAC"}