-
-
Notifications
You must be signed in to change notification settings - Fork 94
feat: add stac agent skills #444
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
6a68dc8
feat: add stac agent skills
divyanshub024 8746c65
feat: add templates for custom Stac actions and widgets
divyanshub024 27f82eb
feat: add pre-check for file existence in scaffold scripts
divyanshub024 bec2cd6
fix: correct indentation in style-recipes documentation
divyanshub024 98b1e2d
fix: update screen name validation in new_screen.py
divyanshub024 fa13f55
docs: enhance cache-debug documentation with code comments
divyanshub024 28be23e
fix: optimize STAC screen annotation check in stac_doctor.py
divyanshub024 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| [ | ||
| { | ||
| "name": "stac-quickstart", | ||
| "description": "Initialize and validate a Stac-enabled Flutter project and run first deploy.", | ||
| "path": "skills/stac-quickstart", | ||
| "default_prompt": "Use $stac-quickstart to initialize my Flutter app with Stac and deploy a first screen." | ||
| }, | ||
| { | ||
| "name": "stac-screen-builder", | ||
| "description": "Build and scaffold Stac DSL screens and themes from product requirements.", | ||
| "path": "skills/stac-screen-builder", | ||
| "default_prompt": "Use $stac-screen-builder to turn product requirements into Stac DSL screens and themes." | ||
| }, | ||
| { | ||
| "name": "stac-custom-extensions", | ||
| "description": "Scaffold custom Stac widget/action models and parser integrations.", | ||
| "path": "skills/stac-custom-extensions", | ||
| "default_prompt": "Use $stac-custom-extensions to scaffold a custom widget or action parser for my Stac app." | ||
| }, | ||
| { | ||
| "name": "stac-troubleshooter", | ||
| "description": "Diagnose Stac build, deploy, rendering, caching, and navigation issues.", | ||
| "path": "skills/stac-troubleshooter", | ||
| "default_prompt": "Use $stac-troubleshooter to diagnose why my Stac screen is not building, deploying, or rendering." | ||
| } | ||
| ] |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,51 @@ | ||
| --- | ||
| name: stac-custom-extensions | ||
| description: Scaffold and integrate custom Stac widgets and actions with parsers and registration checks. Use when users ask to build new StacParser or StacActionParser implementations, generate custom model classes, or verify parser registration inside Stac.initialize. | ||
| --- | ||
|
|
||
| # Stac Custom Extensions | ||
|
|
||
| ## Overview | ||
|
|
||
| Use this skill to add custom widgets/actions that serialize cleanly and register correctly in a Stac app. | ||
|
|
||
| ## Workflow | ||
|
|
||
| 1. Choose extension type: widget or action. | ||
| 2. Scaffold model and parser files with scripts. | ||
| 3. Add generated files to app codebase and run codegen. | ||
| 4. Verify parser registration in `main.dart`. | ||
| 5. Validate runtime wiring with a minimal usage example. | ||
|
|
||
| ## Required Inputs | ||
|
|
||
| - PascalCase extension name. | ||
| - Runtime type id (`type` or `actionType`). | ||
| - Output directory for generated files. | ||
| - Path to `main.dart` for registration check. | ||
|
|
||
| ## Output Contract | ||
|
|
||
| - Produce model + parser pair with consistent type ids. | ||
| - Include registration snippet for `Stac.initialize`. | ||
| - Include `build_runner` command when json serialization is used. | ||
|
|
||
| ## References | ||
|
|
||
| - Read `references/custom-widget-checklist.md` for widget model/parser flow. | ||
| - Read `references/custom-action-checklist.md` for action model/parser flow. | ||
| - Read `references/parser-registration.md` for initialization wiring. | ||
| - Read `references/converters-guide.md` for converter usage patterns. | ||
|
|
||
| ## Scripts | ||
|
|
||
| - `scripts/scaffold_custom_widget.py --name <Name> --type <widgetType> --out-dir <path>` | ||
| - `scripts/scaffold_custom_action.py --name <Name> --action-type <actionType> --out-dir <path>` | ||
| - `scripts/check_parser_registration.py --main-dart <path> --parser-class <ClassName>` | ||
|
|
||
| ## Templates | ||
|
|
||
| - `assets/templates/custom_widget.dart.tmpl` | ||
| - `assets/templates/custom_widget_parser.dart.tmpl` | ||
| - `assets/templates/custom_action.dart.tmpl` | ||
| - `assets/templates/custom_action_parser.dart.tmpl` | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| interface: | ||
| display_name: "Stac Custom Extensions" | ||
| short_description: "Build custom Stac widgets/actions with parsers" | ||
| brand_color: "#15803D" | ||
| default_prompt: "Use $stac-custom-extensions to scaffold a custom widget or action parser for my Stac app." |
22 changes: 22 additions & 0 deletions
22
skills/stac-custom-extensions/assets/templates/custom_action.dart.tmpl
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| import 'package:json_annotation/json_annotation.dart'; | ||
| import 'package:stac_core/stac_core.dart'; | ||
|
|
||
| part '__FILE_BASENAME__.g.dart'; | ||
|
|
||
| @JsonSerializable() | ||
| class __CLASS_NAME__ extends StacAction { | ||
| const __CLASS_NAME__({ | ||
| this.message, | ||
| }); | ||
|
|
||
| final String? message; | ||
|
|
||
| @override | ||
| String get actionType => '__ACTION_TYPE__'; | ||
|
|
||
| factory __CLASS_NAME__.fromJson(Map<String, dynamic> json) => | ||
| _$__CLASS_NAME__FromJson(json); | ||
|
|
||
| @override | ||
| Map<String, dynamic> toJson() => _$__CLASS_NAME__ToJson(this); | ||
| } |
21 changes: 21 additions & 0 deletions
21
skills/stac-custom-extensions/assets/templates/custom_action_parser.dart.tmpl
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| import 'package:flutter/material.dart'; | ||
| import 'package:stac_framework/stac_framework.dart'; | ||
| import '__FILE_BASENAME__.dart'; | ||
divyanshub024 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| class __PARSER_CLASS__ implements StacActionParser<__CLASS_NAME__> { | ||
| const __PARSER_CLASS__(); | ||
|
|
||
| @override | ||
| String get actionType => '__ACTION_TYPE__'; | ||
|
|
||
| @override | ||
| __CLASS_NAME__ getModel(Map<String, dynamic> json) => | ||
| __CLASS_NAME__.fromJson(json); | ||
|
|
||
| @override | ||
| FutureOr<dynamic> onCall(BuildContext context, __CLASS_NAME__ model) { | ||
| debugPrint(model.message ?? '__CLASS_NAME__ called'); | ||
| // TODO: Implement action logic | ||
| return null; | ||
| } | ||
| } | ||
22 changes: 22 additions & 0 deletions
22
skills/stac-custom-extensions/assets/templates/custom_widget.dart.tmpl
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| import 'package:json_annotation/json_annotation.dart'; | ||
| import 'package:stac_core/stac_core.dart'; | ||
|
|
||
| part '__FILE_BASENAME__.g.dart'; | ||
|
|
||
| @JsonSerializable() | ||
| class __CLASS_NAME__ extends StacWidget { | ||
| const __CLASS_NAME__({ | ||
| this.label, | ||
| }); | ||
|
|
||
| final String? label; | ||
|
|
||
| @override | ||
| String get type => '__WIDGET_TYPE__'; | ||
|
|
||
| factory __CLASS_NAME__.fromJson(Map<String, dynamic> json) => | ||
| _$__CLASS_NAME__FromJson(json); | ||
|
|
||
| @override | ||
| Map<String, dynamic> toJson() => _$__CLASS_NAME__ToJson(this); | ||
| } |
19 changes: 19 additions & 0 deletions
19
skills/stac-custom-extensions/assets/templates/custom_widget_parser.dart.tmpl
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| import 'package:flutter/material.dart'; | ||
| import 'package:stac_framework/stac_framework.dart'; | ||
| import '__FILE_BASENAME__.dart'; | ||
|
|
||
| class __PARSER_CLASS__ extends StacParser<__CLASS_NAME__> { | ||
| const __PARSER_CLASS__(); | ||
|
|
||
| @override | ||
| String get type => '__WIDGET_TYPE__'; | ||
|
|
||
| @override | ||
| __CLASS_NAME__ getModel(Map<String, dynamic> json) => | ||
| __CLASS_NAME__.fromJson(json); | ||
|
|
||
| @override | ||
| Widget parse(BuildContext context, __CLASS_NAME__ model) { | ||
| return Text(model.label ?? '__CLASS_NAME__'); | ||
| } | ||
| } |
13 changes: 13 additions & 0 deletions
13
skills/stac-custom-extensions/references/converters-guide.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| # Converters Guide | ||
|
|
||
| ## DoubleConverter | ||
|
|
||
| Use when JSON may send integer values for `double` fields. | ||
|
|
||
| ## StacWidgetConverter | ||
|
|
||
| Use for child widget fields in custom widget models. | ||
|
|
||
| ## General Rule | ||
|
|
||
| Use converters only when field serialization would otherwise fail or lose fidelity. |
9 changes: 9 additions & 0 deletions
9
skills/stac-custom-extensions/references/custom-action-checklist.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| # Custom Action Checklist | ||
|
|
||
| 1. Define action model extending `StacAction`. | ||
| 2. Set unique `actionType` string. | ||
| 3. Add `@JsonSerializable()` and `part` file. | ||
| 4. Implement `fromJson` and `toJson`. | ||
| 5. Create parser implementing `StacActionParser<Model>`. | ||
| 6. Register parser in `Stac.initialize(actionParsers: [...])`. | ||
| 7. Trigger action from widget callback (`onPressed`, `onTap`, etc.). |
13 changes: 13 additions & 0 deletions
13
skills/stac-custom-extensions/references/custom-widget-checklist.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| # Custom Widget Checklist | ||
|
|
||
| 1. Define widget model extending `StacWidget`. | ||
| 2. Set unique `type` string. | ||
| 3. Add `@JsonSerializable()` and `part` file. | ||
| 4. Implement `fromJson` and `toJson`. | ||
| 5. Create parser extending `StacParser<Model>`. | ||
| 6. Register parser in `Stac.initialize(parsers: [...])`. | ||
| 7. Run: | ||
|
|
||
| ```bash | ||
| dart run build_runner build --delete-conflicting-outputs | ||
| ``` |
27 changes: 27 additions & 0 deletions
27
skills/stac-custom-extensions/references/parser-registration.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| # Parser Registration | ||
|
|
||
| ## Widget Parser | ||
|
|
||
| ```dart | ||
| await Stac.initialize( | ||
| options: defaultStacOptions, | ||
| parsers: const [ | ||
| MyWidgetParser(), | ||
| ], | ||
| ); | ||
| ``` | ||
|
|
||
| ## Action Parser | ||
|
|
||
| ```dart | ||
| await Stac.initialize( | ||
| options: defaultStacOptions, | ||
| actionParsers: const [ | ||
| MyActionParser(), | ||
| ], | ||
| ); | ||
| ``` | ||
|
|
||
| ## Validation Rule | ||
|
|
||
| - Parser class name should appear in the same file that calls `Stac.initialize`. |
54 changes: 54 additions & 0 deletions
54
skills/stac-custom-extensions/scripts/check_parser_registration.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,54 @@ | ||
| #!/usr/bin/env python3 | ||
| """Check that a parser class appears in the same file as Stac.initialize.""" | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| import argparse | ||
| from pathlib import Path | ||
| import sys | ||
|
|
||
|
|
||
| def parse_args() -> argparse.Namespace: | ||
| parser = argparse.ArgumentParser( | ||
| description="Validate parser registration in a main.dart file." | ||
| ) | ||
| parser.add_argument("--main-dart", required=True, help="Path to main.dart") | ||
| parser.add_argument( | ||
| "--parser-class", required=True, help="Parser class to locate in file" | ||
| ) | ||
| return parser.parse_args() | ||
|
|
||
|
|
||
| def main() -> int: | ||
| args = parse_args() | ||
| main_dart = Path(args.main_dart).expanduser().resolve() | ||
|
|
||
| if not main_dart.exists(): | ||
| print(f"[FAIL] File not found: {main_dart}") | ||
| return 1 | ||
|
|
||
| content = main_dart.read_text(encoding="utf-8", errors="ignore") | ||
| init_index = content.find("Stac.initialize(") | ||
| parser_index = content.find(args.parser_class) | ||
|
|
||
| if init_index < 0: | ||
| print("[FAIL] Stac.initialize(...) call not found") | ||
| return 1 | ||
|
|
||
| if parser_index < 0: | ||
| print(f"[FAIL] Parser class not found: {args.parser_class}") | ||
| return 1 | ||
|
|
||
| if parser_index < init_index: | ||
| print( | ||
| "[WARN] Parser class appears before initialize call; verify registration location manually" | ||
| ) | ||
| else: | ||
| print(f"[OK] Found parser class near/after initialize call: {args.parser_class}") | ||
|
|
||
| print("Registration check completed.") | ||
| return 0 | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| sys.exit(main()) |
96 changes: 96 additions & 0 deletions
96
skills/stac-custom-extensions/scripts/scaffold_custom_action.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,96 @@ | ||
| #!/usr/bin/env python3 | ||
| """Scaffold custom action model and parser files from templates.""" | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| import argparse | ||
| from pathlib import Path | ||
| import re | ||
| import sys | ||
|
|
||
|
|
||
| def to_snake_case(name: str) -> str: | ||
| return re.sub(r"(?<!^)(?=[A-Z])", "_", name).lower() | ||
|
|
||
|
|
||
| def parse_args() -> argparse.Namespace: | ||
| parser = argparse.ArgumentParser(description="Scaffold custom Stac action files.") | ||
| parser.add_argument("--name", required=True, help="PascalCase class name.") | ||
| parser.add_argument( | ||
| "--action-type", required=True, help="Stac action type identifier." | ||
| ) | ||
| parser.add_argument("--out-dir", required=True, help="Output directory.") | ||
| parser.add_argument("--force", action="store_true", help="Overwrite existing files.") | ||
| return parser.parse_args() | ||
|
|
||
|
|
||
| def render(template: str, mapping: dict[str, str]) -> str: | ||
| content = template | ||
| for key, value in mapping.items(): | ||
| content = content.replace(key, value) | ||
| return content | ||
|
|
||
|
|
||
| def write_file(path: Path, content: str, force: bool) -> None: | ||
| if path.exists() and not force: | ||
| raise FileExistsError(f"File already exists: {path}") | ||
| path.write_text(content, encoding="utf-8") | ||
|
|
||
|
|
||
| def main() -> int: | ||
| args = parse_args() | ||
|
|
||
| if not re.fullmatch(r"[A-Z][A-Za-z0-9]*", args.name): | ||
| print("[FAIL] --name must be PascalCase.") | ||
| return 1 | ||
| if not re.fullmatch(r"[A-Za-z][A-Za-z0-9_]*", args.action_type): | ||
| print("[FAIL] --action-type must start with a letter and use alphanumeric/_ only.") | ||
| return 1 | ||
|
|
||
| skill_root = Path(__file__).resolve().parents[1] | ||
| model_template = (skill_root / "assets/templates/custom_action.dart.tmpl").read_text( | ||
| encoding="utf-8" | ||
| ) | ||
| parser_template = ( | ||
| skill_root / "assets/templates/custom_action_parser.dart.tmpl" | ||
| ).read_text(encoding="utf-8") | ||
|
|
||
| file_basename = f"{to_snake_case(args.name)}_action" | ||
| parser_class = f"Stac{args.name}ActionParser" | ||
| mapping = { | ||
| "__CLASS_NAME__": args.name, | ||
| "__ACTION_TYPE__": args.action_type, | ||
| "__FILE_BASENAME__": file_basename, | ||
| "__PARSER_CLASS__": parser_class, | ||
| } | ||
|
|
||
| out_dir = Path(args.out_dir).expanduser().resolve() | ||
| out_dir.mkdir(parents=True, exist_ok=True) | ||
|
|
||
| model_file = out_dir / f"{file_basename}.dart" | ||
| parser_file = out_dir / f"{file_basename}_parser.dart" | ||
|
|
||
| # Pre-check both files before writing to avoid partial creation | ||
| if not args.force: | ||
| if model_file.exists(): | ||
| print(f"[FAIL] File already exists: {model_file}") | ||
| return 1 | ||
| if parser_file.exists(): | ||
| print(f"[FAIL] File already exists: {parser_file}") | ||
| return 1 | ||
|
|
||
| try: | ||
| write_file(model_file, render(model_template, mapping), args.force) | ||
| write_file(parser_file, render(parser_template, mapping), args.force) | ||
| except FileExistsError as exc: | ||
| # This should not happen due to pre-check, but handle it anyway | ||
| print(f"[FAIL] {exc}") | ||
| return 1 | ||
divyanshub024 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| print(f"[OK] Created {model_file}") | ||
| print(f"[OK] Created {parser_file}") | ||
| return 0 | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| sys.exit(main()) | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.