stratonext-core 0.0.1__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.
File without changes
@@ -0,0 +1,3 @@
1
+ from .skill import AgentsSkill
2
+
3
+ __all__ = ["AgentsSkill"]
@@ -0,0 +1,76 @@
1
+ from __future__ import annotations
2
+
3
+ from types import TracebackType
4
+
5
+ from stratonext_sdk import Client
6
+ from stratonext_sdk.get_agent import GetAgentAgent
7
+ from stratonext_sdk.get_agents import GetAgentsAgentsNodes
8
+ from stratonext_sdk.input_types import CreateAgentInput, UpdateAgentInput
9
+
10
+ from ..client import build_client
11
+ from ..config import ArtemisConfig
12
+
13
+
14
+ class AgentsSkill:
15
+ """Read and manage Artemis agents via the platform GraphQL API."""
16
+
17
+ def __init__(self, client: Client | None = None, config: ArtemisConfig | None = None) -> None:
18
+ self._client = client or build_client(config)
19
+
20
+ async def __aenter__(self) -> AgentsSkill:
21
+ return self
22
+
23
+ async def __aexit__(
24
+ self,
25
+ exc_type: type[BaseException] | None,
26
+ exc: BaseException | None,
27
+ tb: TracebackType | None,
28
+ ) -> None:
29
+ await self._client.__aexit__(exc_type, exc, tb)
30
+
31
+ async def list_agents(
32
+ self, workspace_id: str, after: str | None = None
33
+ ) -> list[GetAgentsAgentsNodes]:
34
+ """List agents in a workspace, paginated 50 at a time."""
35
+ result = await self._client.get_agents(workspace_id=workspace_id, after=after)
36
+ if result.agents is None:
37
+ return []
38
+ return result.agents.nodes or []
39
+
40
+ async def get_agent(self, agent_id: str) -> GetAgentAgent | None:
41
+ """Fetch a single agent by ID, or None if it doesn't exist."""
42
+ result = await self._client.get_agent(agent_id=agent_id)
43
+ return result.agent
44
+
45
+ async def create_agent(
46
+ self,
47
+ workspace_id: str,
48
+ name: str,
49
+ project_id: str | None = None,
50
+ ) -> str:
51
+ """Register an agent in a workspace, returning its ID. project_id is
52
+ required for Workers and must be omitted for Lead-Agents."""
53
+ result = await self._client.create_agent(
54
+ input=CreateAgentInput(
55
+ workspace_id=workspace_id,
56
+ name=name,
57
+ project_id=project_id,
58
+ )
59
+ )
60
+ return result.create_agent.id
61
+
62
+ async def update_agent(self, agent_id: str, name: str) -> str:
63
+ """Rename an agent, returning its ID."""
64
+ result = await self._client.update_agent(
65
+ agent_id=agent_id, input=UpdateAgentInput(name=name)
66
+ )
67
+ return result.update_agent.id
68
+
69
+ async def deactivate_agent(self, agent_id: str) -> str:
70
+ """Deactivate an agent, returning its ID."""
71
+ result = await self._client.deactivate_agent(agent_id=agent_id)
72
+ return result.deactivate_agent.id
73
+
74
+ async def delete_agent(self, agent_id: str) -> None:
75
+ """Delete an agent."""
76
+ await self._client.delete_agent(agent_id=agent_id)
@@ -0,0 +1,3 @@
1
+ from .skill import ApprovalsSkill
2
+
3
+ __all__ = ["ApprovalsSkill"]
@@ -0,0 +1,109 @@
1
+ from __future__ import annotations
2
+
3
+ from types import TracebackType
4
+
5
+ from stratonext_sdk import Client
6
+ from stratonext_sdk.enums import ApprovalDecision, ApprovalRequestType
7
+ from stratonext_sdk.get_approval import GetApprovalApproval
8
+ from stratonext_sdk.get_approvals import GetApprovalsApprovalsNodes
9
+ from stratonext_sdk.input_types import (
10
+ ArtemisApprovalFilterInput,
11
+ ArtemisApprovalSortInput,
12
+ ResolveApprovalInput,
13
+ SubmitApprovalInput,
14
+ )
15
+
16
+ from ..client import build_client
17
+ from ..config import ArtemisConfig
18
+
19
+
20
+ class ApprovalsSkill:
21
+ """Read and manage Artemis approvals via the platform GraphQL API."""
22
+
23
+ def __init__(self, client: Client | None = None, config: ArtemisConfig | None = None) -> None:
24
+ self._client = client or build_client(config)
25
+
26
+ async def __aenter__(self) -> ApprovalsSkill:
27
+ return self
28
+
29
+ async def __aexit__(
30
+ self,
31
+ exc_type: type[BaseException] | None,
32
+ exc: BaseException | None,
33
+ tb: TracebackType | None,
34
+ ) -> None:
35
+ await self._client.__aexit__(exc_type, exc, tb)
36
+
37
+ async def list_approvals(
38
+ self,
39
+ workspace_id: str,
40
+ after: str | None = None,
41
+ where: ArtemisApprovalFilterInput | None = None,
42
+ order: list[ArtemisApprovalSortInput] | None = None,
43
+ ) -> list[GetApprovalsApprovalsNodes]:
44
+ """List approvals in a workspace, paginated 50 at a time. Optionally filter/sort server-side."""
45
+ result = await self._client.get_approvals(
46
+ workspace_id=workspace_id,
47
+ after=after,
48
+ where=where,
49
+ order=order,
50
+ )
51
+ if result.approvals is None:
52
+ return []
53
+ return result.approvals.nodes or []
54
+
55
+ async def get_approval(self, approval_id: str) -> GetApprovalApproval | None:
56
+ """Fetch a single approval by ID, or None if not found."""
57
+ result = await self._client.get_approval(approval_id=approval_id)
58
+ return result.approval
59
+
60
+ async def submit_approval(
61
+ self,
62
+ task_id: str,
63
+ agent_id: str,
64
+ environment_id: str,
65
+ workspace_id: str,
66
+ request_type: ApprovalRequestType,
67
+ name: str,
68
+ action_description: str,
69
+ payload_ref: str,
70
+ ) -> str:
71
+ """Submit an approval request, returning the approval ID.
72
+
73
+ action_description should be a detailed, human-readable description of what the agent
74
+ intends to do, including affected resource IDs and mutation intent. The platform evaluates
75
+ risk automatically using an LLM — the more detail provided, the more accurate the result.
76
+
77
+ If the environment's automation policy permits it, the approval is resolved immediately
78
+ (status APPROVED) and the task remains dispatchable. Otherwise the task is transitioned
79
+ to APPROVAL_REQUIRED and the approval ID should be surfaced to human approvers.
80
+ """
81
+ result = await self._client.submit_approval(
82
+ input=SubmitApprovalInput(
83
+ task_id=task_id,
84
+ agent_id=agent_id,
85
+ environment_id=environment_id,
86
+ workspace_id=workspace_id,
87
+ request_type=request_type,
88
+ name=name,
89
+ action_description=action_description,
90
+ payload_ref=payload_ref,
91
+ )
92
+ )
93
+ return result.submit_approval.id
94
+
95
+ async def approve(self, approval_id: str, notes: str | None = None) -> str:
96
+ """Approve a pending approval, returning the approval ID."""
97
+ result = await self._client.resolve_approval(
98
+ approval_id=approval_id,
99
+ input=ResolveApprovalInput(decision=ApprovalDecision.APPROVED, notes=notes),
100
+ )
101
+ return result.resolve_approval.id
102
+
103
+ async def reject(self, approval_id: str, notes: str | None = None) -> str:
104
+ """Reject a pending approval, returning the approval ID."""
105
+ result = await self._client.resolve_approval(
106
+ approval_id=approval_id,
107
+ input=ResolveApprovalInput(decision=ApprovalDecision.REJECTED, notes=notes),
108
+ )
109
+ return result.resolve_approval.id
@@ -0,0 +1,3 @@
1
+ from .skill import AssetsSkill
2
+
3
+ __all__ = ["AssetsSkill"]
@@ -0,0 +1,141 @@
1
+ from __future__ import annotations
2
+
3
+ import mimetypes
4
+ from pathlib import Path
5
+ from types import TracebackType
6
+
7
+ import httpx
8
+ from stratonext_sdk import Client
9
+ from stratonext_sdk.confirm_asset import ConfirmAssetConfirmAsset
10
+ from stratonext_sdk.enums import AssetCategory, ParentType
11
+ from stratonext_sdk.input_types import RegisterAssetInput
12
+ from stratonext_sdk.register_asset import RegisterAssetRegisterAsset
13
+
14
+ from ..client import build_client
15
+ from ..config import ArtemisConfig
16
+
17
+
18
+ class AssetsSkill:
19
+ """Register, confirm, and fetch download URLs for Artemis assets.
20
+
21
+ ``upload_file``/``download_file`` are the preferred entry points: the raw
22
+ register/confirm calls below leave a stale PENDING asset row (and staging
23
+ object) behind if the caller uploads the bytes and forgets to confirm, or
24
+ crashes in between. A single call removes that window entirely.
25
+ """
26
+
27
+ def __init__(self, client: Client | None = None, config: ArtemisConfig | None = None) -> None:
28
+ self._client = client or build_client(config)
29
+
30
+ async def __aenter__(self) -> AssetsSkill:
31
+ return self
32
+
33
+ async def __aexit__(
34
+ self,
35
+ exc_type: type[BaseException] | None,
36
+ exc: BaseException | None,
37
+ tb: TracebackType | None,
38
+ ) -> None:
39
+ await self._client.__aexit__(exc_type, exc, tb)
40
+
41
+ async def register_asset(
42
+ self,
43
+ name: str,
44
+ description: str,
45
+ media_type: str,
46
+ category: AssetCategory,
47
+ filename: str,
48
+ parent_id: str,
49
+ parent_type: ParentType,
50
+ workspace_id: str,
51
+ ) -> RegisterAssetRegisterAsset:
52
+ """Register a new asset, returning its record and a presigned upload URL."""
53
+ result = await self._client.register_asset(
54
+ input=RegisterAssetInput(
55
+ name=name,
56
+ description=description,
57
+ media_type=media_type,
58
+ category=category,
59
+ filename=filename,
60
+ parent_id=parent_id,
61
+ parent_type=parent_type,
62
+ workspace_id=workspace_id,
63
+ )
64
+ )
65
+ return result.register_asset
66
+
67
+ async def confirm_asset(self, asset_id: str) -> ConfirmAssetConfirmAsset:
68
+ """Confirm an asset after its bytes have been uploaded to the presigned URL."""
69
+ result = await self._client.confirm_asset(asset_id=asset_id)
70
+ return result.confirm_asset
71
+
72
+ async def get_download_url(self, asset_id: str) -> str | None:
73
+ """Get a presigned download URL for an existing asset, or None if it doesn't exist."""
74
+ result = await self._client.get_asset_download_url(asset_id=asset_id)
75
+ return result.asset_download_url
76
+
77
+ async def upload_file(
78
+ self,
79
+ path: str | Path,
80
+ parent_id: str,
81
+ parent_type: ParentType,
82
+ workspace_id: str,
83
+ name: str | None = None,
84
+ description: str = "",
85
+ category: AssetCategory = AssetCategory.GENERIC,
86
+ media_type: str | None = None,
87
+ ) -> ConfirmAssetConfirmAsset:
88
+ """Register, upload, and confirm a local file as one call.
89
+
90
+ Reads ``path`` from disk and uploads it to the presigned staging URL.
91
+ If the upload or confirm step fails, the just-registered asset is
92
+ deleted so no PENDING row is left behind — callers never have to
93
+ handle the register/confirm gap themselves.
94
+ """
95
+ path = Path(path)
96
+ media_type = media_type or mimetypes.guess_type(path.name)[0] or "application/octet-stream"
97
+ registered = await self.register_asset(
98
+ name=name or path.name,
99
+ description=description,
100
+ media_type=media_type,
101
+ category=category,
102
+ filename=path.name,
103
+ parent_id=parent_id,
104
+ parent_type=parent_type,
105
+ workspace_id=workspace_id,
106
+ )
107
+ try:
108
+ # Presigned POST: form fields first, file part last. The signed policy
109
+ # enforces the size cap, so S3 rejects an oversized file here (4xx).
110
+ fields = {f.name: f.value for f in registered.upload.fields}
111
+ async with httpx.AsyncClient() as http_client:
112
+ response = await http_client.post(
113
+ registered.upload.url,
114
+ data=fields,
115
+ files={"file": (path.name, path.read_bytes(), media_type)},
116
+ )
117
+ response.raise_for_status()
118
+ return await self.confirm_asset(registered.asset.id)
119
+ except Exception:
120
+ await self._client.delete_asset(asset_id=registered.asset.id)
121
+ raise
122
+
123
+ async def download_file(self, asset_id: str, dest: str | Path) -> Path:
124
+ """Download an asset's content to ``dest`` as one call.
125
+
126
+ Raises ``ValueError`` if the asset doesn't exist or has no download URL.
127
+ """
128
+ url = await self.get_download_url(asset_id)
129
+ if not url:
130
+ raise ValueError(f"Asset {asset_id!r} not found or has no download URL")
131
+
132
+ dest = Path(dest)
133
+ async with httpx.AsyncClient(follow_redirects=True) as http_client:
134
+ response = await http_client.get(url)
135
+ response.raise_for_status()
136
+ dest.write_bytes(response.content)
137
+ return dest
138
+
139
+ async def delete_asset(self, asset_id: str) -> bool:
140
+ """Delete an asset by ID, returning the platform's deletion result."""
141
+ return (await self._client.delete_asset(asset_id=asset_id)).delete_asset
@@ -0,0 +1,15 @@
1
+ from __future__ import annotations
2
+
3
+ from stratonext_sdk import Client
4
+
5
+ from .config import ArtemisConfig
6
+
7
+
8
+ def build_client(config: ArtemisConfig | None = None) -> Client:
9
+ """Construct a Client for the Artemis platform GraphQL API.
10
+
11
+ Reads connection settings from the environment via ArtemisConfig.from_env()
12
+ unless an explicit config is passed.
13
+ """
14
+ config = config or ArtemisConfig.from_env()
15
+ return Client(url=config.endpoint, headers=config.headers)
@@ -0,0 +1,30 @@
1
+ from __future__ import annotations
2
+
3
+ import os
4
+ from dataclasses import dataclass
5
+
6
+
7
+ @dataclass(frozen=True)
8
+ class ArtemisConfig:
9
+ """Connection settings for the Artemis platform GraphQL API, read from the environment.
10
+
11
+ - ARTEMIS_API_URL: GraphQL endpoint URL. Defaults to the local dev API.
12
+ - ARTEMIS_AUTH_API_TOKEN: bearer token sent as `Authorization: Bearer <token>`. Optional
13
+ for local dev deployments running without auth.
14
+ """
15
+
16
+ endpoint: str
17
+ api_token: str | None = None
18
+
19
+ @property
20
+ def headers(self) -> dict[str, str]:
21
+ if not self.api_token:
22
+ return {}
23
+ return {"Authorization": f"Bearer {self.api_token}"}
24
+
25
+ @classmethod
26
+ def from_env(cls) -> ArtemisConfig:
27
+ return cls(
28
+ endpoint=os.environ.get("ARTEMIS_API_URL", "http://localhost:5555/v1/graphql"),
29
+ api_token=os.environ.get("ARTEMIS_AUTH_API_TOKEN"),
30
+ )
@@ -0,0 +1,3 @@
1
+ from .skill import DossiersSkill
2
+
3
+ __all__ = ["DossiersSkill"]
@@ -0,0 +1,90 @@
1
+ from __future__ import annotations
2
+
3
+ from types import TracebackType
4
+
5
+ from stratonext_sdk import Client
6
+ from stratonext_sdk.enums import DossierStatus, DossierType
7
+ from stratonext_sdk.get_dossier import GetDossierDossier
8
+ from stratonext_sdk.get_dossiers import GetDossiersDossiersNodes
9
+ from stratonext_sdk.input_types import CreateDossierInput, UpdateDossierInput
10
+
11
+ from ..client import build_client
12
+ from ..config import ArtemisConfig
13
+
14
+
15
+ class DossiersSkill:
16
+ """Read and manage Artemis dossiers via the platform GraphQL API."""
17
+
18
+ def __init__(self, client: Client | None = None, config: ArtemisConfig | None = None) -> None:
19
+ self._client = client or build_client(config)
20
+
21
+ async def __aenter__(self) -> DossiersSkill:
22
+ return self
23
+
24
+ async def __aexit__(
25
+ self,
26
+ exc_type: type[BaseException] | None,
27
+ exc: BaseException | None,
28
+ tb: TracebackType | None,
29
+ ) -> None:
30
+ await self._client.__aexit__(exc_type, exc, tb)
31
+
32
+ async def list_dossiers(
33
+ self, workspace_id: str, after: str | None = None
34
+ ) -> list[GetDossiersDossiersNodes]:
35
+ """List dossiers in a workspace, paginated 50 at a time."""
36
+ result = await self._client.get_dossiers(workspace_id=workspace_id, after=after)
37
+ if result.dossiers is None:
38
+ return []
39
+ return result.dossiers.nodes or []
40
+
41
+ async def get_dossier(self, dossier_id: str) -> GetDossierDossier | None:
42
+ """Fetch a single dossier by ID, or None if it doesn't exist."""
43
+ result = await self._client.get_dossier(dossier_id=dossier_id)
44
+ return result.dossier
45
+
46
+ async def create_dossier(
47
+ self,
48
+ workspace_id: str,
49
+ title: str,
50
+ type: DossierType = DossierType.GENERIC,
51
+ body: str | None = None,
52
+ owner_id: str | None = None,
53
+ ) -> str:
54
+ """Create a dossier in a workspace, returning its ID."""
55
+ result = await self._client.create_dossier(
56
+ input=CreateDossierInput(
57
+ workspace_id=workspace_id,
58
+ title=title,
59
+ type_=type,
60
+ body=body,
61
+ owner_id=owner_id,
62
+ )
63
+ )
64
+ return result.create_dossier.id
65
+
66
+ async def update_dossier(
67
+ self,
68
+ dossier_id: str,
69
+ title: str | None = None,
70
+ body: str | None = None,
71
+ owner_id: str | None = None,
72
+ status: DossierStatus | None = None,
73
+ project_ids: list[str] | None = None,
74
+ ) -> str:
75
+ """Update a dossier's fields, returning its ID."""
76
+ result = await self._client.update_dossier(
77
+ dossier_id=dossier_id,
78
+ input=UpdateDossierInput(
79
+ title=title,
80
+ body=body,
81
+ owner_id=owner_id,
82
+ status=status,
83
+ project_ids=project_ids,
84
+ ),
85
+ )
86
+ return result.update_dossier.id
87
+
88
+ async def delete_dossier(self, dossier_id: str) -> None:
89
+ """Delete a dossier."""
90
+ await self._client.delete_dossier(dossier_id=dossier_id)
@@ -0,0 +1,3 @@
1
+ from .skill import EnvironmentsSkill
2
+
3
+ __all__ = ["EnvironmentsSkill"]
@@ -0,0 +1,98 @@
1
+ from __future__ import annotations
2
+
3
+ from types import TracebackType
4
+
5
+ from stratonext_sdk import Client
6
+ from stratonext_sdk.enums import (
7
+ EnvironmentAutomationPolicy,
8
+ EnvironmentClassification,
9
+ EnvironmentProvider,
10
+ )
11
+ from stratonext_sdk.get_environment import GetEnvironmentEnvironment
12
+ from stratonext_sdk.get_environments import GetEnvironmentsEnvironmentsNodes
13
+ from stratonext_sdk.input_types import (
14
+ CreateEnvironmentInput,
15
+ MetadataKvInput,
16
+ UpdateEnvironmentInput,
17
+ )
18
+
19
+ from ..client import build_client
20
+ from ..config import ArtemisConfig
21
+
22
+
23
+ class EnvironmentsSkill:
24
+ """Read and manage Artemis environments via the platform GraphQL API."""
25
+
26
+ def __init__(self, client: Client | None = None, config: ArtemisConfig | None = None) -> None:
27
+ self._client = client or build_client(config)
28
+
29
+ async def __aenter__(self) -> EnvironmentsSkill:
30
+ return self
31
+
32
+ async def __aexit__(
33
+ self,
34
+ exc_type: type[BaseException] | None,
35
+ exc: BaseException | None,
36
+ tb: TracebackType | None,
37
+ ) -> None:
38
+ await self._client.__aexit__(exc_type, exc, tb)
39
+
40
+ async def list_environments(
41
+ self, workspace_id: str, after: str | None = None
42
+ ) -> list[GetEnvironmentsEnvironmentsNodes]:
43
+ """List environments in a workspace, paginated 50 at a time."""
44
+ result = await self._client.get_environments(workspace_id=workspace_id, after=after)
45
+ if result.environments is None:
46
+ return []
47
+ return result.environments.nodes or []
48
+
49
+ async def get_environment(self, environment_id: str) -> GetEnvironmentEnvironment | None:
50
+ """Fetch a single environment by ID, or None if it doesn't exist."""
51
+ result = await self._client.get_environment(environment_id=environment_id)
52
+ return result.environment
53
+
54
+ async def create_environment(
55
+ self,
56
+ workspace_id: str,
57
+ name: str,
58
+ provider: EnvironmentProvider,
59
+ classification: EnvironmentClassification | None = None,
60
+ automation_policy: EnvironmentAutomationPolicy | None = None,
61
+ metadata: list[MetadataKvInput] | None = None,
62
+ ) -> str:
63
+ """Register a new environment in a workspace, returning its ID."""
64
+ result = await self._client.create_environment(
65
+ input=CreateEnvironmentInput(
66
+ workspace_id=workspace_id,
67
+ name=name,
68
+ provider=provider,
69
+ classification=classification,
70
+ automation_policy=automation_policy,
71
+ metadata=metadata,
72
+ )
73
+ )
74
+ return result.create_environment.id
75
+
76
+ async def update_environment(
77
+ self,
78
+ environment_id: str,
79
+ name: str | None = None,
80
+ classification: EnvironmentClassification | None = None,
81
+ automation_policy: EnvironmentAutomationPolicy | None = None,
82
+ metadata: list[MetadataKvInput] | None = None,
83
+ ) -> str:
84
+ """Update an environment's fields, returning its ID."""
85
+ result = await self._client.update_environment(
86
+ environment_id=environment_id,
87
+ input=UpdateEnvironmentInput(
88
+ name=name,
89
+ classification=classification,
90
+ automation_policy=automation_policy,
91
+ metadata=metadata,
92
+ ),
93
+ )
94
+ return result.update_environment.id
95
+
96
+ async def delete_environment(self, environment_id: str) -> None:
97
+ """Delete an environment."""
98
+ await self._client.delete_environment(environment_id=environment_id)
@@ -0,0 +1,3 @@
1
+ from .skill import JobsSkill
2
+
3
+ __all__ = ["JobsSkill"]
@@ -0,0 +1,126 @@
1
+ from __future__ import annotations
2
+
3
+ from types import TracebackType
4
+
5
+ from stratonext_sdk import Client
6
+ from stratonext_sdk.get_job import GetJobJob
7
+ from stratonext_sdk.get_job_run import GetJobRunJobRun
8
+ from stratonext_sdk.get_job_runs import GetJobRunsJobRunsNodes
9
+ from stratonext_sdk.get_jobs import GetJobsJobsNodes
10
+ from stratonext_sdk.input_types import CreateJobInput, UpdateJobInput
11
+ from stratonext_sdk.trigger_job_run import TriggerJobRunTriggerJobRun
12
+
13
+ from ..client import build_client
14
+ from ..config import ArtemisConfig
15
+
16
+
17
+ class JobsSkill:
18
+ """Read and manage Artemis jobs and job runs via the platform GraphQL API."""
19
+
20
+ def __init__(self, client: Client | None = None, config: ArtemisConfig | None = None) -> None:
21
+ self._client = client or build_client(config)
22
+
23
+ async def __aenter__(self) -> JobsSkill:
24
+ return self
25
+
26
+ async def __aexit__(
27
+ self,
28
+ exc_type: type[BaseException] | None,
29
+ exc: BaseException | None,
30
+ tb: TracebackType | None,
31
+ ) -> None:
32
+ await self._client.__aexit__(exc_type, exc, tb)
33
+
34
+ async def list_jobs(self, project_id: str, after: str | None = None) -> list[GetJobsJobsNodes]:
35
+ """List jobs in a project, paginated 50 at a time."""
36
+ result = await self._client.get_jobs(project_id=project_id, after=after)
37
+ if result.jobs is None:
38
+ return []
39
+ return result.jobs.nodes or []
40
+
41
+ async def get_job(self, job_id: str) -> GetJobJob | None:
42
+ """Fetch a single job by ID, or None if it doesn't exist."""
43
+ result = await self._client.get_job(job_id=job_id)
44
+ return result.job
45
+
46
+ async def create_job(
47
+ self,
48
+ workspace_id: str,
49
+ project_id: str,
50
+ name: str,
51
+ agent_id: str,
52
+ schedule: str,
53
+ description: str | None = None,
54
+ instructions: str | None = None,
55
+ deployment_ids: list[str] | None = None,
56
+ max_queue_depth: int | None = None,
57
+ ) -> str:
58
+ """Create a job in a project, returning its ID."""
59
+ result = await self._client.create_job(
60
+ input=CreateJobInput(
61
+ workspace_id=workspace_id,
62
+ project_id=project_id,
63
+ name=name,
64
+ agent_id=agent_id,
65
+ schedule=schedule,
66
+ description=description,
67
+ instructions=instructions,
68
+ deployment_ids=deployment_ids,
69
+ max_queue_depth=max_queue_depth,
70
+ )
71
+ )
72
+ return result.create_job.id
73
+
74
+ async def update_job(
75
+ self,
76
+ job_id: str,
77
+ name: str | None = None,
78
+ description: str | None = None,
79
+ schedule: str | None = None,
80
+ max_queue_depth: int | None = None,
81
+ ) -> str:
82
+ """Update a job's fields, returning its ID."""
83
+ result = await self._client.update_job(
84
+ job_id=job_id,
85
+ input=UpdateJobInput(
86
+ name=name,
87
+ description=description,
88
+ schedule=schedule,
89
+ max_queue_depth=max_queue_depth,
90
+ ),
91
+ )
92
+ return result.update_job.id
93
+
94
+ async def pause_job(self, job_id: str) -> str:
95
+ """Pause a job's schedule, returning its ID."""
96
+ result = await self._client.pause_job(job_id=job_id)
97
+ return result.pause_job.id
98
+
99
+ async def resume_job(self, job_id: str) -> str:
100
+ """Resume a paused job's schedule, returning its ID."""
101
+ result = await self._client.resume_job(job_id=job_id)
102
+ return result.resume_job.id
103
+
104
+ async def disable_job(self, job_id: str) -> str:
105
+ """Permanently disable a job and delete its schedule, returning its ID."""
106
+ result = await self._client.disable_job(job_id=job_id)
107
+ return result.disable_job.id
108
+
109
+ async def trigger_job_run(self, job_id: str) -> TriggerJobRunTriggerJobRun:
110
+ """Manually trigger one job run, bypassing the schedule."""
111
+ result = await self._client.trigger_job_run(job_id=job_id)
112
+ return result.trigger_job_run
113
+
114
+ async def list_job_runs(
115
+ self, job_id: str, after: str | None = None
116
+ ) -> list[GetJobRunsJobRunsNodes]:
117
+ """List runs for a job, paginated 50 at a time."""
118
+ result = await self._client.get_job_runs(job_id=job_id, after=after)
119
+ if result.job_runs is None:
120
+ return []
121
+ return result.job_runs.nodes or []
122
+
123
+ async def get_job_run(self, job_run_id: str) -> GetJobRunJobRun | None:
124
+ """Fetch a single job run by ID, or None if it doesn't exist."""
125
+ result = await self._client.get_job_run(job_run_id=job_run_id)
126
+ return result.job_run
@@ -0,0 +1,3 @@
1
+ from .skill import ProjectsSkill
2
+
3
+ __all__ = ["ProjectsSkill"]
@@ -0,0 +1,92 @@
1
+ from __future__ import annotations
2
+
3
+ from types import TracebackType
4
+
5
+ from stratonext_sdk import Client
6
+ from stratonext_sdk.enums import ProjectStatus, ProjectType
7
+ from stratonext_sdk.get_project import GetProjectProject
8
+ from stratonext_sdk.get_projects import GetProjectsProjectsNodes
9
+ from stratonext_sdk.input_types import CreateProjectInput, ResourceLinkInput, UpdateProjectInput
10
+
11
+ from ..client import build_client
12
+ from ..config import ArtemisConfig
13
+
14
+
15
+ class ProjectsSkill:
16
+ """Read and manage Artemis projects via the platform GraphQL API."""
17
+
18
+ def __init__(self, client: Client | None = None, config: ArtemisConfig | None = None) -> None:
19
+ self._client = client or build_client(config)
20
+
21
+ async def __aenter__(self) -> ProjectsSkill:
22
+ return self
23
+
24
+ async def __aexit__(
25
+ self,
26
+ exc_type: type[BaseException] | None,
27
+ exc: BaseException | None,
28
+ tb: TracebackType | None,
29
+ ) -> None:
30
+ await self._client.__aexit__(exc_type, exc, tb)
31
+
32
+ async def list_projects(
33
+ self, workspace_id: str, after: str | None = None
34
+ ) -> list[GetProjectsProjectsNodes]:
35
+ """List projects in a workspace, paginated 50 at a time."""
36
+ result = await self._client.get_projects(workspace_id=workspace_id, after=after)
37
+ if result.projects is None:
38
+ return []
39
+ return result.projects.nodes or []
40
+
41
+ async def get_project(self, project_id: str) -> GetProjectProject | None:
42
+ """Fetch a single project by ID, or None if it doesn't exist."""
43
+ result = await self._client.get_project(project_id=project_id)
44
+ return result.project
45
+
46
+ async def create_project(
47
+ self,
48
+ workspace_id: str,
49
+ name: str,
50
+ description: str,
51
+ type: ProjectType | None = None,
52
+ ) -> str:
53
+ """Create a project in a workspace, returning its ID."""
54
+ result = await self._client.create_project(
55
+ input=CreateProjectInput(
56
+ workspace_id=workspace_id,
57
+ name=name,
58
+ description=description,
59
+ type_=type,
60
+ )
61
+ )
62
+ return result.create_project.id
63
+
64
+ async def update_project(
65
+ self,
66
+ project_id: str,
67
+ name: str | None = None,
68
+ description: str | None = None,
69
+ status: ProjectStatus | None = None,
70
+ instructions: str | None = None,
71
+ notes: list[str] | None = None,
72
+ links: list[ResourceLinkInput] | None = None,
73
+ type: ProjectType | None = None,
74
+ ) -> str:
75
+ """Update a project's fields, returning its ID."""
76
+ result = await self._client.update_project(
77
+ project_id=project_id,
78
+ input=UpdateProjectInput(
79
+ name=name,
80
+ description=description,
81
+ status=status,
82
+ instructions=instructions,
83
+ notes=notes,
84
+ links=links,
85
+ type_=type,
86
+ ),
87
+ )
88
+ return result.update_project.id
89
+
90
+ async def delete_project(self, project_id: str) -> None:
91
+ """Delete a project."""
92
+ await self._client.delete_project(project_id=project_id)
@@ -0,0 +1,17 @@
1
+ from .agents import AgentsSkill
2
+ from .approvals import ApprovalsSkill
3
+ from .assets import AssetsSkill
4
+ from .dossiers import DossiersSkill
5
+ from .environments import EnvironmentsSkill
6
+ from .projects import ProjectsSkill
7
+ from .tasks import TasksSkill
8
+
9
+ SKILLS: dict[str, type] = {
10
+ "projects": ProjectsSkill,
11
+ "tasks": TasksSkill,
12
+ "environments": EnvironmentsSkill,
13
+ "agents": AgentsSkill,
14
+ "assets": AssetsSkill,
15
+ "approvals": ApprovalsSkill,
16
+ "dossiers": DossiersSkill,
17
+ }
@@ -0,0 +1,3 @@
1
+ from .skill import TasksSkill
2
+
3
+ __all__ = ["TasksSkill"]
@@ -0,0 +1,128 @@
1
+ from __future__ import annotations
2
+
3
+ from types import TracebackType
4
+
5
+ from stratonext_sdk import Client
6
+ from stratonext_sdk.enums import AssigneeType, TaskStatus, VerificationStatus
7
+ from stratonext_sdk.get_task import GetTaskTask
8
+ from stratonext_sdk.get_tasks import GetTasksTasksNodes, GetTasksTasksPageInfo
9
+ from stratonext_sdk.input_types import (
10
+ ArtemisTaskFilterInput,
11
+ ArtemisTaskSortInput,
12
+ CreateTaskInput,
13
+ ResourceLinkInput,
14
+ UpdateTaskInput,
15
+ )
16
+
17
+ from ..client import build_client
18
+ from ..config import ArtemisConfig
19
+
20
+
21
+ class TasksSkill:
22
+ """Read and manage Artemis tasks via the platform GraphQL API."""
23
+
24
+ def __init__(self, client: Client | None = None, config: ArtemisConfig | None = None) -> None:
25
+ self._client = client or build_client(config)
26
+
27
+ async def __aenter__(self) -> TasksSkill:
28
+ return self
29
+
30
+ async def __aexit__(
31
+ self,
32
+ exc_type: type[BaseException] | None,
33
+ exc: BaseException | None,
34
+ tb: TracebackType | None,
35
+ ) -> None:
36
+ await self._client.__aexit__(exc_type, exc, tb)
37
+
38
+ async def list_tasks(
39
+ self,
40
+ workspace_id: str,
41
+ after: str | None = None,
42
+ order: list[ArtemisTaskSortInput] | None = None,
43
+ where: ArtemisTaskFilterInput | None = None,
44
+ ) -> tuple[list[GetTasksTasksNodes], GetTasksTasksPageInfo | None]:
45
+ """Fetch one page of tasks (50). Returns (nodes, page_info); page_info.end_cursor is the next-page token."""
46
+ result = await self._client.get_tasks(
47
+ workspace_id=workspace_id, after=after, order=order, where=where
48
+ )
49
+ if result.tasks is None:
50
+ return [], None
51
+ return result.tasks.nodes or [], result.tasks.page_info
52
+
53
+ async def get_task(self, task_id: str) -> GetTaskTask | None:
54
+ """Fetch a single task by ID, or None if it doesn't exist."""
55
+ result = await self._client.get_task(task_id=task_id)
56
+ return result.task
57
+
58
+ async def create_task(
59
+ self,
60
+ workspace_id: str,
61
+ project_id: str,
62
+ name: str,
63
+ description: str,
64
+ instructions: str | None = None,
65
+ assignee_id: str | None = None,
66
+ assignee_type: AssigneeType | None = None,
67
+ verification_criteria: str | None = None,
68
+ verification_status: VerificationStatus | None = None,
69
+ verification_notes: str | None = None,
70
+ ) -> str:
71
+ """Create a task in a project, returning its ID."""
72
+ result = await self._client.create_task(
73
+ input=CreateTaskInput(
74
+ workspace_id=workspace_id,
75
+ project_id=project_id,
76
+ name=name,
77
+ description=description,
78
+ instructions=instructions,
79
+ assignee_id=assignee_id,
80
+ assignee_type=assignee_type,
81
+ verification_criteria=verification_criteria,
82
+ verification_status=verification_status,
83
+ verification_notes=verification_notes,
84
+ )
85
+ )
86
+ return result.create_task.id
87
+
88
+ async def update_task(
89
+ self,
90
+ task_id: str,
91
+ name: str | None = None,
92
+ description: str | None = None,
93
+ instructions: str | None = None,
94
+ assignee_id: str | None = None,
95
+ assignee_type: AssigneeType | None = None,
96
+ status: TaskStatus | None = None,
97
+ result: str | None = None,
98
+ verification_criteria: str | None = None,
99
+ verification_status: VerificationStatus | None = None,
100
+ verification_notes: str | None = None,
101
+ links: list[ResourceLinkInput] | None = None,
102
+ ) -> str:
103
+ """Update a task's fields, returning its ID.
104
+
105
+ ``links`` replaces the whole task links array — fetch the current links,
106
+ modify the list, and pass the full result (see the CLI add-link/remove-link).
107
+ """
108
+ response = await self._client.update_task(
109
+ task_id=task_id,
110
+ input=UpdateTaskInput(
111
+ name=name,
112
+ description=description,
113
+ instructions=instructions,
114
+ assignee_id=assignee_id,
115
+ assignee_type=assignee_type,
116
+ status=status,
117
+ result=result,
118
+ verification_criteria=verification_criteria,
119
+ verification_status=verification_status,
120
+ verification_notes=verification_notes,
121
+ links=links,
122
+ ),
123
+ )
124
+ return response.update_task.id
125
+
126
+ async def delete_task(self, task_id: str) -> None:
127
+ """Delete a task."""
128
+ await self._client.delete_task(task_id=task_id)
@@ -0,0 +1,7 @@
1
+ Metadata-Version: 2.4
2
+ Name: stratonext-core
3
+ Version: 0.0.1
4
+ Summary: StratoNext agent skills for interacting with the StratoNext platform API.
5
+ Requires-Python: >=3.14
6
+ Requires-Dist: httpx
7
+ Requires-Dist: stratonext-sdk
@@ -0,0 +1,23 @@
1
+ stratonext_core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ stratonext_core/client.py,sha256=5gpdf10sCDBfs3ONqpujv2mGRupua5GnHSLycwKwrZ0,477
3
+ stratonext_core/config.py,sha256=iQzk0hq7qo5KbONfPqM3dKZDRFapOZSciFcXK_6-vwA,917
4
+ stratonext_core/registry.py,sha256=YP-VtNB96JDpWHV4Z8kkBtgFoWzbG6lbt9-75A7dHV0,492
5
+ stratonext_core/agents/__init__.py,sha256=BZGFmDkBoa6PDHPrI-08yykY-xWjMNtXKpmlya3gBKI,58
6
+ stratonext_core/agents/skill.py,sha256=4PW04dXzvCJWtsWC2vBJ1yKACPbiU9mncqy2R29D6Lc,2706
7
+ stratonext_core/approvals/__init__.py,sha256=Xx8jTRQeU3Gc_FHxw8sKl7TVm_RAL6rvGCL4mIzpyl4,64
8
+ stratonext_core/approvals/skill.py,sha256=Zwd43Mss796raPAYY8_xVuxFr7nYNyEZXXp7MaExfwk,4160
9
+ stratonext_core/assets/__init__.py,sha256=0227dc-e2OJbWXF7NiG_bRF--xrBBnckeSstkHlSrFA,58
10
+ stratonext_core/assets/skill.py,sha256=9OB858w2N4XM5YxAzi3iQWMS_5_V85c1oo7v4xf5hk0,5507
11
+ stratonext_core/dossiers/__init__.py,sha256=Flyxv_hSVaC1JieAOXD9oeVkGX0JXgNmC2xZ9CfEdG8,62
12
+ stratonext_core/dossiers/skill.py,sha256=Mzs6yfrMzHS1h_n65WHzUz6U3jL2m-r2OzX6uLfr_SY,3075
13
+ stratonext_core/environments/__init__.py,sha256=XjUOrgEtiYhwsFG91PAMjNWEZTZXaUQ-XdGUpQYb7nY,70
14
+ stratonext_core/environments/skill.py,sha256=EzQ2s55WNel_qhmZcalaC821gDIk7Yg6sqR5VjugUsg,3581
15
+ stratonext_core/jobs/__init__.py,sha256=pKI3pkaCsHvREwebju5C-6GEoY766H1lxS67p5rqzHU,54
16
+ stratonext_core/jobs/skill.py,sha256=C_pjT2C4LR0snuh-Vc4WSISMxF9wi290q-4GYJkKTeE,4662
17
+ stratonext_core/projects/__init__.py,sha256=vTdNpiLzpfgHbYlvTAQSxRCa6FXE3Jalva7rWiQz2-A,62
18
+ stratonext_core/projects/skill.py,sha256=g6YRcMV19_t6sWjxd6ND8vX6eAn9sBXjn4lq3hBKFHA,3182
19
+ stratonext_core/tasks/__init__.py,sha256=PssT4i0Rdg1Gls-BTMPf1GYIpezpYsqRUcYKvKPotw4,56
20
+ stratonext_core/tasks/skill.py,sha256=qjun5wjdflW7r4N16YmTbwZ-WyKuYwYoouORypgzfTE,4655
21
+ stratonext_core-0.0.1.dist-info/METADATA,sha256=Ii8aKmoVWEcAGozuHx5GWtd4ivurZEMMw-APsd2MmJ4,217
22
+ stratonext_core-0.0.1.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
23
+ stratonext_core-0.0.1.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.31.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any