Skip to content

feat(stac_cli): Open-sourcing Stac CLI#456

Merged
divyanshub024 merged 7 commits intodevfrom
dv/stac-cli
Mar 1, 2026
Merged

feat(stac_cli): Open-sourcing Stac CLI#456
divyanshub024 merged 7 commits intodevfrom
dv/stac-cli

Conversation

@divyanshub024
Copy link
Member

@divyanshub024 divyanshub024 commented Feb 28, 2026

Description

This PR introduces the official Stac CLI and open-sources it as part of the Stac monorepo.

The CLI enables developers to manage Stac SDUI projects end to end from the terminal, including authentication, project setup, building JSON artifacts, and deploying to the cloud.

✨ What’s Included

  • Google OAuth based authentication (login, logout, status)
  • stac init for bootstrapping Stac projects
  • stac build to convert Dart widget definitions into JSON
  • stac deploy to upload artifacts to Stac Cloud
  • Project management commands
  • Upgrade command
  • Environment configuration support via ~/.stac/.env
  • Proper error handling and structured command architecture
  • MIT license and package level documentation

This lays the foundation for Stac Cloud workflows and improves the overall developer experience around Server Driven UI with Flutter.

Type of Change

  • New feature (non-breaking change which adds functionality)
  • Bug fix (non-breaking change which fixes an issue)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Code refactor
  • Build configuration change
  • Documentation
  • Chore

Summary by CodeRabbit

  • New Features

    • New stac_cli command-line tool: login/logout/status, init, project (create/list), build, deploy, upgrade, plus --version and --verbose flags; interactive init and auth flows; build-to-JSON and cloud deploy pipelines; self-upgrade support.
    • Environment configuration support for cloud and OAuth credentials.
  • Documentation

    • Added CLI README and MIT license; package .gitignore and package manifest for the CLI.
  • Other

    • Example apps/docs updated to use the consolidated stac_core package and bumped example dependency.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 28, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0153123 and 879ca14.

📒 Files selected for processing (9)
  • docs/concepts/custom_actions.mdx
  • docs/concepts/custom_widgets.mdx
  • docs/concepts/rendering_stac_widgets.mdx
  • docs/concepts/theming.mdx
  • docs/dsl.mdx
  • docs/guides/migration.mdx
  • docs/guides/troubleshooting.mdx
  • docs/project_structure.mdx
  • docs/quickstart.mdx
✅ Files skipped from review due to trivial changes (4)
  • docs/concepts/rendering_stac_widgets.mdx
  • docs/project_structure.mdx
  • docs/dsl.mdx
  • docs/guides/migration.mdx

📝 Walkthrough

Walkthrough

Adds a new stac_cli package (CLI entrypoint, commands, services, models, utils) implementing OAuth, project management, build/deploy, and upgrade flows. Also updates example imports from package:stac/stac.dart to package:stac/stac_core.dart and pins the example stac dependency to ^1.3.1.

Changes

Cohort / File(s) Summary
Repo tooling & example deps
/.gitignore, examples/movie_app/pubspec.yaml
Minor .gitignore newline; pinned stac: ^1.3.1 in example pubspec.
Example app imports
examples/movie_app/lib/default_stac_options.dart, examples/movie_app/lib/widgets/movie_carousel/movie_carousel.dart
Switched imports from package:stac/stac.dart to package:stac/stac_core.dart.
stac_cli package metadata
packages/stac_cli/.gitignore, packages/stac_cli/LICENSE, packages/stac_cli/README.md, packages/stac_cli/analysis_options.yaml, packages/stac_cli/pubspec.yaml, packages/stac_cli/pubspec_overrides.yaml
New package metadata, license, README, analysis options, and dependency overrides for stac_cli.
CLI entry & exports
packages/stac_cli/bin/stac_cli.dart, packages/stac_cli/lib/src/version.dart, packages/stac_cli/lib/stac_cli.dart
Adds CLI entrypoint (main, flags pre-parse, env loading), package version constant, and barrel exports.
Command framework & auth
packages/stac_cli/lib/src/commands/base_command.dart, packages/stac_cli/lib/src/commands/auth/*
New BaseCommand (auth-aware run flow) and auth commands: login, logout, status.
CLI commands (project/build/init/deploy/upgrade)
packages/stac_cli/lib/src/commands/...
Adds project subcommands (create/list), init, build, deploy, and upgrade commands with their CLI options and orchestrations.
Core services
packages/stac_cli/lib/src/services/*
New services: AuthService (PKCE + Firebase), ConfigService, BuildService (Dart → JSON extractor), DeployService (uploads screens/themes), ProjectService, UpgradeService (download/verify/install).
Models & serialization
packages/stac_cli/lib/src/models/*, packages/stac_cli/lib/src/models/*/*.g.dart
Adds models (AuthToken, Project, Subscription, UiLoads, StacDslArtifact, ProjectAccess) with JSON (de)serializers and Firestore date converters.
Exceptions & utils
packages/stac_cli/lib/src/exceptions/*, packages/stac_cli/lib/src/utils/*
Adds StacException hierarchy and utilities: ConsoleLogger, FileUtils, HttpClientService (Dio wrapper + auth interceptor), DateTimeUtils, oauth_pkce helpers, StringUtils.
Public API barrel
packages/stac_cli/lib/stac_cli.dart
Barrel exports for CLI-facing modules (commands, services, exceptions, logger).

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant CLI as stac_cli (bin)
    participant Browser
    participant Google
    participant Firebase
    participant Config as ConfigService

    User->>CLI: stac login
    CLI->>CLI: start local callback server, generate PKCE
    CLI->>Browser: open auth URL (state, code_challenge)
    Browser->>Google: user authorizes
    Google->>Browser: redirect with auth code
    Browser->>CLI: callback POST with code
    CLI->>Google: exchange code + verifier -> tokens
    Google-->>CLI: access_token / id_token
    CLI->>Firebase: sign-in with id_token
    Firebase-->>CLI: firebase tokens (refresh, id)
    CLI->>Config: store AuthToken
    Config-->>CLI: persisted
    CLI-->>User: login success
Loading
sequenceDiagram
    participant User
    participant CLI as stac_cli
    participant BuildSvc as BuildService
    participant FS as FileSystem
    participant DartVM
    participant Writer as JSONWriter

    User->>CLI: stac build
    CLI->>BuildSvc: build(projectPath)
    BuildSvc->>FS: read lib/default_stac_options.dart
    FS-->>BuildSvc: options
    BuildSvc->>BuildSvc: scan source for annotated artifacts
    BuildSvc->>DartVM: generate & run wrapper per artifact
    DartVM-->>BuildSvc: stdout JSON
    BuildSvc->>Writer: clean & normalize JSON
    Writer-->>FS: write stac/.build/screens|themes/*.json
    FS-->>BuildSvc: files persisted
    BuildSvc-->>CLI: build completed
    CLI-->>User: summary
Loading
sequenceDiagram
    participant User
    participant CLI as stac_cli
    participant BuildSvc
    participant DeploySvc
    participant FS as FileSystem
    participant HTTP as HttpClient
    participant API as CloudAPI

    User->>CLI: stac deploy (--skip-build?)
    CLI->>BuildSvc: build(...) [if not skipped]
    BuildSvc-->>CLI: artifacts ready
    CLI->>DeploySvc: deploy(projectPath)
    DeploySvc->>FS: read projectId from lib/default_stac_options.dart
    FS-->>DeploySvc: projectId
    DeploySvc->>FS: list stac/.build/screens & themes
    FS-->>DeploySvc: file list
    loop for each artifact
      DeploySvc->>HTTP: POST /projects/{id}/screens|themes (payload)
      HTTP->>API: request
      API-->>HTTP: response
      HTTP-->>DeploySvc: result
    end
    DeploySvc-->>CLI: deploy summary
    CLI-->>User: deployment result
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

Suggested reviewers

  • Potatomonsta

"🐰
I hopped through code and made a trail,
New CLI commands set sail,
OAuth, build, deploy in a cheery spin,
Stac_cli is ready — let the devs begin! 🥕🚀"

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'feat(stac_cli): Open-sourcing Stac CLI' directly and clearly describes the main change—introducing and open-sourcing a new Stac CLI package to the monorepo. It is concise, follows conventional commit format, and accurately reflects the primary objective evident throughout the raw_summary.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch dv/stac-cli

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 15

Note

Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
examples/movie_app/lib/default_stac_options.dart (1)

3-12: ⚠️ Potential issue | 🟡 Minor

Documentation example uses outdated import path.

Line 3 correctly imports stac_core.dart, but the doc example on line 12 still references the old stac/stac.dart path. This inconsistency may confuse developers copying the example.

📝 Proposed fix
 /// Example:
 /// ```dart
 /// import 'package:flutter/material.dart';
-/// import 'package:stac/stac.dart';
+/// import 'package:stac/stac_core.dart';
 /// import 'default_stac_options.dart';
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/movie_app/lib/default_stac_options.dart` around lines 3 - 12, Update
the documentation example import to match the actual API surface: replace the
outdated doc import "package:stac/stac.dart" with "package:stac/stac_core.dart"
in the comment block of default_stac_options.dart so the example that shows
initializing StacOptions/runApp is consistent with the real import; ensure the
comment still references StacOptions and default_stac_options.dart correctly.
🟡 Minor comments (9)
packages/stac_cli/pubspec.yaml-7-8 (1)

7-8: ⚠️ Potential issue | 🟡 Minor

Reconcile SDK version constraint discrepancy.

The pubspec.yaml specifies sdk: ^3.8.1, but pubspec.lock restricts it to dart: ">=3.10.0 <4.0.0". These constraints should be aligned. Update pubspec.yaml to match the lock file's requirement (minimum 3.10.0) or regenerate the lock file if 3.8.1 compatibility is intended for users of this CLI tool.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/stac_cli/pubspec.yaml` around lines 7 - 8, The SDK constraint in
pubspec.yaml (the environment: sdk field) conflicts with pubspec.lock's dart
">=3.10.0 <4.0.0"; either update pubspec.yaml's sdk constraint to ">=3.10.0
<4.0.0" to match the lock file, or regenerate pubspec.lock (flutter pub get /
dart pub get) and CI artifacts if you intend to support ^3.8.1—modify the
environment: sdk line in pubspec.yaml (or regenerate the lock) accordingly so
both files align.
packages/stac_cli/lib/src/commands/auth/login_command.dart-18-30 (1)

18-30: ⚠️ Potential issue | 🟡 Minor

The --interactive flag is defined but never used.

The flag is registered in the argument parser but execute() doesn't read argResults['interactive'] or pass it to _authService.login(). This appears to be incomplete implementation or dead code.

🔧 Suggested fix if flag should control behavior
   `@override`
   Future<int> execute() async {
-    await _authService.login();
+    final interactive = argResults?['interactive'] as bool? ?? true;
+    await _authService.login(interactive: interactive);
     return 0;
   }

This assumes AuthService.login() accepts an interactive parameter. If non-interactive login isn't supported yet, consider removing the flag until it's implemented.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/stac_cli/lib/src/commands/auth/login_command.dart` around lines 18 -
30, The registered '--interactive' flag on LoginCommand is never read; update
execute() to read argResults['interactive'] and pass that value into
_authService.login(...) (e.g., call _authService.login(interactive:
interactive)) so the flag controls behavior, or if AuthService.login does not
accept such a parameter yet, remove the argParser.addFlag('interactive', ...)
from the LoginCommand constructor to avoid dead code; ensure references are to
LoginCommand, argParser, execute(), argResults['interactive'], and
_authService.login.
packages/stac_cli/lib/src/utils/file_utils.dart-10-15 (1)

10-15: ⚠️ Potential issue | 🟡 Minor

Empty string fallback creates invalid paths.

When HOME or USERPROFILE is unset, returning empty string causes configDirectory to produce paths like /.stac or /AppData/Local/stac_cli, which are invalid or system-level directories. Consider throwing an exception or returning null to force callers to handle this edge case.

🛡️ Suggested fix
   static String get homeDirectory {
     if (Platform.isWindows) {
-      return Platform.environment['USERPROFILE'] ?? '';
+      final home = Platform.environment['USERPROFILE'];
+      if (home == null || home.isEmpty) {
+        throw StateError('USERPROFILE environment variable is not set');
+      }
+      return home;
     }
-    return Platform.environment['HOME'] ?? '';
+    final home = Platform.environment['HOME'];
+    if (home == null || home.isEmpty) {
+      throw StateError('HOME environment variable is not set');
+    }
+    return home;
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/stac_cli/lib/src/utils/file_utils.dart` around lines 10 - 15, The
current static getter homeDirectory returns an empty string when
HOME/USERPROFILE is unset, producing invalid paths; change the implementation of
the static getter homeDirectory to throw a descriptive runtime error (e.g.,
StateError) when the environment variable is missing instead of returning
''—include which variable was missing and the current Platform in the error
message so callers (like configDirectory) must handle the missing-home case;
update the getter signature/implementation in file_utils.dart (static String get
homeDirectory) accordingly.
packages/stac_cli/lib/src/utils/file_utils.dart-68-82 (1)

68-82: ⚠️ Potential issue | 🟡 Minor

Missing exception handling for malformed YAML.

loadYaml throws FormatException on invalid YAML syntax. This would propagate up unexpectedly rather than returning null like other error cases.

🛡️ Suggested fix
   static Future<Map<String, dynamic>?> readYamlFile(String filePath) async {
     if (!await fileExists(filePath)) {
       return null;
     }

-    final content = await readFile(filePath);
-    final yaml = loadYaml(content);
-
-    if (yaml is Map) {
-      return Map<String, dynamic>.from(yaml);
+    try {
+      final content = await readFile(filePath);
+      final yaml = loadYaml(content);
+      if (yaml is Map) {
+        return Map<String, dynamic>.from(yaml);
+      }
+      return null;
+    } on FormatException catch (e) {
+      ConsoleLogger.debug('Failed to parse YAML file $filePath: $e');
+      return null;
     }
-
-    return null;
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/stac_cli/lib/src/utils/file_utils.dart` around lines 68 - 82, The
readYamlFile method currently calls loadYaml and lets FormatException propagate;
wrap the loadYaml(content) call (and the subsequent Map conversion) in a
try-catch that catches FormatException (and optionally other parsing exceptions)
and return null on error so malformed YAML behaves like other missing/error
cases; update function readYamlFile to call fileExists and readFile as-is but
protect loadYaml and Map<String, dynamic>.from(...) with a try { final yaml =
loadYaml(content); if (yaml is Map) return Map<String, dynamic>.from(yaml); } on
FormatException { return null; } (you can also log the exception if desired).
packages/stac_cli/lib/src/commands/init_command.dart-217-219 (1)

217-219: ⚠️ Potential issue | 🟡 Minor

Generated usage example references an outdated import path.

Line 218 still shows package:stac/stac.dart while the generated file itself uses stac_core.dart. Aligning this avoids setup confusion.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/stac_cli/lib/src/commands/init_command.dart` around lines 217 - 219,
The generated example import uses the outdated package path
"package:stac/stac.dart" whereas the rest of the generated file expects
"stac_core.dart"; update the import line in the usage template to import
"stac_core.dart" (replace the string "package:stac/stac.dart" with the correct
"package:stac_core/stac_core.dart" or the local "stac_core.dart" used
elsewhere), and ensure any other generated import lines (e.g.,
default_stac_options.dart) match the actual filenames used by InitCommand's
template so the example compiles without manual edits.
packages/stac_cli/lib/src/services/project_service.dart-82-87 (1)

82-87: ⚠️ Potential issue | 🟡 Minor

Slug normalization can produce edge hyphens.

Project names with surrounding whitespace can produce slugs like -my-project-, which is risky for URL/slug validation.

🔧 Proposed fix
   static String _generateSlug(String name) {
     final slug = name
+        .trim()
         .toLowerCase()
         .replaceAll(_nonAlphanumericRegex, '')
         .replaceAll(_whitespaceRegex, '-')
         .replaceAll(_multipleHyphensRegex, '-')
-        .trim();
+        .replaceAll(_edgeHyphensRegex, '');

     if (slug.isEmpty) {
       throw StacException(
         'Project name must contain at least one alphanumeric character',
       );
     }
     return slug;
   }

   static final _nonAlphanumericRegex = RegExp(r'[^a-z0-9\s-]');
   static final _whitespaceRegex = RegExp(r'\s+');
   static final _multipleHyphensRegex = RegExp(r'-+');
+  static final _edgeHyphensRegex = RegExp(r'^-+|-+$');
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/stac_cli/lib/src/services/project_service.dart` around lines 82 -
87, The current slug build (variable slug using _nonAlphanumericRegex,
_whitespaceRegex, _multipleHyphensRegex) can leave leading/trailing hyphens for
names with surrounding whitespace; fix by either trimming the input before
replacements or by adding a final step that strips leading/trailing hyphens
(e.g. apply name = name.trim() before the replace chain or add a final replace
using a regex like ^-+|-+$ to remove edge hyphens) so the resulting slug has no
leading or trailing '-' characters.
packages/stac_cli/lib/src/models/project/project.dart-48-79 (1)

48-79: ⚠️ Potential issue | 🟡 Minor

copyWith cannot clear nullable fields to null.

Using ?? for nullable properties (slug, description, deletedAt, subscription, uiLoads, etc.) prevents explicit nulling. Consider a sentinel-based copyWith pattern so callers can intentionally clear values.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/stac_cli/lib/src/models/project/project.dart` around lines 48 - 79,
The current Project.copyWith uses `??` so callers cannot explicitly set nullable
fields (e.g., slug, description, deletedAt, subscription, uiLoads) to null;
change the signature to use sentinel defaults (e.g., Object? sentinel =
_undefined) for nullable params in Project.copyWith and update the body to check
identity against the sentinel for each field (if param === sentinel then keep
this.field else use the provided value, allowing provided null to clear the
field). Add a private sentinel constant (e.g., const _undefined = Object()) in
the file and apply this pattern for all nullable parameters (slug, description,
deletedAt, subscription, uiLoads, etc.) so callers can intentionally clear
values.
packages/stac_cli/lib/src/models/project/subscription.dart-171-201 (1)

171-201: ⚠️ Potential issue | 🟡 Minor

copyWith cannot explicitly null nullable properties.

Because nullable fields use param ?? this.field, callers cannot clear values like updatedAt, status, or alertThreshold. Please consider a sentinel-based approach for nullable params.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/stac_cli/lib/src/models/project/subscription.dart` around lines 171
- 201, copyWith currently uses the `param ?? this.field` pattern which prevents
callers from explicitly nulling nullable properties (e.g., `updatedAt`,
`status`, `alertThreshold`); change `Subscription.copyWith` to use a
sentinel-based approach (or explicit wrapper/Optional) for nullable params so
you can detect "parameter provided and set to null" vs "parameter omitted" —
introduce a private sentinel object and make params typed as `Object?` (or use a
dedicated wrapper) then check against the sentinel to decide whether to assign
the new value (including null) or keep
`this.updatedAt`/`this.status`/`this.alertThreshold` when the sentinel is
passed; update the call site/constructor usage to accept the possibly-null
values accordingly while still defaulting to existing fields when the sentinel
is used.
packages/stac_cli/lib/src/commands/build_command.dart-26-30 (1)

26-30: ⚠️ Potential issue | 🟡 Minor

--validate is currently a no-op.

The flag is declared but never read in execute(), so --no-validate won’t change behavior. Please either wire it into BuildService.build(...) or remove the flag until supported.

Also applies to: 42-43

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/stac_cli/lib/src/commands/build_command.dart` around lines 26 - 30,
The 'validate' flag declared via argParser.addFlag('validate') is never read;
update execute() to read the flag from argResults (e.g., final validate =
argResults['validate'] as bool) and pass it into BuildService.build(...) (e.g.,
BuildService.build(..., validate: validate)) so --no-validate takes effect, or
if validation support isn't implemented remove the argParser.addFlag('validate')
declaration; apply the same fix for the other addFlag(...) in this file (the
second flag declaration) so all declared flags are actually consumed by
execute().
🧹 Nitpick comments (11)
packages/stac_cli/lib/src/utils/string_utils.dart (1)

23-31: Consider handling consecutive uppercase letters (acronyms).

The current implementation splits each uppercase letter individually, which produces unexpected results for acronyms:

  • "HTTPClient""h_t_t_p_client" (instead of "http_client")
  • "getURL""get_u_r_l" (instead of "get_url")

If the CLI only processes standard camelCase identifiers without acronyms, this is fine. Otherwise, consider a regex that handles runs of uppercase letters:

♻️ Proposed fix for acronym handling
  /// Converts a camelCase/PascalCase string to snake_case.
  static String toSnakeCase(String value) {
    return value
        .replaceAllMapped(
-          RegExp(r'[A-Z]'),
-          (match) => '_${match.group(0)!.toLowerCase()}',
+          RegExp(r'([A-Z]+)([A-Z][a-z])|([a-z])([A-Z])'),
+          (match) {
+            if (match.group(1) != null) {
+              return '${match.group(1)!.toLowerCase()}_${match.group(2)!.toLowerCase()}';
+            }
+            return '${match.group(3)}_${match.group(4)!.toLowerCase()}';
+          },
        )
-        .replaceFirst(RegExp(r'^_'), '');
+        .toLowerCase();
  }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/stac_cli/lib/src/utils/string_utils.dart` around lines 23 - 31,
toSnakeCase currently inserts an underscore before every single uppercase
letter, breaking acronyms (e.g. "HTTPClient" → "h_t_t_p_client"); update the
toSnakeCase implementation to treat consecutive uppercase letters as a single
acronym run: detect runs of uppercase letters (optionally followed by a
lowercase) and runs that start after a lowercase/digit, insert a single
underscore before the whole run and lowercase the result. Modify the RegExp used
in toSnakeCase (and its replaceAllMapped logic) to match uppercase sequences
rather than individual letters (for example using lookarounds such as
'(?<=[a-z0-9])([A-Z]+)' and '([A-Z]+)(?=[A-Z][a-z]|$)') and transform matches to
'_'+match.toLowerCase(), then trim any leading underscore as before.
packages/stac_cli/pubspec.yaml (1)

33-33: Consider removing or clarifying the trailing comment.

The comment # stac_cli/pubspec_overrides.yaml appears to be an orphaned artifact, possibly from melos setup or development notes. If it's intended to reference the overrides file for developers, consider adding context (e.g., # See pubspec_overrides.yaml for local development overrides) or remove it if unnecessary.

🔧 Suggested fix
-
-# stac_cli/pubspec_overrides.yaml

Or, if keeping it for developer reference:

-# stac_cli/pubspec_overrides.yaml
+# Note: See pubspec_overrides.yaml for melos-managed local path overrides during development
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/stac_cli/pubspec.yaml` at line 33, The trailing orphan comment "#
stac_cli/pubspec_overrides.yaml" should be removed or clarified; either delete
the line if it's unnecessary or replace it with a descriptive comment such as
"See pubspec_overrides.yaml for local development overrides" so its intent is
clear; locate the comment string "# stac_cli/pubspec_overrides.yaml" in the
pubspec.yaml and update accordingly.
packages/stac_cli/README.md (1)

35-35: Clarify environment configuration for end users.

The reference to lib/src/config/env.dart points to an internal implementation file. For users who install the CLI globally via dart pub global activate, this path won't be directly accessible. Consider clarifying whether this is intended for contributors/developers only, or provide the actual mechanism for users to switch environments (e.g., a CLI flag or environment variable).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/stac_cli/README.md` at line 35, The README currently points users to
the internal file lib/src/config/env.dart and the symbol currentEnvironment;
update the documentation to clarify intent: indicate that lib/src/... and
currentEnvironment are for contributors/developers only, and then document the
user-facing mechanism to change environments (e.g., describe the CLI flag or
environment variable to use when running the installed CLI, or explain how to
rebuild with a different environment if applicable). Mention the exact symbol
currentEnvironment and the file lib/src/config/env.dart so maintainers can find
the developer-only option, and add an example command showing the supported user
method (CLI flag or ENV var) for global installs.
packages/stac_cli/lib/src/models/auth_token.dart (1)

35-42: Add defensive null checks in fromJson for clearer error messages.

If the stored token JSON is corrupted or missing required fields, the current implementation will throw unclear cast errors. Consider validating required fields before construction.

♻️ Proposed enhancement
  factory AuthToken.fromJson(Map<String, dynamic> json) {
+   final accessToken = json['access_token'];
+   final expiresAt = json['expires_at'];
+   final scopes = json['scopes'];
+   
+   if (accessToken is! String) {
+     throw FormatException('Invalid or missing access_token');
+   }
+   if (expiresAt is! String) {
+     throw FormatException('Invalid or missing expires_at');
+   }
+   if (scopes is! List) {
+     throw FormatException('Invalid or missing scopes');
+   }
+   
    return AuthToken(
-     accessToken: json['access_token'] as String,
+     accessToken: accessToken,
      refreshToken: json['refresh_token'] as String?,
-     expiresAt: DateTime.parse(json['expires_at'] as String),
-     scopes: List<String>.from(json['scopes'] as List),
+     expiresAt: DateTime.parse(expiresAt),
+     scopes: List<String>.from(scopes),
    );
  }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/stac_cli/lib/src/models/auth_token.dart` around lines 35 - 42, Add
defensive null/type checks in AuthToken.fromJson: validate required keys
access_token, expires_at, and scopes exist and are of expected types before
calling the AuthToken constructor; parse expires_at with try/catch to throw a
clear FormatException if invalid; allow refresh_token to be optional but
validate its type if present; on missing/invalid required fields throw
descriptive exceptions (e.g., "Missing or invalid access_token in
AuthToken.fromJson") to replace unclear cast errors.
packages/stac_cli/lib/src/exceptions/stac_exception.dart (1)

9-10: Consider including cause in toString() for better debugging.

When a cause is present, including it in the string representation would help with debugging and error logs.

♻️ Proposed enhancement
  `@override`
- String toString() => 'StacException: $message';
+ String toString() =>
+     cause != null ? 'StacException: $message (cause: $cause)' : 'StacException: $message';
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/stac_cli/lib/src/exceptions/stac_exception.dart` around lines 9 -
10, The StacException.toString() currently only returns 'StacException:
$message' and should include the optional cause for better debugging; update the
StacException class so its toString() method (toString()) appends the cause when
present (e.g., "StacException: $message; cause: $cause" or similar), ensuring
null-safe handling of the cause and preserving the existing message formatting.
packages/stac_cli/lib/src/models/project/project_access.dart (1)

38-40: isEqual method is redundant for Dart enums.

Dart enums can be compared directly with ==, so isEqual provides no additional value over standard equality comparison. Consider removing it to reduce API surface.

// Current usage:
ProjectAccess.admin.isEqual(ProjectAccess.admin) // true

// Simpler:
ProjectAccess.admin == ProjectAccess.admin // true
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/stac_cli/lib/src/models/project/project_access.dart` around lines 38
- 40, Remove the redundant isEqual method from the ProjectAccess enum and update
any call sites to use Dart's native equality (==) instead; search for the symbol
isEqual in the codebase, delete the isEqual(ProjectAccess other) => value ==
other.value implementation from the ProjectAccess declaration, and replace
usages like someAccess.isEqual(otherAccess) with someAccess == otherAccess so
the enum relies on built-in enum comparison.
packages/stac_cli/lib/src/config/env.dart (2)

7-10: currentEnvironment appears unused.

The Environment enum and currentEnvironment constant are defined but not referenced anywhere in the configuration logic. If this is a placeholder for future environment switching, consider adding a TODO comment or removing it to avoid confusion.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/stac_cli/lib/src/config/env.dart` around lines 7 - 10, The enum
Environment and constant currentEnvironment are defined but unused; either
remove the unused enum/constant (Environment, currentEnvironment) to avoid dead
code, or keep them and add a TODO comment explaining intended future use and
link it to configuration switching (e.g., note which config functions should
read currentEnvironment) so reviewers know it’s deliberate—update the
declaration accordingly to reflect chosen approach.

25-30: Clarify intent: required on nullable field.

Using required with String? means the caller must explicitly pass the parameter (even if null), but doesn't enforce a non-null value. This works correctly with how _value is called, but consider whether making it a positional optional parameter would better express the optionality.

💡 Alternative constructor signature
   const EnvConfig({
     required this.baseApiUrl,
     required this.googleOAuthClientId,
-    required this.googleOAuthClientSecret,
+    this.googleOAuthClientSecret,
     required this.firebaseWebApiKey,
   });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/stac_cli/lib/src/config/env.dart` around lines 25 - 30, The
constructor currently declares nullable fields (baseApiUrl, googleOAuthClientId,
googleOAuthClientSecret, firebaseWebApiKey) but marks them as required in
EnvConfig, which forces callers to pass explicit nulls; decide intended
semantics and update the constructor accordingly: if these values are truly
optional, remove the required keyword from those named parameters (or convert
them to an optional positional parameter list) so callers may omit them; if they
must be present, change the types to non-nullable String and enforce/validate in
the EnvConfig constructor (or assert) so callers cannot pass null. Update
references to EnvConfig and any usage of _value to match the chosen signature.
packages/stac_cli/lib/src/utils/console_logger.dart (1)

122-126: Misleading function name: _leftPaddingForCenter doesn't center content.

The function divides by 4 (placing content ~25% from left) rather than by 2 (true centering). If this is intentional for visual aesthetics, consider renaming to _leftPaddingForBanner or similar to avoid confusion.

🔧 Suggested rename
-  static int _leftPaddingForCenter(int contentLength, int totalWidth) {
+  static int _leftPaddingForBanner(int contentLength, int totalWidth) {
     final remaining = totalWidth - contentLength;
     if (remaining <= 0) return 0;
     return remaining ~/ 4;
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/stac_cli/lib/src/utils/console_logger.dart` around lines 122 - 126,
The function _leftPaddingForCenter currently computes left padding as remaining
~/ 4 which yields a ~25% offset; either fix the logic to actually center by
returning remaining ~/ 2, or if the 25% offset was intentional, rename the
function (e.g., _leftPaddingForBanner or _leftOffsetForAesthetic) and update any
callers and comments accordingly so the name matches behavior; modify the return
expression in _leftPaddingForCenter (or the renamed function) and adjust
references to the function name across the file.
packages/stac_cli/lib/src/models/project/ui_loads.dart (1)

65-85: copyWith cannot clear nullable fields.

Using ?? for nullable properties prevents explicit reset-to-null updates.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/stac_cli/lib/src/models/project/ui_loads.dart` around lines 65 - 85,
The copyWith currently uses `??` which prevents setting nullable fields to null;
update `UiLoads.copyWith` to accept sentinel-based parameters (e.g. change each
parameter type to `Object? param = _unset` using a private const `_unset =
Object()`), then inside the constructor use a ternary that checks `param ==
_unset ? this.field : param as <type>?` for each field (reference the
parameters/currentPeriodUiLoadCount, lastUiLoadCountFlushed,
lastUiLoadsFlushedDelta, lastUiLoadsCountFlushedAt, lastUiLoadsUploadError,
lifetimeUiLoadCount and the `UiLoads` constructor) so callers can explicitly
pass null to clear a nullable property.
packages/stac_cli/lib/src/services/upgrade_service.dart (1)

96-99: upgradeTo accepts version but never uses it.

This makes the API misleading. Either validate that downloadUrl matches version, or remove version from the method signature.

Also applies to: 106-106

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/stac_cli/lib/src/services/upgrade_service.dart` around lines 96 -
99, The upgradeTo method accepts a version parameter but never uses it; either
remove the unused version parameter from upgradeTo (and from all its callers) to
keep the API accurate, or implement explicit validation inside upgradeTo (e.g.,
parse the version from downloadUrl or fetch the package metadata and compare it
to the provided version) and throw/return an error when they don't match; update
the upgradeTo signature and all call sites (and the related duplicate
occurrence) accordingly so the method and its callers stay consistent.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/stac_cli/lib/src/commands/base_command.dart`:
- Around line 48-51: The catch currently maps every StacException to
NotAuthenticatedException; instead, inspect the caught StacException (symbol:
StacException) for signs of authentication failure (e.g., status code == 401,
error code/message indicating "unauth"/"unauthorized", or a dedicated
AuthException subclass) and only throw NotAuthenticatedException when that
condition is met; for all other StacException cases preserve the original error
and stacktrace (use rethrow or throw the original exception) so unrelated
operational failures are not masked (update the catch block where StacException
is handled and replace the unconditional throw const NotAuthenticatedException()
with a conditional throw based on the exception’s auth indicators).

In `@packages/stac_cli/lib/src/commands/init_command.dart`:
- Around line 62-65: The code treats a user-initiated "skip project" (when
_selectOrCreateProjectInteractively() returns null) as an error by returning
exit code 1; change those return values to 0 to indicate successful intentional
skip. Update each branch where Project? project = await
_selectOrCreateProjectInteractively(); followed by if (project == null) return
1; (including occurrences around _selectOrCreateProjectInteractively at the
locations indicated and similar checks at the other noted spots) so they return
0 instead of 1, preserving normal flow when the user chooses to not set up a
default project.
- Around line 205-231: The generated dartConfig embeds project.name,
project.description, and project.id directly into single-quoted Dart string
literals which breaks when values contain quotes or backslashes; update the
generation in init_command.dart to escape these values before interpolation
(e.g., replace backslashes and single quotes and normalize newlines) or use a
safe Dart string-encoding helper so the StacOptions literal for
defaultStacOptions is always valid; locate the dartConfig template and the
interpolation sites for '${project.name}', '${project.description}', and
'${project.id}' and apply the escaping helper there.

In `@packages/stac_cli/lib/src/commands/project/list_command.dart`:
- Around line 1-3: The JSON flag currently prints Dart Map/List via toString(),
which is not valid JSON; update the code paths that output results (e.g., the
ListCommand run/execute method and the other occurrences around lines 35-38) to
import dart:convert and use jsonEncode(...) when --json is set instead of
calling toString(), ensuring any top-level object is serializable (convert
Maps/Lists/primitive types) before printing.

In `@packages/stac_cli/lib/src/commands/upgrade_command.dart`:
- Around line 16-19: The upgrade command's option registration using
argParser.addOption for 'version' currently sets abbr: 'v', which conflicts with
the global --verbose flag; remove the abbr (or change it to an unused short
flag) so the option becomes argParser.addOption('version', help: ...) (or use a
different letter not used globally), ensuring calls to the upgrade command
(e.g., in upgrade_command.dart where 'version' is defined) no longer collide
with the global --verbose defined in stac_cli.dart.

In `@packages/stac_cli/lib/src/services/auth_service.dart`:
- Around line 133-143: The HTTP refresh/login calls in auth_service.dart use
client.postUrl(uri), request.close(), and response.transform(...).join() with no
timeouts, so flows can hang; set a sensible connection timeout on the HttpClient
(client.connectionTimeout = Duration(...)) and wrap the async waits for
client.postUrl(uri), request.close(), and
response.transform(utf8.decoder).join() with Future.timeout(Duration(...)) (or
catch TimeoutException) so stalled Google/Firebase endpoints will error fast;
update the refresh/token/status code paths that use client, request.close(), and
responseBody retrieval to handle and propagate timeout errors appropriately.
- Around line 134-140: The token refresh request currently sets
request.headers.contentType to JSON and writes a JSON payload (payload with
'grant_type' and 'refresh_token'), but Firebase requires
application/x-www-form-urlencoded; change the content type to
ContentType('application', 'x-www-form-urlencoded') and send the body as a
form-encoded string (e.g., URL-encoded key=value pairs derived from the payload
map) instead of json.encode; update the code around the request write call that
constructs and sends the payload (the variables `request`, `payload`, and
`refreshToken`) so the server receives form-encoded data.

In `@packages/stac_cli/lib/src/services/build_service.dart`:
- Around line 84-90: The artifact conversion is recalculating the project root
from Directory.current, which breaks builds that pass build(projectPath: ...);
update the code so the project root is threaded into the conversion functions
instead of recomputing it: modify _convertCallableToJson to accept a projectPath
(or projectRoot) parameter and use Directory(projectPath) when resolving the
root, then change all call sites that invoke _convertCallableToJson (including
where _processArtifacts calls it) to pass the build(projectPath: ...) value
through (e.g., add projectPath argument to _processArtifacts and its callers
such as the screens/artifacts handling and the other listed locations) so
conversions run in the correct project context. Ensure function signatures and
downstream usages (_processArtifacts, _convertCallableToJson, any callers of
build(projectPath: ...)) are updated accordingly.

In `@packages/stac_cli/lib/src/services/deploy_service.dart`:
- Around line 30-31: The deploy code currently hardcodes the build directory via
buildDirPath and buildDir; change it to read the configured build output path
from the project's STAC options (the value defined in default_stac_options.dart)
instead of using 'stac/.build'. Locate where buildDirPath/buildDir is created in
DeployService (or the deploy method) and replace the hardcoded path with the
configured option (e.g., stacOptions.buildOutput or similar), falling back to
'stac/.build' only if the option is absent; ensure you import/use the project
STAC options provider or accept the build output path as a constructor/parameter
so the deploy service uses the configured build output.
- Around line 112-120: The deployment currently only logs warnings on partial
failures and returns success; update the logic after computing totalFailures
(the block using totalFailures, screenSuccess/screenFail,
themeSuccess/themeFail) so that when totalFailures > 0 the command exits with a
non-zero status instead of returning normally — e.g., log an error via
ConsoleLogger.error and then call exit(1) (from dart:io) or throw a specific
exception so the CLI returns a failure code; ensure this change is applied in
the same function where totalFailures is computed to guarantee `stac deploy`
exits non-zero on any upload failures.

In `@packages/stac_cli/lib/src/services/upgrade_service.dart`:
- Around line 329-339: Stage the new binary to a temporary path first instead of
deleting the old one: copy sourcePath to a temp file (e.g., "${destPath}.tmp" or
use File.createTemp) and verify the copy succeeded, then atomically replace the
existing binary by renaming/moving the temp file to destPath (use File.rename or
platform-appropriate replace) so the swap is atomic; update the logic around
isWindows/oldFile/destPath/sourcePath to stop deleting oldFile before staging
and only remove or overwrite via the atomic rename after successful staging, and
ensure file permissions (executable bit) are preserved or set before the final
rename.
- Around line 195-203: Check for a null return from detectOsArch() before
iterating assets and fail fast: call detectOsArch(), if it returns null throw
StacException('Unsupported platform') rather than force-unwrapping osArch in the
for-loop; then proceed to iterate and use _isMatchingAsset(name, osArch) and
assign downloadUrl/binaryAssetName only when osArch is non-null.
- Around line 248-252: The current _isMatchingAsset(String assetName,
Map<String, String> osArch) is too permissive (contains checks) and can match
checksum/signature assets; tighten it by requiring both os and arch to match
case-insensitively AND the filename to have an allowed binary archive/installer
extension (or not have a disallowed extension). Update _isMatchingAsset to
normalize assetName and os/arch to lower-case, check assetName.contains(os) &&
assetName.contains(arch), and additionally require
assetName.match(RegExp(r'\.(tar\.gz|tar\.bz2|zip|gz|exe|dmg|deb|rpm|tgz)$')) or
ensure it does not endWith('.sha256')/'.sig'/' .asc' (pick one approach) so
checksum/signature files are excluded.

In `@packages/stac_cli/lib/src/utils/date_time_utils.dart`:
- Around line 31-33: The Firestore timestamp reconstruction uses
DateTime.fromMillisecondsSinceEpoch without specifying UTC, causing local-time
shifts; update the DateTime construction in the conversion code (the
DateTime.fromMillisecondsSinceEpoch(...) call in date_time_utils.dart) to pass
isUtc: true so the returned DateTime reflects the original UTC instant (i.e.,
DateTime.fromMillisecondsSinceEpoch(<ms>, isUtc: true)).

In `@packages/stac_cli/lib/src/utils/http_client.dart`:
- Around line 93-108: The post method currently catches any non-DioException in
a generic catch and wraps it into a new StacException, which re-wraps existing
StacException instances and loses original details; update the post(String path,
{dynamic data}) method so that in the final catch block you detect if the caught
error is already a StacException and rethrow it unchanged, otherwise wrap
non-StacException errors into a new StacException (matching the behavior used by
get/put/delete); locate the post method and alter the final catch to check `if
(e is StacException) rethrow;` before throwing a new StacException for other
errors.

---

Outside diff comments:
In `@examples/movie_app/lib/default_stac_options.dart`:
- Around line 3-12: Update the documentation example import to match the actual
API surface: replace the outdated doc import "package:stac/stac.dart" with
"package:stac/stac_core.dart" in the comment block of default_stac_options.dart
so the example that shows initializing StacOptions/runApp is consistent with the
real import; ensure the comment still references StacOptions and
default_stac_options.dart correctly.

---

Minor comments:
In `@packages/stac_cli/lib/src/commands/auth/login_command.dart`:
- Around line 18-30: The registered '--interactive' flag on LoginCommand is
never read; update execute() to read argResults['interactive'] and pass that
value into _authService.login(...) (e.g., call _authService.login(interactive:
interactive)) so the flag controls behavior, or if AuthService.login does not
accept such a parameter yet, remove the argParser.addFlag('interactive', ...)
from the LoginCommand constructor to avoid dead code; ensure references are to
LoginCommand, argParser, execute(), argResults['interactive'], and
_authService.login.

In `@packages/stac_cli/lib/src/commands/build_command.dart`:
- Around line 26-30: The 'validate' flag declared via
argParser.addFlag('validate') is never read; update execute() to read the flag
from argResults (e.g., final validate = argResults['validate'] as bool) and pass
it into BuildService.build(...) (e.g., BuildService.build(..., validate:
validate)) so --no-validate takes effect, or if validation support isn't
implemented remove the argParser.addFlag('validate') declaration; apply the same
fix for the other addFlag(...) in this file (the second flag declaration) so all
declared flags are actually consumed by execute().

In `@packages/stac_cli/lib/src/commands/init_command.dart`:
- Around line 217-219: The generated example import uses the outdated package
path "package:stac/stac.dart" whereas the rest of the generated file expects
"stac_core.dart"; update the import line in the usage template to import
"stac_core.dart" (replace the string "package:stac/stac.dart" with the correct
"package:stac_core/stac_core.dart" or the local "stac_core.dart" used
elsewhere), and ensure any other generated import lines (e.g.,
default_stac_options.dart) match the actual filenames used by InitCommand's
template so the example compiles without manual edits.

In `@packages/stac_cli/lib/src/models/project/project.dart`:
- Around line 48-79: The current Project.copyWith uses `??` so callers cannot
explicitly set nullable fields (e.g., slug, description, deletedAt,
subscription, uiLoads) to null; change the signature to use sentinel defaults
(e.g., Object? sentinel = _undefined) for nullable params in Project.copyWith
and update the body to check identity against the sentinel for each field (if
param === sentinel then keep this.field else use the provided value, allowing
provided null to clear the field). Add a private sentinel constant (e.g., const
_undefined = Object()) in the file and apply this pattern for all nullable
parameters (slug, description, deletedAt, subscription, uiLoads, etc.) so
callers can intentionally clear values.

In `@packages/stac_cli/lib/src/models/project/subscription.dart`:
- Around line 171-201: copyWith currently uses the `param ?? this.field` pattern
which prevents callers from explicitly nulling nullable properties (e.g.,
`updatedAt`, `status`, `alertThreshold`); change `Subscription.copyWith` to use
a sentinel-based approach (or explicit wrapper/Optional) for nullable params so
you can detect "parameter provided and set to null" vs "parameter omitted" —
introduce a private sentinel object and make params typed as `Object?` (or use a
dedicated wrapper) then check against the sentinel to decide whether to assign
the new value (including null) or keep
`this.updatedAt`/`this.status`/`this.alertThreshold` when the sentinel is
passed; update the call site/constructor usage to accept the possibly-null
values accordingly while still defaulting to existing fields when the sentinel
is used.

In `@packages/stac_cli/lib/src/services/project_service.dart`:
- Around line 82-87: The current slug build (variable slug using
_nonAlphanumericRegex, _whitespaceRegex, _multipleHyphensRegex) can leave
leading/trailing hyphens for names with surrounding whitespace; fix by either
trimming the input before replacements or by adding a final step that strips
leading/trailing hyphens (e.g. apply name = name.trim() before the replace chain
or add a final replace using a regex like ^-+|-+$ to remove edge hyphens) so the
resulting slug has no leading or trailing '-' characters.

In `@packages/stac_cli/lib/src/utils/file_utils.dart`:
- Around line 10-15: The current static getter homeDirectory returns an empty
string when HOME/USERPROFILE is unset, producing invalid paths; change the
implementation of the static getter homeDirectory to throw a descriptive runtime
error (e.g., StateError) when the environment variable is missing instead of
returning ''—include which variable was missing and the current Platform in the
error message so callers (like configDirectory) must handle the missing-home
case; update the getter signature/implementation in file_utils.dart (static
String get homeDirectory) accordingly.
- Around line 68-82: The readYamlFile method currently calls loadYaml and lets
FormatException propagate; wrap the loadYaml(content) call (and the subsequent
Map conversion) in a try-catch that catches FormatException (and optionally
other parsing exceptions) and return null on error so malformed YAML behaves
like other missing/error cases; update function readYamlFile to call fileExists
and readFile as-is but protect loadYaml and Map<String, dynamic>.from(...) with
a try { final yaml = loadYaml(content); if (yaml is Map) return Map<String,
dynamic>.from(yaml); } on FormatException { return null; } (you can also log the
exception if desired).

In `@packages/stac_cli/pubspec.yaml`:
- Around line 7-8: The SDK constraint in pubspec.yaml (the environment: sdk
field) conflicts with pubspec.lock's dart ">=3.10.0 <4.0.0"; either update
pubspec.yaml's sdk constraint to ">=3.10.0 <4.0.0" to match the lock file, or
regenerate pubspec.lock (flutter pub get / dart pub get) and CI artifacts if you
intend to support ^3.8.1—modify the environment: sdk line in pubspec.yaml (or
regenerate the lock) accordingly so both files align.

---

Nitpick comments:
In `@packages/stac_cli/lib/src/config/env.dart`:
- Around line 7-10: The enum Environment and constant currentEnvironment are
defined but unused; either remove the unused enum/constant (Environment,
currentEnvironment) to avoid dead code, or keep them and add a TODO comment
explaining intended future use and link it to configuration switching (e.g.,
note which config functions should read currentEnvironment) so reviewers know
it’s deliberate—update the declaration accordingly to reflect chosen approach.
- Around line 25-30: The constructor currently declares nullable fields
(baseApiUrl, googleOAuthClientId, googleOAuthClientSecret, firebaseWebApiKey)
but marks them as required in EnvConfig, which forces callers to pass explicit
nulls; decide intended semantics and update the constructor accordingly: if
these values are truly optional, remove the required keyword from those named
parameters (or convert them to an optional positional parameter list) so callers
may omit them; if they must be present, change the types to non-nullable String
and enforce/validate in the EnvConfig constructor (or assert) so callers cannot
pass null. Update references to EnvConfig and any usage of _value to match the
chosen signature.

In `@packages/stac_cli/lib/src/exceptions/stac_exception.dart`:
- Around line 9-10: The StacException.toString() currently only returns
'StacException: $message' and should include the optional cause for better
debugging; update the StacException class so its toString() method (toString())
appends the cause when present (e.g., "StacException: $message; cause: $cause"
or similar), ensuring null-safe handling of the cause and preserving the
existing message formatting.

In `@packages/stac_cli/lib/src/models/auth_token.dart`:
- Around line 35-42: Add defensive null/type checks in AuthToken.fromJson:
validate required keys access_token, expires_at, and scopes exist and are of
expected types before calling the AuthToken constructor; parse expires_at with
try/catch to throw a clear FormatException if invalid; allow refresh_token to be
optional but validate its type if present; on missing/invalid required fields
throw descriptive exceptions (e.g., "Missing or invalid access_token in
AuthToken.fromJson") to replace unclear cast errors.

In `@packages/stac_cli/lib/src/models/project/project_access.dart`:
- Around line 38-40: Remove the redundant isEqual method from the ProjectAccess
enum and update any call sites to use Dart's native equality (==) instead;
search for the symbol isEqual in the codebase, delete the isEqual(ProjectAccess
other) => value == other.value implementation from the ProjectAccess
declaration, and replace usages like someAccess.isEqual(otherAccess) with
someAccess == otherAccess so the enum relies on built-in enum comparison.

In `@packages/stac_cli/lib/src/models/project/ui_loads.dart`:
- Around line 65-85: The copyWith currently uses `??` which prevents setting
nullable fields to null; update `UiLoads.copyWith` to accept sentinel-based
parameters (e.g. change each parameter type to `Object? param = _unset` using a
private const `_unset = Object()`), then inside the constructor use a ternary
that checks `param == _unset ? this.field : param as <type>?` for each field
(reference the parameters/currentPeriodUiLoadCount, lastUiLoadCountFlushed,
lastUiLoadsFlushedDelta, lastUiLoadsCountFlushedAt, lastUiLoadsUploadError,
lifetimeUiLoadCount and the `UiLoads` constructor) so callers can explicitly
pass null to clear a nullable property.

In `@packages/stac_cli/lib/src/services/upgrade_service.dart`:
- Around line 96-99: The upgradeTo method accepts a version parameter but never
uses it; either remove the unused version parameter from upgradeTo (and from all
its callers) to keep the API accurate, or implement explicit validation inside
upgradeTo (e.g., parse the version from downloadUrl or fetch the package
metadata and compare it to the provided version) and throw/return an error when
they don't match; update the upgradeTo signature and all call sites (and the
related duplicate occurrence) accordingly so the method and its callers stay
consistent.

In `@packages/stac_cli/lib/src/utils/console_logger.dart`:
- Around line 122-126: The function _leftPaddingForCenter currently computes
left padding as remaining ~/ 4 which yields a ~25% offset; either fix the logic
to actually center by returning remaining ~/ 2, or if the 25% offset was
intentional, rename the function (e.g., _leftPaddingForBanner or
_leftOffsetForAesthetic) and update any callers and comments accordingly so the
name matches behavior; modify the return expression in _leftPaddingForCenter (or
the renamed function) and adjust references to the function name across the
file.

In `@packages/stac_cli/lib/src/utils/string_utils.dart`:
- Around line 23-31: toSnakeCase currently inserts an underscore before every
single uppercase letter, breaking acronyms (e.g. "HTTPClient" →
"h_t_t_p_client"); update the toSnakeCase implementation to treat consecutive
uppercase letters as a single acronym run: detect runs of uppercase letters
(optionally followed by a lowercase) and runs that start after a
lowercase/digit, insert a single underscore before the whole run and lowercase
the result. Modify the RegExp used in toSnakeCase (and its replaceAllMapped
logic) to match uppercase sequences rather than individual letters (for example
using lookarounds such as '(?<=[a-z0-9])([A-Z]+)' and
'([A-Z]+)(?=[A-Z][a-z]|$)') and transform matches to '_'+match.toLowerCase(),
then trim any leading underscore as before.

In `@packages/stac_cli/pubspec.yaml`:
- Line 33: The trailing orphan comment "# stac_cli/pubspec_overrides.yaml"
should be removed or clarified; either delete the line if it's unnecessary or
replace it with a descriptive comment such as "See pubspec_overrides.yaml for
local development overrides" so its intent is clear; locate the comment string
"# stac_cli/pubspec_overrides.yaml" in the pubspec.yaml and update accordingly.

In `@packages/stac_cli/README.md`:
- Line 35: The README currently points users to the internal file
lib/src/config/env.dart and the symbol currentEnvironment; update the
documentation to clarify intent: indicate that lib/src/... and
currentEnvironment are for contributors/developers only, and then document the
user-facing mechanism to change environments (e.g., describe the CLI flag or
environment variable to use when running the installed CLI, or explain how to
rebuild with a different environment if applicable). Mention the exact symbol
currentEnvironment and the file lib/src/config/env.dart so maintainers can find
the developer-only option, and add an example command showing the supported user
method (CLI flag or ENV var) for global installs.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 752f9a6 and 716012e.

⛔ Files ignored due to path filters (1)
  • packages/stac_cli/pubspec.lock is excluded by !**/*.lock
📒 Files selected for processing (49)
  • .gitignore
  • examples/movie_app/lib/default_stac_options.dart
  • examples/movie_app/lib/widgets/movie_carousel/movie_carousel.dart
  • examples/movie_app/pubspec.yaml
  • packages/stac_cli/.gitignore
  • packages/stac_cli/LICENSE
  • packages/stac_cli/README.md
  • packages/stac_cli/analysis_options.yaml
  • packages/stac_cli/bin/stac_cli.dart
  • packages/stac_cli/lib/src/commands/auth/login_command.dart
  • packages/stac_cli/lib/src/commands/auth/logout_command.dart
  • packages/stac_cli/lib/src/commands/auth/status_command.dart
  • packages/stac_cli/lib/src/commands/base_command.dart
  • packages/stac_cli/lib/src/commands/build_command.dart
  • packages/stac_cli/lib/src/commands/deploy_command.dart
  • packages/stac_cli/lib/src/commands/init_command.dart
  • packages/stac_cli/lib/src/commands/project/create_command.dart
  • packages/stac_cli/lib/src/commands/project/list_command.dart
  • packages/stac_cli/lib/src/commands/project_command.dart
  • packages/stac_cli/lib/src/commands/upgrade_command.dart
  • packages/stac_cli/lib/src/config/env.dart
  • packages/stac_cli/lib/src/exceptions/auth_exception.dart
  • packages/stac_cli/lib/src/exceptions/build_exception.dart
  • packages/stac_cli/lib/src/exceptions/stac_exception.dart
  • packages/stac_cli/lib/src/models/auth_token.dart
  • packages/stac_cli/lib/src/models/project/project.dart
  • packages/stac_cli/lib/src/models/project/project.g.dart
  • packages/stac_cli/lib/src/models/project/project_access.dart
  • packages/stac_cli/lib/src/models/project/subscription.dart
  • packages/stac_cli/lib/src/models/project/subscription.g.dart
  • packages/stac_cli/lib/src/models/project/ui_loads.dart
  • packages/stac_cli/lib/src/models/project/ui_loads.g.dart
  • packages/stac_cli/lib/src/models/stac_dsl_artifact.dart
  • packages/stac_cli/lib/src/services/auth_service.dart
  • packages/stac_cli/lib/src/services/build_service.dart
  • packages/stac_cli/lib/src/services/config_service.dart
  • packages/stac_cli/lib/src/services/deploy_service.dart
  • packages/stac_cli/lib/src/services/project_service.dart
  • packages/stac_cli/lib/src/services/upgrade_service.dart
  • packages/stac_cli/lib/src/utils/console_logger.dart
  • packages/stac_cli/lib/src/utils/date_time_utils.dart
  • packages/stac_cli/lib/src/utils/file_utils.dart
  • packages/stac_cli/lib/src/utils/http_client.dart
  • packages/stac_cli/lib/src/utils/oauth_pkce.dart
  • packages/stac_cli/lib/src/utils/string_utils.dart
  • packages/stac_cli/lib/src/version.dart
  • packages/stac_cli/lib/stac_cli.dart
  • packages/stac_cli/pubspec.yaml
  • packages/stac_cli/pubspec_overrides.yaml

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
packages/stac_cli/lib/src/utils/file_utils.dart (1)

56-58: Make writes resilient by creating the parent directory.

writeFile currently assumes the parent path already exists. Creating it here prevents avoidable FileSystemException failures.

♻️ Proposed change
 static Future<void> writeFile(String filePath, String content) async {
-  await File(filePath).writeAsString(content);
+  final file = File(filePath);
+  await file.parent.create(recursive: true);
+  await file.writeAsString(content);
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/stac_cli/lib/src/utils/file_utils.dart` around lines 56 - 58, The
writeFile method assumes the parent directory exists; update writeFile to create
the parent directory before writing by resolving the parent via
File(filePath).parent and calling create(recursive: true) (handle async) prior
to invoking writeAsString; keep using the same writeFile signature and await
semantics so File(filePath).writeAsString(content) runs after the parent
directory creation.
packages/stac_cli/lib/src/config/env.dart (1)

9-10: Avoid compile-time environment switching for dev/prod.

currentEnvironment is pinned to prod (Line 10), so local/dev routing requires source edits. Prefer runtime selection (for example STAC_ENV) with a safe default to reduce accidental production targeting.

♻️ Proposed change
-// Flip this to switch environments.
-const Environment currentEnvironment = Environment.prod;
+// Runtime-selectable environment. Defaults to prod.
+Environment get currentEnvironment {
+  switch (_value('STAC_ENV')?.toLowerCase()) {
+    case 'dev':
+      return Environment.dev;
+    case 'prod':
+    default:
+      return Environment.prod;
+  }
+}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/stac_cli/lib/src/config/env.dart` around lines 9 - 10, The code
currently pins the compile-time const currentEnvironment to Environment.prod;
change this to a runtime-resolved value so developers can switch without editing
source: replace the const currentEnvironment with a runtime-initialized final
(or getter) that reads Platform.environment['STAC_ENV'] (or similar), map values
like "prod"/"production" to Environment.prod and default to Environment.dev
(safe default to avoid accidental production targeting), and keep the
Environment enum unchanged; update any code that relied on currentEnvironment
being const if necessary.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/stac_cli/lib/src/utils/file_utils.dart`:
- Around line 10-24: The homeDirectory getter currently returns an empty string
when HOME/USERPROFILE is missing which allows configDirectory to resolve to a
relative path; change homeDirectory (the static getter) to fail fast by throwing
a descriptive StateError (or similar) if Platform.environment['HOME'] /
['USERPROFILE'] is null, so callers like the static configDirectory getter
cannot proceed with a bogus empty path and will surface a clear error instead.

---

Nitpick comments:
In `@packages/stac_cli/lib/src/config/env.dart`:
- Around line 9-10: The code currently pins the compile-time const
currentEnvironment to Environment.prod; change this to a runtime-resolved value
so developers can switch without editing source: replace the const
currentEnvironment with a runtime-initialized final (or getter) that reads
Platform.environment['STAC_ENV'] (or similar), map values like
"prod"/"production" to Environment.prod and default to Environment.dev (safe
default to avoid accidental production targeting), and keep the Environment enum
unchanged; update any code that relied on currentEnvironment being const if
necessary.

In `@packages/stac_cli/lib/src/utils/file_utils.dart`:
- Around line 56-58: The writeFile method assumes the parent directory exists;
update writeFile to create the parent directory before writing by resolving the
parent via File(filePath).parent and calling create(recursive: true) (handle
async) prior to invoking writeAsString; keep using the same writeFile signature
and await semantics so File(filePath).writeAsString(content) runs after the
parent directory creation.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 716012e and a12fd50.

📒 Files selected for processing (2)
  • packages/stac_cli/lib/src/config/env.dart
  • packages/stac_cli/lib/src/utils/file_utils.dart

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (4)
packages/stac_cli/lib/src/commands/upgrade_command.dart (3)

6-7: Consider constructor injection for testability.

Instantiating UpgradeService directly makes unit testing difficult. Constructor injection allows mocking the service in tests.

♻️ Proposed refactor for dependency injection
 class UpgradeCommand extends BaseCommand {
-  final UpgradeService _upgradeService = UpgradeService();
+  final UpgradeService _upgradeService;
+
+  UpgradeCommand({UpgradeService? upgradeService})
+      : _upgradeService = upgradeService ?? UpgradeService() {
+    argParser.addOption(
+      'version',
+      help: 'Specific version to install (e.g., 1.2.0)',
+    );
+    argParser.addFlag(
+      'force',
+      abbr: 'f',
+      negatable: false,
+      help: 'Force upgrade even if already on latest version',
+    );
+  }

-  UpgradeCommand() {
-    argParser.addOption(
-      'version',
-      help: 'Specific version to install (e.g., 1.2.0)',
-    );
-    argParser.addFlag(
-      'force',
-      abbr: 'f',
-      negatable: false,
-      help: 'Force upgrade even if already on latest version',
-    );
-  }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/stac_cli/lib/src/commands/upgrade_command.dart` around lines 6 - 7,
The UpgradeCommand currently instantiates UpgradeService directly (final
UpgradeService _upgradeService = UpgradeService()), which hinders unit testing;
change UpgradeCommand to accept an UpgradeService via its constructor (e.g., a
required or optional parameter in UpgradeCommand(...)) and assign it to the
_upgradeService field so tests can inject a mock or fake UpgradeService
instance; update any call sites that create UpgradeCommand to pass the real
UpgradeService and adjust visibility of _upgradeService if necessary.

29-31: Consider validating version format early.

The specificVersion is passed directly to the service without format validation. Invalid input like stac upgrade --version foo could result in unclear error messages from the network call.

final specificVersion = argResults?['version'] as String?;
if (specificVersion != null && !RegExp(r'^\d+\.\d+\.\d+').hasMatch(specificVersion)) {
  ConsoleLogger.error('Invalid version format. Expected semver (e.g., 1.2.0)');
  return 1;
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/stac_cli/lib/src/commands/upgrade_command.dart` around lines 29 -
31, In execute(), validate the user-supplied specificVersion
(argResults?['version']) before passing it to the service: if specificVersion is
non-null ensure it matches a semver pattern (e.g., /^\d+\.\d+\.\d+$/) and on
mismatch call ConsoleLogger.error with a clear message like "Invalid version
format. Expected semver (e.g., 1.2.0)" and return a non-zero exit code (e.g., 1)
to avoid sending invalid input to the network call.

48-61: Consider consolidating redundant early-exit conditions.

When specificVersion == null, targetVersion equals latestVersion. The first condition (line 49) checks string equality currentVersion == targetVersion, while the second (line 56) checks !updateAvailable. These overlap when no specific version is requested.

If updateAvailable is computed via semantic version comparison (which may differ from string equality), both checks provide defense-in-depth. Otherwise, consider consolidating:

♻️ Proposed consolidation
       // Check if upgrade is needed
-      if (!force && checkResult.currentVersion == targetVersion) {
-        ConsoleLogger.success(
-          '✓ Already on the latest version (${checkResult.currentVersion})',
-        );
-        return 0;
-      }
-
-      if (!force && !checkResult.updateAvailable && specificVersion == null) {
+      final alreadyOnTarget = checkResult.currentVersion == targetVersion;
+      final noUpdateNeeded = !checkResult.updateAvailable && specificVersion == null;
+      
+      if (!force && (alreadyOnTarget || noUpdateNeeded)) {
         ConsoleLogger.success(
           '✓ Already on the latest version (${checkResult.currentVersion})',
         );
         return 0;
       }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/stac_cli/lib/src/commands/upgrade_command.dart` around lines 48 -
61, Consolidate the redundant early-exit checks into a single condition: when
not forcing and no specificVersion is requested, combine the existing equality
and update-available guards into one branch that logs the success and returns 0.
Update the logic around checkResult, targetVersion, specificVersion,
updateAvailable and force (used by ConsoleLogger.success) so you only have one
early return for the “already up-to-date” case (e.g. !force && specificVersion
== null && (checkResult.currentVersion == targetVersion ||
!checkResult.updateAvailable)); alternatively if updateAvailable is the
authoritative semantic comparison, use that alone with !force && specificVersion
== null to decide the early exit.
packages/stac_cli/lib/src/commands/init_command.dart (1)

273-277: Command suggestion may be invalid if name/description contain spaces or special characters.

The printed command suggestion doesn't quote the arguments. If name is "My Project", the suggestion becomes stac project create -n My Project -d ... which would fail. Consider quoting the values.

💡 Suggested improvement
           ConsoleLogger.info(
-            'You can also run "stac project create -n ${name.trim()} -d ${description.trim()}" and then re-run "stac init".',
+            'You can also run "stac project create -n \'${name.trim()}\' -d \'${description.trim()}\'" and then re-run "stac init".',
           );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/stac_cli/lib/src/commands/init_command.dart` around lines 273 - 277,
The printed suggestion in init_command.dart uses ConsoleLogger.info with
unquoted name and description, which will break if name/description contain
spaces or special characters; update the suggestion to properly quote and escape
the values (e.g., wrap name.trim() and description.trim() in quotes or
shell-escape them) when building the command string passed to ConsoleLogger.info
(refer to the block around ConsoleLogger.error("Failed to create project: \$e")
and the subsequent ConsoleLogger.info call that uses name and description).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/stac_cli/lib/src/commands/init_command.dart`:
- Around line 138-144: The inner catch block currently uses catch
(ProcessException) but logs ConsoleLogger.error('Failed to manually add
dependency: $e') which references the outer exception variable; update the inner
catch to capture its exception (e.g., catch (ProcessException inner) or catch
(ProcessException e)) and log that captured variable in ConsoleLogger.error so
the actual dart failure is reported (adjust the log message to include the inner
exception variable). Ensure you update only the inner catch signature and the
ConsoleLogger.error call (symbols: the inner catch clause and
ConsoleLogger.error).

In `@packages/stac_cli/lib/src/services/build_service.dart`:
- Around line 165-169: The hidden-directory filter using
entity.path.contains('/.') fails on Windows because it only checks for forward
slashes; update the check around entity.path (the code that decides whether to
add to dartFiles) to detect hidden directories cross-platform by either using
package:path to split path segments and checking any segment.startsWith('.') or
by using a regex that matches either slash or backslash (e.g., one that looks
for '(^|[\\/])\\.'). Also ensure the '.build' check similarly inspects path
segments rather than searching for substring '.build' so it works on Windows
paths; update the condition that currently references entity.path to use these
platform-agnostic checks.
- Around line 594-609: The _cleanJson function currently uses reference equality
(cleanedValue != [] / {}), which never detects empty collections; update the
checks to test for emptiness by type instead — e.g. after computing
cleanedValue, treat it as invalid if it is null or (cleanedValue is List &&
cleanedValue.isEmpty) or (cleanedValue is Map && cleanedValue.isEmpty), and only
add to cleanedMap when it passes these checks; likewise when building
cleanedList filter out items that are null or empty by using the same type-based
(is List/Map) isEmpty tests. Ensure returns remain returning null for empty
map/list results.
- Around line 520-532: The current use of Process.run with .timeout lets the
Dart subprocess keep running if the timeout fires; replace Process.run with
Process.start to obtain a Process object (use the same args: 'dart' and ['run',
path.basename(scriptFile.path)] with workingDirectory: projectDir and
runInShell: Platform.isWindows), capture stdout/stderr into strings, then await
process.exitCode with a timeout and on timeout call process.kill() (and
optionally process.kill(ProcessSignal.sigkill) if still running) before
throwing; update subsequent uses of result.exitCode, result.stdout,
result.stderr to use the Process object's exitCode, captured stdout, and
captured stderr variables instead.

---

Nitpick comments:
In `@packages/stac_cli/lib/src/commands/init_command.dart`:
- Around line 273-277: The printed suggestion in init_command.dart uses
ConsoleLogger.info with unquoted name and description, which will break if
name/description contain spaces or special characters; update the suggestion to
properly quote and escape the values (e.g., wrap name.trim() and
description.trim() in quotes or shell-escape them) when building the command
string passed to ConsoleLogger.info (refer to the block around
ConsoleLogger.error("Failed to create project: \$e") and the subsequent
ConsoleLogger.info call that uses name and description).

In `@packages/stac_cli/lib/src/commands/upgrade_command.dart`:
- Around line 6-7: The UpgradeCommand currently instantiates UpgradeService
directly (final UpgradeService _upgradeService = UpgradeService()), which
hinders unit testing; change UpgradeCommand to accept an UpgradeService via its
constructor (e.g., a required or optional parameter in UpgradeCommand(...)) and
assign it to the _upgradeService field so tests can inject a mock or fake
UpgradeService instance; update any call sites that create UpgradeCommand to
pass the real UpgradeService and adjust visibility of _upgradeService if
necessary.
- Around line 29-31: In execute(), validate the user-supplied specificVersion
(argResults?['version']) before passing it to the service: if specificVersion is
non-null ensure it matches a semver pattern (e.g., /^\d+\.\d+\.\d+$/) and on
mismatch call ConsoleLogger.error with a clear message like "Invalid version
format. Expected semver (e.g., 1.2.0)" and return a non-zero exit code (e.g., 1)
to avoid sending invalid input to the network call.
- Around line 48-61: Consolidate the redundant early-exit checks into a single
condition: when not forcing and no specificVersion is requested, combine the
existing equality and update-available guards into one branch that logs the
success and returns 0. Update the logic around checkResult, targetVersion,
specificVersion, updateAvailable and force (used by ConsoleLogger.success) so
you only have one early return for the “already up-to-date” case (e.g. !force &&
specificVersion == null && (checkResult.currentVersion == targetVersion ||
!checkResult.updateAvailable)); alternatively if updateAvailable is the
authoritative semantic comparison, use that alone with !force && specificVersion
== null to decide the early exit.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a12fd50 and 0153123.

📒 Files selected for processing (4)
  • packages/stac_cli/lib/src/commands/init_command.dart
  • packages/stac_cli/lib/src/commands/project/list_command.dart
  • packages/stac_cli/lib/src/commands/upgrade_command.dart
  • packages/stac_cli/lib/src/services/build_service.dart
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/stac_cli/lib/src/commands/project/list_command.dart

@divyanshub024 divyanshub024 merged commit 2ba4910 into dev Mar 1, 2026
6 checks passed
@divyanshub024 divyanshub024 deleted the dv/stac-cli branch March 1, 2026 09:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant