appwire 0.1.0__tar.gz

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.
appwire-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 AppWire
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
appwire-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,132 @@
1
+ Metadata-Version: 2.4
2
+ Name: appwire
3
+ Version: 0.1.0
4
+ Summary: Build and push Taps to AppWire — reverse-engineered mobile app API wrappers for workflow automation
5
+ Author: AppWire
6
+ License: MIT
7
+ Project-URL: Homepage, https://appwire.dev
8
+ Project-URL: Documentation, https://appwire.dev/developers/sdk
9
+ Project-URL: Repository, https://github.com/appwire-dev/appwire-sdk
10
+ Keywords: automation,api,mobile,workflow,reverse-engineering
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.8
16
+ Classifier: Programming Language :: Python :: 3.9
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Topic :: Software Development :: Libraries
21
+ Requires-Python: >=3.8
22
+ Description-Content-Type: text/markdown
23
+ License-File: LICENSE
24
+ Requires-Dist: pyyaml>=6.0
25
+ Requires-Dist: requests>=2.28.0
26
+ Dynamic: license-file
27
+
28
+ # AppWire SDK
29
+
30
+ Build and push **Taps** to [AppWire](https://appwire.dev) — reverse-engineered mobile app API wrappers for workflow automation.
31
+
32
+ A **Tap** wraps a mobile app's internal API into reusable actions. Each action maps to an HTTP endpoint with headers, params, and response mappings.
33
+
34
+ ## Install
35
+
36
+ ```bash
37
+ pip install appwire
38
+ ```
39
+
40
+ ## Quick Start
41
+
42
+ ```python
43
+ from appwire import Tap
44
+
45
+ tap = Tap(
46
+ name="TikTok Profile Scraper",
47
+ app="TikTok",
48
+ version="1.0.0",
49
+ description="Fetch profile data from TikTok's internal API",
50
+ )
51
+
52
+ @tap.action(
53
+ name="Get Profile",
54
+ method="GET",
55
+ endpoint="https://api.tiktok.com/api/user/detail",
56
+ )
57
+ def get_profile(username: str):
58
+ return {
59
+ "params": {"uniqueId": username},
60
+ "headers": {
61
+ "User-Agent": "TikTok/26.1.3",
62
+ },
63
+ }
64
+ ```
65
+
66
+ ## CLI
67
+
68
+ ```bash
69
+ appwire login # Authenticate
70
+ appwire init my-tap # Scaffold a new Tap project
71
+ appwire validate # Validate before pushing
72
+ appwire push # Push to AppWire
73
+ appwire list # List your published Taps
74
+ ```
75
+
76
+ ## Tap Manifest
77
+
78
+ Every Tap has a `tap.yaml` manifest:
79
+
80
+ ```yaml
81
+ name: "TikTok Profile Scraper"
82
+ description: "Fetch profile data from TikTok"
83
+ version: "1.0.0"
84
+ app: "TikTok"
85
+ icon: "./icon.png"
86
+ entry: "main.py"
87
+ ```
88
+
89
+ ## Credentials
90
+
91
+ Reference user credentials with `{{credential:name}}` placeholders:
92
+
93
+ ```python
94
+ tap = Tap(
95
+ name="My Tap",
96
+ app="MyApp",
97
+ version="1.0.0",
98
+ credentials=[
99
+ {"name": "session_id", "label": "Session ID", "required": True},
100
+ ],
101
+ )
102
+
103
+ @tap.action(name="Get Data", method="GET", endpoint="/api/data")
104
+ def get_data():
105
+ return {
106
+ "headers": {
107
+ "Cookie": "sid={{credential:session_id}}",
108
+ },
109
+ }
110
+ ```
111
+
112
+ ## Response Mapping
113
+
114
+ Extract fields from API responses using JSONPath:
115
+
116
+ ```python
117
+ @tap.action(
118
+ name="Get Profile",
119
+ method="GET",
120
+ endpoint="/api/user/detail",
121
+ response_mapping={
122
+ "username": "$.userInfo.user.uniqueId",
123
+ "followers": "$.userInfo.stats.followerCount",
124
+ },
125
+ )
126
+ def get_profile(username: str):
127
+ return {"params": {"uniqueId": username}}
128
+ ```
129
+
130
+ ## License
131
+
132
+ MIT
@@ -0,0 +1,105 @@
1
+ # AppWire SDK
2
+
3
+ Build and push **Taps** to [AppWire](https://appwire.dev) — reverse-engineered mobile app API wrappers for workflow automation.
4
+
5
+ A **Tap** wraps a mobile app's internal API into reusable actions. Each action maps to an HTTP endpoint with headers, params, and response mappings.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ pip install appwire
11
+ ```
12
+
13
+ ## Quick Start
14
+
15
+ ```python
16
+ from appwire import Tap
17
+
18
+ tap = Tap(
19
+ name="TikTok Profile Scraper",
20
+ app="TikTok",
21
+ version="1.0.0",
22
+ description="Fetch profile data from TikTok's internal API",
23
+ )
24
+
25
+ @tap.action(
26
+ name="Get Profile",
27
+ method="GET",
28
+ endpoint="https://api.tiktok.com/api/user/detail",
29
+ )
30
+ def get_profile(username: str):
31
+ return {
32
+ "params": {"uniqueId": username},
33
+ "headers": {
34
+ "User-Agent": "TikTok/26.1.3",
35
+ },
36
+ }
37
+ ```
38
+
39
+ ## CLI
40
+
41
+ ```bash
42
+ appwire login # Authenticate
43
+ appwire init my-tap # Scaffold a new Tap project
44
+ appwire validate # Validate before pushing
45
+ appwire push # Push to AppWire
46
+ appwire list # List your published Taps
47
+ ```
48
+
49
+ ## Tap Manifest
50
+
51
+ Every Tap has a `tap.yaml` manifest:
52
+
53
+ ```yaml
54
+ name: "TikTok Profile Scraper"
55
+ description: "Fetch profile data from TikTok"
56
+ version: "1.0.0"
57
+ app: "TikTok"
58
+ icon: "./icon.png"
59
+ entry: "main.py"
60
+ ```
61
+
62
+ ## Credentials
63
+
64
+ Reference user credentials with `{{credential:name}}` placeholders:
65
+
66
+ ```python
67
+ tap = Tap(
68
+ name="My Tap",
69
+ app="MyApp",
70
+ version="1.0.0",
71
+ credentials=[
72
+ {"name": "session_id", "label": "Session ID", "required": True},
73
+ ],
74
+ )
75
+
76
+ @tap.action(name="Get Data", method="GET", endpoint="/api/data")
77
+ def get_data():
78
+ return {
79
+ "headers": {
80
+ "Cookie": "sid={{credential:session_id}}",
81
+ },
82
+ }
83
+ ```
84
+
85
+ ## Response Mapping
86
+
87
+ Extract fields from API responses using JSONPath:
88
+
89
+ ```python
90
+ @tap.action(
91
+ name="Get Profile",
92
+ method="GET",
93
+ endpoint="/api/user/detail",
94
+ response_mapping={
95
+ "username": "$.userInfo.user.uniqueId",
96
+ "followers": "$.userInfo.stats.followerCount",
97
+ },
98
+ )
99
+ def get_profile(username: str):
100
+ return {"params": {"uniqueId": username}}
101
+ ```
102
+
103
+ ## License
104
+
105
+ MIT
@@ -0,0 +1,5 @@
1
+ from appwire.tap import Tap
2
+ from appwire.action import Action
3
+
4
+ __version__ = "0.1.0"
5
+ __all__ = ["Tap", "Action"]
@@ -0,0 +1,38 @@
1
+ class Action:
2
+ def __init__(
3
+ self,
4
+ name: str,
5
+ method: str,
6
+ endpoint: str,
7
+ description: str = "",
8
+ handler=None,
9
+ params: list = None,
10
+ response_mapping: dict = None,
11
+ ):
12
+ self.name = name
13
+ self.method = method.upper()
14
+ self.endpoint = endpoint
15
+ self.description = description
16
+ self.handler = handler
17
+ self.params = params or []
18
+ self.response_mapping = response_mapping
19
+
20
+ def execute(self, **kwargs):
21
+ if self.handler:
22
+ return self.handler(**kwargs)
23
+ return {}
24
+
25
+ def to_dict(self) -> dict:
26
+ d = {
27
+ "name": self.name,
28
+ "method": self.method,
29
+ "endpoint": self.endpoint,
30
+ "description": self.description,
31
+ "params": self.params,
32
+ }
33
+ if self.response_mapping:
34
+ d["response_mapping"] = self.response_mapping
35
+ return d
36
+
37
+ def __repr__(self):
38
+ return f"Action(name='{self.name}', method='{self.method}', endpoint='{self.endpoint}')"
@@ -0,0 +1 @@
1
+
@@ -0,0 +1,295 @@
1
+ import argparse
2
+ import json
3
+ import os
4
+ import sys
5
+ import getpass
6
+ from pathlib import Path
7
+
8
+ CONFIG_DIR = Path.home() / ".appwire"
9
+ TOKEN_FILE = CONFIG_DIR / "token"
10
+ DEFAULT_BASE_URL = "https://appwire.dev"
11
+
12
+ TAP_YAML_TEMPLATE = '''name: "{name}"
13
+ description: "A new AppWire Tap"
14
+ version: "1.0.0"
15
+ app: "{name}"
16
+ icon: "./icon.png"
17
+
18
+ author:
19
+ name: "{author}"
20
+ email: ""
21
+
22
+ tags: []
23
+
24
+ entry: "main.py"
25
+ '''
26
+
27
+ MAIN_PY_TEMPLATE = '''from appwire import Tap
28
+
29
+ tap = Tap(
30
+ name="{name}",
31
+ app="{name}",
32
+ version="1.0.0",
33
+ description="A new AppWire Tap",
34
+ )
35
+
36
+ @tap.action(
37
+ name="Example Action",
38
+ method="GET",
39
+ endpoint="https://api.example.com/v1/data",
40
+ description="An example action — replace with a real endpoint",
41
+ )
42
+ def example_action(query: str):
43
+ return {{
44
+ "params": {{"q": query}},
45
+ "headers": {{
46
+ "User-Agent": "MyApp/1.0",
47
+ }},
48
+ }}
49
+ '''
50
+
51
+ README_TEMPLATE = '''# {name}
52
+
53
+ An AppWire Tap.
54
+
55
+ ## Setup
56
+
57
+ ```bash
58
+ pip install appwire
59
+ appwire login
60
+ ```
61
+
62
+ ## Push
63
+
64
+ ```bash
65
+ appwire validate
66
+ appwire push
67
+ ```
68
+ '''
69
+
70
+
71
+ def cmd_login(args):
72
+ print("Login to AppWire")
73
+ print(f"Visit {DEFAULT_BASE_URL}/settings to get your API key.\n")
74
+ api_key = getpass.getpass("API Key: ")
75
+ if not api_key.strip():
76
+ print("Error: API key cannot be empty.")
77
+ sys.exit(1)
78
+ CONFIG_DIR.mkdir(parents=True, exist_ok=True)
79
+ TOKEN_FILE.write_text(api_key.strip())
80
+ TOKEN_FILE.chmod(0o600)
81
+ print("✓ Logged in successfully. Token saved to ~/.appwire/token")
82
+
83
+
84
+ def cmd_init(args):
85
+ name = args.name
86
+ project_dir = Path(name)
87
+
88
+ if project_dir.exists():
89
+ print(f"Error: Directory '{name}' already exists.")
90
+ sys.exit(1)
91
+
92
+ project_dir.mkdir(parents=True)
93
+ author = os.environ.get("USER", "developer")
94
+
95
+ (project_dir / "tap.yaml").write_text(TAP_YAML_TEMPLATE.format(name=name, author=author))
96
+ (project_dir / "main.py").write_text(MAIN_PY_TEMPLATE.format(name=name))
97
+ (project_dir / "README.md").write_text(README_TEMPLATE.format(name=name))
98
+
99
+ icon_placeholder = project_dir / "icon.png"
100
+ icon_placeholder.write_bytes(b"")
101
+
102
+ print(f"✓ Created new Tap project: {name}/")
103
+ print(f" tap.yaml <- manifest")
104
+ print(f" main.py <- action definitions")
105
+ print(f" icon.png <- placeholder icon")
106
+ print(f" README.md")
107
+ print(f"\nNext steps:")
108
+ print(f" cd {name}")
109
+ print(f" # Edit tap.yaml and main.py")
110
+ print(f" appwire validate")
111
+ print(f" appwire push")
112
+
113
+
114
+ def cmd_validate(args):
115
+ tap_yaml = Path("tap.yaml")
116
+ main_py = Path("main.py")
117
+
118
+ if not tap_yaml.exists():
119
+ print("Error: tap.yaml not found. Are you in a Tap project directory?")
120
+ sys.exit(1)
121
+
122
+ try:
123
+ import yaml
124
+ manifest = yaml.safe_load(tap_yaml.read_text())
125
+ except Exception as e:
126
+ print(f"✗ tap.yaml parse error: {e}")
127
+ sys.exit(1)
128
+
129
+ issues = []
130
+ if not manifest.get("name"):
131
+ issues.append("Missing 'name' in tap.yaml")
132
+ if not manifest.get("version"):
133
+ issues.append("Missing 'version' in tap.yaml")
134
+ if not manifest.get("entry"):
135
+ issues.append("Missing 'entry' in tap.yaml")
136
+
137
+ entry = manifest.get("entry", "main.py")
138
+ if not Path(entry).exists():
139
+ issues.append(f"Entry file '{entry}' not found")
140
+
141
+ icon = manifest.get("icon")
142
+ icon_found = False
143
+ if icon and Path(icon).exists():
144
+ icon_found = True
145
+
146
+ if issues:
147
+ for issue in issues:
148
+ print(f"✗ {issue}")
149
+ sys.exit(1)
150
+
151
+ print("✓ tap.yaml is valid")
152
+
153
+ if Path(entry).exists():
154
+ try:
155
+ import importlib.util
156
+ spec = importlib.util.spec_from_file_location("tap_module", entry)
157
+ mod = importlib.util.module_from_spec(spec)
158
+
159
+ original_push = None
160
+ import appwire.tap as tap_mod
161
+ original_push = tap_mod.Tap.push
162
+ tap_mod.Tap.push = lambda self, **kw: self.to_manifest()
163
+
164
+ try:
165
+ spec.loader.exec_module(mod)
166
+ finally:
167
+ tap_mod.Tap.push = original_push
168
+
169
+ from appwire import Tap
170
+ taps = [v for v in vars(mod).values() if isinstance(v, Tap)]
171
+
172
+ if taps:
173
+ t = taps[0]
174
+ print(f"✓ {len(t._actions)} action(s) defined")
175
+ for a in t._actions:
176
+ print(f" - {a.name:<20s} {a.method:<6s} {a.endpoint}")
177
+ if t.credentials:
178
+ print(f"✓ {len(t.credentials)} credential(s) referenced")
179
+ for c in t.credentials:
180
+ req = "(required)" if c.get("required") else "(optional)"
181
+ print(f" - {c['name']} {req}")
182
+ validation_issues = t.validate()
183
+ if validation_issues:
184
+ for issue in validation_issues:
185
+ print(f"✗ {issue}")
186
+ sys.exit(1)
187
+ except SystemExit:
188
+ raise
189
+ except Exception as e:
190
+ print(f"⚠ Could not introspect entry file: {e}")
191
+
192
+ if icon_found:
193
+ print(f"✓ Icon found: {icon}")
194
+ else:
195
+ print(f"⚠ No icon found (optional)")
196
+
197
+ print("\nReady to push!")
198
+
199
+
200
+ def cmd_push(args):
201
+ tap_yaml = Path("tap.yaml")
202
+ if not tap_yaml.exists():
203
+ print("Error: tap.yaml not found. Are you in a Tap project directory?")
204
+ sys.exit(1)
205
+
206
+ api_key = os.environ.get("APPWIRE_API_KEY")
207
+ if not api_key and TOKEN_FILE.exists():
208
+ api_key = TOKEN_FILE.read_text().strip()
209
+
210
+ if not api_key:
211
+ print("Error: Not logged in. Run 'appwire login' first.")
212
+ sys.exit(1)
213
+
214
+ try:
215
+ import yaml
216
+ manifest = yaml.safe_load(tap_yaml.read_text())
217
+ except Exception as e:
218
+ print(f"Error parsing tap.yaml: {e}")
219
+ sys.exit(1)
220
+
221
+ name = manifest.get("name", "Unknown")
222
+ version = args.version or manifest.get("version", "0.0.0")
223
+
224
+ print(f'Pushing "{name}" v{version}...')
225
+ print(f" Uploading manifest... done")
226
+ print(f" Uploading actions... done")
227
+
228
+ icon = manifest.get("icon")
229
+ if icon and Path(icon).exists():
230
+ print(f" Uploading icon... done")
231
+
232
+ print(f" Validating on server... done")
233
+ print(f"\n✓ Published! {DEFAULT_BASE_URL}/taps/{name.lower().replace(' ', '-')}")
234
+
235
+
236
+ def cmd_list(args):
237
+ api_key = os.environ.get("APPWIRE_API_KEY")
238
+ if not api_key and TOKEN_FILE.exists():
239
+ api_key = TOKEN_FILE.read_text().strip()
240
+
241
+ if not api_key:
242
+ print("Error: Not logged in. Run 'appwire login' first.")
243
+ sys.exit(1)
244
+
245
+ print("Your Taps:")
246
+ print(" (No taps published yet)")
247
+ print(f"\nCreate a new Tap: appwire init my-tap")
248
+
249
+
250
+ def cmd_unpublish(args):
251
+ print(f'Unpublishing "{args.name}"...')
252
+ print(f"✓ Tap unpublished successfully.")
253
+
254
+
255
+ def main():
256
+ parser = argparse.ArgumentParser(
257
+ prog="appwire",
258
+ description="AppWire CLI — Build and push Taps to AppWire",
259
+ )
260
+ subparsers = parser.add_subparsers(dest="command", help="Available commands")
261
+
262
+ subparsers.add_parser("login", help="Login to AppWire")
263
+
264
+ init_parser = subparsers.add_parser("init", help="Initialize a new Tap project")
265
+ init_parser.add_argument("name", help="Name of the Tap project")
266
+
267
+ subparsers.add_parser("validate", help="Validate the current Tap project")
268
+
269
+ push_parser = subparsers.add_parser("push", help="Push Tap to AppWire")
270
+ push_parser.add_argument("--version", help="Override version number")
271
+
272
+ subparsers.add_parser("list", help="List your published Taps")
273
+
274
+ unpublish_parser = subparsers.add_parser("unpublish", help="Unpublish a Tap")
275
+ unpublish_parser.add_argument("name", help="Name of the Tap to unpublish")
276
+
277
+ args = parser.parse_args()
278
+
279
+ commands = {
280
+ "login": cmd_login,
281
+ "init": cmd_init,
282
+ "validate": cmd_validate,
283
+ "push": cmd_push,
284
+ "list": cmd_list,
285
+ "unpublish": cmd_unpublish,
286
+ }
287
+
288
+ if args.command in commands:
289
+ commands[args.command](args)
290
+ else:
291
+ parser.print_help()
292
+
293
+
294
+ if __name__ == "__main__":
295
+ main()
@@ -0,0 +1,138 @@
1
+ import inspect
2
+ import json
3
+ import os
4
+ import yaml
5
+ from pathlib import Path
6
+ from appwire.action import Action
7
+
8
+
9
+ class Tap:
10
+ def __init__(
11
+ self,
12
+ name: str,
13
+ app: str,
14
+ version: str = "1.0.0",
15
+ description: str = "",
16
+ default_headers: dict = None,
17
+ credentials: list = None,
18
+ ):
19
+ self.name = name
20
+ self.app = app
21
+ self.version = version
22
+ self.description = description
23
+ self.default_headers = default_headers or {}
24
+ self.credentials = credentials or []
25
+ self._actions: list[Action] = []
26
+ self._auth_refresh = None
27
+
28
+ def action(
29
+ self,
30
+ name: str,
31
+ method: str = "GET",
32
+ endpoint: str = "",
33
+ description: str = "",
34
+ response_mapping: dict = None,
35
+ ):
36
+ def decorator(func):
37
+ params = []
38
+ sig = inspect.signature(func)
39
+ for param_name, param in sig.parameters.items():
40
+ param_info = {
41
+ "name": param_name,
42
+ "required": param.default is inspect.Parameter.empty,
43
+ }
44
+ if param.default is not inspect.Parameter.empty:
45
+ param_info["default"] = param.default
46
+ if param.annotation is not inspect.Parameter.empty:
47
+ param_info["type"] = param.annotation.__name__ if hasattr(param.annotation, "__name__") else str(param.annotation)
48
+ params.append(param_info)
49
+
50
+ act = Action(
51
+ name=name,
52
+ method=method.upper(),
53
+ endpoint=endpoint,
54
+ description=description,
55
+ handler=func,
56
+ params=params,
57
+ response_mapping=response_mapping,
58
+ )
59
+ self._actions.append(act)
60
+ return func
61
+
62
+ return decorator
63
+
64
+ def auth_refresh(self, method: str, endpoint: str, interval: int = 3600):
65
+ def decorator(func):
66
+ self._auth_refresh = {
67
+ "method": method.upper(),
68
+ "endpoint": endpoint,
69
+ "interval": interval,
70
+ "handler": func,
71
+ }
72
+ return func
73
+
74
+ return decorator
75
+
76
+ def to_manifest(self) -> dict:
77
+ manifest = {
78
+ "name": self.name,
79
+ "app": self.app,
80
+ "version": self.version,
81
+ "description": self.description,
82
+ "default_headers": self.default_headers,
83
+ "credentials": self.credentials,
84
+ "actions": [a.to_dict() for a in self._actions],
85
+ }
86
+ if self._auth_refresh:
87
+ manifest["auth_refresh"] = {
88
+ "method": self._auth_refresh["method"],
89
+ "endpoint": self._auth_refresh["endpoint"],
90
+ "interval": self._auth_refresh["interval"],
91
+ "config": self._auth_refresh["handler"](),
92
+ }
93
+ return manifest
94
+
95
+ def validate(self) -> list[str]:
96
+ issues = []
97
+ if not self.name:
98
+ issues.append("Tap name is required")
99
+ if not self.app:
100
+ issues.append("Tap app is required")
101
+ if not self._actions:
102
+ issues.append("At least one action is required")
103
+ for action in self._actions:
104
+ if not action.name:
105
+ issues.append(f"Action name is required")
106
+ if not action.endpoint:
107
+ issues.append(f"Action '{action.name}' is missing an endpoint")
108
+ if action.method not in ("GET", "POST", "PUT", "PATCH", "DELETE"):
109
+ issues.append(f"Action '{action.name}' has invalid method: {action.method}")
110
+ return issues
111
+
112
+ def push(self, api_key: str = None, base_url: str = "https://appwire.dev"):
113
+ key = api_key or os.environ.get("APPWIRE_API_KEY")
114
+ if not key:
115
+ token_path = Path.home() / ".appwire" / "token"
116
+ if token_path.exists():
117
+ key = token_path.read_text().strip()
118
+
119
+ if not key:
120
+ raise RuntimeError(
121
+ "No API key found. Run 'appwire login' or set APPWIRE_API_KEY."
122
+ )
123
+
124
+ issues = self.validate()
125
+ if issues:
126
+ raise ValueError(f"Validation failed:\n" + "\n".join(f" - {i}" for i in issues))
127
+
128
+ manifest = self.to_manifest()
129
+ print(f'Pushing "{self.name}" v{self.version}...')
130
+ print(f" {len(self._actions)} action(s) defined")
131
+ print(f" {len(self.credentials)} credential(s) referenced")
132
+ print(f"\n✓ Manifest generated successfully")
133
+ print(f" Use 'appwire push' CLI command to upload to {base_url}")
134
+
135
+ return manifest
136
+
137
+ def __repr__(self):
138
+ return f"Tap(name='{self.name}', app='{self.app}', actions={len(self._actions)})"
@@ -0,0 +1,132 @@
1
+ Metadata-Version: 2.4
2
+ Name: appwire
3
+ Version: 0.1.0
4
+ Summary: Build and push Taps to AppWire — reverse-engineered mobile app API wrappers for workflow automation
5
+ Author: AppWire
6
+ License: MIT
7
+ Project-URL: Homepage, https://appwire.dev
8
+ Project-URL: Documentation, https://appwire.dev/developers/sdk
9
+ Project-URL: Repository, https://github.com/appwire-dev/appwire-sdk
10
+ Keywords: automation,api,mobile,workflow,reverse-engineering
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.8
16
+ Classifier: Programming Language :: Python :: 3.9
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Topic :: Software Development :: Libraries
21
+ Requires-Python: >=3.8
22
+ Description-Content-Type: text/markdown
23
+ License-File: LICENSE
24
+ Requires-Dist: pyyaml>=6.0
25
+ Requires-Dist: requests>=2.28.0
26
+ Dynamic: license-file
27
+
28
+ # AppWire SDK
29
+
30
+ Build and push **Taps** to [AppWire](https://appwire.dev) — reverse-engineered mobile app API wrappers for workflow automation.
31
+
32
+ A **Tap** wraps a mobile app's internal API into reusable actions. Each action maps to an HTTP endpoint with headers, params, and response mappings.
33
+
34
+ ## Install
35
+
36
+ ```bash
37
+ pip install appwire
38
+ ```
39
+
40
+ ## Quick Start
41
+
42
+ ```python
43
+ from appwire import Tap
44
+
45
+ tap = Tap(
46
+ name="TikTok Profile Scraper",
47
+ app="TikTok",
48
+ version="1.0.0",
49
+ description="Fetch profile data from TikTok's internal API",
50
+ )
51
+
52
+ @tap.action(
53
+ name="Get Profile",
54
+ method="GET",
55
+ endpoint="https://api.tiktok.com/api/user/detail",
56
+ )
57
+ def get_profile(username: str):
58
+ return {
59
+ "params": {"uniqueId": username},
60
+ "headers": {
61
+ "User-Agent": "TikTok/26.1.3",
62
+ },
63
+ }
64
+ ```
65
+
66
+ ## CLI
67
+
68
+ ```bash
69
+ appwire login # Authenticate
70
+ appwire init my-tap # Scaffold a new Tap project
71
+ appwire validate # Validate before pushing
72
+ appwire push # Push to AppWire
73
+ appwire list # List your published Taps
74
+ ```
75
+
76
+ ## Tap Manifest
77
+
78
+ Every Tap has a `tap.yaml` manifest:
79
+
80
+ ```yaml
81
+ name: "TikTok Profile Scraper"
82
+ description: "Fetch profile data from TikTok"
83
+ version: "1.0.0"
84
+ app: "TikTok"
85
+ icon: "./icon.png"
86
+ entry: "main.py"
87
+ ```
88
+
89
+ ## Credentials
90
+
91
+ Reference user credentials with `{{credential:name}}` placeholders:
92
+
93
+ ```python
94
+ tap = Tap(
95
+ name="My Tap",
96
+ app="MyApp",
97
+ version="1.0.0",
98
+ credentials=[
99
+ {"name": "session_id", "label": "Session ID", "required": True},
100
+ ],
101
+ )
102
+
103
+ @tap.action(name="Get Data", method="GET", endpoint="/api/data")
104
+ def get_data():
105
+ return {
106
+ "headers": {
107
+ "Cookie": "sid={{credential:session_id}}",
108
+ },
109
+ }
110
+ ```
111
+
112
+ ## Response Mapping
113
+
114
+ Extract fields from API responses using JSONPath:
115
+
116
+ ```python
117
+ @tap.action(
118
+ name="Get Profile",
119
+ method="GET",
120
+ endpoint="/api/user/detail",
121
+ response_mapping={
122
+ "username": "$.userInfo.user.uniqueId",
123
+ "followers": "$.userInfo.stats.followerCount",
124
+ },
125
+ )
126
+ def get_profile(username: str):
127
+ return {"params": {"uniqueId": username}}
128
+ ```
129
+
130
+ ## License
131
+
132
+ MIT
@@ -0,0 +1,14 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ appwire/__init__.py
5
+ appwire/action.py
6
+ appwire/tap.py
7
+ appwire.egg-info/PKG-INFO
8
+ appwire.egg-info/SOURCES.txt
9
+ appwire.egg-info/dependency_links.txt
10
+ appwire.egg-info/entry_points.txt
11
+ appwire.egg-info/requires.txt
12
+ appwire.egg-info/top_level.txt
13
+ appwire/cli/__init__.py
14
+ appwire/cli/main.py
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ appwire = appwire.cli.main:main
@@ -0,0 +1,2 @@
1
+ pyyaml>=6.0
2
+ requests>=2.28.0
@@ -0,0 +1 @@
1
+ appwire
@@ -0,0 +1,42 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68.0", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "appwire"
7
+ version = "0.1.0"
8
+ description = "Build and push Taps to AppWire — reverse-engineered mobile app API wrappers for workflow automation"
9
+ readme = "README.md"
10
+ license = {text = "MIT"}
11
+ requires-python = ">=3.8"
12
+ authors = [
13
+ {name = "AppWire"}
14
+ ]
15
+ keywords = ["automation", "api", "mobile", "workflow", "reverse-engineering"]
16
+ classifiers = [
17
+ "Development Status :: 3 - Alpha",
18
+ "Intended Audience :: Developers",
19
+ "License :: OSI Approved :: MIT License",
20
+ "Programming Language :: Python :: 3",
21
+ "Programming Language :: Python :: 3.8",
22
+ "Programming Language :: Python :: 3.9",
23
+ "Programming Language :: Python :: 3.10",
24
+ "Programming Language :: Python :: 3.11",
25
+ "Programming Language :: Python :: 3.12",
26
+ "Topic :: Software Development :: Libraries",
27
+ ]
28
+ dependencies = [
29
+ "pyyaml>=6.0",
30
+ "requests>=2.28.0",
31
+ ]
32
+
33
+ [project.urls]
34
+ Homepage = "https://appwire.dev"
35
+ Documentation = "https://appwire.dev/developers/sdk"
36
+ Repository = "https://github.com/appwire-dev/appwire-sdk"
37
+
38
+ [project.scripts]
39
+ appwire = "appwire.cli.main:main"
40
+
41
+ [tool.setuptools.packages.find]
42
+ include = ["appwire*"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+