@vanillagreen/pi-qol 1.8.0 → 2.0.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 CHANGED
@@ -1,8 +1,8 @@
1
1
  # pi-qol
2
2
 
3
- ![QOL extension settings panel](https://raw.githubusercontent.com/vanillagreencom/vstack/main/pi-extensions/pi-qol/assets/settings-panel.png)
4
- ![Session search popup](https://raw.githubusercontent.com/vanillagreencom/vstack/main/pi-extensions/pi-qol/assets/session-search.gif)
5
- ![/context usage breakdown](https://raw.githubusercontent.com/vanillagreencom/vstack/main/pi-extensions/pi-qol/assets/context-usage.png)
3
+ ![QOL extension settings panel](https://raw.githubusercontent.com/vanillagreencom/kendex/main/pi-extensions/pi-qol/assets/settings-panel.png)
4
+ ![Session search popup](https://raw.githubusercontent.com/vanillagreencom/kendex/main/pi-extensions/pi-qol/assets/session-search.gif)
5
+ ![/context usage breakdown](https://raw.githubusercontent.com/vanillagreencom/kendex/main/pi-extensions/pi-qol/assets/context-usage.png)
6
6
 
7
7
  Quality-of-life extension for Pi: compact statusline, multiline input, session naming and search, scheduled prompts, notifications, and a permission gate.
8
8
 
@@ -29,11 +29,11 @@ Via [npm](https://www.npmjs.com/package/@vanillagreen/pi-qol):
29
29
  pi install npm:@vanillagreen/pi-qol
30
30
  ```
31
31
 
32
- Via [vstack](https://github.com/vanillagreencom/vstack):
32
+ Via [kendex](https://github.com/vanillagreencom/kendex):
33
33
 
34
34
  ```bash
35
- cargo install --git https://github.com/vanillagreencom/vstack.git vstack
36
- vstack add vanillagreencom/vstack --pi-extension pi-qol --harness pi -y
35
+ cargo install --git https://github.com/vanillagreencom/kendex.git kendex
36
+ kendex add vanillagreencom/kendex --pi-extension pi-qol --harness pi -y
37
37
  ```
38
38
 
39
39
  ## Commands
@@ -54,7 +54,7 @@ Arguments support autocomplete. `/schedule` accepts `ms`, `s`, `m`, `h`, and `d`
54
54
 
55
55
  ## Settings
56
56
 
57
- Open `/extensions:settings`; settings appear under the **QOL** tab. Names below match the labels shown there, grouped as the editor groups them. Project settings in `.pi/settings.json` apply only after Pi marks the workspace trusted; before trust, vstack Pi extensions read user/global settings only. Glyph style: each package exposes `glyphStyle` (`unicode` default, `ascii` for terminal-safe chrome). `@vanillagreen/pi-tool-renderer.globalGlyphStyleOverride=ascii` forces ASCII chrome across vstack Pi extensions while leaving tool/model/user content unchanged.
57
+ Open `/extensions:settings`; settings appear under the **QOL** tab. Names below match the labels shown there, grouped as the editor groups them. Project settings in `.pi/settings.json` apply only after Pi marks the workspace trusted; before trust, kendex Pi extensions read user/global settings only. Glyph style: each package exposes `glyphStyle` (`unicode` default, `ascii` for terminal-safe chrome). `@vanillagreen/pi-tool-renderer.globalGlyphStyleOverride=ascii` forces ASCII chrome across kendex Pi extensions while leaving tool/model/user content unchanged.
58
58
 
59
59
  | Group | Setting | What it does |
60
60
  | --- | --- | --- |
@@ -2,7 +2,7 @@ import {
2
2
  CAVEMAN_BRIDGE_SYMBOL,
3
3
  PI_AGENTS_STATUSLINE_SYMBOL,
4
4
  QUESTION_SERVICE_SYMBOL,
5
- VSTACK_MODAL_LOCK_SYMBOL,
5
+ KENDEX_MODAL_LOCK_SYMBOL,
6
6
  } from "./constants.js";
7
7
 
8
8
  export interface CavemanBridge {
@@ -37,7 +37,7 @@ export interface QuestionServiceLike {
37
37
  subscribe(listener: (event: any) => void): () => void;
38
38
  }
39
39
 
40
- interface VstackModalLock {
40
+ interface kendexModalLock {
41
41
  depth: number;
42
42
  }
43
43
 
@@ -63,11 +63,11 @@ export function getQuestionService(): QuestionServiceLike | undefined {
63
63
  return undefined;
64
64
  }
65
65
 
66
- export function acquireVstackModalLock(): () => void {
66
+ export function acquirekendexModalLock(): () => void {
67
67
  const host = globalThis as unknown as Record<PropertyKey, unknown>;
68
- const existing = host[VSTACK_MODAL_LOCK_SYMBOL] as VstackModalLock | undefined;
68
+ const existing = host[KENDEX_MODAL_LOCK_SYMBOL] as kendexModalLock | undefined;
69
69
  const lock = existing && typeof existing.depth === "number" ? existing : { depth: 0 };
70
- host[VSTACK_MODAL_LOCK_SYMBOL] = lock;
70
+ host[KENDEX_MODAL_LOCK_SYMBOL] = lock;
71
71
  lock.depth += 1;
72
72
  let released = false;
73
73
  return () => {
@@ -151,7 +151,7 @@ export function handoffStampedPath(baseDir: string, timestamp: number): string {
151
151
  }
152
152
 
153
153
  export function handoffBaseDir(sessionId: string, root: string = piUserDir()): string {
154
- return join(root, "vstack", "sessions", safeFileName(sessionId), QOL_BUDGET_HANDOFF_FOLDER);
154
+ return join(root, "kendex", "sessions", safeFileName(sessionId), QOL_BUDGET_HANDOFF_FOLDER);
155
155
  }
156
156
 
157
157
  export interface WriteHandoffOptions {
@@ -1,4 +1,4 @@
1
- export const INSTALL_SYMBOL = Symbol.for("vstack.pi-qol.installed");
1
+ export const INSTALL_SYMBOL = Symbol.for("kendex.pi-qol.installed");
2
2
  export const CONFIG_ID = "@vanillagreen/pi-qol";
3
3
 
4
4
  export const STATUS_KEY = "qol-attachments";
@@ -10,22 +10,22 @@ export const SESSION_MANAGER_STATUS_KEY = "session-manager";
10
10
  export const IMAGE_EXTENSIONS = new Set([".png", ".jpg", ".jpeg", ".gif", ".webp", ".bmp", ".tif", ".tiff", ".heic", ".heif"]);
11
11
  export const IMAGE_PATH_PATTERN = /(^|[\s(\[{<"'`])(@?(?:~|\.\.?|\/)[^\s)\]}>"'`]+?\.(?:png|jpe?g|gif|webp|bmp|tiff?|heic|heif))(?=$|[\s)\]}>"'`,.;:!?])/gi;
12
12
 
13
- export const QUESTION_SERVICE_SYMBOL = Symbol.for("vstack.pi-questions.service");
14
- export const QOL_NOTIFICATION_SERVICE_SYMBOL = Symbol.for("vstack.pi-qol.notification-service");
15
- export const VSTACK_MODAL_LOCK_SYMBOL = Symbol.for("vstack.pi.modal-lock");
16
- export const CAVEMAN_BRIDGE_SYMBOL = Symbol.for("vstack.pi.caveman");
17
- export const PI_AGENTS_STATUSLINE_SYMBOL = Symbol.for("vstack.pi-agents-tmux.statusline");
13
+ export const QUESTION_SERVICE_SYMBOL = Symbol.for("kendex.pi-questions.service");
14
+ export const QOL_NOTIFICATION_SERVICE_SYMBOL = Symbol.for("kendex.pi-qol.notification-service");
15
+ export const KENDEX_MODAL_LOCK_SYMBOL = Symbol.for("kendex.pi.modal-lock");
16
+ export const CAVEMAN_BRIDGE_SYMBOL = Symbol.for("kendex.pi.caveman");
17
+ export const PI_AGENTS_STATUSLINE_SYMBOL = Symbol.for("kendex.pi-agents-tmux.statusline");
18
18
 
19
19
  export const CAVEMAN_ICON_ACTIVE = "\uee9a";
20
20
  export const CAVEMAN_ICON_INACTIVE = "\u{f19e0}";
21
21
 
22
- export const THINKING_TIMER_STORE_SYMBOL = Symbol.for("vstack.pi-qol.thinking-timer.store");
23
- export const THINKING_TIMER_PATCH_SYMBOL = Symbol.for("vstack.pi-qol.thinking-timer.patch");
24
- export const SESSION_SEARCH_PENDING_SYMBOL = Symbol.for("vstack.pi-qol.session-search.pending-context");
25
- export const PENDING_QUEUE_THEME_PATCH_SYMBOL = Symbol.for("vstack.pi-qol.pending-queue.theme-patch");
26
- export const STATUS_TEXT_ALIGNMENT_PATCH_SYMBOL = Symbol.for("vstack.pi-qol.status-text-alignment-patch");
22
+ export const THINKING_TIMER_STORE_SYMBOL = Symbol.for("kendex.pi-qol.thinking-timer.store");
23
+ export const THINKING_TIMER_PATCH_SYMBOL = Symbol.for("kendex.pi-qol.thinking-timer.patch");
24
+ export const SESSION_SEARCH_PENDING_SYMBOL = Symbol.for("kendex.pi-qol.session-search.pending-context");
25
+ export const PENDING_QUEUE_THEME_PATCH_SYMBOL = Symbol.for("kendex.pi-qol.pending-queue.theme-patch");
26
+ export const STATUS_TEXT_ALIGNMENT_PATCH_SYMBOL = Symbol.for("kendex.pi-qol.status-text-alignment-patch");
27
27
 
28
- export const QUESTION_OPENED_EVENT = "vstack:pi-questions:opened";
28
+ export const QUESTION_OPENED_EVENT = "kendex:pi-questions:opened";
29
29
  export const QUESTION_NOTIFY_DEDUP_MS = 2000;
30
30
 
31
31
  export const DEFAULT_NOTIFICATION_TITLE = "Pi";
@@ -19,14 +19,14 @@ function projectSettingsPath(cwd: string): string {
19
19
  while (true) {
20
20
  const candidate = join(current, ".pi", "settings.json");
21
21
  if (existsSync(candidate)) return candidate;
22
- if (existsSync(join(current, ".pi")) || existsSync(join(current, ".git")) || existsSync(join(current, ".vstack-lock.json"))) return candidate;
22
+ if (existsSync(join(current, ".pi")) || existsSync(join(current, ".git")) || existsSync(join(current, ".kendex-lock.json"))) return candidate;
23
23
  const parent = dirname(current);
24
24
  if (parent === current) return join(resolve(cwd), ".pi", "settings.json");
25
25
  current = parent;
26
26
  }
27
27
  }
28
28
 
29
- const PROJECT_TRUST_SYMBOL = Symbol.for("vstack.pi.project-trust");
29
+ const PROJECT_TRUST_SYMBOL = Symbol.for("kendex.pi.project-trust");
30
30
 
31
31
  interface ProjectTrustRegistry {
32
32
  projectSettings?: Map<string, boolean>;
@@ -72,7 +72,7 @@ function readPackageConfig(packageId: string, cwd?: string): Record<string, unkn
72
72
  if (!existsSync(settingsPath)) continue;
73
73
  try {
74
74
  const parsed = JSON.parse(readFileSync(settingsPath, "utf8"));
75
- const config = parsed?.vstack?.extensionManager?.config?.[packageId];
75
+ const config = parsed?.kendex?.extensionManager?.config?.[packageId];
76
76
  if (config && typeof config === "object" && !Array.isArray(config)) Object.assign(merged, config);
77
77
  } catch {
78
78
  // Ignore malformed optional manager config.
@@ -2,7 +2,7 @@ import type { AgentMessage } from "@earendil-works/pi-agent-core";
2
2
  import type { Message } from "@earendil-works/pi-ai";
3
3
  import { complete } from "./pi-ai-compat.js";
4
4
  import { BorderedLoader, type ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
5
- import { acquireVstackModalLock } from "./bridges.js";
5
+ import { acquirekendexModalLock } from "./bridges.js";
6
6
  import { serializeMessagesForSummary } from "./compaction.js";
7
7
  import { HANDOFF_SYSTEM_PROMPT } from "./constants.js";
8
8
  import { settingBoolean } from "./settings.js";
@@ -40,7 +40,7 @@ export async function runHandoff(args: string, ctx: ExtensionCommandContext): Pr
40
40
  const conversationText = serializeMessagesForSummary(messages);
41
41
  const currentSessionFile = ctx.sessionManager.getSessionFile();
42
42
 
43
- const releaseModalLock = acquireVstackModalLock();
43
+ const releaseModalLock = acquirekendexModalLock();
44
44
  let result: string | null = null;
45
45
  let generationError: unknown;
46
46
  try {
@@ -4,7 +4,7 @@ import { ansiRed } from "./ansi.js";
4
4
  import { boundedSettingNumber, settingBoolean, settingString } from "./settings.js";
5
5
  import { stringifyError } from "./util.js";
6
6
 
7
- export const RATE_LIMIT_AUTO_RESUME_EVENT = "vstack:rate-limit";
7
+ export const RATE_LIMIT_AUTO_RESUME_EVENT = "kendex:rate-limit";
8
8
  export const DEFAULT_RATE_LIMIT_RESUME_MESSAGE = "Hit API rate limit. It has been reset. Continue";
9
9
 
10
10
  const MAX_TIMER_DELAY_MS = 2_147_000_000;
@@ -1,7 +1,7 @@
1
1
  import type { AgentMessage } from "@earendil-works/pi-agent-core";
2
2
  import { SessionManager, type ExtensionAPI, type ExtensionContext, type Theme } from "@earendil-works/pi-coding-agent";
3
3
  import { Text } from "@earendil-works/pi-tui";
4
- import { acquireVstackModalLock } from "../bridges.js";
4
+ import { acquirekendexModalLock } from "../bridges.js";
5
5
  import { generateQolSummary, serializeMessagesForSummary } from "../compaction.js";
6
6
  import {
7
7
  DEFAULT_SESSION_SEARCH_SUMMARY_INPUT_CHARS,
@@ -89,7 +89,7 @@ export async function buildSessionSearchContextMessageWithLoader(ctx: ExtensionC
89
89
  const title = sessionDisplayName(result);
90
90
  if (!ctx.hasUI) return buildSessionSearchContextMessage(ctx, result, customPrompt);
91
91
  ctx.ui.setStatus(SESSION_SEARCH_STATUS_KEY, `Summarizing ${title}...`);
92
- const releaseModalLock = acquireVstackModalLock();
92
+ const releaseModalLock = acquirekendexModalLock();
93
93
  let failure: unknown;
94
94
  try {
95
95
  const message = await ctx.ui.custom<QolSessionSearchPendingMessage | null>((tui: any, theme: any, _kb: any, done: (value: QolSessionSearchPendingMessage | null) => void) => {
@@ -1,5 +1,5 @@
1
1
  import type { ExtensionAPI, ExtensionCommandContext, ExtensionContext } from "@earendil-works/pi-coding-agent";
2
- import { acquireVstackModalLock } from "../bridges.js";
2
+ import { acquirekendexModalLock } from "../bridges.js";
3
3
  import { SESSION_SEARCH_CONTEXT_TYPE, SESSION_SEARCH_STATUS_KEY } from "../constants.js";
4
4
  import { settingNumber } from "../settings.js";
5
5
  import { stringifyError } from "../util.js";
@@ -122,7 +122,7 @@ export async function openQolSessionSearch(pi: ExtensionAPI, ctx: ExtensionConte
122
122
  return;
123
123
  }
124
124
  const sessions = await refreshQolSessionSearchCache(ctx);
125
- const releaseModalLock = acquireVstackModalLock();
125
+ const releaseModalLock = acquirekendexModalLock();
126
126
  let action: QolSessionPaletteAction | undefined;
127
127
  try {
128
128
  const currentModel = ctx.model ? { provider: ctx.model.provider, id: ctx.model.id } : undefined;
@@ -3,7 +3,7 @@ import { homedir } from "node:os";
3
3
  import { dirname, join, resolve } from "node:path";
4
4
  import { CONFIG_ID } from "./constants.js";
5
5
 
6
- export type VstackConfig = Record<string, unknown>;
6
+ export type kendexConfig = Record<string, unknown>;
7
7
 
8
8
  export function expandHome(input: string): string {
9
9
  if (input === "~") return homedir();
@@ -16,14 +16,14 @@ export function projectSettingsPath(cwd: string): string {
16
16
  while (true) {
17
17
  const candidate = join(current, ".pi", "settings.json");
18
18
  if (existsSync(candidate)) return candidate;
19
- if (existsSync(join(current, ".pi")) || existsSync(join(current, ".git")) || existsSync(join(current, ".vstack-lock.json"))) return candidate;
19
+ if (existsSync(join(current, ".pi")) || existsSync(join(current, ".git")) || existsSync(join(current, ".kendex-lock.json"))) return candidate;
20
20
  const parent = dirname(current);
21
21
  if (parent === current) return join(resolve(cwd), ".pi", "settings.json");
22
22
  current = parent;
23
23
  }
24
24
  }
25
25
 
26
- const PROJECT_TRUST_SYMBOL = Symbol.for("vstack.pi.project-trust");
26
+ const PROJECT_TRUST_SYMBOL = Symbol.for("kendex.pi.project-trust");
27
27
 
28
28
  interface ProjectTrustRegistry {
29
29
  projectSettings?: Map<string, boolean>;
@@ -63,13 +63,13 @@ export function piSettingsPaths(cwd = process.cwd()): string[] {
63
63
  return projectSettingsTrusted(project) ? [user, project] : [user];
64
64
  }
65
65
 
66
- export function readVstackConfig(cwd?: string): VstackConfig {
67
- const merged: VstackConfig = {};
66
+ export function readkendexConfig(cwd?: string): kendexConfig {
67
+ const merged: kendexConfig = {};
68
68
  for (const path of piSettingsPaths(cwd)) {
69
69
  if (!existsSync(path)) continue;
70
70
  try {
71
71
  const parsed = JSON.parse(readFileSync(path, "utf8"));
72
- const config = parsed?.vstack?.extensionManager?.config?.[CONFIG_ID];
72
+ const config = parsed?.kendex?.extensionManager?.config?.[CONFIG_ID];
73
73
  if (config && typeof config === "object" && !Array.isArray(config)) Object.assign(merged, config);
74
74
  } catch {
75
75
  // Ignore malformed optional manager config.
@@ -79,17 +79,17 @@ export function readVstackConfig(cwd?: string): VstackConfig {
79
79
  }
80
80
 
81
81
  export function settingBoolean(key: string, fallback: boolean, cwd?: string): boolean {
82
- const value = readVstackConfig(cwd)[key];
82
+ const value = readkendexConfig(cwd)[key];
83
83
  return typeof value === "boolean" ? value : fallback;
84
84
  }
85
85
 
86
86
  export function settingString(key: string, fallback: string, cwd?: string): string {
87
- const value = readVstackConfig(cwd)[key];
87
+ const value = readkendexConfig(cwd)[key];
88
88
  return typeof value === "string" && value.trim().length > 0 ? value.trim() : fallback;
89
89
  }
90
90
 
91
91
  export function settingStringAllowEmpty(key: string, fallback: string, cwd?: string): string {
92
- const value = readVstackConfig(cwd)[key];
92
+ const value = readkendexConfig(cwd)[key];
93
93
  return typeof value === "string" ? value.trim() : fallback;
94
94
  }
95
95
 
@@ -99,7 +99,7 @@ export function newlineFallbackKey(cwd?: string): "ctrl+j" | "none" {
99
99
  }
100
100
 
101
101
  export function settingNumber(key: string, fallback: number, cwd?: string): number {
102
- const value = readVstackConfig(cwd)[key];
102
+ const value = readkendexConfig(cwd)[key];
103
103
  const parsed = typeof value === "number" ? value : typeof value === "string" ? Number(value) : Number.NaN;
104
104
  return Number.isFinite(parsed) ? parsed : fallback;
105
105
  }
package/extensions/qol.ts CHANGED
@@ -924,7 +924,7 @@ export default function qol(pi: ExtensionAPI): void {
924
924
 
925
925
  const tryOpenExtensionManagerSettings = async (ctx: ExtensionCommandContext): Promise<boolean> => {
926
926
  const host = globalThis as unknown as Record<PropertyKey, unknown>;
927
- const openQuickSettings = host[Symbol.for("vstack.pi.extension-manager.open-quick-settings")];
927
+ const openQuickSettings = host[Symbol.for("kendex.pi.extension-manager.open-quick-settings")];
928
928
  if (typeof openQuickSettings !== "function") return false;
929
929
  try {
930
930
  await (openQuickSettings as (ctx: ExtensionCommandContext, hint?: string) => Promise<void>)(ctx, "pi-qol");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vanillagreen/pi-qol",
3
- "version": "1.8.0",
3
+ "version": "2.0.0",
4
4
  "description": "Pi quality-of-life extension: compact statusline/π prompt, reliable multiline input, styled pasted-image chips, session naming/search/context import, scheduled prompts, handoff, permission prompts, notifications, custom compaction, and a collapsed-thinking timer.",
5
5
  "license": "MIT",
6
6
  "keywords": [
@@ -14,12 +14,12 @@
14
14
  "extensions": [
15
15
  "./extensions/qol.ts"
16
16
  ],
17
- "image": "https://raw.githubusercontent.com/vanillagreencom/vstack/main/pi-extensions/pi-qol/assets/settings-panel.png"
17
+ "image": "https://raw.githubusercontent.com/vanillagreencom/kendex/main/pi-extensions/pi-qol/assets/settings-panel.png"
18
18
  },
19
19
  "scripts": {
20
20
  "test": "bun test ./tests"
21
21
  },
22
- "vstack": {
22
+ "kendex": {
23
23
  "extensionManager": {
24
24
  "displayName": "QOL",
25
25
  "settings": [
@@ -36,7 +36,7 @@
36
36
  {
37
37
  "key": "glyphStyle",
38
38
  "label": "Glyph style",
39
- "description": "Glyphs for vstack-owned UI chrome. Unicode is the default; ASCII uses plain box/status/separator characters for terminal compatibility. pi-tool-renderer's global override wins when set.",
39
+ "description": "Glyphs for kendex-owned UI chrome. Unicode is the default; ASCII uses plain box/status/separator characters for terminal compatibility. pi-tool-renderer's global override wins when set.",
40
40
  "type": "enum",
41
41
  "enumValues": [
42
42
  "unicode",
@@ -911,7 +911,7 @@
911
911
  {
912
912
  "key": "compaction.handoffArtifactEnabled",
913
913
  "label": "Write pre-compaction handoff artifact",
914
- "description": "When enabled, write a JSON handoff artifact under ~/.pi/agent/vstack/sessions/<session>/pi-qol/handoff/ containing the previous summary, last task state, and recently-referenced file/artifact paths. Each write creates a stamped file and a latest.json pointer. Applies to every budget-guard compaction (regardless of compaction.customEnabled), and to manual/idle compactions only when compaction.customEnabled is on. Write failures surface a QOL warning and a handoffArtifactError field in the compaction details.",
914
+ "description": "When enabled, write a JSON handoff artifact under ~/.pi/agent/kendex/sessions/<session>/pi-qol/handoff/ containing the previous summary, last task state, and recently-referenced file/artifact paths. Each write creates a stamped file and a latest.json pointer. Applies to every budget-guard compaction (regardless of compaction.customEnabled), and to manual/idle compactions only when compaction.customEnabled is on. Write failures surface a QOL warning and a handoffArtifactError field in the compaction details.",
915
915
  "type": "boolean",
916
916
  "default": true,
917
917
  "category": "Compaction Budget Guard",
@@ -976,12 +976,12 @@
976
976
  ],
977
977
  "repository": {
978
978
  "type": "git",
979
- "url": "git+https://github.com/vanillagreencom/vstack.git",
979
+ "url": "git+https://github.com/vanillagreencom/kendex.git",
980
980
  "directory": "pi-extensions/pi-qol"
981
981
  },
982
- "homepage": "https://github.com/vanillagreencom/vstack/tree/main/pi-extensions/pi-qol",
982
+ "homepage": "https://github.com/vanillagreencom/kendex/tree/main/pi-extensions/pi-qol",
983
983
  "bugs": {
984
- "url": "https://github.com/vanillagreencom/vstack/issues"
984
+ "url": "https://github.com/vanillagreencom/kendex/issues"
985
985
  },
986
986
  "author": "vanillagreen",
987
987
  "publishConfig": {