amazon-ads-cli 0.1.2__tar.gz → 0.1.3__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.
- amazon_ads_cli-0.1.3/LICENSE +21 -0
- {amazon_ads_cli-0.1.2 → amazon_ads_cli-0.1.3}/PKG-INFO +3 -1
- amazon_ads_cli-0.1.3/amazon_ads_cli/__main__.py +6 -0
- {amazon_ads_cli-0.1.2 → amazon_ads_cli-0.1.3}/amazon_ads_cli/main.py +146 -3
- {amazon_ads_cli-0.1.2 → amazon_ads_cli-0.1.3}/amazon_ads_cli.egg-info/PKG-INFO +3 -1
- {amazon_ads_cli-0.1.2 → amazon_ads_cli-0.1.3}/amazon_ads_cli.egg-info/SOURCES.txt +2 -0
- {amazon_ads_cli-0.1.2 → amazon_ads_cli-0.1.3}/setup.py +1 -1
- {amazon_ads_cli-0.1.2 → amazon_ads_cli-0.1.3}/README.md +0 -0
- {amazon_ads_cli-0.1.2 → amazon_ads_cli-0.1.3}/amazon_ads_cli/__init__.py +0 -0
- {amazon_ads_cli-0.1.2 → amazon_ads_cli-0.1.3}/amazon_ads_cli.egg-info/dependency_links.txt +0 -0
- {amazon_ads_cli-0.1.2 → amazon_ads_cli-0.1.3}/amazon_ads_cli.egg-info/entry_points.txt +0 -0
- {amazon_ads_cli-0.1.2 → amazon_ads_cli-0.1.3}/amazon_ads_cli.egg-info/requires.txt +0 -0
- {amazon_ads_cli-0.1.2 → amazon_ads_cli-0.1.3}/amazon_ads_cli.egg-info/top_level.txt +0 -0
- {amazon_ads_cli-0.1.2 → amazon_ads_cli-0.1.3}/setup.cfg +0 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Stellar Aether
|
|
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.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: amazon-ads-cli
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.3
|
|
4
4
|
Summary: CLI tool for Amazon Advertising API v3
|
|
5
5
|
Home-page: https://github.com/stellaraether/amazon-ads-cli
|
|
6
6
|
Author: Lunan Li
|
|
@@ -14,6 +14,7 @@ Classifier: Programming Language :: Python :: 3.9
|
|
|
14
14
|
Classifier: Programming Language :: Python :: 3.10
|
|
15
15
|
Classifier: Programming Language :: Python :: 3.11
|
|
16
16
|
Requires-Python: >=3.8
|
|
17
|
+
License-File: LICENSE
|
|
17
18
|
Requires-Dist: click>=8.0
|
|
18
19
|
Requires-Dist: python-amazon-ad-api>=0.8.0
|
|
19
20
|
Requires-Dist: requests>=2.27.0
|
|
@@ -21,6 +22,7 @@ Dynamic: author
|
|
|
21
22
|
Dynamic: author-email
|
|
22
23
|
Dynamic: classifier
|
|
23
24
|
Dynamic: home-page
|
|
25
|
+
Dynamic: license-file
|
|
24
26
|
Dynamic: requires-dist
|
|
25
27
|
Dynamic: requires-python
|
|
26
28
|
Dynamic: summary
|
|
@@ -2,22 +2,142 @@
|
|
|
2
2
|
"""Amazon Ads CLI - Command line interface for Amazon Advertising API v3."""
|
|
3
3
|
|
|
4
4
|
import json
|
|
5
|
+
import os
|
|
5
6
|
from datetime import datetime, timedelta
|
|
6
7
|
|
|
7
8
|
import click
|
|
9
|
+
import yaml
|
|
8
10
|
from ad_api.api import reports, sponsored_products
|
|
9
11
|
from ad_api.base import Marketplaces
|
|
10
12
|
|
|
13
|
+
DEFAULT_CREDENTIALS_PATH = os.path.expanduser("~/.config/python-ad-api/credentials.yml")
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _check_path():
|
|
17
|
+
"""Check if the CLI is accessible in PATH and warn if not."""
|
|
18
|
+
import shutil
|
|
19
|
+
import sys
|
|
20
|
+
|
|
21
|
+
if not shutil.which("amz-ads"):
|
|
22
|
+
print(
|
|
23
|
+
"\n⚠️ Note: 'amz-ads' is not in your PATH.",
|
|
24
|
+
file=sys.stderr,
|
|
25
|
+
)
|
|
26
|
+
print(
|
|
27
|
+
" You can still use: python3 -m amazon_ads_cli",
|
|
28
|
+
file=sys.stderr,
|
|
29
|
+
)
|
|
30
|
+
print(
|
|
31
|
+
" To add to PATH, add this to your shell config:",
|
|
32
|
+
file=sys.stderr,
|
|
33
|
+
)
|
|
34
|
+
print(
|
|
35
|
+
f' export PATH="{sys.prefix}/bin:$PATH"',
|
|
36
|
+
file=sys.stderr,
|
|
37
|
+
)
|
|
38
|
+
print("", file=sys.stderr)
|
|
39
|
+
|
|
11
40
|
|
|
12
41
|
@click.group()
|
|
13
42
|
@click.option("--profile", "-p", default="default", help="Credential profile")
|
|
14
43
|
@click.pass_context
|
|
15
44
|
def cli(ctx, profile):
|
|
16
45
|
"""Amazon Ads CLI - Manage campaigns, keywords, and reports."""
|
|
46
|
+
_check_path()
|
|
17
47
|
ctx.ensure_object(dict)
|
|
18
48
|
ctx.obj["profile"] = profile
|
|
19
49
|
|
|
20
50
|
|
|
51
|
+
@cli.group()
|
|
52
|
+
def auth():
|
|
53
|
+
"""Authentication commands."""
|
|
54
|
+
pass
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@auth.command("setup")
|
|
58
|
+
@click.option(
|
|
59
|
+
"--path", default=DEFAULT_CREDENTIALS_PATH, help="Path to save credentials"
|
|
60
|
+
)
|
|
61
|
+
@click.pass_context
|
|
62
|
+
def auth_setup(ctx, path):
|
|
63
|
+
"""Interactive setup for Amazon Ads API credentials."""
|
|
64
|
+
click.echo("🔐 Amazon Ads API Credential Setup")
|
|
65
|
+
click.echo("=" * 50)
|
|
66
|
+
click.echo()
|
|
67
|
+
click.echo("You'll need the following from your Amazon Developer account:")
|
|
68
|
+
click.echo(" 1. Refresh Token (from LWA authorization)")
|
|
69
|
+
click.echo(" 2. Client ID (from your app registration)")
|
|
70
|
+
click.echo(" 3. Client Secret (from your app registration)")
|
|
71
|
+
click.echo(" 4. Profile ID (your Amazon Ads account ID)")
|
|
72
|
+
click.echo()
|
|
73
|
+
|
|
74
|
+
profile = click.prompt("Profile name", default="default")
|
|
75
|
+
refresh_token = click.prompt("Refresh token", hide_input=True)
|
|
76
|
+
client_id = click.prompt("Client ID")
|
|
77
|
+
client_secret = click.prompt("Client secret", hide_input=True)
|
|
78
|
+
profile_id = click.prompt("Profile ID (numeric)")
|
|
79
|
+
|
|
80
|
+
credentials = {
|
|
81
|
+
"version": "1.0",
|
|
82
|
+
profile: {
|
|
83
|
+
"refresh_token": refresh_token,
|
|
84
|
+
"client_id": client_id,
|
|
85
|
+
"client_secret": client_secret,
|
|
86
|
+
"profile_id": profile_id,
|
|
87
|
+
},
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
# Merge with existing if present
|
|
91
|
+
if os.path.exists(path):
|
|
92
|
+
try:
|
|
93
|
+
with open(path, "r") as f:
|
|
94
|
+
existing = yaml.safe_load(f) or {}
|
|
95
|
+
existing[profile] = credentials[profile]
|
|
96
|
+
credentials = existing
|
|
97
|
+
click.echo(f"\n📝 Merged with existing credentials at {path}")
|
|
98
|
+
except Exception as e:
|
|
99
|
+
click.echo(f"⚠️ Could not read existing file: {e}")
|
|
100
|
+
|
|
101
|
+
os.makedirs(os.path.dirname(path), exist_ok=True)
|
|
102
|
+
with open(path, "w") as f:
|
|
103
|
+
yaml.dump(credentials, f, default_flow_style=False, sort_keys=False)
|
|
104
|
+
|
|
105
|
+
click.echo(f"✅ Credentials saved to {path}")
|
|
106
|
+
click.echo(f" Profile: {profile}")
|
|
107
|
+
click.echo(f" Profile ID: {profile_id}")
|
|
108
|
+
click.echo()
|
|
109
|
+
click.echo(
|
|
110
|
+
"You can now use: python -m amazon_ads_cli.main --profile {profile} campaigns list"
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
@auth.command("show")
|
|
115
|
+
@click.option(
|
|
116
|
+
"--path", default=DEFAULT_CREDENTIALS_PATH, help="Path to credentials file"
|
|
117
|
+
)
|
|
118
|
+
@click.pass_context
|
|
119
|
+
def auth_show(ctx, path):
|
|
120
|
+
"""Show configured profiles (without secrets)."""
|
|
121
|
+
if not os.path.exists(path):
|
|
122
|
+
click.echo(f"❌ No credentials file found at {path}")
|
|
123
|
+
click.echo("Run: python -m amazon_ads_cli.main auth setup")
|
|
124
|
+
return
|
|
125
|
+
|
|
126
|
+
with open(path, "r") as f:
|
|
127
|
+
creds = yaml.safe_load(f) or {}
|
|
128
|
+
|
|
129
|
+
click.echo(f"\n📄 Credentials file: {path}")
|
|
130
|
+
click.echo("-" * 40)
|
|
131
|
+
|
|
132
|
+
for profile, data in creds.items():
|
|
133
|
+
if profile == "version":
|
|
134
|
+
continue
|
|
135
|
+
click.echo(f"Profile: {profile}")
|
|
136
|
+
click.echo(f" Client ID: {data.get('client_id', 'N/A')[:20]}...")
|
|
137
|
+
click.echo(f" Profile ID: {data.get('profile_id', 'N/A')}")
|
|
138
|
+
click.echo()
|
|
139
|
+
|
|
140
|
+
|
|
21
141
|
@cli.group()
|
|
22
142
|
def campaigns():
|
|
23
143
|
"""Campaign management commands."""
|
|
@@ -181,7 +301,12 @@ def list_negatives(ctx, campaign_id):
|
|
|
181
301
|
"""List negative keywords for a campaign."""
|
|
182
302
|
result = sponsored_products.NegativeKeywordsV3(
|
|
183
303
|
marketplace=Marketplaces.NA
|
|
184
|
-
).list_negative_keywords(
|
|
304
|
+
).list_negative_keywords(
|
|
305
|
+
body={
|
|
306
|
+
"campaignIdFilter": {"include": [campaign_id]},
|
|
307
|
+
"stateFilter": {"include": ["ENABLED"]},
|
|
308
|
+
}
|
|
309
|
+
)
|
|
185
310
|
negatives = result.payload.get("negativeKeywords", [])
|
|
186
311
|
|
|
187
312
|
click.echo(f"\n{'Negative Keyword':<35} {'Match':<15}")
|
|
@@ -194,6 +319,7 @@ def list_negatives(ctx, campaign_id):
|
|
|
194
319
|
|
|
195
320
|
@negatives.command("add")
|
|
196
321
|
@click.argument("campaign-id")
|
|
322
|
+
@click.argument("ad-group-id")
|
|
197
323
|
@click.argument("keyword-text")
|
|
198
324
|
@click.option(
|
|
199
325
|
"--match-type",
|
|
@@ -201,16 +327,17 @@ def list_negatives(ctx, campaign_id):
|
|
|
201
327
|
help="Match type: NEGATIVE_EXACT, NEGATIVE_PHRASE",
|
|
202
328
|
)
|
|
203
329
|
@click.pass_context
|
|
204
|
-
def add_negative(ctx, campaign_id, keyword_text, match_type):
|
|
330
|
+
def add_negative(ctx, campaign_id, ad_group_id, keyword_text, match_type):
|
|
205
331
|
"""Add a negative keyword to a campaign."""
|
|
206
332
|
try:
|
|
207
333
|
sponsored_products.NegativeKeywordsV3(
|
|
208
334
|
marketplace=Marketplaces.NA
|
|
209
|
-
).
|
|
335
|
+
).create_negative_keyword(
|
|
210
336
|
body={
|
|
211
337
|
"negativeKeywords": [
|
|
212
338
|
{
|
|
213
339
|
"campaignId": campaign_id,
|
|
340
|
+
"adGroupId": ad_group_id,
|
|
214
341
|
"keywordText": keyword_text,
|
|
215
342
|
"matchType": match_type,
|
|
216
343
|
"state": "ENABLED",
|
|
@@ -223,6 +350,22 @@ def add_negative(ctx, campaign_id, keyword_text, match_type):
|
|
|
223
350
|
click.echo(f"❌ Error: {e}")
|
|
224
351
|
|
|
225
352
|
|
|
353
|
+
@negatives.command("remove")
|
|
354
|
+
@click.argument("negative-keyword-id")
|
|
355
|
+
@click.pass_context
|
|
356
|
+
def remove_negative(ctx, negative_keyword_id):
|
|
357
|
+
"""Remove a negative keyword by ID."""
|
|
358
|
+
try:
|
|
359
|
+
sponsored_products.NegativeKeywordsV3(
|
|
360
|
+
marketplace=Marketplaces.NA
|
|
361
|
+
).delete_negative_keywords(
|
|
362
|
+
body={"negativeKeywordIdFilter": {"include": [negative_keyword_id]}}
|
|
363
|
+
)
|
|
364
|
+
click.echo(f"✅ Removed negative keyword: {negative_keyword_id}")
|
|
365
|
+
except Exception as e:
|
|
366
|
+
click.echo(f"❌ Error: {e}")
|
|
367
|
+
|
|
368
|
+
|
|
226
369
|
@cli.group()
|
|
227
370
|
def report():
|
|
228
371
|
"""Report commands."""
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: amazon-ads-cli
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.3
|
|
4
4
|
Summary: CLI tool for Amazon Advertising API v3
|
|
5
5
|
Home-page: https://github.com/stellaraether/amazon-ads-cli
|
|
6
6
|
Author: Lunan Li
|
|
@@ -14,6 +14,7 @@ Classifier: Programming Language :: Python :: 3.9
|
|
|
14
14
|
Classifier: Programming Language :: Python :: 3.10
|
|
15
15
|
Classifier: Programming Language :: Python :: 3.11
|
|
16
16
|
Requires-Python: >=3.8
|
|
17
|
+
License-File: LICENSE
|
|
17
18
|
Requires-Dist: click>=8.0
|
|
18
19
|
Requires-Dist: python-amazon-ad-api>=0.8.0
|
|
19
20
|
Requires-Dist: requests>=2.27.0
|
|
@@ -21,6 +22,7 @@ Dynamic: author
|
|
|
21
22
|
Dynamic: author-email
|
|
22
23
|
Dynamic: classifier
|
|
23
24
|
Dynamic: home-page
|
|
25
|
+
Dynamic: license-file
|
|
24
26
|
Dynamic: requires-dist
|
|
25
27
|
Dynamic: requires-python
|
|
26
28
|
Dynamic: summary
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|