@nm-lang/cli 0.0.0-stage → 0.2.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/LICENSE.txt ADDED
@@ -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.vercel.app/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.
package/NOTICE.txt ADDED
@@ -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/README.md CHANGED
@@ -1,3 +1,74 @@
1
- # Temporary Holding Version
1
+ # @nm-lang/cli
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ N/M 0.2.0 Preview command-line tools for running, formatting, linting, testing,
4
+ exporting and managing local N/M workspaces.
5
+
6
+ ## Install / Kurulum
7
+
8
+ Requires Node.js 20 or newer. Release checks use Node.js 22.23.1.
9
+
10
+ ```sh
11
+ npm install --global @nm-lang/cli@next
12
+ nm --version
13
+ ```
14
+
15
+ Or run without a global install:
16
+
17
+ ```sh
18
+ npx --package=@nm-lang/cli@next nm --version
19
+ ```
20
+
21
+ The `next` tag selects the preview channel. To pin this version after publication,
22
+ replace `@next` with `@0.2.0`. npm installs the matching Core dependency.
23
+ No repository checkout or TypeScript build is needed to use a published package.
24
+
25
+ Türkçe: `next` önizleme kanalını seçer. Yayından sonra `@0.2.0` ile sürümü
26
+ sabitleyebilirsiniz. Core bağımlılığı otomatik kurulur; depoyu indirmeniz gerekmez.
27
+
28
+ ## First program / İlk program
29
+
30
+ Save the following as `bell.nm`:
31
+
32
+ ```nm
33
+ module npm_bell;
34
+ fn main() {
35
+ let q = qreg[2];
36
+ H(q[0]);
37
+ CNOT(q[0], q[1]);
38
+ let a = measure(q[0]);
39
+ let b = measure(q[1]);
40
+ return (a, b);
41
+ }
42
+ ```
43
+
44
+ ```sh
45
+ nm run bell.nm --json
46
+ ```
47
+
48
+ The program creates and measures a Bell pair using a local simulator.
49
+ Türkçe: Kodu `bell.nm` olarak kaydedip komutla çalıştırın. Sonuç yerel
50
+ simülasyondan gelir; gerçek kuantum donanımı kanıtı değildir.
51
+
52
+ ## Language options / Dil seçenekleri
53
+
54
+ New CLI invocations enable the default language profile: classical functions,
55
+ typed values/Result, module visibility, parameter groups and compute/apply.
56
+ Use `--legacy-features` for the previous opt-in behavior. Other experimental
57
+ features still require their own flags; export/backend limits remain enforced.
58
+
59
+ Türkçe: Yeni CLI çağrılarında varsayılan dil profili açıktır. Eski davranış için
60
+ `--legacy-features` kullanın. Diğer deneysel özellikler ayrıca açılmalıdır.
61
+
62
+ - [CLI documentation](https://quantumsoftware-tech.vercel.app/en/docs/nm-language/cli)
63
+ - [Türkçe CLI rehberi](https://quantumsoftware-tech.vercel.app/tr/docs/nm-language/cli)
64
+ - [Support / Destek](https://quantumsoftware-tech.vercel.app/en/#contact)
65
+
66
+ ## License / Lisans
67
+
68
+ Free personal, educational and commercial use; source-code rights are reserved.
69
+ Modification or redistribution of QuantumSoftware-owned package code requires
70
+ separate permission. Your N/M programs and results remain yours. See
71
+ [LICENSE.txt](./LICENSE.txt) and [NOTICE.txt](./NOTICE.txt).
72
+
73
+ Türkçe: Kullanım ücretsizdir; açık kaynak lisansı verilmez. Paket kodunu
74
+ değiştirme ve yeniden dağıtma için ayrıca izin gerekir.
package/bin/nm.mjs ADDED
@@ -0,0 +1,315 @@
1
+ #!/usr/bin/env node
2
+ import { readFileSync, readdirSync, writeFileSync } from "node:fs";
3
+ import { existsSync } from "node:fs";
4
+ import path from "node:path";
5
+ import { fileURLToPath } from "node:url";
6
+
7
+ let runNMCLI;
8
+ let validateNMWorkspaceManifestJsonText;
9
+ let NM_WORKSPACE_MAX_DOCUMENTS;
10
+ let NM_WORKSPACE_MAX_SOURCE_BYTES;
11
+ let nmWorkspaceDiagnosticCodes;
12
+ let getNMWorkspaceModuleName;
13
+ const workspaceRoot = path.resolve(
14
+ path.dirname(fileURLToPath(import.meta.url)),
15
+ "../../..",
16
+ );
17
+ const workspaceCLIPath = path.join(workspaceRoot, "lib/nm/cli.ts");
18
+ const workspaceCache = new Map();
19
+ const cliArgs = process.argv.slice(2);
20
+ const isPackedVersionCommand =
21
+ process.env.NM_CLI_FORCE_PACKED === "1" &&
22
+ (cliArgs[0] === "--version" || cliArgs[0] === "version");
23
+
24
+ if (isPackedVersionCommand) {
25
+ const { nmVersionManifest } = await import("../dist/version.js");
26
+ process.stdout.write(
27
+ cliArgs.includes("--json")
28
+ ? `${JSON.stringify(nmVersionManifest, null, 2)}\n`
29
+ : `N/M ${nmVersionManifest.languageVersion} (${nmVersionManifest.stability})\n`,
30
+ );
31
+ } else {
32
+ if (existsSync(workspaceCLIPath) && process.env.NM_CLI_FORCE_PACKED !== "1") {
33
+ const [{ default: Module }, { createJiti }] = await Promise.all([
34
+ import("node:module"),
35
+ import("jiti"),
36
+ ]);
37
+ installWorkspaceAliasResolver(Module);
38
+ const jiti = createJiti(import.meta.url);
39
+ ({
40
+ runNMCLI,
41
+ validateNMWorkspaceManifestJsonText,
42
+ NM_WORKSPACE_MAX_DOCUMENTS,
43
+ NM_WORKSPACE_MAX_SOURCE_BYTES,
44
+ nmWorkspaceDiagnosticCodes,
45
+ getNMWorkspaceModuleName,
46
+ } = jiti(workspaceCLIPath));
47
+ } else {
48
+ try {
49
+ ({
50
+ runNMCLI,
51
+ validateNMWorkspaceManifestJsonText,
52
+ NM_WORKSPACE_MAX_DOCUMENTS,
53
+ NM_WORKSPACE_MAX_SOURCE_BYTES,
54
+ nmWorkspaceDiagnosticCodes,
55
+ getNMWorkspaceModuleName,
56
+ } = await import("../dist/index.js"));
57
+ } catch {
58
+ ({
59
+ runNMCLI,
60
+ validateNMWorkspaceManifestJsonText,
61
+ NM_WORKSPACE_MAX_DOCUMENTS,
62
+ NM_WORKSPACE_MAX_SOURCE_BYTES,
63
+ nmWorkspaceDiagnosticCodes,
64
+ getNMWorkspaceModuleName,
65
+ } = await import("../../nm-core/dist/index.js"));
66
+ }
67
+ }
68
+ const packageAction = cliArgs[0] === "package" ? cliArgs[1] : undefined;
69
+ const crypto =
70
+ packageAction === "sign" || packageAction === "verify"
71
+ ? await import("node:crypto")
72
+ : undefined;
73
+ const packageSigner =
74
+ packageAction === "sign"
75
+ ? createConfiguredPackageSigner(crypto)
76
+ : undefined;
77
+ const packageVerifier =
78
+ packageAction === "verify"
79
+ ? createConfiguredPackageVerifier(crypto)
80
+ : undefined;
81
+ const result = runNMCLI(cliArgs, {
82
+ readFile: (filePath) => readFileSync(filePath, "utf8"),
83
+ readWorkspaceModules: (entryPath) => getWorkspaceIndex(entryPath).modules,
84
+ readWorkspaceDocuments: (entryPath) =>
85
+ getWorkspaceIndex(entryPath).documents,
86
+ fileExists: (filePath) => existsSync(filePath),
87
+ writeFile: (filePath, content, { overwrite }) =>
88
+ writeFileSync(filePath, content, {
89
+ encoding: "utf8",
90
+ flag: overwrite ? "w" : "wx",
91
+ }),
92
+ packageSigner,
93
+ packageVerifier,
94
+ });
95
+
96
+ if (result.stdout.length > 0) {
97
+ process.stdout.write(`${result.stdout.join("\n")}\n`);
98
+ }
99
+ if (result.stderr.length > 0) {
100
+ process.stderr.write(`${result.stderr.join("\n")}\n`);
101
+ }
102
+ process.exitCode = result.exitCode;
103
+ }
104
+
105
+ function getWorkspaceIndex(entryPath) {
106
+ const cacheKey = path.resolve(entryPath);
107
+ const cached = workspaceCache.get(cacheKey);
108
+ if (cached) {
109
+ return cached;
110
+ }
111
+
112
+ const entryDirectory = path.dirname(cacheKey);
113
+ const manifestPath = findWorkspaceManifest(entryDirectory);
114
+ const manifest = manifestPath
115
+ ? validateWorkspaceManifest(manifestPath)
116
+ : undefined;
117
+ const root = manifestPath ? path.dirname(manifestPath) : entryDirectory;
118
+ const sourceRoots = manifest
119
+ ? manifest.sourceRoots.map((sourceRoot) =>
120
+ resolveInsideWorkspace(root, sourceRoot),
121
+ )
122
+ : [root];
123
+ const modules = Object.create(null);
124
+ const documents = [];
125
+ const seenFiles = new Set();
126
+ let sourceBytes = 0;
127
+ for (const sourceRoot of sourceRoots) {
128
+ for (const filePath of collectNMFiles(sourceRoot)) {
129
+ if (seenFiles.has(filePath)) {
130
+ continue;
131
+ }
132
+ seenFiles.add(filePath);
133
+ const source = readFileSync(filePath, "utf8");
134
+ sourceBytes += new TextEncoder().encode(source).byteLength;
135
+ if (sourceBytes > NM_WORKSPACE_MAX_SOURCE_BYTES) {
136
+ throw new Error(
137
+ `${nmWorkspaceDiagnosticCodes.limit}: N/M workspace source exceeded ${NM_WORKSPACE_MAX_SOURCE_BYTES} UTF-8 bytes.`,
138
+ );
139
+ }
140
+ const moduleName = getNMWorkspaceModuleName(source);
141
+ if (!moduleName) {
142
+ continue;
143
+ }
144
+ if (Object.prototype.hasOwnProperty.call(modules, moduleName)) {
145
+ throw new Error(
146
+ `${nmWorkspaceDiagnosticCodes.duplicateModule}: Duplicate N/M workspace module: ${moduleName}.`,
147
+ );
148
+ }
149
+ modules[moduleName] = source;
150
+ documents.push({
151
+ path: path.relative(root, filePath).split(path.sep).join("/"),
152
+ source,
153
+ });
154
+ }
155
+ }
156
+ const index = { modules, documents, manifest, root };
157
+ workspaceCache.set(cacheKey, index);
158
+ return index;
159
+ }
160
+
161
+ function findWorkspaceManifest(startDirectory) {
162
+ let directory = startDirectory;
163
+ while (true) {
164
+ const candidate = path.join(directory, "nm-workspace.json");
165
+ if (existsSync(candidate)) {
166
+ return candidate;
167
+ }
168
+ const parent = path.dirname(directory);
169
+ if (parent === directory) {
170
+ return undefined;
171
+ }
172
+ directory = parent;
173
+ }
174
+ }
175
+
176
+ function validateWorkspaceManifest(manifestPath) {
177
+ const validation = validateNMWorkspaceManifestJsonText(
178
+ readFileSync(manifestPath, "utf8"),
179
+ );
180
+ if (!validation.valid) {
181
+ throw new Error(
182
+ `Invalid ${manifestPath}: ${validation.issues.map((issue) => `${issue.code} ${issue.path}: ${issue.message}`).join(" ")}`,
183
+ );
184
+ }
185
+ return validation.manifest;
186
+ }
187
+
188
+ function resolveInsideWorkspace(root, relativePath) {
189
+ const resolved = path.resolve(root, relativePath);
190
+ const relative = path.relative(root, resolved);
191
+ if (relative.startsWith("..") || path.isAbsolute(relative)) {
192
+ throw new Error(
193
+ `Workspace source root escapes its manifest directory: ${relativePath}.`,
194
+ );
195
+ }
196
+ return resolved;
197
+ }
198
+
199
+ function collectNMFiles(root) {
200
+ const ignoredDirectories = new Set(["node_modules", ".git", ".next", "dist"]);
201
+ const files = [];
202
+ const pending = [root];
203
+ while (pending.length > 0) {
204
+ const directory = pending.pop();
205
+ if (!directory) {
206
+ continue;
207
+ }
208
+ for (const entry of readdirSync(directory, { withFileTypes: true })) {
209
+ const entryPath = path.join(directory, entry.name);
210
+ if (entry.isDirectory() && !ignoredDirectories.has(entry.name)) {
211
+ pending.push(entryPath);
212
+ } else if (entry.isFile() && entry.name.endsWith(".nm")) {
213
+ files.push(entryPath);
214
+ }
215
+ }
216
+ if (files.length > NM_WORKSPACE_MAX_DOCUMENTS) {
217
+ throw new Error(
218
+ `${nmWorkspaceDiagnosticCodes.limit}: N/M workspace index exceeded ${NM_WORKSPACE_MAX_DOCUMENTS} files under ${root}.`,
219
+ );
220
+ }
221
+ }
222
+ return files.sort();
223
+ }
224
+
225
+ function installWorkspaceAliasResolver(Module) {
226
+ const originalResolveFilename = Module._resolveFilename;
227
+ Module._resolveFilename = function resolveWorkspaceAlias(
228
+ request,
229
+ parent,
230
+ isMain,
231
+ options,
232
+ ) {
233
+ if (typeof request === "string" && request.startsWith("@/")) {
234
+ const absoluteRequest = path.join(workspaceRoot, request.slice(2));
235
+ const tsRequest = `${absoluteRequest}.ts`;
236
+ return originalResolveFilename.call(
237
+ this,
238
+ existsSync(tsRequest) ? tsRequest : absoluteRequest,
239
+ parent,
240
+ isMain,
241
+ options,
242
+ );
243
+ }
244
+ return originalResolveFilename.call(this, request, parent, isMain, options);
245
+ };
246
+ }
247
+
248
+ function createConfiguredPackageSigner(crypto) {
249
+ const privateKeyText = process.env.NM_PACKAGE_SIGNING_PRIVATE_KEY?.replaceAll(
250
+ "\\n",
251
+ "\n",
252
+ ).trim();
253
+ const keyId = process.env.NM_PACKAGE_SIGNING_KEY_ID?.trim();
254
+ if (!privateKeyText && !keyId) return undefined;
255
+ if (!privateKeyText || !keyId) {
256
+ throw new Error(
257
+ "NM_PACKAGE_SIGNING_PRIVATE_KEY and NM_PACKAGE_SIGNING_KEY_ID must be configured together.",
258
+ );
259
+ }
260
+ const privateKey = crypto.createPrivateKey(privateKeyText);
261
+ if (privateKey.asymmetricKeyType !== "ed25519")
262
+ throw new Error("NM package signing key must be Ed25519.");
263
+ return {
264
+ algorithm: "ed25519",
265
+ keyId,
266
+ sign: (payload) =>
267
+ crypto.sign(null, payload, privateKey).toString("base64url"),
268
+ };
269
+ }
270
+
271
+ function createConfiguredPackageVerifier(crypto) {
272
+ const keyringText = process.env.NM_PACKAGE_TRUSTED_PUBLIC_KEYS_JSON?.trim();
273
+ if (!keyringText) return undefined;
274
+ let keyringValue;
275
+ try {
276
+ keyringValue = JSON.parse(keyringText);
277
+ } catch {
278
+ throw new Error("NM_PACKAGE_TRUSTED_PUBLIC_KEYS_JSON must be valid JSON.");
279
+ }
280
+ if (
281
+ !keyringValue ||
282
+ typeof keyringValue !== "object" ||
283
+ Array.isArray(keyringValue)
284
+ ) {
285
+ throw new Error(
286
+ "NM_PACKAGE_TRUSTED_PUBLIC_KEYS_JSON must be an object keyed by key id.",
287
+ );
288
+ }
289
+ const keyring = new Map();
290
+ for (const [keyId, publicKeyText] of Object.entries(keyringValue)) {
291
+ if (typeof publicKeyText !== "string" || !publicKeyText.trim())
292
+ throw new Error(`Trusted public key is invalid: ${keyId}.`);
293
+ const publicKey = crypto.createPublicKey(
294
+ publicKeyText.replaceAll("\\n", "\n"),
295
+ );
296
+ if (publicKey.asymmetricKeyType !== "ed25519")
297
+ throw new Error(`Trusted public key must be Ed25519: ${keyId}.`);
298
+ keyring.set(keyId, publicKey);
299
+ }
300
+ return {
301
+ verify: ({ algorithm, keyId, payload, signature }) => {
302
+ const publicKey = keyring.get(keyId);
303
+ return (
304
+ algorithm === "ed25519" &&
305
+ Boolean(publicKey) &&
306
+ crypto.verify(
307
+ null,
308
+ payload,
309
+ publicKey,
310
+ Buffer.from(signature, "base64url"),
311
+ )
312
+ );
313
+ },
314
+ };
315
+ }
@@ -0,0 +1,2 @@
1
+ export { getNMWorkspaceModuleName, evaluateNMLanguagePreviewReadiness, NM_CLI_CONTRACT_VERSION, NM_LANGUAGE_PREVIEW_READINESS_FORMAT, NM_LANGUAGE_PREVIEW_READINESS_VERSION, NM_WORKSPACE_MAX_DOCUMENTS, NM_WORKSPACE_MAX_SOURCE_BYTES, nmCliCommandContracts, nmCliCommands, nmCliExitCodeContract, nmLanguagePreviewReadiness, nmLanguageReleaseChangelog, nmVersionManifest, nmWorkspaceDiagnosticCodes, runNMCLI, validateNMWorkspaceManifestJsonText } from "@nm-lang/core/cli";
2
+ export type { NMCapabilityStatus, NMCLICommand, NMCLICommandContract, NMCLIDeterminism, NMCLIExportTarget, NMCLIOptions, NMCLIOutputKind, NMCLIResult, NMLanguagePreviewReadiness, NMLanguageReleaseChange, NMVersionManifest } from "@nm-lang/core/cli";