caraer-cli 0.1.2__py3-none-any.whl

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 (83) hide show
  1. caraer_cli/__init__.py +4 -0
  2. caraer_cli/api/__init__.py +1 -0
  3. caraer_cli/api/apps.py +78 -0
  4. caraer_cli/api/auth.py +53 -0
  5. caraer_cli/api/client.py +179 -0
  6. caraer_cli/api/functions.py +72 -0
  7. caraer_cli/api/installation.py +93 -0
  8. caraer_cli/api/integration_runtime.py +77 -0
  9. caraer_cli/api/projects.py +213 -0
  10. caraer_cli/api/sandboxes.py +29 -0
  11. caraer_cli/api/webhooks.py +59 -0
  12. caraer_cli/app_sync.py +697 -0
  13. caraer_cli/apps_local.py +274 -0
  14. caraer_cli/commands/__init__.py +1 -0
  15. caraer_cli/commands/apps.py +2025 -0
  16. caraer_cli/commands/auth.py +173 -0
  17. caraer_cli/commands/company.py +68 -0
  18. caraer_cli/commands/installation.py +182 -0
  19. caraer_cli/commands/profile.py +239 -0
  20. caraer_cli/commands/publish.py +40 -0
  21. caraer_cli/commands/sandbox.py +92 -0
  22. caraer_cli/commands/skill.py +165 -0
  23. caraer_cli/commands/webhooks.py +68 -0
  24. caraer_cli/completion.py +77 -0
  25. caraer_cli/completion_callbacks.py +162 -0
  26. caraer_cli/context.py +41 -0
  27. caraer_cli/errors.py +84 -0
  28. caraer_cli/formatters/__init__.py +1 -0
  29. caraer_cli/formatters/output.py +381 -0
  30. caraer_cli/formatters/timestamps.py +98 -0
  31. caraer_cli/install_completion.py +38 -0
  32. caraer_cli/local_app.py +135 -0
  33. caraer_cli/main.py +104 -0
  34. caraer_cli/models/__init__.py +1 -0
  35. caraer_cli/models/api.py +52 -0
  36. caraer_cli/project/__init__.py +17 -0
  37. caraer_cli/project/app_bars_sync.py +154 -0
  38. caraer_cli/project/app_manifest_template.py +68 -0
  39. caraer_cli/project/inbound_sync.py +192 -0
  40. caraer_cli/project/json_schemas.py +66 -0
  41. caraer_cli/project/lifecycle_sync.py +119 -0
  42. caraer_cli/project/local_dev.py +825 -0
  43. caraer_cli/project/manifest_edit.py +51 -0
  44. caraer_cli/project/marketplace_assemble.py +165 -0
  45. caraer_cli/project/oauth_providers_sync.py +180 -0
  46. caraer_cli/project/paths.py +102 -0
  47. caraer_cli/project/pricing_sync.py +81 -0
  48. caraer_cli/project/push_plan.py +590 -0
  49. caraer_cli/project/release.py +128 -0
  50. caraer_cli/project/scaffold.py +537 -0
  51. caraer_cli/project/schedules_sync.py +170 -0
  52. caraer_cli/project/schema.py +160 -0
  53. caraer_cli/project/settings_sync.py +77 -0
  54. caraer_cli/project/state.py +51 -0
  55. caraer_cli/project/sync.py +308 -0
  56. caraer_cli/project/typegen.py +278 -0
  57. caraer_cli/project/validate_app.py +1215 -0
  58. caraer_cli/project/webhooks_sync.py +233 -0
  59. caraer_cli/resolve.py +55 -0
  60. caraer_cli/schemas/app.caraer.schema.json +175 -0
  61. caraer_cli/schemas/function.caraer.schema.json +29 -0
  62. caraer_cli/schemas/inbound.caraer.schema.json +49 -0
  63. caraer_cli/schemas/lifecycle.caraer.schema.json +50 -0
  64. caraer_cli/schemas/schedule.caraer.schema.json +39 -0
  65. caraer_cli/schemas/webhook.caraer.schema.json +50 -0
  66. caraer_cli/skills/.gitkeep +2 -0
  67. caraer_cli/skills/caraer-apps/SKILL.md +172 -0
  68. caraer_cli/skills/caraer-apps/reference.md +163 -0
  69. caraer_cli/state/__init__.py +1 -0
  70. caraer_cli/state/config.py +148 -0
  71. caraer_cli/state/session.py +98 -0
  72. caraer_cli/utils.py +40 -0
  73. caraer_cli/wizard/__init__.py +9 -0
  74. caraer_cli/wizard/catalog.py +170 -0
  75. caraer_cli/wizard/marketplace.py +350 -0
  76. caraer_cli/wizard/prompts.py +99 -0
  77. caraer_cli/wizard/public_app.py +519 -0
  78. caraer_cli/wizard/scope_macros.py +214 -0
  79. caraer_cli-0.1.2.dist-info/METADATA +266 -0
  80. caraer_cli-0.1.2.dist-info/RECORD +83 -0
  81. caraer_cli-0.1.2.dist-info/WHEEL +4 -0
  82. caraer_cli-0.1.2.dist-info/entry_points.txt +2 -0
  83. caraer_cli-0.1.2.dist-info/licenses/LICENSE +8 -0
@@ -0,0 +1,135 @@
1
+ """Helpers for local app YAML/JSON selection and discovery."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import re
6
+ from pathlib import Path
7
+ from typing import Any
8
+
9
+ from caraer_cli.project.app_manifest_template import APP_MANIFEST_NAMES
10
+ from caraer_cli.project.paths import (
11
+ LEGACY_PROJECT_FILE,
12
+ WORKSPACE_FILE,
13
+ app_manifest_path,
14
+ )
15
+ from caraer_cli.utils import load_structured_file
16
+
17
+ UUID_RE = re.compile(
18
+ r"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$"
19
+ )
20
+ APP_FILE_NAMES = APP_MANIFEST_NAMES
21
+
22
+
23
+ def looks_like_uuid(value: str) -> bool:
24
+ return bool(UUID_RE.match(value.strip()))
25
+
26
+
27
+ def looks_like_local_app_ref(value: str) -> bool:
28
+ """Heuristic: path-like refs should be treated as local app files/dirs."""
29
+ text = value.strip()
30
+ if not text:
31
+ return False
32
+ if looks_like_uuid(text):
33
+ return False
34
+ path = Path(text)
35
+ if path.exists() or (Path.cwd() / text).exists():
36
+ return True
37
+ suffix = path.suffix.lower()
38
+ if suffix in {".json", ".yaml", ".yml"}:
39
+ return True
40
+ if "/" in text or "\\" in text:
41
+ return True
42
+ return False
43
+
44
+
45
+ def _candidate_app_files_in_dir(directory: Path) -> list[Path]:
46
+ candidates: list[Path] = []
47
+ if (directory / WORKSPACE_FILE).is_file() or (directory / LEGACY_PROJECT_FILE).is_file():
48
+ candidates.append(app_manifest_path(directory))
49
+ for name in APP_FILE_NAMES:
50
+ candidates.append(directory / name)
51
+ candidates.append(directory / "src" / "app" / name)
52
+ return candidates
53
+
54
+
55
+ def resolve_app_file_path(path: str | Path) -> Path:
56
+ """Resolve a local app file, accepting an app directory or direct file path."""
57
+ raw = Path(path).expanduser()
58
+ resolved = raw.resolve() if raw.exists() else raw
59
+
60
+ if resolved.is_file():
61
+ return resolved.resolve()
62
+
63
+ if resolved.is_dir() or not resolved.suffix:
64
+ directory = resolved if resolved.is_dir() else raw
65
+ # Prefer resolving relative to cwd when the directory does not exist yet.
66
+ search_root = directory if directory.is_dir() else Path.cwd() / directory
67
+ if search_root.is_dir():
68
+ for candidate in _candidate_app_files_in_dir(search_root.resolve()):
69
+ if candidate.is_file():
70
+ return candidate.resolve()
71
+ raise FileNotFoundError(
72
+ f"No app.caraer.yaml found under app directory: {search_root}"
73
+ )
74
+
75
+ if not resolved.exists():
76
+ raise FileNotFoundError(f"Local app file not found: {resolved}")
77
+ raise ValueError(f"Local app path is not a file: {resolved}")
78
+
79
+
80
+ def load_local_app(path: str | Path) -> dict[str, Any]:
81
+ payload = load_structured_file(str(resolve_app_file_path(path)))
82
+ if not isinstance(payload, dict):
83
+ raise ValueError("Local app file must contain a YAML/JSON object.")
84
+ return payload
85
+
86
+
87
+ def local_app_summary(path: Path, payload: dict[str, Any] | None = None) -> dict[str, Any]:
88
+ data = payload if payload is not None else load_local_app(path)
89
+ return {
90
+ "source": "local",
91
+ "path": str(path),
92
+ "name": data.get("name"),
93
+ "label": data.get("label"),
94
+ "uuid": data.get("uuid"),
95
+ "privateApp": data.get("privateApp"),
96
+ }
97
+
98
+
99
+ def discover_local_app_files(start: Path | None = None) -> list[Path]:
100
+ """Find candidate local app definition files near the current directory."""
101
+ root = (start or Path.cwd()).resolve()
102
+ found: list[Path] = []
103
+ seen: set[Path] = set()
104
+
105
+ def _add(path: Path) -> None:
106
+ if not path.is_file():
107
+ return
108
+ # Ignore function manifests.
109
+ if path.name == "function.caraer.json":
110
+ return
111
+ resolved = path.resolve()
112
+ if resolved in seen:
113
+ return
114
+ seen.add(resolved)
115
+ found.append(resolved)
116
+
117
+ for name in APP_FILE_NAMES:
118
+ _add(root / name)
119
+
120
+ for name in APP_FILE_NAMES:
121
+ _add(root / "src" / "app" / name)
122
+
123
+ # Child app folders: <dir>/caraer.json → src/app/app.caraer.yaml
124
+ for marker in (WORKSPACE_FILE, LEGACY_PROJECT_FILE):
125
+ for workspace in sorted(root.glob(f"*/{marker}")):
126
+ _add(app_manifest_path(workspace.parent))
127
+
128
+ # Also allow immediate child app manifests.
129
+ for name in APP_FILE_NAMES:
130
+ for path in sorted(root.glob(f"*/{name}")):
131
+ _add(path)
132
+ for path in sorted(root.glob(f"*/src/app/{name}")):
133
+ _add(path)
134
+
135
+ return found
caraer_cli/main.py ADDED
@@ -0,0 +1,104 @@
1
+ from __future__ import annotations
2
+
3
+ import typer
4
+
5
+ from caraer_cli import __version__
6
+ from caraer_cli.commands import (
7
+ apps,
8
+ auth,
9
+ company,
10
+ profile,
11
+ publish,
12
+ sandbox,
13
+ skill,
14
+ webhooks,
15
+ )
16
+ from caraer_cli.completion_callbacks import complete_output_format, complete_profile
17
+ from caraer_cli.context import AppContext
18
+ from caraer_cli.errors import ApiError
19
+ from caraer_cli.formatters.output import print_error
20
+ from caraer_cli.state.config import active_profile, load_config
21
+ from caraer_cli.state.session import get_session_token
22
+
23
+ app = typer.Typer(
24
+ help="Caraer external developer CLI.",
25
+ no_args_is_help=True,
26
+ add_completion=False,
27
+ )
28
+ app.add_typer(auth.app, name="auth")
29
+ app.add_typer(company.app, name="company")
30
+ app.add_typer(apps.app, name="apps")
31
+ app.add_typer(webhooks.app, name="webhooks")
32
+ app.add_typer(publish.app, name="publish")
33
+ app.add_typer(sandbox.app, name="sandbox")
34
+ app.add_typer(profile.app, name="profile")
35
+ app.add_typer(skill.app, name="skill")
36
+
37
+
38
+ def _version_callback(value: bool) -> None:
39
+ if value:
40
+ typer.echo(f"caraer-cli {__version__}")
41
+ raise typer.Exit()
42
+
43
+
44
+ @app.callback()
45
+ def root(
46
+ ctx: typer.Context,
47
+ profile: str = typer.Option(
48
+ None,
49
+ "--profile",
50
+ help="Override config profile for this command.",
51
+ autocompletion=complete_profile,
52
+ ),
53
+ output: str = typer.Option(
54
+ None,
55
+ "--output",
56
+ help="table|json|yaml",
57
+ autocompletion=complete_output_format,
58
+ ),
59
+ debug: bool = typer.Option(False, "--debug", help="Show debug details on API errors."),
60
+ version: bool = typer.Option(
61
+ False,
62
+ "--version",
63
+ help="Show the CLI version and exit.",
64
+ callback=_version_callback,
65
+ is_eager=True,
66
+ ),
67
+ ) -> None:
68
+ """Caraer external developer CLI."""
69
+ # During shell completion Click/Typer sets resilient_parsing; keep side effects light.
70
+ if ctx.resilient_parsing:
71
+ ctx.obj = None
72
+ return
73
+
74
+ cfg = load_config()
75
+ profile_name, profile_cfg = active_profile(cfg, profile)
76
+ selected_output = output or profile_cfg.output
77
+ token = get_session_token(profile_name)
78
+ ctx.obj = AppContext(
79
+ config=cfg,
80
+ profile_name=profile_name,
81
+ profile=profile_cfg,
82
+ token=token,
83
+ output=selected_output,
84
+ debug=debug,
85
+ )
86
+
87
+
88
+ def run() -> None:
89
+ try:
90
+ app()
91
+ except ApiError as exc:
92
+ print_error(str(exc))
93
+ raise SystemExit(2) from None
94
+ except ValueError as exc:
95
+ print_error(str(exc))
96
+ raise SystemExit(2) from None
97
+ except typer.Exit:
98
+ raise
99
+ except SystemExit:
100
+ raise
101
+
102
+
103
+ if __name__ == "__main__":
104
+ run()
@@ -0,0 +1 @@
1
+ """Pydantic models for API envelopes."""
@@ -0,0 +1,52 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any
4
+
5
+ from pydantic import BaseModel, Field
6
+
7
+
8
+ class CaraerValidationError(BaseModel):
9
+ field: str | None = None
10
+ message: str | None = None
11
+ correctionSuggestion: str | None = None
12
+
13
+
14
+ class ErrorEnvelope(BaseModel):
15
+ message: str = "Request failed."
16
+ status: int = 500
17
+ errors: list[CaraerValidationError] | None = None
18
+ roles: list[str] | None = None
19
+ scopes: list[str] | None = None
20
+ stackTrace: str | None = None
21
+ requestId: str | None = None
22
+ correlationId: str | None = None
23
+
24
+
25
+ class SuccessEnvelope(BaseModel):
26
+ message: str = "Success"
27
+ data: Any = None
28
+
29
+
30
+ class PaginationEnvelope(SuccessEnvelope):
31
+ total: int = 0
32
+ page: int = 1
33
+ perPage: int = 25
34
+ lastPage: int = 1
35
+
36
+
37
+ class AuthTokenModel(BaseModel):
38
+ token: str | None = None
39
+ expiresAt: int | None = None
40
+
41
+
42
+ class CompanyModel(BaseModel):
43
+ uuid: str
44
+ name: str | None = None
45
+
46
+
47
+ class UserModel(BaseModel):
48
+ uuid: str
49
+ email: str | None = None
50
+ globalRole: str | None = None
51
+ token: AuthTokenModel | None = None
52
+ companies: list[CompanyModel] = Field(default_factory=list)
@@ -0,0 +1,17 @@
1
+ """Local Caraer developer project helpers."""
2
+
3
+ from caraer_cli.project.schema import (
4
+ PLATFORM_VERSION,
5
+ FunctionManifest,
6
+ ProjectConfig,
7
+ load_project_config,
8
+ save_project_config,
9
+ )
10
+
11
+ __all__ = [
12
+ "PLATFORM_VERSION",
13
+ "FunctionManifest",
14
+ "ProjectConfig",
15
+ "load_project_config",
16
+ "save_project_config",
17
+ ]
@@ -0,0 +1,154 @@
1
+ """Discover / write modular appBars files under src/app/app-bars/."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import re
7
+ from pathlib import Path
8
+ from typing import Any
9
+
10
+ from caraer_cli.project.paths import app_bars_dir
11
+ from caraer_cli.project.schema import ProjectConfig
12
+
13
+ LOCAL_APP_BAR_KEYS = (
14
+ "uuid",
15
+ "location",
16
+ "label",
17
+ "tooltipLabel",
18
+ "description",
19
+ "actionLabel",
20
+ "iframeUrl",
21
+ "icon",
22
+ "settingsSchema",
23
+ "webhook",
24
+ )
25
+
26
+ ACTION_BASED_LOCATIONS = frozenset({"RECORD_PREVIEW", "RECORD_OVERVIEW"})
27
+
28
+
29
+ def _slug(value: str) -> str:
30
+ text = value.strip().lower()
31
+ text = re.sub(r"[^a-z0-9]+", "-", text)
32
+ return text.strip("-") or "app-bar"
33
+
34
+
35
+ def app_bar_filename(item: dict[str, Any]) -> str:
36
+ location = _slug(str(item.get("location") or "bar"))
37
+ label = _slug(str(item.get("label") or "app-bar"))
38
+ return f"{location}-{label}.json"
39
+
40
+
41
+ def app_bar_identity(item: dict[str, Any]) -> str:
42
+ location = str(item.get("location") or "").strip().upper()
43
+ label = str(item.get("label") or "").strip().lower()
44
+ return f"{location}|{label}"
45
+
46
+
47
+ def _sanitize_webhook(webhook: Any) -> dict[str, Any] | None:
48
+ if not isinstance(webhook, dict):
49
+ return None
50
+ cleaned: dict[str, Any] = {}
51
+ for key in (
52
+ "uuid",
53
+ "topic",
54
+ "deliveryMode",
55
+ "url",
56
+ "description",
57
+ "enabled",
58
+ "webhookFormat",
59
+ "serverlessFunction",
60
+ ):
61
+ if key in webhook and webhook[key] is not None:
62
+ cleaned[key] = webhook[key]
63
+ sf = cleaned.get("serverlessFunction")
64
+ if isinstance(sf, dict):
65
+ sf_out: dict[str, Any] = {}
66
+ if sf.get("name"):
67
+ sf_out["name"] = sf["name"]
68
+ elif sf.get("uuid"):
69
+ sf_out["uuid"] = sf["uuid"]
70
+ if sf_out:
71
+ cleaned["serverlessFunction"] = sf_out
72
+ else:
73
+ cleaned.pop("serverlessFunction", None)
74
+ return cleaned or None
75
+
76
+
77
+ def sanitize_app_bar(item: dict[str, Any]) -> dict[str, Any]:
78
+ payload: dict[str, Any] = {}
79
+ for key in LOCAL_APP_BAR_KEYS:
80
+ if key not in item or item[key] is None:
81
+ continue
82
+ if key == "webhook":
83
+ webhook = _sanitize_webhook(item[key])
84
+ if webhook:
85
+ payload["webhook"] = webhook
86
+ else:
87
+ payload[key] = item[key]
88
+ return payload
89
+
90
+
91
+ def discover_local_app_bars(
92
+ root: Path, config: ProjectConfig
93
+ ) -> list[tuple[Path, dict[str, Any]]]:
94
+ base = app_bars_dir(root, config.srcDir)
95
+ if not base.is_dir():
96
+ return []
97
+ found: list[tuple[Path, dict[str, Any]]] = []
98
+ for path in sorted(base.glob("*.json")):
99
+ data = json.loads(path.read_text(encoding="utf-8"))
100
+ if isinstance(data, dict) and data.get("label") and data.get("location"):
101
+ found.append((path, sanitize_app_bar(data)))
102
+ return found
103
+
104
+
105
+ def write_app_bars_files(
106
+ root: Path, config: ProjectConfig, items: list[dict[str, Any]]
107
+ ) -> int:
108
+ base = app_bars_dir(root, config.srcDir)
109
+ base.mkdir(parents=True, exist_ok=True)
110
+ for existing in base.glob("*.json"):
111
+ existing.unlink()
112
+ count = 0
113
+ for item in items:
114
+ if not isinstance(item, dict):
115
+ continue
116
+ sanitized = sanitize_app_bar(item)
117
+ if not app_bar_identity(sanitized).strip("|"):
118
+ continue
119
+ path = base / app_bar_filename(sanitized)
120
+ path.write_text(json.dumps(sanitized, indent=2) + "\n", encoding="utf-8")
121
+ count += 1
122
+ return count
123
+
124
+
125
+ def resolve_app_bar_functions(
126
+ item: dict[str, Any],
127
+ *,
128
+ fn_by_name: dict[str, str],
129
+ strict: bool = True,
130
+ ) -> dict[str, Any] | None:
131
+ out = dict(item)
132
+ out.pop("uuid", None)
133
+ webhook = out.get("webhook")
134
+ if not isinstance(webhook, dict):
135
+ return out
136
+ webhook = dict(webhook)
137
+ webhook.pop("uuid", None)
138
+ sf = webhook.get("serverlessFunction")
139
+ if isinstance(sf, dict):
140
+ if sf.get("uuid"):
141
+ webhook["serverlessFunction"] = {"uuid": sf["uuid"]}
142
+ else:
143
+ name = sf.get("name")
144
+ if name and name in fn_by_name:
145
+ webhook["serverlessFunction"] = {"uuid": fn_by_name[name]}
146
+ elif name:
147
+ if strict:
148
+ raise ValueError(
149
+ f"App bar references function '{name}' but no UUID is known. "
150
+ "Push functions first."
151
+ )
152
+ return None
153
+ out["webhook"] = webhook
154
+ return out
@@ -0,0 +1,68 @@
1
+ """Render and locate local app manifests (YAML preferred)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+ import yaml
8
+
9
+ APP_MANIFEST_YAML = "app.caraer.yaml"
10
+ APP_MANIFEST_JSON = "app.caraer.json" # legacy; still loaded if present
11
+ APP_MANIFEST_NAMES = (APP_MANIFEST_YAML, APP_MANIFEST_JSON, "app.yaml", "app.yml", "app.json")
12
+
13
+ _EXAMPLE_REQUIRED_SCOPES = """\
14
+ # Example scopes (uncomment and edit):
15
+ # requiredScopes:
16
+ # - tools.forms.all
17
+ # - records.candidate.properties_all
18
+ """
19
+
20
+ _EXAMPLE_SETTINGS_SCHEMA = """\
21
+ # Edit settingsSchema above, or: caraer apps add-setting
22
+ # Optional modular files also work: src/app/settings/<name>.json (--modular)
23
+ """
24
+
25
+ _EXAMPLE_PRICING_PLANS = """\
26
+ # Edit pricingPlans above, or: caraer apps add-pricing-plan
27
+ # Optional modular files: src/app/pricing/<slug>.json (--modular)
28
+ """
29
+
30
+ _EXAMPLE_APP_BARS = """\
31
+ # Edit appBars above, or: caraer apps add-app-bar
32
+ # Optional modular files: src/app/app-bars/<slug>.json (--modular)
33
+ # Lifecycle hooks: src/app/lifecycle/ + functions/on-* (caraer apps add-lifecycle-hook)
34
+ """
35
+
36
+
37
+ def render_app_manifest(payload: dict[str, Any], *, include_examples: bool = True) -> str:
38
+ """Serialize an app manifest as YAML, optionally with commented-out examples."""
39
+ from caraer_cli.project.json_schemas import YAML_LANGUAGE_SERVER_COMMENT
40
+
41
+ data = dict(payload)
42
+ for key in ("requiredScopes", "settingsSchema", "pricingPlans", "appBars"):
43
+ data.setdefault(key, [])
44
+
45
+ body = yaml.safe_dump(
46
+ data,
47
+ sort_keys=False,
48
+ allow_unicode=True,
49
+ default_flow_style=False,
50
+ ).rstrip() + "\n"
51
+ header = YAML_LANGUAGE_SERVER_COMMENT + "\n"
52
+ if not include_examples:
53
+ return header + body
54
+
55
+ lines = body.splitlines()
56
+ out: list[str] = [YAML_LANGUAGE_SERVER_COMMENT]
57
+ for line in lines:
58
+ out.append(line)
59
+ stripped = line.strip()
60
+ if stripped == "requiredScopes: []":
61
+ out.append(_EXAMPLE_REQUIRED_SCOPES.rstrip("\n"))
62
+ elif stripped == "settingsSchema: []":
63
+ out.append(_EXAMPLE_SETTINGS_SCHEMA.rstrip("\n"))
64
+ elif stripped == "pricingPlans: []":
65
+ out.append(_EXAMPLE_PRICING_PLANS.rstrip("\n"))
66
+ elif stripped == "appBars: []":
67
+ out.append(_EXAMPLE_APP_BARS.rstrip("\n"))
68
+ return "\n".join(out) + "\n"