@nm-lang/cli 0.5.0 → 0.7.0

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.
package/dist/version.d.ts CHANGED
@@ -29,7 +29,7 @@ export type NMLanguageReleaseChange = {
29
29
  };
30
30
  export declare const nmVersionManifest: {
31
31
  readonly languageVersion: "0.2.0";
32
- readonly packageVersion: "0.5.0";
32
+ readonly packageVersion: "0.7.0";
33
33
  readonly irVersion: "0.1";
34
34
  readonly qasm3SidecarVersion: "0.3";
35
35
  readonly learningPackageVersion: "0.1";
package/dist/version.js CHANGED
@@ -2,7 +2,7 @@ export const NM_LANGUAGE_PREVIEW_READINESS_FORMAT = "nm-language-preview-readine
2
2
  export const NM_LANGUAGE_PREVIEW_READINESS_VERSION = "0.1";
3
3
  export const nmVersionManifest = {
4
4
  languageVersion: "0.2.0",
5
- packageVersion: "0.5.0",
5
+ packageVersion: "0.7.0",
6
6
  irVersion: "0.1",
7
7
  qasm3SidecarVersion: "0.3",
8
8
  learningPackageVersion: "0.1",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nm-lang/cli",
3
- "version": "0.5.0",
3
+ "version": "0.7.0",
4
4
  "private": false,
5
5
  "description": "Command-line tools for formatting, linting, testing, workspace snapshots, execution, and exports for N/M quantum programs.",
6
6
  "repository": {
@@ -36,6 +36,10 @@
36
36
  ".": {
37
37
  "types": "./dist/index.d.ts",
38
38
  "import": "./dist/index.js"
39
+ },
40
+ "./project-create": {
41
+ "types": "./bin/project-create.d.mts",
42
+ "import": "./bin/project-create.mjs"
39
43
  }
40
44
  },
41
45
  "bin": {
@@ -49,7 +53,7 @@
49
53
  "NOTICE.txt"
50
54
  ],
51
55
  "dependencies": {
52
- "@nm-lang/core": "0.5.0",
56
+ "@nm-lang/core": "0.7.0",
53
57
  "jiti": "^2.7.0"
54
58
  },
55
59
  "scripts": {
@@ -0,0 +1,57 @@
1
+ QuantumSoftware N/M npm Packages — Free Use License
2
+ Version 1.0, 5 October 2026
3
+
4
+ Copyright (c) 2026 QuantumSoftware.tech contributors. All rights reserved.
5
+
6
+ 1. Scope
7
+ This license covers QuantumSoftware-owned code and assets in the official
8
+ @nm-lang/core, @nm-lang/cli, @nm-lang/language-server and @nm-lang/mcp npm
9
+ packages. It does not license the rest of the repository or the separately
10
+ distributed VS Code extension. Third-party components remain subject to their
11
+ respective licenses; their notices and permissions take precedence for those
12
+ components.
13
+
14
+ 2. Free use
15
+ You may download, install and use these official packages without charge for
16
+ personal, educational and commercial purposes, import their APIs into your own
17
+ programs, and make backup copies for your own use. QuantumSoftware authorizes
18
+ distribution of its official packages through the nm-lang organization on the
19
+ npm registry, including the registry's normal delivery and package-manager
20
+ installation and caching mechanisms.
21
+
22
+ 3. Reserved rights
23
+ This is not an open-source license. Except for the permissions above, rights
24
+ to QuantumSoftware-owned code and assets are reserved. Modification,
25
+ redistribution, sublicensing, sale or publication of that code or those assets
26
+ requires separate permission from the rights holders, unless applicable law
27
+ provides otherwise. Do not remove copyright or license notices. These
28
+ restrictions do not override rights granted by any third-party license.
29
+
30
+ 4. Your programs
31
+ This license imposes no restrictions on N/M programs you write, their source
32
+ code, or simulation results produced using these packages. It does not
33
+ transfer ownership of your work to QuantumSoftware. This clause does not grant
34
+ additional redistribution rights to QuantumSoftware-owned runtime code bundled
35
+ with your application; those rights are governed by sections 2 and 3.
36
+
37
+ 5. No warranty
38
+ THE PACKAGES ARE PROVIDED "AS IS", WITHOUT WARRANTIES OF ANY KIND, TO THE
39
+ EXTENT PERMITTED BY APPLICABLE LAW. TO THAT SAME EXTENT, THE RIGHTS HOLDERS
40
+ ARE NOT LIABLE FOR DAMAGES ARISING FROM USE OF THE PACKAGES. RIGHTS THAT
41
+ CANNOT BE EXCLUDED UNDER APPLICABLE LAW ARE NOT AFFECTED.
42
+
43
+ 6. Permission requests
44
+ For additional permissions, contact the publisher through:
45
+ https://quantumsoftware.tech/en/#contact
46
+
47
+ Türkçe açıklama
48
+ Resmî dört npm paketi kişisel, eğitim ve ticari amaçlarla ücretsiz kurulup
49
+ kullanılabilir; API'leri kendi programlarınızdan çağırabilir ve kendi kullanımınız
50
+ için yedek kopya alabilirsiniz. Bu bir açık kaynak lisansı değildir.
51
+ QuantumSoftware'a ait kod ve varlıkları değiştirmek veya yeniden dağıtmak için
52
+ ayrıca izin gerekir. Paketleri indirilebilir kılmak, kaynak kodu üzerinde açık
53
+ kaynak hakları vermez. Üçüncü taraf lisanslarının ve yürürlükteki hukukun verdiği
54
+ haklar korunur. Yazdığınız N/M programları ve ürettiğiniz sonuçların mülkiyeti
55
+ size aittir. Uygulamanızla birlikte dağıtacağınız QuantumSoftware çalışma zamanı
56
+ dosyaları için yukarıdaki izin ve kısıtlamalar geçerlidir. Yukarıdaki İngilizce
57
+ metin lisansın koşullarını belirler.
@@ -0,0 +1,11 @@
1
+ QuantumSoftware N/M package notices
2
+
3
+ Copyright (c) 2026 QuantumSoftware.tech contributors. All rights reserved.
4
+ QuantumSoftware-owned code is licensed under LICENSE.txt.
5
+
6
+ Runtime dependencies listed in package.json are installed separately by npm.
7
+ Their own licenses and notices remain applicable. The QuantumSoftware license
8
+ does not restrict the rights granted for those third-party components.
9
+
10
+ jiti 2.x is a separately installed runtime dependency licensed under MIT.
11
+ Its original LICENSE is included in the jiti package.
package/python/README.md CHANGED
@@ -1,69 +1,174 @@
1
- # N/M Python/Jupyter bridge — CLI 0.5.0 / CLI köprüsü
1
+ # N/M Python/Jupyter adapter / adaptörü
2
2
 
3
- Included in @nm-lang/cli 0.5.0; not a separate PyPI distribution.
4
- @nm-lang/cli 0.5.0 içinde bulunur; ayrı bir PyPI dağıtımı değildir.
5
- Python 3.10+ syntax; standard library only. Jupyter is installed separately.
6
- Add this directory to `sys.path`, then `from nm_bridge import NMClient, NMResult`.
7
- The default client runs the adjacent `../bin/nm.mjs` with Node directly.
3
+ This source tree adds a **local development wheel**, `nm-jupyter-adapter==0.0.0.dev0`,
4
+ over the stdlib bridge included in `@nm-lang/cli`. This is not a PyPI release or
5
+ an npm/VSIX version change. Install Node and a trusted N/M CLI separately; the
6
+ wheel contains only the adapter, optional extension and usage/license metadata.
7
+ Source-code rights remain reserved under the unchanged LICENSE.txt and NOTICE.txt.
8
+
9
+ Bu kaynak ağacı, `@nm-lang/cli` içindeki standart kütüphane köprüsünü
10
+ `nm-jupyter-adapter==0.0.0.dev0` **yerel geliştirme wheel'i** olarak kurmayı sağlar.
11
+ PyPI yayını veya npm/VSIX sürüm değişikliği değildir. Node ve güvenilir N/M CLI
12
+ ayrıca kurulur. Wheel yalnız adaptörü, isteğe bağlı uzantıyı ve kullanım/lisans
13
+ metaverisini içerir. LICENSE.txt ve NOTICE.txt değişmeden korunur; kaynak hakları saklıdır.
14
+
15
+ ## Installation / Kurulum
16
+
17
+ From the repository root, create disposable environments; nothing is installed
18
+ into global Python. / Depo kökünde geçici ortamlar oluşturun; global Python'a
19
+ kurulum yapılmaz.
20
+
21
+ ```powershell
22
+ py -m venv .qa/python-adapter/tools
23
+ .qa/python-adapter/tools/Scripts/python.exe -m pip install -r tests/python/requirements-adapter-build.txt
24
+ .qa/python-adapter/tools/Scripts/python.exe -B scripts/build-nm-python-adapter.py
25
+ py -m venv .qa/python-adapter/notebook
26
+ .qa/python-adapter/notebook/Scripts/python.exe -m pip install --no-index --no-deps .qa/python-adapter/dist/nm_jupyter_adapter-0.0.0.dev0-py3-none-any.whl
27
+ # Optional IPython/Jupyter; Python 3.11+ / İsteğe bağlı IPython/Jupyter; Python 3.11+
28
+ .qa/python-adapter/notebook/Scripts/python.exe -m pip install -r tests/python/requirements-notebook.txt
29
+ ```
30
+
31
+ On Unix, use `python3` and `bin/python` instead of `py` and `Scripts/python.exe`.
32
+ The stdlib adapter and legacy imports support Python 3.10+. The pinned optional
33
+ IPython 9.17.1 notebook environment requires Python 3.11+; installing the
34
+ `notebook` extra on Python 3.10 does not install IPython. No global kernel is registered.
35
+ Unix'te `python3` ve `bin/python` kullanın. Standart kütüphane adaptörü Python
36
+ 3.10+ destekler; sabitlenmiş IPython 9.17.1 notebook ortamı Python 3.11+ gerektirir.
37
+ Python 3.10'da `notebook` ek bağımlılığı IPython kurmaz. Global kernel kaydı yapılmaz.
38
+
39
+ ## Explicit CLI selection / Açık CLI seçimi
40
+
41
+ ```python
42
+ from pathlib import Path
43
+ from nm_bridge import NMClient, NMResult # Legacy import stays valid / Eski import korunur.
44
+
45
+ # Select an existing trusted CLI script; replace this example absolute path.
46
+ # Mevcut güvenilir CLI dosyanızı seçin; örnek mutlak yolu değiştirin.
47
+ client = NMClient(cli_path=Path("C:/trusted/nm-cli/bin/nm.mjs"), locale="tr",
48
+ experimental=("hamiltonianEvolution", "measurementGrouping"), timeout=30)
49
+ record = client.train(source, options={"steps": 2, "targetAccuracy": {"absoluteError": 0.5}})
50
+ record # Escaped Jupyter table / Kaçışlanmış Jupyter tablosu
51
+ restored = NMResult.from_json(record.to_json()) # Data only / Yalnız veri
52
+ ```
53
+
54
+ Standalone wheel clients require `cli_path=` or the existing trusted `command=`
55
+ argument sequence. `cli_path` must resolve to an existing absolute file and is
56
+ mutually exclusive with `command`. No PATH/cwd search for the N/M CLI occurs.
57
+ Inside the original npm `python/` layout, `NMClient()` still selects the adjacent
58
+ `../bin/nm.mjs`; adding that `python/` directory to `sys.path` remains supported.
59
+ `from nm_jupyter import NMClient, NMResult` exposes the same classes.
60
+ The npm default verifies the physical module origin, the parent `package.json`
61
+ identity (`@nm-lang/cli` and its `nm` entry), and local Python distribution
62
+ metadata. Wheel/RECORD ownership or incomplete/ambiguous markers require explicit
63
+ CLI selection even when the wheel target directory happens to be named `python`.
64
+
65
+ Bağımsız wheel, açık `cli_path=` veya mevcut güvenilir `command=` dizisi gerektirir.
66
+ `cli_path`, mevcut bir mutlak dosyaya çözülmelidir ve `command` ile birlikte
67
+ verilemez. N/M CLI için PATH/çalışma dizini araması yapılmaz. npm paketinin özgün
68
+ `python/` diziliminde `NMClient()` komşu `../bin/nm.mjs` dosyasını kullanmaya devam
69
+ eder; bu dizini `sys.path` içine eklemek desteklenir. `nm_jupyter` aynı sınıfları dışa aktarır.
70
+ npm varsayılanı gerçek modül konumunu, üst `package.json` kimliğini ve yerel Python
71
+ dağıtım metaverisini doğrular. Wheel/RECORD sahipliği veya eksik/belirsiz işaretler,
72
+ kurulum hedefinin adı `python` olsa bile açık CLI seçimi gerektirir.
8
73
 
9
74
  Default Node lookup accepts only absolute PATH directories outside the working
10
- directory and resolves an absolute executable path. Empty/relative entries and
11
- cwd aliases are skipped on every supported Python version. Missing Node raises
12
- `NM-PY-011`; no bare-name fallback is used. Explicit `command=` is trusted input
13
- and bypasses this lookup unchanged. Trust the selected executable and absolute
14
- PATH directories; records never grant permissions or execute themselves.
75
+ directory and resolves an absolute executable. Empty/relative entries and cwd
76
+ aliases are skipped on every supported Python version. Missing Node raises
77
+ `NM-PY-011`; no bare-name fallback is used. Missing/invalid CLI selection raises
78
+ `NM-PY-012`. Explicit `command=` remains trusted caller input, unchanged and passed
79
+ with `shell=False`; trust its executable and arguments yourself.
80
+
15
81
  Varsayılan Node araması yalnız çalışma dizini dışındaki mutlak PATH dizinlerini
16
- kullanır; boş/göreli girdiler ve çalışma dizinine çözülen yollar atlanır. Node
17
- bulunamazsa `NM-PY-011` verilir. Açık `command=` güvenilir girdidir ve değiştirilmez;
18
- seçilen programın güvenilirliği çağırana aittir. Kayıt okumak izin vermez veya çalıştırmaz.
19
-
20
- Pipe I/O and EOF waiting share the bounded deadline, including custom launchers
21
- whose descendants retain output handles. No I/O threads survive a call. Windows
22
- Python 3.10/3.11 uses a stdlib Win32 compatibility path; only the direct process
23
- is terminated, so this is not a process-tree sandbox.
24
- Giriş/çıkış ve EOF aynı süre sınırına bağlıdır; çağrı sonrası I/O iş parçacığı
25
- kalmaz. Özel başlatıcının alt süreci boruyu açık tutarsa eksik sonuç kabul edilmez.
26
- Windows Python 3.10/3.11 desteklenir; yalnız doğrudan başlatılan süreç sonlandırılır.
82
+ kullanır. Boş/göreli girdiler ve çalışma dizinine çözülen yollar atlanır. Node
83
+ bulunamazsa `NM-PY-011`, CLI seçimi eksik/geçersizse `NM-PY-012` verilir.
84
+ Yalın program adı fallback'i yoktur. Açık `command=` değiştirilmeden `shell=False`
85
+ ile kullanılır; program ve argümanların güvenilirliğini çağıran sağlar.
86
+
87
+ ## Optional explicit cells / İsteğe bağlı açık hücreler
27
88
 
28
89
  ```python
29
- client = NMClient(experimental=("hamiltonianEvolution", "measurementGrouping"), locale="tr")
30
- record = client.train(source, options={"steps": 2, "targetAccuracy": {"absoluteError": 0.5}})
31
- record # Jupyter table / Jupyter tablosu
32
- data = record.to_json() # Complete original source + options + result
33
- restored = NMResult.from_json(data) # Data only; no execution or permission grant
90
+ %load_ext nm_jupyter
91
+ ```
92
+
93
+ ```text
94
+ %%nm --client client --check
95
+ module notebook_check;
96
+ fn main(){let q=qreg[1];H(q[0]);return q;}
97
+ ```
98
+
99
+ Use `--run` for simulation or `--train` for training; exactly one mode and an
100
+ existing Python variable naming an explicitly configured `NMClient` are required
101
+ on every cell. Names are direct identifier lookups, not Python expressions.
102
+ Import/load/save/display never invokes a client. Execution occurs only when you
103
+ explicitly run a cell or call the Python API. `%unload_ext nm_jupyter` removes the magic.
104
+
105
+ Simülasyon için `--run`, eğitim için `--train` seçin. Her hücre tam bir mod ve
106
+ açıkça yapılandırılmış bir `NMClient` Python değişkeni gerektirir. Adlar doğrudan
107
+ aranır; Python ifadesi değildir. Import/uzantı yükleme/kaydetme/gösterim CLI çağırmaz.
108
+ Yürütme yalnız hücreyi veya Python API'yi açıkça çalıştırınca olur.
109
+
110
+ ```text
111
+ %%nm --client client --train --steps 2 --learning-rate 0.17 --optimizer adam --objective minimize --objective-target energy --gradient-method parameter-shift --measurement-allocation adaptive --pilot-shots 8 --absolute-error 1e-9
112
+ module notebook_training;
113
+ @seed(27);
114
+ @shots_budget(4096);
115
+ param theta:Angle=0;
116
+ fn main(){
117
+ let q=qreg[1];
118
+ observable Hamiltonian=theta*X(q[0])+0.25*theta*I;
119
+ evolve Hamiltonian for 0.4 steps 1 order 1 on q;
120
+ let energy=expect Y(q[0])+0.5*Z(q[0]);
121
+ return q;
122
+ }
34
123
  ```
35
124
 
36
- Incoming JSON and compact saved records are limited to 4 MiB. Compact saving preserves
37
- wire number spellings; Python's float formatting cannot grow an accepted record past
38
- the limit. Pretty output may exceed the limit and is rejected.
39
- Gelen JSON ve kompakt kayıt sınırı 4 MiB'dır. Kompakt kayıt özgün sayı gösterimini
40
- korur; Python'ın sayı biçimlendirmesi geçerli kaydı sınırın üzerine büyütmez.
41
- Girintili çıktı sınırı aşarsa reddedilir.
42
-
43
- `source` is your N/M source text / kendi N/M kaynak metninizdir. `examples/training.ipynb`
44
- contains runnable TR/EN sources with one total 4096-shot budget, separate pilot
45
- measurements and target precision. Outputs are not pre-executed or saved.
46
-
47
- Feature permissions come from the client, never the record. Replay with
48
- `client.execute(restored.request)` requires independent explicit permissions.
49
- Source snapshots are not checkpoints. Precision targets do not certify convergence.
50
- `targetAccuracy.requiredShotsEstimate` estimates final-cost shots from current
51
- group proportions and confidence assumptions, excluding adaptive pilots. It is not
52
- a total training budget or a guarantee; estimates above 4096, unsafe integer counts
53
- and numeric resolution limits have explicit states. No extra shots are taken.
54
- Original source, training options, shot accounting and confidence assumptions are
55
- retained; output is escaped rather than treated as executable HTML.
56
-
57
- İzinler istemciden gelir; kaydı okumak izin vermez veya çalıştırmaz. Kaynak,
58
- eğitim seçenekleri, ölçüm bütçesi ve güven aralığı varsayımları korunur. Pilotlar
59
- bütçeye dahildir; hassasiyet hedefi yakınsama belgesi değildir. `locale="tr"` ve
60
- `locale="en"` desteklenir. Süre/boyut sınırları aşılırsa kısmi başarı üretilmez.
61
- `targetAccuracy.requiredShotsEstimate` son maliyet için gereken ölçümleri bilgi
62
- amaçlı tahmin eder; pilotlar dahil edilmez. Toplam eğitim bütçesi veya garanti değildir.
63
- 4096 sınırı, güvenli tamsayı aralığı ve sayısal çözünürlük engelleri açıkça gösterilir;
64
- ek ölçüm yapılmaz.
65
-
66
- Full guide / Tam rehber:
67
- `docs/NM_PYTHON_JUPYTER_BRIDGE_GUIDE.md` in the repository. This preview is not a
68
- separately published PyPI distribution. It is covered by the adjacent npm package's
69
- LICENSE.txt and NOTICE.txt; source-code rights remain reserved.
125
+ The cell returns `NMResult`; in the next Python cell, use `record = _` to retain it.
126
+ Options omitted from the magic remain absent, preserving source training defaults.
127
+ Training flags are accepted only with `--train`. Steps: 1–100; learning rate:
128
+ finite `(0,5]`; adaptive pilot shots: 2–32; absolute error: finite `(0,1000000]`.
129
+ No shot override or feature grant is accepted in the magic. See the full guide
130
+ for the enum choices. Nested SPSA/QNG tuning remains in `NMClient.train(options=...)`.
131
+
132
+ Hücre `NMResult` döndürür; sonraki Python hücresinde `record = _` ile saklayın.
133
+ Verilmeyen eğitim seçenekleri eklenmez; kaynağın varsayılanları korunur. Eğitim
134
+ bayrakları yalnız `--train` ile geçerlidir. Adım 1–100, öğrenme oranı sonlu `(0,5]`,
135
+ adaptif pilot ölçüm 2–32, mutlak hata sonlu `(0,1000000]` olur. Hücreye ölçüm sayısı
136
+ override'ı veya özellik izni eklenmez. İç SPSA/QNG ayarları Python API'sinde kalır.
137
+
138
+ ## Limits and records / Sınırlar ve kayıtlar
139
+
140
+ The bridge v1 limits remain: 32 KiB source, 256 KiB request, 4 MiB response,
141
+ 64 KiB stderr, request/result JSON depth 32/64, 14 qubits, 8 training parameters,
142
+ 100 steps, one inclusive shot budget up to 4096 and a 0.1–60 second deadline.
143
+ Pipe I/O and EOF share the deadline; no I/O threads survive a call. Windows
144
+ Python 3.10/3.11 retains the stdlib Win32 compatibility path. Cancellation kills
145
+ the direct process and yields `NM-PY-009`; this is not a process-tree sandbox.
146
+
147
+ Köprü v1 sınırları korunur: 32 KiB kaynak, 256 KiB istek, 4 MiB yanıt, 64 KiB stderr,
148
+ 32/64 JSON derinliği, 14 kübit, 8 parametre, 100 adım, pilotlar dahil en fazla
149
+ 4096 ölçüm ve 0.1–60 saniye süre. Giriş/çıkış ve EOF aynı süreye bağlıdır; I/O iş
150
+ parçacığı kalmaz. Windows Python 3.10/3.11 desteği korunur. İptal doğrudan süreci
151
+ sonlandırıp `NM-PY-009` verir; süreç ağacı sandbox'ı değildir.
152
+
153
+ Saved JSON is data and grants no permission. Replay with
154
+ `client.execute(restored.request)` needs independent caller permissions. Compact
155
+ saving preserves wire number spellings; pretty output exceeding 4 MiB is rejected.
156
+ Source snapshots are not checkpoints. Confidence and precision targets do not
157
+ certify convergence, an optimum or hardware accuracy. Required-shot estimates
158
+ are informational final-cost estimates, exclude pilots and never take extra shots.
159
+ Source, options, shot accounting and confidence assumptions remain in the record;
160
+ all user text in the rich table is HTML escaped.
161
+
162
+ Kaydedilmiş JSON veridir; izin vermez. Yeniden yürütme bağımsız çağıran yetkisi
163
+ gerektirir. Kompakt kayıt özgün sayı gösterimini korur; 4 MiB'ı aşan girintili çıktı
164
+ reddedilir. Kaynak checkpoint, hassasiyet hedefi yakınsama/optimum/donanım belgesi
165
+ değildir. Ölçüm tahmini bilgi amaçlıdır; pilotları içermez ve ek ölçüm almaz.
166
+ Özgün kaynak, seçenekler ve ölçüm/güven varsayımları kayıtta kalır; tablo metni kaçışlanır.
167
+
168
+ `examples/magic.ipynb` is an unexecuted TR/EN explicit-cell example. It is included
169
+ with the npm source layout, not bundled into the minimal wheel. The existing
170
+ `examples/training.ipynb` and legacy Python bridge API remain usable.
171
+
172
+ Full guide / Tam rehber: `docs/NM_PYTHON_JUPYTER_ADAPTER_GUIDE.md`.
173
+ Protocol and precision details / Protokol ve hassasiyet:
174
+ `docs/NM_PYTHON_JUPYTER_BRIDGE_GUIDE.md`.
@@ -0,0 +1,98 @@
1
+ {
2
+ "nbformat": 4,
3
+ "nbformat_minor": 5,
4
+ "metadata": {"kernelspec": {"display_name": "Python 3", "language": "python", "name": "python3"}, "language_info": {"name": "python"}},
5
+ "cells": [
6
+ {"cell_type": "markdown", "id": "scope", "metadata": {}, "source": [
7
+ "# Explicit N/M cells / Açık N/M hücreleri\n",
8
+ "Local development adapter; not a PyPI release. / Yerel geliştirme adaptörü; PyPI yayını değildir.\n",
9
+ "Install the local wheel and optional Python 3.11+ IPython/kernel requirements first. / Önce yerel wheel ve isteğe bağlı Python 3.11+ IPython/kernel gereksinimlerini kurun.\n",
10
+ "Select an existing trusted absolute CLI file by explicitly setting NM_ADAPTER_CLI_PATH before running this setup cell. The adapter does not discover it. / Setup hücresinden önce NM_ADAPTER_CLI_PATH ile güvenilir mutlak CLI dosyasını açıkça seçin.\n",
11
+ "Opening or saving this notebook does not execute it. Outputs are not pre-executed or saved. / Notebook'u açmak veya kaydetmek yürütme başlatmaz; önceden çalıştırılmış çıktı yoktur."
12
+ ]},
13
+ {"cell_type": "code", "id": "setup", "metadata": {}, "execution_count": null, "outputs": [], "source": [
14
+ "import os\n",
15
+ "from pathlib import Path\n",
16
+ "from nm_bridge import NMClient, NMResult\n",
17
+ "cli_path = Path(os.environ['NM_ADAPTER_CLI_PATH']) # Explicit trusted user/test selection / Açık güvenilir seçim\n",
18
+ "features = ('hamiltonianEvolution', 'measurementGrouping')\n",
19
+ "client_en = NMClient(cli_path=cli_path, locale='en', experimental=features)\n",
20
+ "client_tr = NMClient(cli_path=cli_path, locale='tr', experimental=features)\n",
21
+ "%load_ext nm_jupyter"
22
+ ]},
23
+ {"cell_type": "code", "id": "check-en", "metadata": {}, "execution_count": null, "outputs": [], "source": [
24
+ "%%nm --client client_en --check\n",
25
+ "module magic_check;\n",
26
+ "fn main(){let q=qreg[1];H(q[0]);return q;}\n",
27
+ "// İı çğ üş 😀 <script>source is data</script> $literal {literal}"
28
+ ]},
29
+ {"cell_type": "code", "id": "retain-check-en", "metadata": {}, "execution_count": null, "outputs": [], "source": ["checked_en = _\n", "assert checked_en.success, checked_en.to_json()"]},
30
+ {"cell_type": "code", "id": "run-en", "metadata": {}, "execution_count": null, "outputs": [], "source": [
31
+ "%%nm --client client_en --run\n",
32
+ "module magic_run;\n",
33
+ "@seed(17);\n",
34
+ "fn main(){let q=qreg[1];H(q[0]);let cost=expect Z(q[0]);return cost;}\n",
35
+ "// İı çğ üş 😀 <script>source is data</script>"
36
+ ]},
37
+ {"cell_type": "code", "id": "retain-run-en", "metadata": {}, "execution_count": null, "outputs": [], "source": ["ran_en = _\n", "assert ran_en.success, ran_en.to_json()"]},
38
+ {"cell_type": "code", "id": "train-en", "metadata": {}, "execution_count": null, "outputs": [], "source": [
39
+ "%%nm --client client_en --train --steps 2 --learning-rate 0.17 --optimizer adam --objective minimize --objective-target energy --gradient-method parameter-shift --measurement-allocation adaptive --pilot-shots 8 --absolute-error 1e-9\n",
40
+ "module magic_training;\n",
41
+ "@seed(27);\n",
42
+ "@shots_budget(4096);\n",
43
+ "param theta:Angle=0;\n",
44
+ "fn main(){\n",
45
+ " let q=qreg[1];\n",
46
+ " observable Hamiltonian=theta*X(q[0])+0.25*theta*I;\n",
47
+ " evolve Hamiltonian for 0.4 steps 1 order 1 on q;\n",
48
+ " let energy=expect Y(q[0])+0.5*Z(q[0]);\n",
49
+ " return q;\n",
50
+ "}\n",
51
+ "// İı çğ üş 😀 <script>source is data</script>"
52
+ ]},
53
+ {"cell_type": "code", "id": "retain-train-en", "metadata": {}, "execution_count": null, "outputs": [], "source": ["trained_en = _\n", "assert trained_en.success, trained_en.to_json()"]},
54
+ {"cell_type": "code", "id": "check-tr", "metadata": {}, "execution_count": null, "outputs": [], "source": [
55
+ "%%nm --client client_tr --check\n",
56
+ "module magic_kontrol;\n",
57
+ "@dil(\"tr\");\n",
58
+ "fn main(){let q=qreg[1];H(q[0]);return q;}\n",
59
+ "// İı çğ üş 😀 <script>kaynak veridir</script>"
60
+ ]},
61
+ {"cell_type": "code", "id": "retain-check-tr", "metadata": {}, "execution_count": null, "outputs": [], "source": ["checked_tr = _\n", "assert checked_tr.success, checked_tr.to_json()"]},
62
+ {"cell_type": "code", "id": "run-tr", "metadata": {}, "execution_count": null, "outputs": [], "source": [
63
+ "%%nm --client client_tr --run\n",
64
+ "module magic_yurutme;\n",
65
+ "@dil(\"tr\");\n",
66
+ "@seed(17);\n",
67
+ "fn main(){let q=qreg[1];H(q[0]);let cost=expect Z(q[0]);return cost;}\n",
68
+ "// İı çğ üş 😀 <script>kaynak veridir</script>"
69
+ ]},
70
+ {"cell_type": "code", "id": "retain-run-tr", "metadata": {}, "execution_count": null, "outputs": [], "source": ["ran_tr = _\n", "assert ran_tr.success, ran_tr.to_json()"]},
71
+ {"cell_type": "code", "id": "train-tr", "metadata": {}, "execution_count": null, "outputs": [], "source": [
72
+ "%%nm --client client_tr --train --steps 2 --learning-rate 0.17 --optimizer adam --objective minimize --objective-target energy --gradient-method parameter-shift --measurement-allocation adaptive --pilot-shots 8 --absolute-error 1e-9\n",
73
+ "module magic_egitimi;\n",
74
+ "@dil(\"tr\");\n",
75
+ "@seed(27);\n",
76
+ "@shots_budget(4096);\n",
77
+ "param theta:Angle=0;\n",
78
+ "fn main(){\n",
79
+ " let q=qreg[1];\n",
80
+ " observable Hamiltonian=theta*X(q[0])+0.25*theta*I;\n",
81
+ " evolve Hamiltonian for 0.4 adımlar 1 order 1 on q;\n",
82
+ " let energy=expect Y(q[0])+0.5*Z(q[0]);\n",
83
+ " return q;\n",
84
+ "}\n",
85
+ "// İı çğ üş 😀 <script>kaynak veridir</script>"
86
+ ]},
87
+ {"cell_type": "code", "id": "retained-records", "metadata": {}, "execution_count": null, "outputs": [], "source": [
88
+ "trained_tr = _\n",
89
+ "assert trained_tr.success, trained_tr.to_json()\n",
90
+ "assert trained_tr.result == trained_en.result\n",
91
+ "restored = NMResult.from_json(trained_en.to_json())\n",
92
+ "assert restored.record == trained_en.record\n",
93
+ "denied = NMClient(cli_path=cli_path).execute(restored.request)\n",
94
+ "assert not denied.success and denied.record['error']['code'] == 'NM-PY-003'\n",
95
+ "# Replay needs a separate explicit API call with client permissions. / Yeniden yürütme ayrıca izinli API çağrısı gerektirir."
96
+ ]}
97
+ ]
98
+ }
@@ -1,12 +1,13 @@
1
1
  """Bounded local N/M JSON/CLI bridge (Python 3.10+, standard library only).
2
2
 
3
3
  Not a Python evaluator, checkpoint, hardware connector or permission-bearing IR.
4
- The adjacent npm CLI must be built/installed. No network or shell is used here.
4
+ The npm CLI must be built/installed separately. No network or shell is used here.
5
5
  """
6
6
  from __future__ import annotations
7
7
 
8
8
  import copy
9
9
  import html
10
+ import importlib.metadata
10
11
  import json
11
12
  import math
12
13
  import os
@@ -32,6 +33,7 @@ _COPY = {
32
33
  "cancel": "Bridge cancelled; the process was killed. No completed result is available.",
33
34
  "output": "Bridge output exceeded its byte limit; no partial result was accepted.",
34
35
  "node": "Node.js was not found in an absolute PATH directory outside the working directory. Install Node.js or supply a trusted command sequence.",
36
+ "cli": "A trusted absolute N/M CLI file is required. Install the adjacent npm CLI or supply cli_path= or a trusted command sequence.",
35
37
  "title": "N/M local bridge", "status": "Execution succeeded", "failed": "Execution failed",
36
38
  "rejected": "Request rejected", "shots": "Total shots used / budget", "remaining": "Unallocated shots",
37
39
  "pilot": "Pilot shots (allocation only)", "held_out": "Held-out estimation shots",
@@ -54,6 +56,7 @@ _COPY = {
54
56
  "cancel": "Köprü iptal edildi; işlem sonlandırıldı. Tamamlanmış sonuç yoktur.",
55
57
  "output": "Köprü çıktısı bayt sınırını aştı; kısmi sonuç kabul edilmedi.",
56
58
  "node": "Çalışma dizini dışındaki mutlak PATH dizinlerinde Node.js bulunamadı. Node.js kurun veya güvenilir bir komut dizisi verin.",
59
+ "cli": "Güvenilir mutlak N/M CLI dosyası gerekir. Komşu npm CLI'yi kurun veya cli_path= ya da güvenilir bir komut dizisi verin.",
57
60
  "title": "N/M yerel köprüsü", "status": "Yürütme başarılı", "failed": "Yürütme başarısız",
58
61
  "rejected": "İstek reddedildi", "shots": "Kullanılan toplam ölçüm / bütçe", "remaining": "Dağıtılmamış ölçüm",
59
62
  "pilot": "Pilot ölçümler (yalnız dağılım için)", "held_out": "Ayrı değerlendirme ölçümleri",
@@ -325,15 +328,61 @@ class NMResult:
325
328
  return table + "<details><summary>" + html.escape(labels["source"]) + "</summary><pre>" + html.escape(str(source)) + "</pre></details>"
326
329
 
327
330
 
331
+ def _npm_adjacent_cli(module_path: Path) -> Path:
332
+ """Use the npm default only when the physical module has unambiguous origin.
333
+
334
+ A wheel may be installed with --target into a directory named ``python``.
335
+ Its local distribution identity/RECORD must take precedence over any sibling
336
+ npm-looking layout. Metadata elsewhere on sys.path does not own this module.
337
+ """
338
+ module_path = module_path.resolve(strict=True)
339
+ directory = module_path.parent
340
+ if module_path.name != "nm_bridge.py" or directory.name != "python":
341
+ raise ValueError("The standalone adapter requires explicit CLI selection")
342
+ package = directory.parent
343
+ marker = package / "package.json"
344
+ if marker.resolve(strict=True).parent != package:
345
+ raise ValueError("Ambiguous npm package marker")
346
+ with marker.open("rb") as stream:
347
+ manifest = _strict_json(stream.read(65537).decode("utf-8"), 65536, safe_integers=True)
348
+ if (not isinstance(manifest, dict) or manifest.get("name") != "@nm-lang/cli"
349
+ or not isinstance(manifest.get("bin"), dict) or manifest["bin"].get("nm") != "./bin/nm.mjs"):
350
+ raise ValueError("The module is not in the original N/M npm package")
351
+ markers = [entry for entry in directory.iterdir() if entry.name.endswith((".dist-info", ".egg-info"))]
352
+ if any(entry.is_symlink() for entry in markers):
353
+ raise ValueError("Ambiguous local Python distribution marker")
354
+ if any(entry.name.lower().replace("_", "-").replace(".", "-").startswith("nm-jupyter-adapter-") for entry in markers):
355
+ raise ValueError("Standalone adapter installation marker")
356
+ distributions = list(importlib.metadata.distributions(path=[str(directory)]))
357
+ if len(distributions) != len(markers):
358
+ raise ValueError("Unrecognized local Python distribution metadata")
359
+ for distribution in distributions:
360
+ names = distribution.metadata.get_all("Name", [])
361
+ name = names[0] if len(names) == 1 else None
362
+ files = distribution.files
363
+ if not isinstance(name, str) or not name or files is None:
364
+ raise ValueError("Incomplete local Python distribution metadata")
365
+ normalized = name.lower().replace("_", "-").replace(".", "-")
366
+ if normalized == "nm-jupyter-adapter" or any(
367
+ Path(distribution.locate_file(file)).resolve() == module_path for file in files):
368
+ raise ValueError("An installed Python distribution owns the bridge")
369
+ cli = (package / "bin/nm.mjs").resolve(strict=True)
370
+ if not cli.is_relative_to(package):
371
+ raise ValueError("The adjacent CLI resolves outside its npm package")
372
+ return cli
373
+
374
+
328
375
  class NMClient:
329
- """Each call launches one adjacent CLI process, with independently chosen permissions.
376
+ """Each call launches one CLI process, with independently chosen permissions.
330
377
 
331
378
  command is a trusted argument sequence, never a shell string. The default uses
332
379
  an absolute Node executable from PATH and the npm package's adjacent nm.mjs.
333
380
  Empty/relative PATH entries and the working directory are never searched.
381
+ Standalone wheel users select an absolute trusted script with cli_path=.
382
+ That option and the existing trusted command= sequence are mutually exclusive.
334
383
  """
335
384
  def __init__(self, *, experimental: Sequence[str] = (), locale: str = "en", timeout: float = 30,
336
- command: Sequence[str] | None = None):
385
+ command: Sequence[str] | None = None, cli_path: str | os.PathLike[str] | None = None):
337
386
  if locale not in _COPY:
338
387
  raise ValueError("locale must be en or tr")
339
388
  self.locale = locale
@@ -342,8 +391,19 @@ class NMClient:
342
391
  or any(not isinstance(name, str) or not name.isascii() or not name.isalpha() for name in experimental)
343
392
  or len(set(experimental)) != len(experimental)):
344
393
  raise NMBridgeError("NM-PY-001", "input", locale)
394
+ if cli_path is not None and command is not None:
395
+ raise NMBridgeError("NM-PY-001", "input", locale)
345
396
  if command is None:
346
- command = (_resolve_node(locale), str(Path(__file__).resolve().parents[1] / "bin" / "nm.mjs"))
397
+ try:
398
+ cli = Path(cli_path) if cli_path is not None else _npm_adjacent_cli(Path(__file__))
399
+ if not cli.is_absolute():
400
+ raise ValueError("CLI path must be absolute")
401
+ cli = cli.resolve(strict=True)
402
+ if not cli.is_file():
403
+ raise ValueError("CLI path must be a file")
404
+ except (OSError, RuntimeError, TypeError, ValueError, UnicodeError, KeyError, IndexError):
405
+ raise NMBridgeError("NM-PY-012", "cli", locale) from None
406
+ command = (_resolve_node(locale), str(cli))
347
407
  if (isinstance(command, (str, bytes)) or not isinstance(command, Sequence) or not command
348
408
  or any(not isinstance(arg, str) or not arg or "\0" in arg for arg in command)):
349
409
  raise NMBridgeError("NM-PY-001", "input", locale)
@@ -0,0 +1,20 @@
1
+ """Optional, explicitly invoked IPython cells over the stdlib N/M bridge.
2
+
3
+ Importing this package neither imports IPython nor launches the CLI. The legacy
4
+ ``from nm_bridge import NMClient, NMResult`` remains supported.
5
+ """
6
+ from nm_bridge import NMBridgeError, NMClient, NMResult
7
+
8
+ __all__ = ["NMBridgeError", "NMClient", "NMResult"]
9
+
10
+
11
+ def load_ipython_extension(ipython):
12
+ """Register %%nm only when IPython explicitly loads this extension."""
13
+ from .extension import load_ipython_extension as load
14
+ load(ipython)
15
+
16
+
17
+ def unload_ipython_extension(ipython):
18
+ """Remove this extension's magic without changing other shell magics."""
19
+ from .extension import unload_ipython_extension as unload
20
+ unload(ipython)