universal-mcp-applications 0.1.29__py3-none-any.whl → 0.1.30__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.

Potentially problematic release.


This version of universal-mcp-applications might be problematic. Click here for more details.

@@ -1,3 +1,4 @@
1
+ import os
1
2
  from dotenv import load_dotenv
2
3
 
3
4
  load_dotenv()
@@ -8,9 +9,6 @@ from loguru import logger
8
9
  from universal_mcp.applications.application import APIApplication
9
10
  from universal_mcp.integrations import Integration
10
11
 
11
- from universal_mcp.applications.unipile import UnipileApp
12
-
13
-
14
12
  class ScraperApp(APIApplication):
15
13
  """
16
14
  Application for interacting with LinkedIn API.
@@ -22,11 +20,56 @@ class ScraperApp(APIApplication):
22
20
  if self.integration:
23
21
  credentials = self.integration.get_credentials()
24
22
  self.account_id = credentials.get("account_id")
25
- self._unipile_app = UnipileApp(integration=self.integration)
26
23
  else:
27
24
  logger.warning("Integration not found")
28
25
  self.account_id = None
29
- self._unipile_app = None
26
+
27
+ @property
28
+ def base_url(self) -> str:
29
+ if not self._base_url:
30
+ unipile_dsn = os.getenv("UNIPILE_DSN")
31
+ if not unipile_dsn:
32
+ logger.error(
33
+ "UnipileApp: UNIPILE_DSN environment variable is not set."
34
+ )
35
+ raise ValueError(
36
+ "UnipileApp: UNIPILE_DSN environment variable is required."
37
+ )
38
+ self._base_url = f"https://{unipile_dsn}"
39
+ return self._base_url
40
+
41
+ @base_url.setter
42
+ def base_url(self, base_url: str) -> None:
43
+ self._base_url = base_url
44
+ logger.info(f"UnipileApp: Base URL set to {self._base_url}")
45
+
46
+ def _get_headers(self) -> dict[str, str]:
47
+ """
48
+ Get the headers for Unipile API requests.
49
+ Overrides the base class method to use X-Api-Key.
50
+ """
51
+ if not self.integration:
52
+ logger.warning(
53
+ "UnipileApp: No integration configured, returning empty headers."
54
+ )
55
+ return {}
56
+
57
+ api_key = os.getenv("UNIPILE_API_KEY")
58
+ if not api_key:
59
+ logger.error(
60
+ "UnipileApp: API key not found in integration credentials for Unipile."
61
+ )
62
+ return { # Or return minimal headers if some calls might not need auth (unlikely for Unipile)
63
+ "Content-Type": "application/json",
64
+ "Cache-Control": "no-cache",
65
+ }
66
+
67
+ logger.debug("UnipileApp: Using X-Api-Key for authentication.")
68
+ return {
69
+ "x-api-key": api_key,
70
+ "Content-Type": "application/json",
71
+ "Cache-Control": "no-cache", # Often good practice for APIs
72
+ }
30
73
 
31
74
  def linkedin_search(
32
75
  self,
@@ -34,13 +77,15 @@ class ScraperApp(APIApplication):
34
77
  cursor: str | None = None,
35
78
  limit: int | None = None,
36
79
  keywords: str | None = None,
37
- date_posted: str | None = None,
80
+ date_posted: Literal["past_day", "past_week", "past_month"] | None = None,
38
81
  sort_by: Literal["relevance", "date"] = "relevance",
39
82
  minimum_salary_value: int = 40,
40
83
  ) -> dict[str, Any]:
41
84
  """
42
- Performs a general LinkedIn search for posts, people, companies, or jobs using keywords and various filters. This function provides broad, keyword-based discovery across the platform, distinct from other methods that retrieve content from a specific user or company profile. It supports pagination and filtering criteria.
43
-
85
+ Performs a comprehensive LinkedIn search for people, companies, posts, or jobs using keywords.
86
+ Supports pagination and targets either the classic or Sales Navigator API for posts.
87
+ For people, companies, and jobs, it uses the classic API.
88
+
44
89
  Args:
45
90
  category: Type of search to perform. Valid values are "people", "companies", "posts", or "jobs".
46
91
  cursor: Pagination cursor for the next page of entries.
@@ -49,82 +94,107 @@ class ScraperApp(APIApplication):
49
94
  date_posted: Filter by when the post was posted (posts only). Valid values are "past_day", "past_week", or "past_month".
50
95
  sort_by: How to sort the results (for posts and jobs). Valid values are "relevance" or "date".
51
96
  minimum_salary_value: The minimum salary to filter for (jobs only).
52
-
97
+
53
98
  Returns:
54
99
  A dictionary containing search results and pagination details.
55
-
100
+
56
101
  Raises:
57
102
  httpx.HTTPError: If the API request fails.
58
-
103
+ ValueError: If the category is empty.
104
+
59
105
  Tags:
60
- linkedin, search, posts, people, companies, api, scrapper, important
106
+ linkedin, search, people, companies, posts, jobs, api, important
61
107
  """
62
- return self._unipile_app.search(
63
- account_id=self.account_id,
64
- category=category,
65
- cursor=cursor,
66
- limit=limit,
67
- keywords=keywords,
68
- date_posted=date_posted,
69
- sort_by=sort_by,
70
- minimum_salary_value=minimum_salary_value,
71
- )
108
+ if not category:
109
+ raise ValueError("Category cannot be empty.")
110
+
111
+ url = f"{self.base_url}/api/v1/linkedin/search"
112
+
113
+ params: dict[str, Any] = {"account_id": self.account_id}
114
+ if cursor:
115
+ params["cursor"] = cursor
116
+ if limit is not None:
117
+ params["limit"] = limit
118
+
119
+ payload: dict[str, Any] = {"api": "classic", "category": category}
120
+
121
+ if keywords:
122
+ payload["keywords"] = keywords
123
+
124
+ if category == "posts":
125
+ if date_posted:
126
+ payload["date_posted"] = date_posted
127
+ if sort_by:
128
+ payload["sort_by"] = sort_by
129
+
130
+ elif category == "jobs":
131
+ payload["minimum_salary"] = {
132
+ "currency": "USD",
133
+ "value": minimum_salary_value,
134
+ }
135
+ if sort_by:
136
+ payload["sort_by"] = sort_by
137
+
138
+ response = self._post(url, params=params, data=payload)
139
+ return self._handle_response(response)
72
140
 
73
141
  def linkedin_list_profile_posts(
74
142
  self,
75
- identifier: str,
143
+ identifier: str, # User or Company provider internal ID
76
144
  cursor: str | None = None,
77
- limit: int | None = None,
145
+ limit: int | None = None, # 1-100 (spec says max 250)
146
+ is_company: bool | None = None,
78
147
  ) -> dict[str, Any]:
79
148
  """
80
- Fetches a paginated list of all posts from a specific user or company profile using their unique identifier. This function retrieves content directly from a single profile, unlike the broader, keyword-based `linkedin_search` which searches across the entire LinkedIn platform.
81
-
149
+ Retrieves a paginated list of posts from a specific user or company profile using their provider ID. An authorizing `account_id` is required, and the `is_company` flag must specify the entity type, distinguishing this from `retrieve_post` which fetches a single post by its own ID.
150
+
82
151
  Args:
83
- identifier: The entity's provider internal ID (LinkedIn ID).starts with ACo for users, while for companies it's a series of numbers. You can get it in the results of linkedin_search.
84
- cursor: Pagination cursor for the next page of entries.
85
- limit: Number of items to return (1-100, though spec allows up to 250).
86
-
152
+ identifier: The entity's provider internal ID (LinkedIn ID).
153
+ cursor: Pagination cursor.
154
+ limit: Number of items to return (1-100, as per Unipile example, though spec allows up to 250).
155
+ is_company: Boolean indicating if the identifier is for a company.
156
+
87
157
  Returns:
88
158
  A dictionary containing a list of post objects and pagination details.
89
-
159
+
90
160
  Raises:
91
161
  httpx.HTTPError: If the API request fails.
92
-
162
+
93
163
  Tags:
94
164
  linkedin, post, list, user_posts, company_posts, content, api, important
95
165
  """
96
-
97
- return self._unipile_app.list_profile_posts(
98
- identifier=identifier,
99
- account_id=self.account_id,
100
- cursor=cursor,
101
- limit=limit,
102
- )
103
-
104
- def linkedin_retrieve_profile(
105
- self,
106
- identifier: str,
107
- ) -> dict[str, Any]:
166
+ url = f"{self.base_url}/api/v1/users/{identifier}/posts"
167
+ params: dict[str, Any] = {"account_id": self.account_id}
168
+ if cursor:
169
+ params["cursor"] = cursor
170
+ if limit:
171
+ params["limit"] = limit
172
+ if is_company is not None:
173
+ params["is_company"] = is_company
174
+
175
+ response = self._get(url, params=params)
176
+ return response.json()
177
+
178
+ def linkedin_retrieve_profile(self, identifier: str) -> dict[str, Any]:
108
179
  """
109
- Retrieves a specific LinkedIn user's profile using their unique identifier (e.g., public username). Unlike other methods that list posts or comments, this function focuses on fetching the user's core profile data by delegating the request to the integrated Unipile application.
110
-
180
+ Retrieves a specific LinkedIn user's profile using their public or internal ID. Unlike `retrieve_own_profile`, which fetches the authenticated user's details, this function targets and returns data for any specified third-party user profile on the platform.
181
+
111
182
  Args:
112
- identifier: Use the id from linkedin_search results. It starts with ACo for users and is a series of numbers for companies.
113
-
183
+ identifier: Can be the provider's internal id OR the provider's public id of the requested user.For example, for https://www.linkedin.com/in/manojbajaj95/, the identifier is "manojbajaj95".
184
+
114
185
  Returns:
115
186
  A dictionary containing the user's profile details.
116
-
187
+
117
188
  Raises:
118
189
  httpx.HTTPError: If the API request fails.
119
-
190
+
120
191
  Tags:
121
192
  linkedin, user, profile, retrieve, get, api, important
122
193
  """
123
-
124
- return self._unipile_app.retrieve_user_profile(
125
- identifier=identifier,
126
- account_id=self.account_id,
127
- )
194
+ url = f"{self.base_url}/api/v1/users/{identifier}"
195
+ params: dict[str, Any] = {"account_id": self.account_id}
196
+ response = self._get(url, params=params)
197
+ return self._handle_response(response)
128
198
 
129
199
  def linkedin_list_post_comments(
130
200
  self,
@@ -134,31 +204,34 @@ class ScraperApp(APIApplication):
134
204
  limit: int | None = None,
135
205
  ) -> dict[str, Any]:
136
206
  """
137
- Fetches a paginated list of comments for a specified LinkedIn post. Providing a `comment_id` retrieves replies to that specific comment instead of top-level ones. This function exclusively targets post interactions, differentiating it from others that list posts or retrieve entire profiles.
138
-
207
+ Fetches comments for a specific post. Providing an optional `comment_id` retrieves threaded replies instead of top-level comments. This read-only operation contrasts with `create_post_comment`, which publishes new comments, and `list_content_reactions`, which retrieves 'likes'.
208
+
139
209
  Args:
140
- post_id: The social ID of the post. Example urn:li:ugcPost:7386500271624896512
210
+ post_id: The social ID of the post.
141
211
  comment_id: If provided, retrieves replies to this comment ID instead of top-level comments.
142
212
  cursor: Pagination cursor.
143
- limit: Number of comments to return.
144
-
213
+ limit: Number of comments to return. (OpenAPI spec shows type string, passed as string if provided).
214
+
145
215
  Returns:
146
216
  A dictionary containing a list of comment objects and pagination details.
147
-
217
+
148
218
  Raises:
149
219
  httpx.HTTPError: If the API request fails.
150
-
220
+
151
221
  Tags:
152
222
  linkedin, post, comment, list, content, api, important
153
223
  """
154
-
155
- return self._unipile_app.list_post_comments(
156
- post_id=post_id,
157
- account_id=self.account_id,
158
- comment_id=comment_id,
159
- cursor=cursor,
160
- limit=limit,
161
- )
224
+ url = f"{self.base_url}/api/v1/posts/{post_id}/comments"
225
+ params: dict[str, Any] = {"account_id": self.account_id}
226
+ if cursor:
227
+ params["cursor"] = cursor
228
+ if limit is not None:
229
+ params["limit"] = str(limit)
230
+ if comment_id:
231
+ params["comment_id"] = comment_id
232
+
233
+ response = self._get(url, params=params)
234
+ return response.json()
162
235
 
163
236
  def list_tools(self):
164
237
  """
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: universal-mcp-applications
3
- Version: 0.1.29
3
+ Version: 0.1.30
4
4
  Summary: A Universal MCP Application: universal_mcp_applications
5
5
  Project-URL: Homepage, https://github.com/universal-mcp/applications
6
6
  Project-URL: Repository, https://github.com/universal-mcp/applications
@@ -144,9 +144,9 @@ universal_mcp/applications/jira/app.py,sha256=V9GunK_s4gEuCw6Qnf45lDXaXwjVrNAhWm
144
144
  universal_mcp/applications/klaviyo/README.md,sha256=6f2wbhCFF-BbgLr0nEE2Nb-Pz0M0b7U42Dp-ZILXz0I,53609
145
145
  universal_mcp/applications/klaviyo/__init__.py,sha256=YS2GhW7my_I1tfyLlxlkeTFmlz2WD8vIpMThtdBflqQ,28
146
146
  universal_mcp/applications/klaviyo/app.py,sha256=xHQxEZFVIWPCBmL6YoYxuVREibwPRH21izw0psmOzFc,423692
147
- universal_mcp/applications/linkedin/README.md,sha256=SYVyzNuADMzYz3ElsA_46qy1tGWlI32WGUfLQ-3PITE,1458
147
+ universal_mcp/applications/linkedin/README.md,sha256=gwbNgrPKUsGzQEsta3kp8SK5RJJ5mPg23WGG548Y6no,4476
148
148
  universal_mcp/applications/linkedin/__init__.py,sha256=Yj-713vb4ZYykIlXlwOkKkIXIOB3opCW8wvp_CCqlKk,29
149
- universal_mcp/applications/linkedin/app.py,sha256=mR_JuO54xv5Cf6ahhg7IKWflDGhPo2n2Gzbfx6to7Ns,11174
149
+ universal_mcp/applications/linkedin/app.py,sha256=jiKf5A6BwTKeShU0JcSd4Q1iTi282tUqSREyhSPsIIM,27304
150
150
  universal_mcp/applications/mailchimp/README.md,sha256=xOR32HA8h-WMS9ntcBxyllM3UOBYiyvZ6tJBHlAuU7k,33802
151
151
  universal_mcp/applications/mailchimp/__init__.py,sha256=wmXVl-NJyTNkFT5db29OZmeiLWAGu9jXwdZC5o2jZBw,30
152
152
  universal_mcp/applications/mailchimp/app.py,sha256=_a6iByjDK1SuM3UoT5lTokptdEryUzrS8JsYNLCTwi4,466723
@@ -197,7 +197,7 @@ universal_mcp/applications/rocketlane/__init__.py,sha256=jl3PjnTvPdjnbFXJgLywSlE
197
197
  universal_mcp/applications/rocketlane/app.py,sha256=Ae2hQFI5PylCLtNPJkTqWMLGsLx5fDd4wRFDhxTzTXQ,240689
198
198
  universal_mcp/applications/scraper/README.md,sha256=JUNLshHABs4T1f24nvQeee62YIElSkxpU-zs2kuS0Gw,1497
199
199
  universal_mcp/applications/scraper/__init__.py,sha256=W5Buzq8QbetUQm5m9xXCHeWcvVObU2vZ4xbvYtZImJo,28
200
- universal_mcp/applications/scraper/app.py,sha256=-BE_Ghfnh4_2XeE7MvxuYcY5eodpIlVeR4_XE3Z9jIQ,6997
200
+ universal_mcp/applications/scraper/app.py,sha256=B5toOQGPEoaFHAZXaMbtCu3VsYJLJmtw54cys7c4Lug,9691
201
201
  universal_mcp/applications/semanticscholar/README.md,sha256=JpLY_698pvstgoNfQ5Go8C8ehQ-o68uFDX5kr86upK0,2834
202
202
  universal_mcp/applications/semanticscholar/__init__.py,sha256=eR36chrc0pbBsSE1GadvmQH0OmtKnSC91xbE7HcDPf0,36
203
203
  universal_mcp/applications/semanticscholar/app.py,sha256=OHTFkR-IwRU5Rvb1bEu7XmRHikht3hEgZxszLQu6kFI,22234
@@ -256,9 +256,6 @@ universal_mcp/applications/twitter/api_segments/trends_api.py,sha256=Boc6f2kqm53
256
256
  universal_mcp/applications/twitter/api_segments/tweets_api.py,sha256=YPXpFYMJm1DmbM9OpRS9OKVwdf8YtsE6hNvrnwYCT3k,90859
257
257
  universal_mcp/applications/twitter/api_segments/usage_api.py,sha256=Wx-bR8mBsFmjzo59hTl5MVm6_BgCejAPDwax1YWy5W4,1564
258
258
  universal_mcp/applications/twitter/api_segments/users_api.py,sha256=cYyUrVmqNRLn5EWjrjcJu0yflTdxwv1m5nta18ajma4,81261
259
- universal_mcp/applications/unipile/README.md,sha256=gh_YsmeJ0pAU7XNBZD8OO-AgH7ed2kG8QPbxm2wYjlY,5205
260
- universal_mcp/applications/unipile/__init__.py,sha256=0UZVOiYo_dDXbvTmtHrZ_fgvrbpasjWV17EEm4pZq9E,28
261
- universal_mcp/applications/unipile/app.py,sha256=FUMDk0JZb8kRmMlPn6_NdIiCz0SeduDJABG7UHAsl04,30206
262
259
  universal_mcp/applications/whatsapp/README.md,sha256=ZYbpVN0EqMW21ZCMcLNdheKX8OSPCkt3wcN8p8OZk5E,3849
263
260
  universal_mcp/applications/whatsapp/__init__.py,sha256=miHfSBpu5lx226T8dMbvOw_5H6MJCWOKVK_WbqMma-8,29
264
261
  universal_mcp/applications/whatsapp/app.py,sha256=jq7lukBtITqpm_K6l8r23G4N4QnU-u6AZQFToWDQY6g,23582
@@ -279,7 +276,7 @@ universal_mcp/applications/youtube/app.py,sha256=eqgqe0b53W9Mj0FZGW3ZqY3xkGF4NbO
279
276
  universal_mcp/applications/zenquotes/README.md,sha256=FJyoTGRCaZjF_bsCBqg1CrYcvIfuUG_Qk616G1wjhF8,512
280
277
  universal_mcp/applications/zenquotes/__init__.py,sha256=C5nEHZ3Xy6nYUarq0BqQbbJnHs0UtSlqhk0DqmvWiHk,58
281
278
  universal_mcp/applications/zenquotes/app.py,sha256=7xIEnSZWAGYu5583Be2ZjSCtLUAfMWRzucSpp7hw_h4,1299
282
- universal_mcp_applications-0.1.29.dist-info/METADATA,sha256=cHdzZVGcvF6XjJbqilkT9shgIYCYXGxyk7r5SzxN3fc,2956
283
- universal_mcp_applications-0.1.29.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
284
- universal_mcp_applications-0.1.29.dist-info/licenses/LICENSE,sha256=NweDZVPslBAZFzlgByF158b85GR0f5_tLQgq1NS48To,1063
285
- universal_mcp_applications-0.1.29.dist-info/RECORD,,
279
+ universal_mcp_applications-0.1.30.dist-info/METADATA,sha256=CsP9zWf3Urh3IQgtDsP6skM7LsoVqryYOi3PMSnqTfU,2956
280
+ universal_mcp_applications-0.1.30.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
281
+ universal_mcp_applications-0.1.30.dist-info/licenses/LICENSE,sha256=NweDZVPslBAZFzlgByF158b85GR0f5_tLQgq1NS48To,1063
282
+ universal_mcp_applications-0.1.30.dist-info/RECORD,,
@@ -1,28 +0,0 @@
1
- # UnipileApp MCP Server
2
-
3
- An MCP Server for the UnipileApp API.
4
-
5
- ## 🛠️ Tool List
6
-
7
- This is automatically generated from OpenAPI schema for the UnipileApp API.
8
-
9
-
10
- | Tool | Description |
11
- |------|-------------|
12
- | `list_all_chats` | Retrieves a paginated list of all chat conversations across linked accounts. Supports filtering by unread status, date range, account provider, and specific account IDs, distinguishing it from functions listing messages within a single chat. |
13
- | `list_chat_messages` | Retrieves messages from a specific chat identified by `chat_id`. Supports pagination and filtering by date or sender. Unlike `list_all_messages`, which fetches from all chats, this function targets the contents of a single conversation. |
14
- | `send_chat_message` | Sends a text message to a specific chat conversation using its `chat_id`. This function creates a new message via a POST request, distinguishing it from read-only functions like `list_chat_messages`. It returns the API's response, which typically confirms the successful creation of the message. |
15
- | `retrieve_chat` | Retrieves a single chat's details using its Unipile or provider-specific ID. Requires an `account_id` when using a provider ID for context. This function is distinct from `list_all_chats`, which returns a collection, by targeting one specific conversation. |
16
- | `list_all_messages` | Retrieves a paginated list of messages from all chats associated with the account(s). Unlike `list_chat_messages` which targets a specific conversation, this function provides a global message view, filterable by sender, account, and date range. |
17
- | `list_all_accounts` | Retrieves a paginated list of all social media accounts linked to the Unipile service. This is crucial for obtaining the `account_id` required by other methods to specify which user account should perform an action, like sending a message or retrieving user-specific posts. |
18
- | `retrieve_linked_account` | Retrieves details for a specific account linked to Unipile using its ID. It fetches metadata about the connection itself (e.g., a linked LinkedIn account), differentiating it from `retrieve_user_profile` which fetches a user's profile from the external platform. |
19
- | `list_profile_posts` | Retrieves a paginated list of posts from a specific user or company profile using their provider ID. An authorizing `account_id` is required, and the `is_company` flag must specify the entity type, distinguishing this from `retrieve_post` which fetches a single post by its own ID. |
20
- | `retrieve_own_profile` | Retrieves the profile details for the user associated with the specified Unipile account ID. This function targets the API's 'me' endpoint to fetch the authenticated user's profile, distinct from `retrieve_user_profile` which fetches profiles of other users by their public identifier. |
21
- | `retrieve_user_profile` | Retrieves a specific LinkedIn user's profile using their public or internal ID, authorized via the provided `account_id`. Unlike `retrieve_own_profile`, which fetches the authenticated user's details, this function targets and returns data for any specified third-party user profile on the platform. |
22
- | `retrieve_post` | Fetches a specific post's details by its unique ID, requiring an `account_id` for authorization. Unlike `list_profile_posts`, which retrieves a collection of posts from a user or company profile, this function targets one specific post and returns its full object. |
23
- | `list_post_comments` | Fetches comments for a specific post using an `account_id` for authorization. Providing an optional `comment_id` retrieves threaded replies instead of top-level comments. This read-only operation contrasts with `create_post_comment`, which publishes new comments, and `list_content_reactions`, which retrieves 'likes'. |
24
- | `create_post` | Publishes a new top-level post from a specified account, including text, user mentions, and an external link. This function creates original content, distinguishing it from `create_post_comment` which adds replies to existing posts. |
25
- | `list_content_reactions` | Retrieves a paginated list of reactions for a given post or, optionally, a specific comment. This read-only operation uses the provided `account_id` for the request, distinguishing it from the `create_reaction` function which adds new reactions. |
26
- | `create_post_comment` | Publishes a comment on a specified post. By providing an optional `comment_id`, it creates a threaded reply to an existing comment instead of a new top-level one. This function's dual capability distinguishes it from `list_post_comments`, which only retrieves comments and their replies. |
27
- | `create_reaction` | Adds a specified reaction (e.g., 'like', 'love') to a LinkedIn post or, optionally, to a specific comment. This function performs a POST request to create the reaction, differentiating it from `list_content_reactions` which only retrieves existing ones. |
28
- | `search` | Performs a comprehensive LinkedIn search for people, companies, posts, or jobs using granular filters like keywords and location. Alternatively, it can execute a search from a direct LinkedIn URL. Supports pagination and targets either the classic or Sales Navigator API. |
@@ -1 +0,0 @@
1
- from .app import UnipileApp