nexscope-cli 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. nexscope_cli/__init__.py +4 -0
  2. nexscope_cli/api-contract.json +37 -0
  3. nexscope_cli/capabilities/__init__.py +3 -0
  4. nexscope_cli/capabilities/catalog.py +73 -0
  5. nexscope_cli/capabilities/mock.py +53 -0
  6. nexscope_cli/capabilities/plan.py +142 -0
  7. nexscope_cli/catalog.json +69 -0
  8. nexscope_cli/compat/__init__.py +4 -0
  9. nexscope_cli/compat/legacy.py +143 -0
  10. nexscope_cli/core/__init__.py +3 -0
  11. nexscope_cli/core/auth.py +81 -0
  12. nexscope_cli/core/datasets.py +305 -0
  13. nexscope_cli/core/errors.py +11 -0
  14. nexscope_cli/core/evidence.py +134 -0
  15. nexscope_cli/core/execution.py +171 -0
  16. nexscope_cli/core/remote.py +22 -0
  17. nexscope_cli/core/transport.py +61 -0
  18. nexscope_cli/dependencies.lock +282 -0
  19. nexscope_cli/dependency-lock.json +371 -0
  20. nexscope_cli/doctor.py +136 -0
  21. nexscope_cli/main.py +313 -0
  22. nexscope_cli/reports/__init__.py +3 -0
  23. nexscope_cli/reports/compare.py +135 -0
  24. nexscope_cli/reports/formats.py +99 -0
  25. nexscope_cli/resources/__init__.py +3 -0
  26. nexscope_cli/resources/fields.json +33 -0
  27. nexscope_cli/resources/mock-products.json +9 -0
  28. nexscope_cli/resources/products-sample.csv +5 -0
  29. nexscope_cli/resources/products-sample.json +6 -0
  30. nexscope_cli/resources/products-template.csv +1 -0
  31. nexscope_cli/resources/products.schema.json +182 -0
  32. nexscope_cli/resources/report.html +15 -0
  33. nexscope_cli/runner.py +43 -0
  34. nexscope_cli/skills/amazon-product-detail/SKILL.md +170 -0
  35. nexscope_cli/skills/amazon-product-detail/references/api.md +239 -0
  36. nexscope_cli/skills/amazon-product-detail/requirements.txt +1 -0
  37. nexscope_cli/skills/amazon-product-detail/scripts/amazon_product_detail.py +26 -0
  38. nexscope_cli/skills/amazon-search/SKILL.md +197 -0
  39. nexscope_cli/skills/amazon-search/references/api.md +189 -0
  40. nexscope_cli/skills/amazon-search/requirements.txt +1 -0
  41. nexscope_cli/skills/amazon-search/scripts/amazon_search.py +26 -0
  42. nexscope_cli/workflows/__init__.py +3 -0
  43. nexscope_cli/workflows/compare.py +213 -0
  44. nexscope_cli-0.1.0.dist-info/METADATA +257 -0
  45. nexscope_cli-0.1.0.dist-info/RECORD +74 -0
  46. nexscope_cli-0.1.0.dist-info/WHEEL +5 -0
  47. nexscope_cli-0.1.0.dist-info/entry_points.txt +2 -0
  48. nexscope_cli-0.1.0.dist-info/licenses/ASSET-NOTICES.md +9 -0
  49. nexscope_cli-0.1.0.dist-info/licenses/LICENSE +202 -0
  50. nexscope_cli-0.1.0.dist-info/licenses/NOTICE +20 -0
  51. nexscope_cli-0.1.0.dist-info/licenses/THIRD-PARTY-NOTICES.md +27 -0
  52. nexscope_cli-0.1.0.dist-info/licenses/third-party-notices/backports-tarfile/LICENSE +17 -0
  53. nexscope_cli-0.1.0.dist-info/licenses/third-party-notices/certifi/LICENSE +20 -0
  54. nexscope_cli-0.1.0.dist-info/licenses/third-party-notices/cffi/LICENSE +23 -0
  55. nexscope_cli-0.1.0.dist-info/licenses/third-party-notices/charset-normalizer/LICENSE +21 -0
  56. nexscope_cli-0.1.0.dist-info/licenses/third-party-notices/cryptography/LICENSE +3 -0
  57. nexscope_cli-0.1.0.dist-info/licenses/third-party-notices/cryptography/LICENSE.APACHE +202 -0
  58. nexscope_cli-0.1.0.dist-info/licenses/third-party-notices/cryptography/LICENSE.BSD +27 -0
  59. nexscope_cli-0.1.0.dist-info/licenses/third-party-notices/idna/LICENSE.md +31 -0
  60. nexscope_cli-0.1.0.dist-info/licenses/third-party-notices/importlib-metadata/LICENSE +73 -0
  61. nexscope_cli-0.1.0.dist-info/licenses/third-party-notices/jaraco-classes/LICENSE +17 -0
  62. nexscope_cli-0.1.0.dist-info/licenses/third-party-notices/jaraco-context/LICENSE +18 -0
  63. nexscope_cli-0.1.0.dist-info/licenses/third-party-notices/jaraco-functools/LICENSE +18 -0
  64. nexscope_cli-0.1.0.dist-info/licenses/third-party-notices/jeepney/LICENSE +21 -0
  65. nexscope_cli-0.1.0.dist-info/licenses/third-party-notices/keyring/LICENSE +18 -0
  66. nexscope_cli-0.1.0.dist-info/licenses/third-party-notices/more-itertools/LICENSE +19 -0
  67. nexscope_cli-0.1.0.dist-info/licenses/third-party-notices/pycparser/LICENSE +27 -0
  68. nexscope_cli-0.1.0.dist-info/licenses/third-party-notices/pywin32-ctypes/LICENSE.txt +28 -0
  69. nexscope_cli-0.1.0.dist-info/licenses/third-party-notices/requests/LICENSE +175 -0
  70. nexscope_cli-0.1.0.dist-info/licenses/third-party-notices/requests/NOTICE +2 -0
  71. nexscope_cli-0.1.0.dist-info/licenses/third-party-notices/secretstorage/LICENSE +25 -0
  72. nexscope_cli-0.1.0.dist-info/licenses/third-party-notices/urllib3/LICENSE.txt +21 -0
  73. nexscope_cli-0.1.0.dist-info/licenses/third-party-notices/zipp/LICENSE +18 -0
  74. nexscope_cli-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,213 @@
1
+ # Copyright 2026 Nexscope
2
+ # SPDX-License-Identifier: Apache-2.0
3
+ """A single task entry: plan, optional search, details, evidence and report."""
4
+ from datetime import datetime, timezone
5
+ import hashlib
6
+ import json
7
+
8
+ from ..capabilities.catalog import DOMAINS
9
+ from ..capabilities.plan import plan
10
+ from ..core.datasets import product
11
+ from ..core.evidence import Artifacts, decode, read_bundle, validate_request
12
+ from ..core.errors import Failure
13
+ from ..reports.compare import render_format
14
+ from ..reports.formats import FORMATS
15
+
16
+
17
+ def read_candidates(folder):
18
+ bundle = read_bundle(folder)
19
+ if set(bundle) != {'plan.json', 'request-1.json', 'candidates.json', 'result.json'}:
20
+ raise Failure('Resume requires a completed candidates-only output; failed calls are never replayed.')
21
+ previous = decode(bundle['plan.json'])
22
+ result = decode(bundle['result.json'])
23
+ search = validate_request(decode(bundle['request-1.json']))
24
+ if (not isinstance(previous, dict) or previous.get('schemaVersion') != 'nexscope.comparison-plan.v1'
25
+ or previous.get('status') != 'PLANNED' or type(previous.get('maxCalls')) is not int
26
+ or previous.get('marketplace') not in DOMAINS
27
+ or previous.get('authMode') not in ('browser-account', 'env-api-key')
28
+ or previous.get('asins') is not None or previous.get('selectedAsins') is not None
29
+ or previous.get('importedSearch') is not None or not previous.get('keyword')
30
+ or not isinstance(result, dict) or result.get('schemaVersion') != 'nexscope.comparison-result.v1'
31
+ or result.get('status') != 'CANDIDATES_READY' or result.get('error') != 'SELECTION_REQUIRED'
32
+ or result.get('sourceType') not in ('mock', 'remote')):
33
+ raise Failure('Resume requires a versioned CANDIDATES_READY comparison checkpoint.')
34
+ rebuilt = workflow_plan(keyword=previous['keyword'], market=previous.get('marketplace'),
35
+ max_calls=previous.get('maxCalls'), auth_mode=previous.get('authMode'))
36
+ execution = previous.get('execution')
37
+ if (not isinstance(execution, dict) or execution.get('sourceType') != result['sourceType']
38
+ or execution.get('origin') != search['origin'] or execution.get('requestsSent') != 0
39
+ or previous.get('maximumRequests') != 1 or search['capability'] != 'amazon-search'
40
+ or search['status'] != 'SUCCESS' or search['cacheHit'] or search['sequence'] != 1
41
+ or search['authMode'] != rebuilt['authMode'] or search['params'] != rebuilt['steps'][0]['requests'][0]['body']
42
+ or not isinstance(previous.get('steps'), list) or len(previous['steps']) != 1
43
+ or not isinstance(previous['steps'][0], dict)
44
+ or previous['steps'][0].get('requests') != rebuilt['steps'][0]['requests']
45
+ or previous['steps'][0].get('capability') != 'amazon-search'
46
+ or previous['steps'][0].get('contractVersion') != search['contractVersion']
47
+ or previous['steps'][0].get('source') != search['source']
48
+ or not isinstance(previous['steps'][0].get('auth'), dict)
49
+ or previous['steps'][0]['auth'].get('mode') != search['authMode']
50
+ or previous['steps'][0]['auth'].get('origin') != search['origin']
51
+ or (result['sourceType'] == 'mock' and search['origin'] != 'https://mock.invalid')):
52
+ raise Failure('Checkpoint plan and search evidence disagree.')
53
+ candidates = search['response']['data']['products']
54
+ budget = result.get('budget')
55
+ if (decode(bundle['candidates.json']) != candidates or result.get('candidates') != candidates
56
+ or not isinstance(budget, dict) or type(budget.get('requestsSent')) is not int or budget['requestsSent'] != 1
57
+ or budget.get('maxCalls') != rebuilt['maxCalls'] or budget.get('cacheHits') != 0
58
+ or budget.get('stoppedAfterFailure') is not False):
59
+ raise Failure('Checkpoint candidates or accounting disagree with search evidence.')
60
+ return previous, result, search, bundle
61
+
62
+
63
+ def workflow_plan(*, asins=None, keyword=None, resume=None, selected=None, market=None, max_calls=None, auth_mode=None, api_base=None):
64
+ if sum(bool(value) for value in (asins, keyword, resume)) != 1:
65
+ raise Failure('Provide exactly one of asins, keyword or resume.')
66
+ history = None
67
+ if resume:
68
+ previous, result, search, bundle = read_candidates(resume)
69
+ if ((market is not None and market != previous['marketplace'])
70
+ or (auth_mode is not None and auth_mode != previous['authMode'])
71
+ or (api_base is not None and api_base.rstrip('/') != search['origin'])):
72
+ raise Failure('Resume must keep the original marketplace, authentication mode and origin.')
73
+ market, auth_mode = previous['marketplace'], previous['authMode']
74
+ if auth_mode == 'browser-account':
75
+ api_base = search['origin']
76
+ max_calls = previous['maxCalls'] if max_calls is None else max_calls
77
+ if type(max_calls) is not int or max_calls > previous['maxCalls']:
78
+ raise Failure('Resume cannot increase the original max-calls budget.')
79
+ if not selected:
80
+ raise Failure('Resume requires select-asins from the saved candidates.')
81
+ if not set(selected.split(',')) <= {row['asin'] for row in search['response']['data']['products']}:
82
+ raise Failure('Selected ASINs must come from the saved search evidence.')
83
+ history = {'historical': True, 'sourceType': result['sourceType'], 'authMode': search['authMode'],
84
+ 'origin': search['origin'], 'fetchedAt': search['fetchedAt'], 'cacheHit': False,
85
+ 'evidenceFile': 'request-1.json', 'planFile': 'search-plan.json',
86
+ 'planSha256': hashlib.sha256(bundle['plan.json']).hexdigest(), 'requestsSent': 1}
87
+ market = market if market is not None else 'amazon.com'
88
+ auth_mode = auth_mode if auth_mode is not None else 'browser-account'
89
+ max_calls = max_calls if max_calls is not None else 4
90
+ if market not in DOMAINS:
91
+ raise Failure('Choose a supported Amazon marketplace.')
92
+ if selected and not (keyword or resume):
93
+ raise Failure('select-asins is only valid with keyword search.')
94
+ if asins or selected:
95
+ detail = plan('amazon-product-detail', {'asins': asins or selected, 'amazonDomain': market}, auth_mode=auth_mode, max_calls=max_calls, api_base=api_base)
96
+ if len(detail['requests']) < 2:
97
+ raise Failure('A comparison requires two or three distinct ASINs.')
98
+ else:
99
+ detail = None
100
+ search = plan('amazon-search', {'keyword': keyword, 'amazonDomain': market}, auth_mode=auth_mode, max_calls=max_calls, api_base=api_base) if keyword else None
101
+ maximum = (len(detail['requests']) if detail else 0) + (1 if search or history else 0)
102
+ if maximum > max_calls:
103
+ raise Failure('Search plus details exceeds max-calls; no requests were sent.')
104
+ return {'schemaVersion': 'nexscope.comparison-plan.v1', 'status': 'PLANNED',
105
+ 'authMode': auth_mode, 'marketplace': market, 'asins': asins, 'keyword': keyword,
106
+ 'selectedAsins': selected, 'maximumRequests': maximum, 'maxCalls': max_calls,
107
+ 'estimatedCredits': None, 'billingCap': None, 'requestsSent': 1 if history else 0,
108
+ 'newRequests': maximum - (1 if history else 0), 'importedSearch': history,
109
+ 'selectionPolicy': 'explicit-ASINs-only; otherwise return candidates',
110
+ 'steps': [item for item in (search, detail) if item], 'liveExecutionAvailable': False}
111
+
112
+
113
+ def compare(executor, folder, *, asins=None, keyword=None, resume=None, selected=None, market=None, mock=False, format='html', allow_paid=False):
114
+ if not mock and not allow_paid:
115
+ raise Failure('LIVE_EXECUTION_UNAVAILABLE: remote execution requires explicit --allow-paid authorization.')
116
+ source_type = 'mock' if mock else 'remote'
117
+ if format not in (*FORMATS, 'all'):
118
+ raise Failure('Choose html, csv, markdown or all.')
119
+ execution_plan = workflow_plan(asins=asins, keyword=keyword, resume=resume, selected=selected, market=market,
120
+ max_calls=executor.max_calls, auth_mode=executor.context.mode,
121
+ api_base=executor.context.base if executor.context.mode == 'browser-account' else None)
122
+ market = execution_plan['marketplace']
123
+ if executor.sent or executor.results or executor.imported_results:
124
+ raise Failure('A comparison requires a fresh executor and its own request budget.')
125
+ imported = execution_plan['importedSearch']
126
+ if imported and (imported['sourceType'] != source_type or imported['origin'] != executor.context.base):
127
+ raise Failure('Resume must keep the original source type and authentication origin.')
128
+ execution_plan['execution'] = {'sourceType': source_type, 'origin': executor.context.base, 'requestsSent': 0}
129
+ execution_plan['liveExecutionAvailable'] = allow_paid and not mock
130
+ execution_plan['reportFormat'] = format
131
+ for step in execution_plan['steps']:
132
+ step['auth'].update(origin=executor.context.base, originSource='fictional mock transport' if mock else 'selected authentication context')
133
+ artifacts = Artifacts(folder, execution_plan)
134
+ if resume:
135
+ _, _, historical_search, bundle = read_candidates(resume)
136
+ artifacts.write_bytes('search-plan.json', bundle['plan.json'])
137
+ artifacts.write_bytes('search-result.json', bundle['result.json'])
138
+ for name in ('request-1.json', 'candidates.json'):
139
+ artifacts.write_bytes(name, bundle[name])
140
+ executor.imported_results = [historical_search]
141
+ executor.record = artifacts.record
142
+ chosen = asins or selected
143
+ if keyword:
144
+ search = executor.execute('amazon-search', {'keyword': keyword, 'amazonDomain': market})[0]
145
+ candidates = search['response']['data']['products'] if search['status'] == 'SUCCESS' else []
146
+ artifacts.write('candidates.json', candidates)
147
+ available = {row['asin'] for row in candidates}
148
+ if search['status'] != 'SUCCESS' or not chosen or not set(chosen.split(',')) <= available:
149
+ status = 'CANDIDATES_READY' if search['status'] == 'SUCCESS' and not chosen else 'FAILED'
150
+ result = {'schemaVersion': 'nexscope.comparison-result.v1', 'status': status, 'sourceType': source_type, 'report': None,
151
+ 'error': 'SELECTION_REQUIRED' if status == 'CANDIDATES_READY' else 'SEARCH_OR_SELECTION_FAILED',
152
+ 'candidates': candidates, 'output': str(artifacts.root), 'budget': executor.summary()}
153
+ artifacts.write('result.json', dict(result, output='.'))
154
+ artifacts.finish()
155
+ return result
156
+ details = executor.execute('amazon-product-detail', {'asins': chosen, 'amazonDomain': market})
157
+ rows = [product(result, market) for result in details if result['status'] == 'SUCCESS']
158
+ for row in rows:
159
+ evidence_file = artifacts.root / row['extra']['evidenceFile']
160
+ row['extra']['evidenceSha256'] = hashlib.sha256(evidence_file.read_bytes()).hexdigest()
161
+ errors = [{'asin': result['params']['asins'], 'status': result['status'], 'error': result['error']}
162
+ for result in details if result['status'] != 'SUCCESS']
163
+ status = ('COMPLETE' if not errors else 'PARTIAL') if len(rows) >= 2 else 'INSUFFICIENT_DATA'
164
+ digest = hashlib.sha256((artifacts.root / 'plan.json').read_bytes()).hexdigest()
165
+ dataset = {'schemaVersion': 1, 'sourceType': source_type, 'inputName': 'plan.json', 'inputSha256': digest,
166
+ 'importedAt': datetime.now(timezone.utc).isoformat(), 'products': rows,
167
+ 'collectionStatus': status, 'errors': errors, 'budget': executor.summary()}
168
+ artifacts.write('dataset.json', dataset)
169
+ result = {'schemaVersion': 'nexscope.comparison-result.v1', 'status': status, 'sourceType': source_type, 'requested': len(chosen.split(',')),
170
+ 'succeeded': len(rows), 'errors': errors, 'budget': executor.summary(),
171
+ 'output': str(artifacts.root), 'report': None, 'reports': {},
172
+ 'liveRequests': 0 if mock else executor.summary()['requestsSent'],
173
+ 'liveRequestsThisRun': 0 if mock else executor.sent}
174
+ if len(rows) >= 2:
175
+ # Report beside its evidence; all links stay relative when sharing folder.
176
+ for selected_format in FORMATS if format == 'all' else (format,):
177
+ report = artifacts.root / ('comparison' + FORMATS[selected_format])
178
+ with report.open('x', encoding='utf-8', newline='') as stream:
179
+ stream.write(render_format(dataset, '.', selected_format))
180
+ result['reports'][selected_format] = str(report)
181
+ result['report'] = next(iter(result['reports'].values()))
182
+ result['reportFormat'] = format
183
+ artifacts.write('result.json', dict(result, output='.',
184
+ report=next((('comparison' + FORMATS[name]) for name in result['reports']), None),
185
+ reports={name: 'comparison' + FORMATS[name] for name in result['reports']}))
186
+ artifacts.finish()
187
+ return result
188
+
189
+
190
+ def command(args):
191
+ if args.mock and args.api_base is not None:
192
+ raise Failure('Mock workflow uses a fictional origin only; omit --api-base.')
193
+ execution_plan = workflow_plan(asins=args.asins, keyword=args.keyword, resume=args.resume, selected=args.select_asins,
194
+ market=args.market, max_calls=args.max_calls, auth_mode=args.auth_mode, api_base=args.api_base)
195
+ args.market, args.max_calls, args.auth_mode = execution_plan['marketplace'], execution_plan['maxCalls'], execution_plan['authMode']
196
+ imported = execution_plan['importedSearch']
197
+ if imported and imported['sourceType'] != ('mock' if args.mock else 'remote'):
198
+ raise Failure('Resume must keep the original source type; choose the same mock or remote execution mode.')
199
+ if args.dry_run:
200
+ execution_plan['reportFormat'] = args.format
201
+ return execution_plan
202
+ if not args.mock and not args.allow_paid:
203
+ raise Failure('LIVE_EXECUTION_UNAVAILABLE: --execute requires --mock or explicit --allow-paid.')
204
+ if args.mock and args.allow_paid:
205
+ raise Failure('Choose mock execution or paid execution, not both.')
206
+ if not args.out:
207
+ raise Failure('Choose a new output directory with --out.')
208
+ if imported and args.auth_mode == 'browser-account':
209
+ args.api_base = imported['origin']
210
+ from ..core.remote import engine_for
211
+ engine = engine_for(args)
212
+ return compare(engine, args.out, asins=args.asins, keyword=args.keyword, resume=args.resume,
213
+ selected=args.select_asins, market=args.market, mock=bool(args.mock), format=args.format, allow_paid=args.allow_paid)
@@ -0,0 +1,257 @@
1
+ Metadata-Version: 2.4
2
+ Name: nexscope-cli
3
+ Version: 0.1.0
4
+ Summary: Offline product comparison reports and bounded Nexscope ecommerce data workflows
5
+ Author: Nexscope
6
+ License-Expression: Apache-2.0
7
+ Project-URL: Repository, https://github.com/nexscope-ai/nexscope-cli
8
+ Project-URL: Issues, https://github.com/nexscope-ai/nexscope-cli/issues
9
+ Requires-Python: >=3.11
10
+ Description-Content-Type: text/markdown
11
+ License-File: LICENSE
12
+ License-File: NOTICE
13
+ License-File: ASSET-NOTICES.md
14
+ License-File: THIRD-PARTY-NOTICES.md
15
+ License-File: third-party-notices/backports-tarfile/LICENSE
16
+ License-File: third-party-notices/certifi/LICENSE
17
+ License-File: third-party-notices/cffi/LICENSE
18
+ License-File: third-party-notices/charset-normalizer/LICENSE
19
+ License-File: third-party-notices/cryptography/LICENSE
20
+ License-File: third-party-notices/cryptography/LICENSE.APACHE
21
+ License-File: third-party-notices/cryptography/LICENSE.BSD
22
+ License-File: third-party-notices/idna/LICENSE.md
23
+ License-File: third-party-notices/importlib-metadata/LICENSE
24
+ License-File: third-party-notices/jaraco-classes/LICENSE
25
+ License-File: third-party-notices/jaraco-context/LICENSE
26
+ License-File: third-party-notices/jaraco-functools/LICENSE
27
+ License-File: third-party-notices/jeepney/LICENSE
28
+ License-File: third-party-notices/keyring/LICENSE
29
+ License-File: third-party-notices/more-itertools/LICENSE
30
+ License-File: third-party-notices/pycparser/LICENSE
31
+ License-File: third-party-notices/pywin32-ctypes/LICENSE.txt
32
+ License-File: third-party-notices/requests/LICENSE
33
+ License-File: third-party-notices/requests/NOTICE
34
+ License-File: third-party-notices/secretstorage/LICENSE
35
+ License-File: third-party-notices/urllib3/LICENSE.txt
36
+ License-File: third-party-notices/zipp/LICENSE
37
+ Requires-Dist: keyring<26,>=25
38
+ Requires-Dist: requests<3,>=2.32
39
+ Dynamic: license-file
40
+
41
+ # Nexscope CLI
42
+
43
+ Create product comparison reports from your own CSV or JSON, entirely offline.
44
+ No account, API key, keyring, LLM or upload is needed. HTML, CSV and Markdown
45
+ reports include the exact input, validated rows and evidence hashes.
46
+
47
+ This is the independent CLI repository. Its first version is **0.1.0**.
48
+ Verified behavior and remaining acceptance limits are recorded in ACCEPTANCE.md.
49
+ The published Codex plugin and independent Skills are separate distributions.
50
+
51
+ ## Install and try your own data
52
+
53
+ Python 3.11 or newer is required. Build the wheel from this repository, or obtain
54
+ the exact reviewed wheel and its SHA-256 from a release of this repository.
55
+ Do not install an unverified package just because it has the same name.
56
+
57
+ From source on macOS or Linux:
58
+
59
+ ```sh
60
+ git clone https://github.com/nexscope-ai/nexscope-cli.git
61
+ cd nexscope-cli
62
+ python3 -m venv .venv
63
+ . .venv/bin/activate
64
+ python -m pip wheel --no-deps --wheel-dir dist .
65
+ python -m pip install --no-index --no-deps dist/nexscope_cli-0.1.0-py3-none-any.whl
66
+ ```
67
+
68
+ On Windows PowerShell, create the environment with `py -3 -m venv .venv` and use
69
+ `.venv\Scripts\python.exe` for the Python commands; activation is optional.
70
+ There is no need to change PowerShell's execution policy.
71
+
72
+ If you already have the reviewed wheel, install it with:
73
+
74
+ ```sh
75
+ python -m pip install --no-index --no-deps /absolute/path/nexscope_cli-0.1.0-py3-none-any.whl
76
+ nexscope doctor --offline
77
+ nexscope report template --format csv --out products.csv
78
+ nexscope report describe --schema
79
+ nexscope report demo --format json --out sample.json
80
+ nexscope report compare --input sample.json --format html --out comparison.html
81
+ nexscope report compare --input sample.json --format csv --out comparison.csv
82
+ nexscope report compare --input sample.json --format markdown --out comparison.md
83
+ ```
84
+
85
+ Use fresh output names. Reports never overwrite files or silently fall back to
86
+ another directory. Keep each report with its sibling `.evidence` directory.
87
+ The samples are fictional. `nexscope demo product-comparison --out demo-output`
88
+ also exercises the complete three-product workflow without credentials or network.
89
+
90
+ `product_id` and `title` are required. A price requires an explicit currency;
91
+ a rating requires its explicit positive scale. Missing values stay unknown.
92
+ Identifiers must be strings and unique within a marketplace. Additional input
93
+ columns are retained as metadata. There is no inferred sales, profit, currency
94
+ conversion or ranking across different markets, currencies or rating scales.
95
+ Remote currency symbols such as `$` are ambiguous. The CLI keeps the product and
96
+ records `unresolvedCurrency`/`unresolvedPrice` alongside its raw evidence, while
97
+ leaving comparable currency and price unknown. It never infers USD from a symbol
98
+ or marketplace. Known fictional search candidates retain their fields in mock
99
+ details. Report dry-runs validate parent directories and existing outputs before
100
+ export. Successful live keyword search and continuation remain unaccepted; see
101
+ ACCEPTANCE.md for the exact results.
102
+
103
+ Raw UTF-8 CSV/JSON inputs are limited to 10 MiB and 10,000 products. Local imports
104
+ have no three-product limit. CSV errors use logical record numbers including the
105
+ header; JSON errors use one-based array positions. Invalid rows are explained,
106
+ never silently dropped. Versioned datasets allow 32 MiB because normalization
107
+ adds metadata; evidence bundles allow 64 MiB. See `report describe` for fields.
108
+
109
+ ## Product comparison workflows
110
+
111
+ Remote planning is free of network and credential access:
112
+
113
+ ```sh
114
+ nexscope workflow compare --asins B000000001,B000000002,B000000003 --dry-run
115
+ nexscope workflow compare --keyword lamp --dry-run
116
+ ```
117
+
118
+ The bounded workflow accepts two or three distinct ASINs, or one search page
119
+ followed by explicit selection of two or three candidates. Use `--mock complete`
120
+ for fictional execution. `--mock partial` demonstrates partial completion.
121
+
122
+ ```sh
123
+ nexscope workflow compare --keyword lamp --execute --mock complete --max-calls 4 --out candidates
124
+ nexscope workflow compare --resume candidates --select-asins B000000001,B000000002 --dry-run --mock complete
125
+ nexscope workflow compare --resume candidates --select-asins B000000001,B000000002 --execute --mock complete --format all --out finished
126
+ nexscope report compare --input finished/dataset.json --format markdown --out another.md
127
+ ```
128
+
129
+ `--resume` imports the original search evidence, sends only the selected detail
130
+ requests and carries forward the original budget. One search plus two details
131
+ is three total requests. The original marketplace, source type, auth mode and
132
+ origin must agree; a resumed budget cannot increase. Omitted marketplace, auth
133
+ mode and max-calls inherit the checkpoint. `--keyword --select-asins` retains its
134
+ existing meaning of a new search plus details; use `--resume` after selection
135
+ to avoid paying for another search.
136
+
137
+ Only completed `CANDIDATES_READY` outputs with a valid manifest may be resumed.
138
+ Failed/incomplete outputs are never automatically replayed. A deliberate new
139
+ command using the same candidates starts another branch of work; there is no
140
+ server-side spending lock. `max-calls` is a request limit, not a monetary cap.
141
+ Ambiguous failures consume a call. No automatic retries, pagination or ASIN
142
+ expansion occur. Exit code 1 marks failures, partial or insufficient results;
143
+ inspect saved results and evidence before deciding what to do next.
144
+
145
+ Historical search imports are explicit user-selected evidence, not cache hits,
146
+ fresh live queries, proof of account identity or server-signed records. They are
147
+ never inserted into a credential context's cache. New requests use only the
148
+ currently selected credentials. Each finished directory contains the original
149
+ search plan/response, current plan, all details and hashes, with relative links.
150
+
151
+ `report compare` can read this version's `dataset.json` without refreshing data.
152
+ It checks the complete bundle, input digest, request contracts, product mappings,
153
+ selection and accounting, preserving `sourceType`, retrieval times, errors and
154
+ the original `inputName/inputSha256`. `report-input.json` separately identifies
155
+ the exact input selected for re-export. These hashes detect inconsistent edits
156
+ or corruption; they do not authenticate an author who can rewrite the whole
157
+ bundle. Do not treat third-party bundles as verified live market data.
158
+ Older preview datasets without manifests must be regenerated; this version does
159
+ not silently relabel them as user imports.
160
+
161
+ ## Connect
162
+
163
+ Online browser/plugin use needs the existing hash-pinned dependency profile:
164
+ Set `APPROVED_API_ORIGIN` to the approved service origin. For paid data, set
165
+ `ASIN_1` and `ASIN_2` to verified real identifiers and explicitly approve the
166
+ request budget. The fictional identifiers used by mock examples are not live
167
+ test inputs; empty responses can still incur charges.
168
+
169
+ ```sh
170
+ python -m pip install --require-hashes -r /absolute/checkout/cli/nexscope_cli/dependencies.lock
171
+ nexscope --api-base "$APPROVED_API_ORIGIN" auth login --source-client python-cli --source-channel github
172
+ # Complete the returned connection URL in your browser, then:
173
+ nexscope --api-base "$APPROVED_API_ORIGIN" auth poll
174
+ nexscope --api-base "$APPROVED_API_ORIGIN" account
175
+ nexscope --api-base "$APPROVED_API_ORIGIN" workflow compare --asins "$ASIN_1,$ASIN_2" --max-calls 2 --execute --allow-paid --out live-comparison
176
+ ```
177
+
178
+ Browser credentials use the OS credential store. Supported backends are Windows
179
+ Credential Manager, macOS Keychain, Secret Service and KWallet. The CLI does not
180
+ read an environment key in `browser-account` mode or fall back when login fails.
181
+ `--api-base` is a global option before the command when a different approved
182
+ browser API origin is needed. Authenticated redirects are refused.
183
+
184
+ Independent Skills and explicit `--auth-mode env-api-key` use only
185
+ `NEXSCOPE_API_KEY` and `NEXSCOPE_PROXY_BASE`, privately configured in your shell.
186
+ They require no browser login or keyring. Do not put keys into command history,
187
+ documents or Git. Omit `--api-base` in this mode. Credential failure never
188
+ switches modes. Cache entries are valid-success-only, memory-only, expire after
189
+ 300 seconds and are isolated by immutable auth context, origin, capability,
190
+ parameters, marketplace and contract. Separate processes never share a cache.
191
+
192
+ `get-skills`, browser-account `run ... -- <old script arguments>` and `export`
193
+ remain available. The bundled standalone scripts require exactly this wheel
194
+ version and preserve their JSON argument, `--inline`, `--no-cache`, original
195
+ environment configuration and legacy response/exit semantics. They forward a
196
+ legacy batch/page once without the bounded workflow's three-ASIN/one-page limits.
197
+ Legacy JSON business errors can still exit zero: inspect the response code/error.
198
+ Do not replace published Skills with these adapters before installation and live
199
+ compatibility acceptance. No dependencies are auto-installed at query time.
200
+
201
+ Only Amazon search/detail are adapted. Credits are unknown (`null`); response
202
+ `cost` is not treated as a credit charge. Real account, balance and billing
203
+ acceptance remain separate gates. Reports contain no telemetry or automatic
204
+ upload. Review product data before sharing a report or its evidence.
205
+
206
+ ## Development and verification
207
+
208
+ ```sh
209
+ python -m pip install --no-deps .
210
+ python -m unittest discover -s tests -v
211
+ python scripts/check_resources.py
212
+ python -m pip wheel --no-deps --wheel-dir dist .
213
+ python scripts/verify_wheel.py dist/nexscope_cli-0.1.0-py3-none-any.whl
214
+ ```
215
+
216
+ The standard-library test suite runs with no service credentials. The wheel
217
+ verification creates a fresh environment outside the checkout, clears source
218
+ path injection, installs the actual wheel without dependencies, blocks network
219
+ and keyring for offline checks, then runs the installed test suite and CLI cases.
220
+ It also tests old adapters against a fictional loopback server. Loopback fixtures
221
+ do not constitute live Nexscope acceptance. CI runs the same checks on Windows,
222
+ macOS and Linux with Python 3.11 and 3.12; a configured matrix is not a passing run.
223
+
224
+ Use a separate environment for upgrades and rollback. Install the prior exact
225
+ verified wheel, and restore any paired Skill directory to its matching version.
226
+ The verification script accepts `--previous-wheel /path/to/prior.whl` to exercise
227
+ upgrade/rollback while checking that user reports and test credential sentinels
228
+ survive. No public package, tag, release or plugin upload is made by these scripts.
229
+
230
+ On a machine with a real supported OS credential store, add `--with-os-keyring`
231
+ alongside `--with-dependencies` to test an isolated fictional credential. The
232
+ verifier checks write/read, upgrade/rollback and uninstall persistence, then
233
+ deletes only its own uniquely named entry. It does not log in to a service or
234
+ modify an existing account credential. A missing desktop credential store is
235
+ reported as a prerequisite failure; it never falls back to plaintext storage.
236
+
237
+ For an Ubuntu host without `ensurepip`, use `--pip-wheel /path/to/reviewed/pip.whl`
238
+ to bootstrap pip inside the fresh venv from an explicit trusted wheel, such as
239
+ Python's bundled ensurepip wheel. This does not install into the system Python.
240
+ Windows and Ubuntu WSL2 installation acceptance has been run locally; WSL2 does
241
+ not certify macOS or a Linux desktop credential store.
242
+
243
+ When intentionally changing a bundled adapter, run
244
+ `python scripts/check_resources.py --update` and review the catalog diff. This
245
+ updates current-file hashes only; historical source snapshot hashes are retained.
246
+ Keep CLI behavior here and have consumers pin its version; do not maintain a
247
+ second CLI copy in the published plugin.
248
+
249
+ See [ACCEPTANCE.md](ACCEPTANCE.md) for verified scope and outstanding release gates,
250
+ and [the macOS handoff](docs/HANDOFF-MACOS.md) for the remaining acceptance work.
251
+ Report reproducible problems, without keys or private reports, in
252
+ [GitHub Issues](https://github.com/nexscope-ai/nexscope-cli/issues). Nexscope is the
253
+ maintainer; named pilot/release ownership still needs assignment before release.
254
+
255
+ Apache-2.0. Initial source: plugin commit
256
+ `814f28016af6b3ff57ef12093a6bb133c4fb20b9`. Original Skill provenance remains in
257
+ the catalog. See `NOTICE`, `ASSET-NOTICES.md` and `THIRD-PARTY-NOTICES.md`.
@@ -0,0 +1,74 @@
1
+ nexscope_cli/__init__.py,sha256=MA6r0Ji2BlUrNJZytOG-Qrf1Ks8M7GIRKUQQwvPQqB4,134
2
+ nexscope_cli/api-contract.json,sha256=DqG4ePGE4rHAF1n8F57dgws6kRC1MdkmQT5oGyAOOEw,1366
3
+ nexscope_cli/catalog.json,sha256=szJeqEps5D1vNIZ5W0pzRBXw78HHwfl_vYiwGao-zxk,3782
4
+ nexscope_cli/dependencies.lock,sha256=Vig5T24dYtoSD89m-AraThIQn0W2gOXWHp0faUlsk_s,23092
5
+ nexscope_cli/dependency-lock.json,sha256=8fvy3PWAjaG9RCa1muu6n2g0_MHKS3Wfe7PELaZp7Gk,22633
6
+ nexscope_cli/doctor.py,sha256=fk3U3KcDjhKuiy88OegBpcM8jJq40ly_1CrKa5NahCM,7885
7
+ nexscope_cli/main.py,sha256=ek6hx-JG8eNOCs-Hec0vaNENaweFrmsysBvOjux5vRc,18549
8
+ nexscope_cli/runner.py,sha256=K3XczQQtUFE_t7JaNrzsnMnQD44S6ktrvdVmL-eYKgg,1712
9
+ nexscope_cli/capabilities/__init__.py,sha256=yC8NklpF_pD51UvpgZXe9V6FAdQ_l7kEdjljgwMlGfQ,147
10
+ nexscope_cli/capabilities/catalog.py,sha256=ColpJeAEFTlmkH5OowOZNSKa83tYPWvl2W95NM3zaS0,4181
11
+ nexscope_cli/capabilities/mock.py,sha256=9GRWHL-dxw3TF5V6GqV4V1RHuBtqTVX60zaP2T1gitE,2486
12
+ nexscope_cli/capabilities/plan.py,sha256=uXPx7Q7KSa-ERYA_mZ2wVS1biRM2SbZn3r4EaV2SxNg,7104
13
+ nexscope_cli/compat/__init__.py,sha256=pZzFzapYd9ZWJYP05f-36a5xrI7YHl1jc0Cyfwy7tHI,156
14
+ nexscope_cli/compat/legacy.py,sha256=WFAiutykjH4MmnzQIb_R2XWkPMOVr4UyZ0WyJaBkYow,6988
15
+ nexscope_cli/core/__init__.py,sha256=G10sB-SuPgPDM-eKwACSR8rRUlRBui-YtDJDtTZ3kHo,114
16
+ nexscope_cli/core/auth.py,sha256=0_wqGcj0CVS5iEnHYV6goJ78W2c9IM1rfx65mUR7alc,3633
17
+ nexscope_cli/core/datasets.py,sha256=4DpQAmkBfOIsWFM2n-CzvK59myi9KTAhIBFiGb2g8P4,18254
18
+ nexscope_cli/core/errors.py,sha256=9x6q2djbY5vD9wyly6Xlj0f0GeCpBCoSp6Q6ZuaNH5E,196
19
+ nexscope_cli/core/evidence.py,sha256=0ZXqJK4btTfrZGLKWydZPKnoenojCYb3FJGUzDtoaxs,5899
20
+ nexscope_cli/core/execution.py,sha256=8gkrxrBTYz3-vV-TIMunnzny_EuMIXZws-B3dbqMZEc,8674
21
+ nexscope_cli/core/remote.py,sha256=_cgdF42GtEQOa1MsLkZxLAY4l37eufUnIUIAjNyjCxo,915
22
+ nexscope_cli/core/transport.py,sha256=EB676UQiQZXu0ar4dnfgDkWVkpwDuosvfQPLqty_geE,2420
23
+ nexscope_cli/reports/__init__.py,sha256=yPZe1M_GvAljCuk7nhnS8c0n1wt53ZJkKNYkBpH-my4,139
24
+ nexscope_cli/reports/compare.py,sha256=vg6KI6bUybgS5ZafLU0X8GCDrCRyIACWwbUl49w5gOw,7195
25
+ nexscope_cli/reports/formats.py,sha256=OQxn7qYOg6T7o0UNF09IfqmDCi7e2N2IYi9_JuTPnBk,5108
26
+ nexscope_cli/resources/__init__.py,sha256=LCYY0xAIWGldghAQQVbtBNTn6OAeHDkyKIR_gHCGk2c,136
27
+ nexscope_cli/resources/fields.json,sha256=VcGNy3fzeGDdKNiDd30qYI8yX_jJNRwc2ykCaytA4Sc,2243
28
+ nexscope_cli/resources/mock-products.json,sha256=0A7bvwm4galLD-giB3m1UtbP9UMb-ugNEXxuCoWBEaU,658
29
+ nexscope_cli/resources/products-sample.csv,sha256=CSNfqSboxonl55xf6qDPOMgABfkyCo5R8k651v_7J_8,511
30
+ nexscope_cli/resources/products-sample.json,sha256=Z2Pp83wA7gFS4lWZGygz2Gyvr4ckDMcOhTnoCVsHH5c,858
31
+ nexscope_cli/resources/products-template.csv,sha256=gBnfKUox-H_2TtRk6sa6lBtZUVvcIWBl8x95o956PDc,100
32
+ nexscope_cli/resources/products.schema.json,sha256=ST7pYiqAX6Nl7B-IPxIO5aXuA6lWkjK-0_FfSwehZU0,4011
33
+ nexscope_cli/resources/report.html,sha256=xrl8ydpn11P3xWd1dUAkUm8OIieGAcEmNVLIF6MZcsk,1710
34
+ nexscope_cli/skills/amazon-product-detail/SKILL.md,sha256=kRmC04bf0sY5NvbjB_Dfxp4Uif8k00IxDL1GABRtF_8,12167
35
+ nexscope_cli/skills/amazon-product-detail/requirements.txt,sha256=8skrREsVeyayEmb00-9vXjJJdxNGmZp9jt0GS9IVw-k,20
36
+ nexscope_cli/skills/amazon-product-detail/references/api.md,sha256=KtU314YeZQoewZV5HnqkiGFBMuEwVHPFOvDHpj9NDVk,10929
37
+ nexscope_cli/skills/amazon-product-detail/scripts/amazon_product_detail.py,sha256=z36uqUhlpufJ3HJ_5eT4AArJGmfIpOTtvmGKyEst5uk,1028
38
+ nexscope_cli/skills/amazon-search/SKILL.md,sha256=kEErz2BwoGmVk7AoUmQh05Z1jx8HBlyJfgNUEXX-cQw,11985
39
+ nexscope_cli/skills/amazon-search/requirements.txt,sha256=8skrREsVeyayEmb00-9vXjJJdxNGmZp9jt0GS9IVw-k,20
40
+ nexscope_cli/skills/amazon-search/references/api.md,sha256=rC9nZN2vvUqw4Zy188_jeDjr12h5xgStYsQKTc1ud-o,8074
41
+ nexscope_cli/skills/amazon-search/scripts/amazon_search.py,sha256=Vp738AVF37pSu11_EjnAjOiqnsMAOuNh3qPD1YW8PC0,1020
42
+ nexscope_cli/workflows/__init__.py,sha256=YeGkzL96b4uUwhBzBHTcN54zdlOQH-YKam00xtCFL1c,136
43
+ nexscope_cli/workflows/compare.py,sha256=Tg2NFZVqBiBufIqZ-hivfND32PA0t5f0CXDL82MkIzs,14835
44
+ nexscope_cli-0.1.0.dist-info/licenses/ASSET-NOTICES.md,sha256=tL-BhhfpHa8I6eIXYaPG4QS2SjJtsjHuptTWl0aBY0Y,449
45
+ nexscope_cli-0.1.0.dist-info/licenses/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
46
+ nexscope_cli-0.1.0.dist-info/licenses/NOTICE,sha256=yBCNot83LMzDRtVSqDU0AoTHf8JummN91R3yAmo8lZ4,1053
47
+ nexscope_cli-0.1.0.dist-info/licenses/THIRD-PARTY-NOTICES.md,sha256=xQyGc1Qotvlhsv3guhu_Pcu25OemcQLJ7czVBUqVYGY,2496
48
+ nexscope_cli-0.1.0.dist-info/licenses/third-party-notices/backports-tarfile/LICENSE,sha256=htoPAa6uRjSKPD1GUZXcHOzN55956HdppkuNoEsqR0E,1023
49
+ nexscope_cli-0.1.0.dist-info/licenses/third-party-notices/certifi/LICENSE,sha256=6TcW2mucDVpKHfYP5pWzcPBpVgPSH2-D8FPkLPwQyvc,989
50
+ nexscope_cli-0.1.0.dist-info/licenses/third-party-notices/cffi/LICENSE,sha256=W6JN3FcGf5JJrdZEw6_EGl1tw34jQz73Wdld83Cwr2M,1123
51
+ nexscope_cli-0.1.0.dist-info/licenses/third-party-notices/charset-normalizer/LICENSE,sha256=bQ1Bv-FwrGx9wkjJpj4lTQ-0WmDVCoJX0K-SxuJJuIc,1071
52
+ nexscope_cli-0.1.0.dist-info/licenses/third-party-notices/cryptography/LICENSE,sha256=Pgx8CRqUi4JTO6mP18u0BDLW8amsv4X1ki0vmak65rs,197
53
+ nexscope_cli-0.1.0.dist-info/licenses/third-party-notices/cryptography/LICENSE.APACHE,sha256=qsc7MUj20dcRHbyjIJn2jSbGRMaBOuHk8F9leaomY_4,11360
54
+ nexscope_cli-0.1.0.dist-info/licenses/third-party-notices/cryptography/LICENSE.BSD,sha256=YCxMdILeZHndLpeTzaJ15eY9dz2s0eymiSMqtwCPtPs,1532
55
+ nexscope_cli-0.1.0.dist-info/licenses/third-party-notices/idna/LICENSE.md,sha256=GppPDj1HmickDd1ZqRN6ZqtKD539yMphiMwL_YUYfwQ,1541
56
+ nexscope_cli-0.1.0.dist-info/licenses/third-party-notices/importlib-metadata/LICENSE,sha256=4ve7BVfno4FJnxW6p8HXdtg9ZhZZkXnXHTEJuOOfeyU,10278
57
+ nexscope_cli-0.1.0.dist-info/licenses/third-party-notices/jaraco-classes/LICENSE,sha256=htoPAa6uRjSKPD1GUZXcHOzN55956HdppkuNoEsqR0E,1023
58
+ nexscope_cli-0.1.0.dist-info/licenses/third-party-notices/jaraco-context/LICENSE,sha256=l1WhhRlmbl8PTK49qtPXASvK5IpgCzEjfXXp_hNOZoM,1076
59
+ nexscope_cli-0.1.0.dist-info/licenses/third-party-notices/jaraco-functools/LICENSE,sha256=l1WhhRlmbl8PTK49qtPXASvK5IpgCzEjfXXp_hNOZoM,1076
60
+ nexscope_cli-0.1.0.dist-info/licenses/third-party-notices/jeepney/LICENSE,sha256=GyKwSbUmfW38I6Z79KhNjsBLn9-xpR02DkK0NCyLQVQ,1081
61
+ nexscope_cli-0.1.0.dist-info/licenses/third-party-notices/keyring/LICENSE,sha256=WlfLTbheKi3YjCkGKJCK3VfjRRRJ4KmnH9-zh3b9dZ0,1076
62
+ nexscope_cli-0.1.0.dist-info/licenses/third-party-notices/more-itertools/LICENSE,sha256=CfHIyelBrz5YTVlkHqm4fYPAyw_QB-te85Gn4mQ8GkY,1053
63
+ nexscope_cli-0.1.0.dist-info/licenses/third-party-notices/pycparser/LICENSE,sha256=DIRjmTaep23de1xE_m0WSXQV_PAV9cu1CMJL-YuBxbE,1543
64
+ nexscope_cli-0.1.0.dist-info/licenses/third-party-notices/pywin32-ctypes/LICENSE.txt,sha256=36g7PicJrfzbg42a1VgjymdKu3gOYFY9ndlUTMv3hek,1644
65
+ nexscope_cli-0.1.0.dist-info/licenses/third-party-notices/requests/LICENSE,sha256=CeipvOyAZxBGUsFoaFqwkx54aPnIKEtm9a5u2uXxEws,10142
66
+ nexscope_cli-0.1.0.dist-info/licenses/third-party-notices/requests/NOTICE,sha256=9REJct7a0rTp0xRRja87fXLW4C5Jms2AIYHeb3RXHcw,38
67
+ nexscope_cli-0.1.0.dist-info/licenses/third-party-notices/secretstorage/LICENSE,sha256=5wA1dZK7k3BMsEDOSmZjqFKScWpAQbaiZ_WY8hZYRm8,1504
68
+ nexscope_cli-0.1.0.dist-info/licenses/third-party-notices/urllib3/LICENSE.txt,sha256=Ew46ZNX91dCWp1JpRjSn2d8oRGnehuVzIQAmgEHj1oY,1093
69
+ nexscope_cli-0.1.0.dist-info/licenses/third-party-notices/zipp/LICENSE,sha256=l1WhhRlmbl8PTK49qtPXASvK5IpgCzEjfXXp_hNOZoM,1076
70
+ nexscope_cli-0.1.0.dist-info/METADATA,sha256=L-4MVoenH7TzK4Q7Son7iGay7-LRjfgkTC8ltmfP7Ig,14309
71
+ nexscope_cli-0.1.0.dist-info/WHEEL,sha256=mOdXR9C3wMVov7cSY8PY-XCzo3SmmWCHvGQr5csJoao,91
72
+ nexscope_cli-0.1.0.dist-info/entry_points.txt,sha256=izkHi1XuzJJ-NnMB-21B4fT7aP7Q6W-qS8dhYW4FUDU,52
73
+ nexscope_cli-0.1.0.dist-info/top_level.txt,sha256=nayB2rke499IWWFGp9mH-63cKyCrz5VG_bAOslgOsUE,13
74
+ nexscope_cli-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (84.2.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ nexscope = nexscope_cli.main:main
@@ -0,0 +1,9 @@
1
+ # Bundled asset attribution
2
+
3
+ Copyright 2026 Nexscope.
4
+
5
+ This independent CLI contains report templates and fictional product samples.
6
+ Templates use system fonts, with no external font, JavaScript or image assets.
7
+ The original plugin icon and plugin entrypoint are not part of this distribution.
8
+ Apache-2.0 Section 6 governs trademark use; the copyright license does not grant
9
+ trademark rights or imply Nexscope endorsement of modified distributions.