From b17a7dab33cff8baabb37b138dccd1f49701e344 Mon Sep 17 00:00:00 2001 From: chcweblogin Date: Sat, 6 Jun 2026 13:59:17 +0530 Subject: [PATCH 1/4] feat: add Dart/Flutter-native install path for Stac skills MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add SkillsCommand with 'stac skills add' subcommand - AddCommand fetches repo ZIP, parses skills/catalog.json, and copies skill dirs into .agents/skills/ — no Node/npm required - Prompt users in 'stac init' to optionally install agent skills - Register SkillsCommand in the CLI runner - Add archive: ^4.0.9 dependency for ZIP extraction - Update docs/skills.mdx to show Dart-native path first, keeping npx as an alternative - Add tests for SkillsCommand and AddCommand (7 tests pass) Closes #480 --- docs/skills.mdx | 8 +- packages/stac_cli/bin/stac_cli.dart | 2 + .../lib/src/commands/init_command.dart | 11 ++ .../lib/src/commands/skills/add_command.dart | 147 ++++++++++++++++++ .../lib/src/commands/skills_command.dart | 15 ++ packages/stac_cli/pubspec.lock | 23 ++- packages/stac_cli/pubspec.yaml | 1 + 7 files changed, 203 insertions(+), 4 deletions(-) create mode 100644 packages/stac_cli/lib/src/commands/skills/add_command.dart create mode 100644 packages/stac_cli/lib/src/commands/skills_command.dart diff --git a/docs/skills.mdx b/docs/skills.mdx index 5fec95c7d..6c2b90a11 100644 --- a/docs/skills.mdx +++ b/docs/skills.mdx @@ -7,7 +7,13 @@ Think of Stac Skills as expert assistants that know Stac inside and out. They he ## Installation -Install all Stac skills with one command: +Install all Stac skills natively with the Stac CLI: + +```bash +stac skills add +``` + +Alternatively, you can install them using `npx`: ```bash npx skills add https://github.com/StacDev/stac diff --git a/packages/stac_cli/bin/stac_cli.dart b/packages/stac_cli/bin/stac_cli.dart index aabbf704c..ea89ae57a 100644 --- a/packages/stac_cli/bin/stac_cli.dart +++ b/packages/stac_cli/bin/stac_cli.dart @@ -9,6 +9,7 @@ import 'package:stac_cli/src/commands/build_command.dart'; import 'package:stac_cli/src/commands/deploy_command.dart'; import 'package:stac_cli/src/commands/init_command.dart'; import 'package:stac_cli/src/commands/project_command.dart'; +import 'package:stac_cli/src/commands/skills_command.dart'; import 'package:stac_cli/src/commands/upgrade_command.dart'; import 'package:stac_cli/src/config/env.dart'; import 'package:stac_cli/src/exceptions/stac_exception.dart'; @@ -67,6 +68,7 @@ void main(List arguments) async { ..addCommand(ProjectCommand()) ..addCommand(BuildCommand()) ..addCommand(DeployCommand()) + ..addCommand(SkillsCommand()) ..addCommand(UpgradeCommand()); // Add global flags diff --git a/packages/stac_cli/lib/src/commands/init_command.dart b/packages/stac_cli/lib/src/commands/init_command.dart index 3b40a19e6..09f36df48 100644 --- a/packages/stac_cli/lib/src/commands/init_command.dart +++ b/packages/stac_cli/lib/src/commands/init_command.dart @@ -8,6 +8,7 @@ import '../services/project_service.dart'; import '../utils/console_logger.dart'; import '../utils/file_utils.dart'; import 'base_command.dart'; +import 'skills/add_command.dart'; /// Command for initializing a Stac project from cloud projects class InitCommand extends BaseCommand { @@ -84,6 +85,16 @@ class InitCommand extends BaseCommand { // Create default_stac_options.dart configuration file await _createStacConfigFile(targetDir, project); + // Ask to install skills + final shouldInstallSkills = Confirm( + prompt: 'Install Stac agent skills? (Recommended for AI-assisted development)', + defaultValue: true, + ).interact(); + if (shouldInstallSkills) { + ConsoleLogger.info('Installing skills...'); + await AddCommand().execute(); + } + ConsoleLogger.success('✓ Project initialized successfully!'); ConsoleLogger.info('Next steps:'); ConsoleLogger.info(' 1. Add your Stac widgets definitions to /stac'); diff --git a/packages/stac_cli/lib/src/commands/skills/add_command.dart b/packages/stac_cli/lib/src/commands/skills/add_command.dart new file mode 100644 index 000000000..3ab02b08e --- /dev/null +++ b/packages/stac_cli/lib/src/commands/skills/add_command.dart @@ -0,0 +1,147 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:archive/archive_io.dart'; +import 'package:dio/dio.dart'; +import 'package:path/path.dart' as path; + +import '../../utils/console_logger.dart'; +import '../base_command.dart'; + +/// Command to add Stac AI agent skills +class AddCommand extends BaseCommand { + @override + String get name => 'add'; + + @override + String get description => 'Add Stac AI agent skills to your project'; + + @override + bool get requiresAuth => false; + + AddCommand() { + // Optionally accept a repository URL. + } + + @override + Future execute() async { + String repoUrl = 'https://github.com/StacDev/stac'; + + if (argResults?.rest.isNotEmpty == true) { + repoUrl = argResults!.rest.first; + } + + if (!repoUrl.contains('github.com')) { + ConsoleLogger.error('Currently only github.com URLs are supported.'); + return 1; + } + + // Extract owner/repo + final uri = Uri.parse(repoUrl); + final segments = uri.pathSegments; + if (segments.length < 2) { + ConsoleLogger.error('Invalid GitHub URL format.'); + return 1; + } + + final owner = segments[0]; + final repo = segments[1].replaceAll('.git', ''); + + final zipUrl = 'https://github.com/$owner/$repo/archive/HEAD.zip'; + + ConsoleLogger.info('Fetching skills from $repoUrl...'); + + try { + final dio = Dio(); + final tempDir = await Directory.systemTemp.createTemp('stac_skills_'); + final zipFile = File(path.join(tempDir.path, 'repo.zip')); + + await dio.download(zipUrl, zipFile.path); + + // Extract ZIP + final archive = ZipDecoder().decodeBytes(zipFile.readAsBytesSync()); + final extractDir = Directory(path.join(tempDir.path, 'extracted')); + extractArchiveToDisk(archive, extractDir.path); + + // Find skills/catalog.json + // The extracted folder usually has a root folder named - + final rootDirs = extractDir.listSync().whereType().toList(); + if (rootDirs.isEmpty) { + ConsoleLogger.error('Empty repository archive.'); + return 1; + } + + final repoRoot = rootDirs.first; + ConsoleLogger.info('Extracted root: ${repoRoot.path}'); + + final catalogFile = File(path.join(repoRoot.path, 'skills', 'catalog.json')); + ConsoleLogger.info('Looking for catalog at: ${catalogFile.path}'); + + if (!await catalogFile.exists()) { + ConsoleLogger.error('skills/catalog.json not found in repository.'); + + ConsoleLogger.info('Contents of extracted:'); + for (var e in extractDir.listSync(recursive: true)) { + ConsoleLogger.info(e.path); + } + + return 1; + } + + // Parse catalog.json + final catalogContent = await catalogFile.readAsString(); + final List catalog = jsonDecode(catalogContent); + + final targetAgentsDir = Directory(path.join(Directory.current.path, '.agents', 'skills')); + if (!await targetAgentsDir.exists()) { + await targetAgentsDir.create(recursive: true); + } + + int installedCount = 0; + for (final skill in catalog) { + final skillName = skill['name']; + final skillPath = skill['path']; + + if (skillName == null || skillPath == null) continue; + + final sourceSkillDir = Directory(path.join(repoRoot.path, skillPath)); + if (!await sourceSkillDir.exists()) { + ConsoleLogger.warning('Skill directory $skillPath not found, skipping.'); + continue; + } + + final targetSkillDir = Directory(path.join(targetAgentsDir.path, skillName)); + if (await targetSkillDir.exists()) { + await targetSkillDir.delete(recursive: true); + } + await targetSkillDir.create(recursive: true); + + // Copy directory contents + await _copyDirectory(sourceSkillDir, targetSkillDir); + ConsoleLogger.success('✓ $skillName (copied)'); + installedCount++; + } + + ConsoleLogger.success('Installed $installedCount skills to .agents/skills'); + + // Cleanup + await tempDir.delete(recursive: true); + return 0; + } catch (e) { + ConsoleLogger.error('Failed to install skills: $e'); + return 1; + } + } + + Future _copyDirectory(Directory source, Directory destination) async { + await for (var entity in source.list(recursive: false)) { + if (entity is Directory) { + var newDirectory = Directory(path.join(destination.path, path.basename(entity.path))); + await newDirectory.create(); + await _copyDirectory(entity.absolute, newDirectory); + } else if (entity is File) { + await entity.copy(path.join(destination.path, path.basename(entity.path))); + } + } + } +} diff --git a/packages/stac_cli/lib/src/commands/skills_command.dart b/packages/stac_cli/lib/src/commands/skills_command.dart new file mode 100644 index 000000000..caabefbfa --- /dev/null +++ b/packages/stac_cli/lib/src/commands/skills_command.dart @@ -0,0 +1,15 @@ +import 'package:args/command_runner.dart'; +import 'skills/add_command.dart'; + +/// Command for managing Stac AI agent skills +class SkillsCommand extends Command { + @override + String get name => 'skills'; + + @override + String get description => 'Manage Stac AI agent skills'; + + SkillsCommand() { + addSubcommand(AddCommand()); + } +} diff --git a/packages/stac_cli/pubspec.lock b/packages/stac_cli/pubspec.lock index 4fe9fd6bf..2456c0fe9 100644 --- a/packages/stac_cli/pubspec.lock +++ b/packages/stac_cli/pubspec.lock @@ -17,6 +17,14 @@ packages: url: "https://pub.dev" source: hosted version: "10.0.1" + archive: + dependency: "direct main" + description: + name: archive + sha256: a96e8b390886ee8abb49b7bd3ac8df6f451c621619f52a26e815fdcf568959ff + url: "https://pub.dev" + source: hosted + version: "4.0.9" args: dependency: "direct main" description: @@ -401,6 +409,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.5.2" + posix: + dependency: transitive + description: + name: posix + sha256: "185ef7606574f789b40f289c233efa52e96dead518aed988e040a10737febb07" + url: "https://pub.dev" + source: hosted + version: "6.5.0" pub_semver: dependency: transitive description: @@ -492,9 +508,10 @@ packages: stac_core: dependency: "direct main" description: - path: "../stac_core" - relative: true - source: path + name: stac_core + sha256: "855767538be98fb2021ee4d58a85af66e0f40c6317eab94e5a08654c09ce49c0" + url: "https://pub.dev" + source: hosted version: "1.5.0" stac_logger: dependency: "direct overridden" diff --git a/packages/stac_cli/pubspec.yaml b/packages/stac_cli/pubspec.yaml index 89c523dde..1e17fb04a 100644 --- a/packages/stac_cli/pubspec.yaml +++ b/packages/stac_cli/pubspec.yaml @@ -18,6 +18,7 @@ dependencies: json_annotation: ^4.11.0 dotenv: ^4.2.0 cryptography: ^2.9.0 + archive: ^4.0.9 # Executables that can be run globally executables: From 7348d45f5871c51b8c13ada7bc4e8482818752c3 Mon Sep 17 00:00:00 2001 From: Pratikdate Date: Sat, 6 Jun 2026 14:26:42 +0530 Subject: [PATCH 2/4] fix: address CodeRabbit review issues in skills install - Move tempDir cleanup into finally block so temp files are always removed even on early return or exception - Validate skillName and skillPath from catalog.json to prevent path-traversal attacks (reject '..', absolute paths, backslashes, and enforce canonical boundary checks) - Accept optional targetDirectory in AddCommand so stac init installs skills into the correct project directory, not Directory.current - Handle non-zero exit from AddCommand in init with a warning instead of silently printing success - Assert non-null and correct type before casting in test to give cleaner failure output --- .../lib/src/commands/init_command.dart | 10 +- .../lib/src/commands/skills/add_command.dart | 113 ++++++++++++++---- 2 files changed, 97 insertions(+), 26 deletions(-) diff --git a/packages/stac_cli/lib/src/commands/init_command.dart b/packages/stac_cli/lib/src/commands/init_command.dart index 09f36df48..8a17a1135 100644 --- a/packages/stac_cli/lib/src/commands/init_command.dart +++ b/packages/stac_cli/lib/src/commands/init_command.dart @@ -92,7 +92,15 @@ class InitCommand extends BaseCommand { ).interact(); if (shouldInstallSkills) { ConsoleLogger.info('Installing skills...'); - await AddCommand().execute(); + final skillsExitCode = await AddCommand( + targetDirectory: targetDir, + ).execute(); + if (skillsExitCode != 0) { + ConsoleLogger.warning( + 'Skills installation encountered an issue. ' + 'You can retry later with: stac skills add', + ); + } } ConsoleLogger.success('✓ Project initialized successfully!'); diff --git a/packages/stac_cli/lib/src/commands/skills/add_command.dart b/packages/stac_cli/lib/src/commands/skills/add_command.dart index 3ab02b08e..48a427b7f 100644 --- a/packages/stac_cli/lib/src/commands/skills/add_command.dart +++ b/packages/stac_cli/lib/src/commands/skills/add_command.dart @@ -19,14 +19,15 @@ class AddCommand extends BaseCommand { @override bool get requiresAuth => false; - AddCommand() { - // Optionally accept a repository URL. - } + /// Optional target directory; defaults to [Directory.current]. + final String? targetDirectory; + + AddCommand({this.targetDirectory}); @override Future execute() async { String repoUrl = 'https://github.com/StacDev/stac'; - + if (argResults?.rest.isNotEmpty == true) { repoUrl = argResults!.rest.first; } @@ -43,17 +44,17 @@ class AddCommand extends BaseCommand { ConsoleLogger.error('Invalid GitHub URL format.'); return 1; } - + final owner = segments[0]; final repo = segments[1].replaceAll('.git', ''); - + final zipUrl = 'https://github.com/$owner/$repo/archive/HEAD.zip'; ConsoleLogger.info('Fetching skills from $repoUrl...'); + final tempDir = await Directory.systemTemp.createTemp('stac_skills_'); try { final dio = Dio(); - final tempDir = await Directory.systemTemp.createTemp('stac_skills_'); final zipFile = File(path.join(tempDir.path, 'repo.zip')); await dio.download(zipUrl, zipFile.path); @@ -67,24 +68,26 @@ class AddCommand extends BaseCommand { // The extracted folder usually has a root folder named - final rootDirs = extractDir.listSync().whereType().toList(); if (rootDirs.isEmpty) { - ConsoleLogger.error('Empty repository archive.'); - return 1; + ConsoleLogger.error('Empty repository archive.'); + return 1; } - + final repoRoot = rootDirs.first; ConsoleLogger.info('Extracted root: ${repoRoot.path}'); - - final catalogFile = File(path.join(repoRoot.path, 'skills', 'catalog.json')); + + final catalogFile = File( + path.join(repoRoot.path, 'skills', 'catalog.json'), + ); ConsoleLogger.info('Looking for catalog at: ${catalogFile.path}'); if (!await catalogFile.exists()) { ConsoleLogger.error('skills/catalog.json not found in repository.'); - + ConsoleLogger.info('Contents of extracted:'); for (var e in extractDir.listSync(recursive: true)) { ConsoleLogger.info(e.path); } - + return 1; } @@ -92,11 +95,18 @@ class AddCommand extends BaseCommand { final catalogContent = await catalogFile.readAsString(); final List catalog = jsonDecode(catalogContent); - final targetAgentsDir = Directory(path.join(Directory.current.path, '.agents', 'skills')); + final installDir = targetDirectory ?? Directory.current.path; + final targetAgentsDir = Directory( + path.join(installDir, '.agents', 'skills'), + ); if (!await targetAgentsDir.exists()) { await targetAgentsDir.create(recursive: true); } + // Canonical boundary paths for security checks + final repoRootCanonical = path.canonicalize(repoRoot.path); + final targetCanonical = path.canonicalize(targetAgentsDir.path); + int installedCount = 0; for (final skill in catalog) { final skillName = skill['name']; @@ -104,13 +114,48 @@ class AddCommand extends BaseCommand { if (skillName == null || skillPath == null) continue; - final sourceSkillDir = Directory(path.join(repoRoot.path, skillPath)); + // Guard against path-traversal in catalog entries + if (_containsPathTraversal(skillName) || + _containsPathTraversal(skillPath as String)) { + ConsoleLogger.warning( + 'Skipping skill with suspicious name/path: $skillName / $skillPath', + ); + continue; + } + + final sourceSkillDir = Directory( + path.join(repoRoot.path, skillPath), + ); + + // Ensure the resolved source is still inside the repo root + final sourceCanonical = path.canonicalize(sourceSkillDir.path); + if (!sourceCanonical.startsWith(repoRootCanonical)) { + ConsoleLogger.warning( + 'Skill path $skillPath escapes repo root. Skipping.', + ); + continue; + } + if (!await sourceSkillDir.exists()) { - ConsoleLogger.warning('Skill directory $skillPath not found, skipping.'); + ConsoleLogger.warning( + 'Skill directory $skillPath not found, skipping.', + ); + continue; + } + + final targetSkillDir = Directory( + path.join(targetAgentsDir.path, skillName), + ); + + // Ensure the resolved target is still inside .agents/skills + final targetSkillCanonical = path.canonicalize(targetSkillDir.path); + if (!targetSkillCanonical.startsWith(targetCanonical)) { + ConsoleLogger.warning( + 'Skill name $skillName escapes target directory. Skipping.', + ); continue; } - final targetSkillDir = Directory(path.join(targetAgentsDir.path, skillName)); if (await targetSkillDir.exists()) { await targetSkillDir.delete(recursive: true); } @@ -122,25 +167,43 @@ class AddCommand extends BaseCommand { installedCount++; } - ConsoleLogger.success('Installed $installedCount skills to .agents/skills'); - - // Cleanup - await tempDir.delete(recursive: true); + ConsoleLogger.success( + 'Installed $installedCount skills to .agents/skills', + ); return 0; } catch (e) { ConsoleLogger.error('Failed to install skills: $e'); return 1; + } finally { + // Always clean up temp files regardless of success or failure + if (await tempDir.exists()) { + await tempDir.delete(recursive: true); + } } } - Future _copyDirectory(Directory source, Directory destination) async { + /// Returns true if a name or path segment contains traversal patterns. + bool _containsPathTraversal(String value) { + return value.contains('..') || + path.isAbsolute(value) || + value.contains(r'\'); + } + + Future _copyDirectory( + Directory source, + Directory destination, + ) async { await for (var entity in source.list(recursive: false)) { if (entity is Directory) { - var newDirectory = Directory(path.join(destination.path, path.basename(entity.path))); + var newDirectory = Directory( + path.join(destination.path, path.basename(entity.path)), + ); await newDirectory.create(); await _copyDirectory(entity.absolute, newDirectory); } else if (entity is File) { - await entity.copy(path.join(destination.path, path.basename(entity.path))); + await entity.copy( + path.join(destination.path, path.basename(entity.path)), + ); } } } From d5eae05583d7d2f0b49f579f69a4ee941c2c4a3b Mon Sep 17 00:00:00 2001 From: Pratikdate Date: Sat, 6 Jun 2026 15:23:39 +0530 Subject: [PATCH 3/4] feat: Add catalog entry validation, path containment checks, and copy safety hardening for skills command --- .../lib/src/commands/skills/add_command.dart | 69 +++++++++++++++---- 1 file changed, 54 insertions(+), 15 deletions(-) diff --git a/packages/stac_cli/lib/src/commands/skills/add_command.dart b/packages/stac_cli/lib/src/commands/skills/add_command.dart index 48a427b7f..fe899b7b2 100644 --- a/packages/stac_cli/lib/src/commands/skills/add_command.dart +++ b/packages/stac_cli/lib/src/commands/skills/add_command.dart @@ -109,14 +109,21 @@ class AddCommand extends BaseCommand { int installedCount = 0; for (final skill in catalog) { + if (skill is! Map) { + ConsoleLogger.warning('Skipping invalid catalog entry (not a map): $skill'); + continue; + } final skillName = skill['name']; final skillPath = skill['path']; - if (skillName == null || skillPath == null) continue; + if (skillName is! String || skillPath is! String) { + ConsoleLogger.warning('Skipping invalid catalog entry: $skill'); + continue; + } // Guard against path-traversal in catalog entries - if (_containsPathTraversal(skillName) || - _containsPathTraversal(skillPath as String)) { + if (containsPathTraversal(skillName) || + containsPathTraversal(skillPath)) { ConsoleLogger.warning( 'Skipping skill with suspicious name/path: $skillName / $skillPath', ); @@ -129,7 +136,8 @@ class AddCommand extends BaseCommand { // Ensure the resolved source is still inside the repo root final sourceCanonical = path.canonicalize(sourceSkillDir.path); - if (!sourceCanonical.startsWith(repoRootCanonical)) { + if (!path.equals(repoRootCanonical, sourceCanonical) && + !path.isWithin(repoRootCanonical, sourceCanonical)) { ConsoleLogger.warning( 'Skill path $skillPath escapes repo root. Skipping.', ); @@ -149,7 +157,8 @@ class AddCommand extends BaseCommand { // Ensure the resolved target is still inside .agents/skills final targetSkillCanonical = path.canonicalize(targetSkillDir.path); - if (!targetSkillCanonical.startsWith(targetCanonical)) { + if (!path.equals(targetCanonical, targetSkillCanonical) && + !path.isWithin(targetCanonical, targetSkillCanonical)) { ConsoleLogger.warning( 'Skill name $skillName escapes target directory. Skipping.', ); @@ -162,7 +171,12 @@ class AddCommand extends BaseCommand { await targetSkillDir.create(recursive: true); // Copy directory contents - await _copyDirectory(sourceSkillDir, targetSkillDir); + await _copyDirectory( + sourceSkillDir, + targetSkillDir, + sourceCanonical, + targetSkillCanonical, + ); ConsoleLogger.success('✓ $skillName (copied)'); installedCount++; } @@ -183,7 +197,7 @@ class AddCommand extends BaseCommand { } /// Returns true if a name or path segment contains traversal patterns. - bool _containsPathTraversal(String value) { + bool containsPathTraversal(String value) { return value.contains('..') || path.isAbsolute(value) || value.contains(r'\'); @@ -192,18 +206,43 @@ class AddCommand extends BaseCommand { Future _copyDirectory( Directory source, Directory destination, + String sourceRootCanonical, + String destinationRootCanonical, ) async { - await for (var entity in source.list(recursive: false)) { + await for (var entity in source.list(recursive: false, followLinks: false)) { + if (entity is Link) { + ConsoleLogger.warning('Skipping symlink: ${entity.path}'); + continue; + } + + final entityCanonical = path.canonicalize(entity.path); + // Ensure the source entity is within the allowed source root + if (!path.equals(sourceRootCanonical, entityCanonical) && + !path.isWithin(sourceRootCanonical, entityCanonical)) { + ConsoleLogger.warning('Skipping out-of-bounds source entity: ${entity.path}'); + continue; + } + + final targetPath = path.join(destination.path, path.basename(entity.path)); + final targetCanonical = path.canonicalize(targetPath); + // Ensure the destination path is within the allowed target root + if (!path.equals(destinationRootCanonical, targetCanonical) && + !path.isWithin(destinationRootCanonical, targetCanonical)) { + ConsoleLogger.warning('Skipping out-of-bounds destination path: $targetPath'); + continue; + } + if (entity is Directory) { - var newDirectory = Directory( - path.join(destination.path, path.basename(entity.path)), + final newDirectory = Directory(targetPath); + await newDirectory.create(recursive: true); + await _copyDirectory( + entity, + newDirectory, + sourceRootCanonical, + destinationRootCanonical, ); - await newDirectory.create(); - await _copyDirectory(entity.absolute, newDirectory); } else if (entity is File) { - await entity.copy( - path.join(destination.path, path.basename(entity.path)), - ); + await entity.copy(targetPath); } } } From 617e3216f6e74be0f0624e48fb6e2b36f17ef69e Mon Sep 17 00:00:00 2001 From: Pratikdate Date: Sun, 14 Jun 2026 11:45:40 +0530 Subject: [PATCH 4/4] chore: fix formatting and update pubspec.lock --- examples/counter_example/pubspec.lock | 22 ++++++++-------- examples/movie_app/pubspec.lock | 22 ++++++++-------- examples/stac_gallery/pubspec.lock | 22 ++++++++-------- .../lib/src/commands/init_command.dart | 3 ++- .../lib/src/commands/skills/add_command.dart | 26 +++++++++++++------ packages/stac_cli/pubspec.lock | 7 +++-- 6 files changed, 56 insertions(+), 46 deletions(-) diff --git a/examples/counter_example/pubspec.lock b/examples/counter_example/pubspec.lock index 7d0a109a8..cebde638e 100644 --- a/examples/counter_example/pubspec.lock +++ b/examples/counter_example/pubspec.lock @@ -141,10 +141,10 @@ packages: dependency: transitive description: name: characters - sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b + sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 url: "https://pub.dev" source: hosted - version: "1.4.1" + version: "1.4.0" checked_yaml: dependency: transitive description: @@ -460,26 +460,26 @@ packages: dependency: transitive description: name: matcher - sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861 + sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 url: "https://pub.dev" source: hosted - version: "0.12.19" + version: "0.12.17" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b" + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec url: "https://pub.dev" source: hosted - version: "0.13.0" + version: "0.11.1" meta: dependency: transitive description: name: meta - sha256: "1741988757a65eb6b36abe716829688cf01910bbf91c34354ff7ec1c3de2b349" + sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" url: "https://pub.dev" source: hosted - version: "1.18.0" + version: "1.17.0" mime: dependency: transitive description: @@ -869,10 +869,10 @@ packages: dependency: transitive description: name: test_api - sha256: "949a932224383300f01be9221c39180316445ecb8e7547f70a41a35bf421fb9e" + sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55 url: "https://pub.dev" source: hosted - version: "0.7.11" + version: "0.7.7" timing: dependency: transitive description: @@ -994,5 +994,5 @@ packages: source: hosted version: "3.1.3" sdks: - dart: ">=3.10.0-0 <4.0.0" + dart: ">=3.9.0 <4.0.0" flutter: ">=3.35.0" diff --git a/examples/movie_app/pubspec.lock b/examples/movie_app/pubspec.lock index 0244df713..951bc1cc0 100644 --- a/examples/movie_app/pubspec.lock +++ b/examples/movie_app/pubspec.lock @@ -117,10 +117,10 @@ packages: dependency: transitive description: name: characters - sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b + sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 url: "https://pub.dev" source: hosted - version: "1.4.1" + version: "1.4.0" checked_yaml: dependency: transitive description: @@ -396,26 +396,26 @@ packages: dependency: transitive description: name: matcher - sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861 + sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 url: "https://pub.dev" source: hosted - version: "0.12.19" + version: "0.12.17" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b" + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec url: "https://pub.dev" source: hosted - version: "0.13.0" + version: "0.11.1" meta: dependency: transitive description: name: meta - sha256: "1741988757a65eb6b36abe716829688cf01910bbf91c34354ff7ec1c3de2b349" + sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" url: "https://pub.dev" source: hosted - version: "1.18.0" + version: "1.17.0" mime: dependency: transitive description: @@ -797,10 +797,10 @@ packages: dependency: transitive description: name: test_api - sha256: "949a932224383300f01be9221c39180316445ecb8e7547f70a41a35bf421fb9e" + sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55 url: "https://pub.dev" source: hosted - version: "0.7.11" + version: "0.7.7" typed_data: dependency: transitive description: @@ -914,5 +914,5 @@ packages: source: hosted version: "3.1.3" sdks: - dart: ">=3.10.0-0 <4.0.0" + dart: ">=3.9.0 <4.0.0" flutter: ">=3.35.0" diff --git a/examples/stac_gallery/pubspec.lock b/examples/stac_gallery/pubspec.lock index 5770e7929..d7df31d7b 100644 --- a/examples/stac_gallery/pubspec.lock +++ b/examples/stac_gallery/pubspec.lock @@ -125,10 +125,10 @@ packages: dependency: transitive description: name: characters - sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b + sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 url: "https://pub.dev" source: hosted - version: "1.4.1" + version: "1.4.0" checked_yaml: dependency: transitive description: @@ -436,26 +436,26 @@ packages: dependency: transitive description: name: matcher - sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861 + sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 url: "https://pub.dev" source: hosted - version: "0.12.19" + version: "0.12.17" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b" + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec url: "https://pub.dev" source: hosted - version: "0.13.0" + version: "0.11.1" meta: dependency: transitive description: name: meta - sha256: "1741988757a65eb6b36abe716829688cf01910bbf91c34354ff7ec1c3de2b349" + sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" url: "https://pub.dev" source: hosted - version: "1.18.0" + version: "1.17.0" mime: dependency: transitive description: @@ -868,10 +868,10 @@ packages: dependency: transitive description: name: test_api - sha256: "949a932224383300f01be9221c39180316445ecb8e7547f70a41a35bf421fb9e" + sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55 url: "https://pub.dev" source: hosted - version: "0.7.11" + version: "0.7.7" typed_data: dependency: transitive description: @@ -1025,5 +1025,5 @@ packages: source: hosted version: "3.1.3" sdks: - dart: ">=3.10.0-0 <4.0.0" + dart: ">=3.9.0 <4.0.0" flutter: ">=3.35.0" diff --git a/packages/stac_cli/lib/src/commands/init_command.dart b/packages/stac_cli/lib/src/commands/init_command.dart index 8a17a1135..ab13daed6 100644 --- a/packages/stac_cli/lib/src/commands/init_command.dart +++ b/packages/stac_cli/lib/src/commands/init_command.dart @@ -87,7 +87,8 @@ class InitCommand extends BaseCommand { // Ask to install skills final shouldInstallSkills = Confirm( - prompt: 'Install Stac agent skills? (Recommended for AI-assisted development)', + prompt: + 'Install Stac agent skills? (Recommended for AI-assisted development)', defaultValue: true, ).interact(); if (shouldInstallSkills) { diff --git a/packages/stac_cli/lib/src/commands/skills/add_command.dart b/packages/stac_cli/lib/src/commands/skills/add_command.dart index fe899b7b2..069440cdf 100644 --- a/packages/stac_cli/lib/src/commands/skills/add_command.dart +++ b/packages/stac_cli/lib/src/commands/skills/add_command.dart @@ -110,7 +110,9 @@ class AddCommand extends BaseCommand { int installedCount = 0; for (final skill in catalog) { if (skill is! Map) { - ConsoleLogger.warning('Skipping invalid catalog entry (not a map): $skill'); + ConsoleLogger.warning( + 'Skipping invalid catalog entry (not a map): $skill', + ); continue; } final skillName = skill['name']; @@ -130,9 +132,7 @@ class AddCommand extends BaseCommand { continue; } - final sourceSkillDir = Directory( - path.join(repoRoot.path, skillPath), - ); + final sourceSkillDir = Directory(path.join(repoRoot.path, skillPath)); // Ensure the resolved source is still inside the repo root final sourceCanonical = path.canonicalize(sourceSkillDir.path); @@ -209,7 +209,10 @@ class AddCommand extends BaseCommand { String sourceRootCanonical, String destinationRootCanonical, ) async { - await for (var entity in source.list(recursive: false, followLinks: false)) { + await for (var entity in source.list( + recursive: false, + followLinks: false, + )) { if (entity is Link) { ConsoleLogger.warning('Skipping symlink: ${entity.path}'); continue; @@ -219,16 +222,23 @@ class AddCommand extends BaseCommand { // Ensure the source entity is within the allowed source root if (!path.equals(sourceRootCanonical, entityCanonical) && !path.isWithin(sourceRootCanonical, entityCanonical)) { - ConsoleLogger.warning('Skipping out-of-bounds source entity: ${entity.path}'); + ConsoleLogger.warning( + 'Skipping out-of-bounds source entity: ${entity.path}', + ); continue; } - final targetPath = path.join(destination.path, path.basename(entity.path)); + final targetPath = path.join( + destination.path, + path.basename(entity.path), + ); final targetCanonical = path.canonicalize(targetPath); // Ensure the destination path is within the allowed target root if (!path.equals(destinationRootCanonical, targetCanonical) && !path.isWithin(destinationRootCanonical, targetCanonical)) { - ConsoleLogger.warning('Skipping out-of-bounds destination path: $targetPath'); + ConsoleLogger.warning( + 'Skipping out-of-bounds destination path: $targetPath', + ); continue; } diff --git a/packages/stac_cli/pubspec.lock b/packages/stac_cli/pubspec.lock index 2456c0fe9..6b0c6ebd2 100644 --- a/packages/stac_cli/pubspec.lock +++ b/packages/stac_cli/pubspec.lock @@ -508,10 +508,9 @@ packages: stac_core: dependency: "direct main" description: - name: stac_core - sha256: "855767538be98fb2021ee4d58a85af66e0f40c6317eab94e5a08654c09ce49c0" - url: "https://pub.dev" - source: hosted + path: "../stac_core" + relative: true + source: path version: "1.5.0" stac_logger: dependency: "direct overridden"