diff --git a/agentstack/cli/__init__.py b/agentstack/cli/__init__.py index fba3c0c2..7e7d2bac 100644 --- a/agentstack/cli/__init__.py +++ b/agentstack/cli/__init__.py @@ -1,9 +1,15 @@ -from .cli import configure_default_model, welcome_message, get_validated_input, parse_insertion_point +from .cli import ( + configure_default_model, + welcome_message, + get_validated_input, + parse_insertion_point, + undo, +) from .init import init_project from .wizard import run_wizard from .run import run_project from .tools import list_tools, add_tool, remove_tool from .tasks import add_task from .agents import add_agent -from .templates import insert_template, export_template +from .templates import insert_template, export_template, switch_framework diff --git a/agentstack/cli/cli.py b/agentstack/cli/cli.py index a40e83ff..70e7c3ba 100644 --- a/agentstack/cli/cli.py +++ b/agentstack/cli/cli.py @@ -7,6 +7,7 @@ from agentstack.exceptions import ValidationError from agentstack.utils import validator_not_empty, is_snake_case from agentstack.generation import InsertionPoint +from agentstack import repo PREFERRED_MODELS = [ @@ -34,6 +35,25 @@ def welcome_message(): log.info(border) +def undo() -> None: + """Undo the last committed changes.""" + conf.assert_project() + + changed_files = repo.get_uncommitted_files() + if changed_files: + log.warning("There are uncommitted changes that may be overwritten.") + for changed in changed_files: + log.info(f" - {changed}") + should_continue = inquirer.confirm( + message="Do you want to continue?", + default=False, + ) + if not should_continue: + return + + repo.revert_last_commit(hard=True) + + def configure_default_model(): """Set the default model""" agentstack_config = ConfigFile() diff --git a/agentstack/cli/templates.py b/agentstack/cli/templates.py index 81a99fec..48a23f8f 100644 --- a/agentstack/cli/templates.py +++ b/agentstack/cli/templates.py @@ -65,7 +65,7 @@ def insert_template(name: str, template: TemplateConfig, framework: Optional[str cookiecutter(str(template_path), no_input=True, extra_context=None) -def export_template(output_filename: str): +def _export_template_data() -> TemplateConfig: """ Export the current project as a template. """ @@ -137,7 +137,7 @@ def export_template(output_filename: str): ] ) - template = TemplateConfig( + return TemplateConfig( template_version=CURRENT_VERSION, name=metadata.project_name, description=metadata.project_description, @@ -151,8 +151,33 @@ def export_template(output_filename: str): graph=graph, ) + +def export_template(output_filename: str): + """ + Export the current project as a template file. + """ + template = _export_template_data() try: template.write_to_file(conf.PATH / output_filename) log.success(f"Template saved to: {conf.PATH / output_filename}") except Exception as e: raise Exception(f"Failed to write template to file: {e}") + + +def switch_framework(framework: str) -> None: + """ + Switch the current project to a different framework. + + - export the current project as a template. + - create a new branch & switch to it. + - empty the directory (hopefully partially, but cookiecutter wants an empty dir) + - generate a new project with `insert_template` + """ + if not framework in frameworks.SUPPORTED_FRAMEWORKS: + raise ValueError(f"Framework {framework} is not supported.") + + raise NotImplementedError("switch_framework is not implemented") + template = _export_template_data() + # TODO we can't do this with cookiecutter + insert_template(template.name, template, framework=framework) + diff --git a/agentstack/main.py b/agentstack/main.py index 3139a9a5..eaccada1 100644 --- a/agentstack/main.py +++ b/agentstack/main.py @@ -13,6 +13,8 @@ add_task, run_project, export_template, + undo, + switch_framework, ) from agentstack.telemetry import track_cli_command, update_telemetry from agentstack.utils import get_version, term_color @@ -154,7 +156,14 @@ def _main(): ) export_parser.add_argument('filename', help='The name of the file to export to') - update = subparsers.add_parser('update', aliases=['u'], help='Check for updates', parents=[global_parser]) + switch_parser = subparsers.add_parser('switch', help='Switch frameworks', parents=[global_parser]) + switch_subparsers = switch_parser.add_subparsers(dest='switch_command', help='Switch commands') + + switch_framework_parser = switch_subparsers.add_parser('framework', help='Switch frameworks') + switch_framework_parser.add_argument('framework', help='The framework to switch to') + + undo_parser = subparsers.add_parser('undo', help='Undo the last change to your project', parents=[global_parser]) + update_parser = subparsers.add_parser('update', aliases=['u'], help='Check for updates', parents=[global_parser]) # Parse known args and store unknown args in extras; some commands use them later on args, extra_args = parser.parse_known_args() @@ -228,6 +237,13 @@ def _main(): generate_parser.print_help() elif args.command in ['export', 'e']: export_template(args.filename) + elif args.command in ['switch']: + if args.switch_command in ['framework']: + switch_framework(args.framework) + else: + switch_parser.print_help() + elif args.command in ['undo']: + undo() else: parser.print_help() diff --git a/agentstack/repo.py b/agentstack/repo.py index e550041a..ec1f32cb 100644 --- a/agentstack/repo.py +++ b/agentstack/repo.py @@ -190,3 +190,24 @@ def get_uncommitted_files() -> list[str]: untracked = repo.untracked_files modified = [item.a_path for item in repo.index.diff(None) if item.a_path] return untracked + modified + + +def revert_last_commit(hard: bool = False) -> None: + """ + Revert the last commit in the current project. + """ + try: + repo = _get_repo() + except EnvironmentError as e: + return # git is not installed or tracking is disabled + + if len(repo.head.commit.parents) == 0: + log.error("No commits to revert.") + return + + def _format_commit_message(commit): + return commit.message.split('\n')[0] + + log.info(f"Reverting: {_format_commit_message(repo.head.commit)}") + repo.git.reset('HEAD~1', hard=hard) + log.info(f"Head is now at: {_format_commit_message(repo.head.commit)}") diff --git a/docs/llms.txt b/docs/llms.txt index 2e0af5b7..fa23ea5c 100644 --- a/docs/llms.txt +++ b/docs/llms.txt @@ -514,10 +514,6 @@ which adheres to a common pattern or exporting your project to share. Templates are versioned, and each previous version provides a method to convert it's content to the current version. -> TODO: Templates are currently identified as `proj_templates` since they conflict -with the templates used by `generation`. Move existing templates to be part of -the generation package. - ### `TemplateConfig.from_user_input(identifier: str)` `` Returns a `TemplateConfig` object for either a URL, file path, or builtin template name. @@ -716,7 +712,7 @@ title: 'System Analyzer' description: 'Inspect a project directory and improve it' --- -[View Template](https://github.com/AgentOps-AI/AgentStack/blob/main/agentstack/templates/proj_templates/system_analyzer.json) +[View Template](https://github.com/AgentOps-AI/AgentStack/blob/main/agentstack/templates/system_analyzer.json) ```bash agentstack init --template=system_analyzer @@ -737,7 +733,7 @@ title: 'Researcher' description: 'Research and report result from a query' --- -[View Template](https://github.com/AgentOps-AI/AgentStack/blob/main/agentstack/templates/proj_templates/research.json) +[View Template](https://github.com/AgentOps-AI/AgentStack/blob/main/agentstack/templates/research.json) ```bash agentstack init --template=research @@ -828,7 +824,54 @@ title: 'Content Creator' description: 'Research a topic and create content on it' --- -[View Template](https://github.com/AgentOps-AI/AgentStack/blob/main/agentstack/templates/proj_templates/content_creator.json) +[View Template](https://github.com/AgentOps-AI/AgentStack/blob/main/agentstack/templates/content_creator.json) + +## frameworks/list.mdx + +--- +title: Frameworks +description: 'Supported frameworks in AgentStack' +icon: 'ship' +--- + +These are documentation links to the frameworks supported directly by AgentStack. + +To start a project with one of these frameworks, use +```bash +agentstack init --framework +``` + +## Framework Docs + + + An intuitive agentic framework (recommended) + + + A complex but capable framework with a _steep_ learning curve + + + A simple framework with a cult following + + + An expansive framework with many ancillary features + + ## tools/package-structure.mdx @@ -1043,7 +1086,7 @@ You can pass the `--wizard` flag to `agentstack init` to use an interactive proj You can also pass a `--template=` argument to `agentstack init` which will pre-populate your project with functionality from a built-in template, or one found on the internet. A `template_name` can be one of three identifiers: -- A built-in AgentStack template (see the `templates/proj_templates` directory in the AgentStack repo for bundled templates). +- A built-in AgentStack template (see the `templates` directory in the AgentStack repo for bundled templates). - A template file from the internet; pass the full https URL of the template. - A local template file; pass an absolute or relative path.