naas-abi-cli 1.8.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. naas_abi_cli/__init__.py +0 -0
  2. naas_abi_cli/cli/__init__.py +65 -0
  3. naas_abi_cli/cli/agent.py +30 -0
  4. naas_abi_cli/cli/chat.py +29 -0
  5. naas_abi_cli/cli/config.py +49 -0
  6. naas_abi_cli/cli/deploy.py +212 -0
  7. naas_abi_cli/cli/init.py +13 -0
  8. naas_abi_cli/cli/module.py +28 -0
  9. naas_abi_cli/cli/new/__init__.py +4 -0
  10. naas_abi_cli/cli/new/module.py +55 -0
  11. naas_abi_cli/cli/new/new.py +6 -0
  12. naas_abi_cli/cli/new/project.py +63 -0
  13. naas_abi_cli/cli/new/templates/module/__init__.py +31 -0
  14. naas_abi_cli/cli/new/templates/module/agents/README.md +0 -0
  15. naas_abi_cli/cli/new/templates/module/agents/{{module_name_pascal}}Agent.py +52 -0
  16. naas_abi_cli/cli/new/templates/module/orchestrations/README.md +0 -0
  17. naas_abi_cli/cli/new/templates/module/pipelines/README.md +0 -0
  18. naas_abi_cli/cli/new/templates/module/workflows/README.md +0 -0
  19. naas_abi_cli/cli/new/templates/project/.github/workflows/release.yaml +31 -0
  20. naas_abi_cli/cli/new/templates/project/.gitignore +3 -0
  21. naas_abi_cli/cli/new/templates/project/Dockerfile +11 -0
  22. naas_abi_cli/cli/new/templates/project/README.md +0 -0
  23. naas_abi_cli/cli/new/templates/project/config.prod.yaml +70 -0
  24. naas_abi_cli/cli/new/templates/project/config.yaml +62 -0
  25. naas_abi_cli/cli/new/templates/project/pyproject.toml +21 -0
  26. naas_abi_cli/cli/run.py +18 -0
  27. naas_abi_cli/cli/secret.py +79 -0
  28. naas_abi_cli/cli/utils/Copier.py +84 -0
  29. naas_abi_cli-1.8.0.dist-info/METADATA +243 -0
  30. naas_abi_cli-1.8.0.dist-info/RECORD +32 -0
  31. naas_abi_cli-1.8.0.dist-info/WHEEL +4 -0
  32. naas_abi_cli-1.8.0.dist-info/entry_points.txt +2 -0
File without changes
@@ -0,0 +1,65 @@
1
+ import os
2
+ import subprocess
3
+ import sys
4
+
5
+ import click
6
+
7
+ from .agent import agent
8
+ from .chat import chat
9
+ from .config import config
10
+ from .deploy import deploy
11
+ from .init import init
12
+ from .module import module
13
+ from .new import new
14
+ from .run import run
15
+ from .secret import secrets
16
+
17
+
18
+ @click.group("abi")
19
+ def _main():
20
+ pass
21
+
22
+
23
+ _main.add_command(secrets)
24
+ _main.add_command(config)
25
+ _main.add_command(module)
26
+ _main.add_command(agent)
27
+ _main.add_command(chat)
28
+ _main.add_command(new)
29
+ _main.add_command(init)
30
+ _main.add_command(deploy)
31
+ _main.add_command(run)
32
+
33
+ ran = False
34
+
35
+
36
+ def main():
37
+ global ran
38
+ if ran:
39
+ return
40
+ ran = True
41
+
42
+ # Check how the project is being runned.
43
+ if os.getenv("LOCAL_UV_RAN") is None:
44
+ if "pyproject.toml" in os.listdir(os.getcwd()):
45
+ with open("pyproject.toml", "r") as file:
46
+ if "naas-abi-cli" in file.read():
47
+ arguments = (
48
+ "uv run --active python -m naas_abi_cli.cli".split(" ")
49
+ + sys.argv[1:]
50
+ )
51
+ try:
52
+ subprocess.run(
53
+ arguments,
54
+ cwd=os.getcwd(),
55
+ env={**os.environ, "LOCAL_UV_RAN": "true"},
56
+ check=True,
57
+ )
58
+ except Exception:
59
+ pass
60
+
61
+ return
62
+ _main()
63
+
64
+
65
+ main()
@@ -0,0 +1,30 @@
1
+ import click
2
+ from rich.console import Console
3
+ from rich.table import Table
4
+
5
+ from naas_abi_core.engine.Engine import Engine
6
+
7
+
8
+ @click.group("agent")
9
+ def agent():
10
+ pass
11
+
12
+
13
+ @agent.command("list")
14
+ def list():
15
+ engine = Engine()
16
+ engine.load()
17
+
18
+ console = Console()
19
+ table = Table(
20
+ title="Available Agents", show_header=True, header_style="bold magenta"
21
+ )
22
+ table.add_column("Module", style="cyan", no_wrap=True)
23
+ table.add_column("Agent", style="green")
24
+
25
+ modules = engine.modules
26
+ for module in modules:
27
+ for agent in modules[module].agents:
28
+ table.add_row(module, agent.__name__)
29
+
30
+ console.print(table)
@@ -0,0 +1,29 @@
1
+ import click
2
+ from naas_abi_core import logger
3
+
4
+
5
+ @click.command("chat")
6
+ @click.argument("module-name", type=str, default="")
7
+ @click.argument("agent-name", type=str, default="")
8
+ def chat(module_name: str = "", agent_name: str = ""):
9
+ from naas_abi_core.engine.Engine import Engine
10
+
11
+ engine = Engine()
12
+
13
+ if module_name == "" and agent_name == "":
14
+ module_name, agent_name = engine.configuration.default_agent.split(" ")
15
+
16
+ engine.load(module_names=[module_name])
17
+
18
+ from naas_abi_core.apps.terminal_agent.main import run_agent
19
+
20
+ if module_name not in engine.modules:
21
+ raise ValueError(f"Module {module_name} not found")
22
+
23
+ logger.debug(f"Module agents: {engine.modules[module_name].agents}")
24
+
25
+ for agent_class in engine.modules[module_name].agents:
26
+ logger.debug(f"Agent class: {agent_class.__name__}")
27
+ if agent_class.__name__ == agent_name:
28
+ run_agent(agent_class.New())
29
+ break
@@ -0,0 +1,49 @@
1
+ import os
2
+
3
+ import click
4
+ import yaml
5
+
6
+ from naas_abi_core.engine.engine_configuration.EngineConfiguration import (
7
+ EngineConfiguration,
8
+ )
9
+
10
+
11
+ @click.group("config")
12
+ def config():
13
+ pass
14
+
15
+
16
+ @config.command("validate")
17
+ @click.option("--configuration-file", type=str, required=False, default=None)
18
+ def validate(configuration_file: str | None):
19
+ configuration_content: str | None = None
20
+
21
+ if configuration_file is not None:
22
+ if not os.path.exists(configuration_file):
23
+ raise FileNotFoundError(
24
+ f"Configuration file {configuration_file} not found"
25
+ )
26
+ with open(configuration_file, "r") as file:
27
+ configuration_content = file.read()
28
+
29
+ EngineConfiguration.load_configuration(configuration_content)
30
+ print("Configuration is valid")
31
+
32
+
33
+ @config.command("render")
34
+ @click.option("--configuration-file", type=str, required=False, default=None)
35
+ def render(configuration_file: str | None):
36
+ configuration_content: str | None = None
37
+
38
+ if configuration_file is not None:
39
+ if not os.path.exists(configuration_file):
40
+ raise FileNotFoundError(
41
+ f"Configuration file {configuration_file} not found"
42
+ )
43
+ with open(configuration_file, "r") as file:
44
+ configuration_content = file.read()
45
+
46
+ configuration: EngineConfiguration = EngineConfiguration.load_configuration(
47
+ configuration_content
48
+ )
49
+ print(yaml.dump(configuration.model_dump(), indent=2))
@@ -0,0 +1,212 @@
1
+ import os
2
+ import subprocess
3
+ from uuid import uuid4
4
+
5
+ import click
6
+ import requests
7
+ from naas_abi_core import logger
8
+ from naas_abi_core.engine.engine_configuration.EngineConfiguration import (
9
+ EngineConfiguration,
10
+ )
11
+ from pydantic import BaseModel
12
+ from rich.console import Console
13
+ from rich.markdown import Markdown
14
+
15
+
16
+ @click.group("deploy")
17
+ def deploy():
18
+ pass
19
+
20
+
21
+ class Container(BaseModel):
22
+ name: str
23
+ image: str
24
+ port: int
25
+ cpu: str
26
+ memory: str
27
+ env: dict
28
+
29
+
30
+ class Space(BaseModel):
31
+ name: str
32
+ containers: list[Container]
33
+
34
+
35
+ class NaasAPIClient:
36
+ naas_api_key: str
37
+ base_url: str
38
+
39
+ def __init__(self, naas_api_key: str):
40
+ self.naas_api_key = naas_api_key
41
+ self.base_url = "https://api.naas.ai"
42
+
43
+ def create_registry(self, name: str):
44
+ response = requests.post(
45
+ f"{self.base_url}/registry/",
46
+ headers={"Authorization": f"Bearer {self.naas_api_key}"},
47
+ json={"name": name},
48
+ )
49
+ if response.status_code == 409:
50
+ return self.get_registry(name)
51
+ response.raise_for_status()
52
+ return response.json()
53
+
54
+ def get_registry(self, name: str):
55
+ response = requests.get(
56
+ f"{self.base_url}/registry/{name}",
57
+ headers={"Authorization": f"Bearer {self.naas_api_key}"},
58
+ )
59
+ response.raise_for_status()
60
+ return response.json()
61
+
62
+ def get_registry_credentials(self, name: str):
63
+ response = requests.get(
64
+ f"{self.base_url}/registry/{name}/credentials",
65
+ headers={"Authorization": f"Bearer {self.naas_api_key}"},
66
+ )
67
+ response.raise_for_status()
68
+ return response.json()
69
+
70
+ def update_space(self, space: Space) -> dict:
71
+ response = requests.put(
72
+ f"{self.base_url}/space/{space.name}",
73
+ headers={"Authorization": f"Bearer {self.naas_api_key}"},
74
+ json=space.model_dump(),
75
+ )
76
+ response.raise_for_status()
77
+ return response.json()
78
+
79
+ def create_space(self, space: Space) -> dict:
80
+ response = requests.post(
81
+ f"{self.base_url}/space/",
82
+ headers={"Authorization": f"Bearer {self.naas_api_key}"},
83
+ json=space.model_dump(),
84
+ )
85
+
86
+ if response.status_code == 409:
87
+ return self.update_space(space)
88
+ elif response.status_code == 402:
89
+ raise click.ClickException(
90
+ "You must have an active subscription to create a space on naas.ai."
91
+ )
92
+
93
+ response.raise_for_status()
94
+ return response.json()
95
+
96
+ def get_space(self, name: str) -> dict:
97
+ response = requests.get(
98
+ f"{self.base_url}/space/{name}",
99
+ headers={"Authorization": f"Bearer {self.naas_api_key}"},
100
+ )
101
+ response.raise_for_status()
102
+ return response.json()
103
+
104
+
105
+ class NaasDeployer:
106
+ image_name: str
107
+
108
+ naas_api_client: NaasAPIClient
109
+
110
+ def __init__(self, configuration: EngineConfiguration):
111
+ self.configuration = configuration
112
+ self.image_name = str(uuid4())
113
+ if configuration.deploy is None:
114
+ # Fail fast with a clear, user-facing error instead of an assertion.
115
+ raise click.ClickException(
116
+ "Deploy configuration is missing; please add a deploy section before running this command."
117
+ )
118
+ self.naas_api_client = NaasAPIClient(configuration.deploy.naas_api_key)
119
+
120
+ def docker_build(self, image_name: str):
121
+ subprocess.run(
122
+ f"docker build -t {image_name} . --platform linux/amd64", shell=True
123
+ )
124
+
125
+ def deploy(self):
126
+ assert self.configuration.deploy is not None
127
+ registry = self.naas_api_client.create_registry(
128
+ self.configuration.deploy.space_name
129
+ )
130
+
131
+ uid = str(uuid4())
132
+
133
+ image_name = f"{registry['registry']['uri']}:{uid}"
134
+ self.docker_build(image_name)
135
+ credentials = self.naas_api_client.get_registry_credentials(
136
+ self.configuration.deploy.space_name
137
+ )
138
+ docker_login_command = f"docker login -u {credentials['credentials']['username']} -p {credentials['credentials']['password']} {registry['registry']['uri']}"
139
+ subprocess.run(docker_login_command, shell=True)
140
+ subprocess.run(f"docker push {image_name}", shell=True)
141
+
142
+ image_sha = (
143
+ subprocess.run(
144
+ "docker inspect --format='{{index .RepoDigests 0}}' "
145
+ + image_name
146
+ + " | cut -d'@' -f2",
147
+ shell=True,
148
+ capture_output=True,
149
+ )
150
+ .stdout.strip()
151
+ .decode("utf-8")
152
+ )
153
+
154
+ if image_sha is None or image_sha == "":
155
+ raise click.ClickException(
156
+ "Failed to get image SHA. Please check if the image is correctly built and pushed to the registry."
157
+ )
158
+
159
+ image_name_with_sha = f"{image_name.replace(':' + uid, '')}@{image_sha}"
160
+
161
+ self.naas_api_client.create_space(
162
+ Space(
163
+ name=self.configuration.deploy.space_name,
164
+ containers=[
165
+ Container(
166
+ name="api",
167
+ image=image_name_with_sha,
168
+ port=9879,
169
+ cpu="1",
170
+ memory="1Gi",
171
+ env=self.configuration.deploy.env,
172
+ )
173
+ ],
174
+ )
175
+ )
176
+
177
+ self.naas_api_client.get_space(self.configuration.deploy.space_name)
178
+
179
+ Console().print(
180
+ Markdown(f"""
181
+ # Deployment successful
182
+
183
+ - Space: {self.configuration.deploy.space_name}
184
+ - Image: {image_name_with_sha}
185
+ - URL: https://{self.configuration.deploy.space_name}.default.space.naas.ai
186
+
187
+ """)
188
+ )
189
+
190
+
191
+ @deploy.command("naas")
192
+ @click.option(
193
+ "-e",
194
+ "--env",
195
+ type=str,
196
+ default="prod",
197
+ help="Environment to use (default: prod). This is used to know which configuration file to load. (config.prod.yaml, config.yaml, ...)",
198
+ )
199
+ def naas(env: str):
200
+ # Set the ENV environment variable for the EngineConfiguration.load_configuration() to load the correct configuration file.
201
+ os.environ["ENV"] = env
202
+
203
+ configuration: EngineConfiguration = EngineConfiguration.load_configuration()
204
+
205
+ if configuration.deploy is None:
206
+ logger.error(
207
+ "Deploy configuration not found in the yaml configuration file. Please add a deploy section to the configuration file."
208
+ )
209
+ raise click.ClickException("Missing deploy configuration; aborting.")
210
+
211
+ deployer = NaasDeployer(configuration)
212
+ deployer.deploy()
@@ -0,0 +1,13 @@
1
+ import os
2
+
3
+ import click
4
+
5
+
6
+ @click.command("init")
7
+ @click.argument("path")
8
+ def init(path: str):
9
+ if path == ".":
10
+ path = os.getcwd()
11
+
12
+ os.makedirs(path, exist_ok=True)
13
+ # os.exec(f"cd {path} && uv init .")
@@ -0,0 +1,28 @@
1
+ import click
2
+ from naas_abi_core.engine.engine_configuration.EngineConfiguration import (
3
+ EngineConfiguration,
4
+ )
5
+ from rich.console import Console
6
+ from rich.table import Table
7
+
8
+
9
+ @click.group("module")
10
+ def module():
11
+ pass
12
+
13
+
14
+ @module.command("list")
15
+ def list() -> None:
16
+ configuration: EngineConfiguration = EngineConfiguration.load_configuration()
17
+
18
+ console = Console()
19
+ table = Table(
20
+ title="Available Modules", show_header=True, header_style="bold magenta"
21
+ )
22
+ table.add_column("Module", style="cyan", no_wrap=True)
23
+ table.add_column("Enabled", style="green")
24
+
25
+ for module in configuration.modules:
26
+ table.add_row(module.module, str(module.enabled))
27
+
28
+ console.print(table)
@@ -0,0 +1,4 @@
1
+ import naas_abi_cli.cli.new.module as module # noqa: F401
2
+ import naas_abi_cli.cli.new.project as project # noqa: F401
3
+
4
+ from .new import new # noqa: F401
@@ -0,0 +1,55 @@
1
+ import os
2
+
3
+ import click
4
+ import naas_abi_cli
5
+ from naas_abi_cli.cli.utils.Copier import Copier
6
+
7
+ from .new import new
8
+
9
+
10
+ def sanitize_module_name(module_name: str) -> str:
11
+ return module_name.replace("-", "_").replace(" ", "_").lower()
12
+
13
+
14
+ @new.command("module")
15
+ @click.argument("module_name", required=True)
16
+ @click.argument("module_path", required=False, default=".")
17
+ def _new_module(module_name: str, module_path: str = "."):
18
+ new_module(module_name, module_path)
19
+
20
+
21
+ def new_module(module_name: str, module_path: str = ".", quiet: bool = False):
22
+ module_name = sanitize_module_name(module_name)
23
+
24
+ if module_path == ".":
25
+ module_path = os.path.join(os.getcwd(), module_name)
26
+ else:
27
+ module_path = os.path.join(module_path, module_name)
28
+
29
+ if not os.path.exists(module_path):
30
+ os.makedirs(module_path, exist_ok=True)
31
+ elif len(os.listdir(module_path)) > 0:
32
+ print(f"Folder {module_path} already exists and is not empty.")
33
+ exit(1)
34
+
35
+ copier = Copier(
36
+ templates_path=os.path.join(
37
+ os.path.dirname(naas_abi_cli.__file__), "cli/new/templates/module"
38
+ ),
39
+ destination_path=module_path,
40
+ )
41
+
42
+ copier.copy(
43
+ values={
44
+ "module_name": module_name,
45
+ "module_name_snake": module_name.replace("-", "_"),
46
+ "module_name_pascal": module_name.replace("-", "").capitalize(),
47
+ }
48
+ )
49
+
50
+ if not quiet:
51
+ print(f"\nModule '{module_name}' has been created at:\n {module_path}\n")
52
+ print("To enable this module, add the following to your config.yaml:\n")
53
+ print("modules:")
54
+ print(f" - path: {module_path}")
55
+ print(" enabled: true\n")
@@ -0,0 +1,6 @@
1
+ import click
2
+
3
+
4
+ @click.group("new")
5
+ def new():
6
+ pass
@@ -0,0 +1,63 @@
1
+ import os
2
+ import subprocess
3
+
4
+ import click
5
+ import naas_abi_cli
6
+ from naas_abi_cli.cli.utils.Copier import Copier
7
+
8
+ from .module import new_module
9
+ from .new import new
10
+
11
+
12
+ @new.command("project")
13
+ @click.argument("project-name", required=False, default=None)
14
+ @click.argument("project-path", required=False, default=None)
15
+ def new_project(project_name: str | None, project_path: str | None):
16
+ # Defaults must be evaluated at runtime so they reflect the caller's CWD.
17
+ if project_name is None:
18
+ project_name = os.path.basename(os.getcwd())
19
+ if project_path is None:
20
+ project_path = os.getcwd()
21
+ # Resolve relative segments (., ..) and user home (~) to a normalized absolute path.
22
+ project_path = os.path.abspath(os.path.expanduser(project_path))
23
+
24
+ # Ensure the last path component matches the project name, not just the suffix.
25
+ if os.path.basename(os.path.normpath(project_path)) != project_name:
26
+ project_path = os.path.join(project_path, project_name)
27
+
28
+ if not os.path.exists(project_path):
29
+ os.makedirs(project_path, exist_ok=True)
30
+ elif len(os.listdir(project_path)) > 0:
31
+ print(f"Folder {project_path} already exists and is not empty.")
32
+ exit(1)
33
+
34
+ copier = Copier(
35
+ templates_path=os.path.join(
36
+ os.path.dirname(naas_abi_cli.__file__), "cli/new/templates/project"
37
+ ),
38
+ destination_path=project_path,
39
+ )
40
+ copier.copy(
41
+ values={
42
+ "project_name": project_name,
43
+ "project_name_snake": project_name.replace("-", "_"),
44
+ "project_name_pascal": project_name.replace("-", "").capitalize(),
45
+ }
46
+ )
47
+
48
+ # Calling new_module to create the module in the src folder
49
+ new_module(project_name, os.path.join(project_path, "src"), quiet=True)
50
+
51
+ # Run dependency install without shell to avoid quoting issues on paths with spaces.
52
+ subprocess.run(
53
+ [
54
+ "uv",
55
+ "add",
56
+ "naas-abi-core[all]",
57
+ "naas-abi-marketplace[ai-chatgpt]",
58
+ "naas-abi",
59
+ "naas-abi-cli",
60
+ ],
61
+ cwd=project_path,
62
+ check=True,
63
+ )
@@ -0,0 +1,31 @@
1
+ from naas_abi_core.module.Module import (
2
+ BaseModule,
3
+ ModuleConfiguration,
4
+ ModuleDependencies,
5
+ )
6
+
7
+ # from naas_abi_core.services.object_storage.ObjectStorageService import ObjectStorageService
8
+ # from naas_abi_core.services.secret.Secret import Secret
9
+ # from naas_abi_core.services.triple_store.TripleStoreService import TripleStoreService
10
+ # from naas_abi_core.services.vector_store.VectorStoreService import VectorStoreService
11
+
12
+
13
+ class ABIModule(BaseModule):
14
+ dependencies: ModuleDependencies = ModuleDependencies(
15
+ modules=[
16
+ "naas_abi_marketplace.ai.chatgpt",
17
+ ],
18
+ services=[
19
+ # Secret,
20
+ # TripleStoreService,
21
+ # ObjectStorageService,
22
+ # VectorStoreService
23
+ ],
24
+ )
25
+
26
+ class Configuration(ModuleConfiguration):
27
+ pass
28
+ # example: str
29
+
30
+ def on_initialized(self):
31
+ super().on_initialized()
File without changes
@@ -0,0 +1,52 @@
1
+ from typing import Optional
2
+
3
+ from naas_abi_core.services.agent.Agent import (
4
+ Agent,
5
+ AgentConfiguration,
6
+ AgentSharedState,
7
+ )
8
+
9
+ NAME = "{{module_name_snake}} Agent"
10
+ DESCRIPTION = "An helpful agent that can help you with your tasks."
11
+ SYSTEM_PROMPT = """
12
+ You are {{module_name_snake}} Agent.
13
+ """
14
+
15
+
16
+ def create_agent(
17
+ agent_shared_state: Optional[AgentSharedState] = None,
18
+ agent_configuration: Optional[AgentConfiguration] = None,
19
+ ) -> Optional[Agent]:
20
+ #from {{module_name_snake}} import ABIModule
21
+
22
+ # Set model
23
+ from naas_abi_marketplace.ai.chatgpt.models.gpt_5 import model as chatgpt_model
24
+
25
+ model = chatgpt_model.model
26
+
27
+ # Use provided configuration or create default one
28
+ if agent_configuration is None:
29
+ agent_configuration = AgentConfiguration(system_prompt=SYSTEM_PROMPT)
30
+
31
+ # Use provided shared state or create new one
32
+ if agent_shared_state is None:
33
+ agent_shared_state = AgentSharedState()
34
+
35
+ tools: list = []
36
+
37
+ agents: list = []
38
+
39
+ return {{module_name_pascal}}Agent(
40
+ name=NAME,
41
+ description=DESCRIPTION,
42
+ chat_model=model,
43
+ tools=tools,
44
+ agents=agents,
45
+ memory=None,
46
+ state=agent_shared_state,
47
+ configuration=agent_configuration,
48
+ )
49
+
50
+
51
+ class {{module_name_pascal}}Agent(Agent):
52
+ pass
@@ -0,0 +1,31 @@
1
+ name: Deploy
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - main
7
+
8
+ jobs:
9
+ deploy:
10
+ runs-on: ubuntu-latest
11
+
12
+ steps:
13
+ - name: Checkout repository
14
+ uses: actions/checkout@v4
15
+
16
+ - name: Set up Python
17
+ uses: actions/setup-python@v5
18
+ with:
19
+ python-version: "3.11"
20
+
21
+ - name: Install dependencies
22
+ run: |
23
+ python -m pip install uv
24
+
25
+ - name: Deploy
26
+ env:
27
+ NAAS_API_KEY: ${{ '{{ secrets.NAAS_API_KEY }}' }}
28
+ ENV: prod
29
+ run: |
30
+ uv sync --no-dev
31
+ uv run python -m naas_abi_cli.cli deploy naas