suitescript-verify 0.0.0-stage → 0.0.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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Eric Nemchik
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,35 @@
1
- # Temporary Holding Version
1
+ # suitescript-verify
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
+ Run probes in a NetSuite **sandbox** to verify what SuiteScript modules do, from a CLI that is safe to hand to an agent: sandbox only, a person approves every write at a terminal, and output is scrubbed of identifiers.
4
+
5
+ > This package never accepts Oracle's SuiteCloud license. Install `@oracle/suitecloud-cli` in your project and accept the license yourself.
6
+
7
+ Early version: `help`, `init`, `doctor` and `deploy` exist. The rest of the CLI (`certificate`, `passkey`, `env`, `setup-ci`, `run`, `cleanup`) is being extracted from an existing project.
8
+
9
+ ```sh
10
+ npm install --save-dev suitescript-verify @oracle/suitecloud-cli
11
+ npx suitescript-verify help
12
+ ```
13
+
14
+ `init` scaffolds `verification/probes`, `verification/sdf`, `docs/verification/results` and a committed `verification/.env` of layout keys, then prints a to-do list. It creates no credentials. `doctor` checks the SuiteCloud CLI, account, sandbox, certificates, passkey and project files, and says which failing steps are the maintainer's own; it exits 1 while any fails.
15
+
16
+ `deploy` dry-runs the runner deployment through your project's SuiteCloud CLI from `verification/sdf` (`--dir <path>` moves `verification/`). A real deploy needs `--approved` and a person at a terminal who types `DEPLOY`.
17
+
18
+ See [docs/verification/README.md](docs/verification/README.md) and [docs/verification/lessons.md](docs/verification/lessons.md).
19
+
20
+ ## Development
21
+
22
+ You need the Node.js and npm versions in `package.json` `devEngines`.
23
+
24
+ ```sh
25
+ npm install
26
+ npm run skills:install # optional: restores the agent skills for Claude Code
27
+ npm test # builds, then runs the CLI tests against a fake SuiteCloud CLI
28
+ npm run format # type-checks, then applies Biome's fixes
29
+ ```
30
+
31
+ Git hooks (lefthook) run Biome on staged files before each commit.
32
+
33
+ ## License
34
+
35
+ MIT
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ import { main } from "../cli.js";
3
+ process.exitCode = await main(process.argv.slice(2));
package/dist/cli.js ADDED
@@ -0,0 +1,128 @@
1
+ import { spawnSync } from "node:child_process";
2
+ import { existsSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ import { createInterface } from "node:readline/promises";
5
+ import { loadConfig } from "./config.js";
6
+ import { formatChecks, runChecks } from "./doctor.js";
7
+ import { init } from "./init.js";
8
+ export const USAGE = `Usage: suitescript-verify <command> [options]
9
+
10
+ Commands:
11
+ deploy [--approved] [--dir <path>] Dry-run the runner deployment; --approved deploys, from your own terminal
12
+ doctor [--dir <path>] Check the setup; each failing step says who does it
13
+ init [--dir <path>] Scaffold the folders and .env, and print a to-do list
14
+ help Show this help
15
+
16
+ Probes run in a NetSuite sandbox only. The package never accepts Oracle's SuiteCloud license:
17
+ install @oracle/suitecloud-cli in your project and accept the license yourself.`;
18
+ class UsageError extends Error {
19
+ }
20
+ /** The project's own SuiteCloud CLI (a peer dependency), never a global one. */
21
+ function suitecloudEntry(project) {
22
+ const entry = join(project, "node_modules", "@oracle", "suitecloud-cli", "src", "suitecloud.js");
23
+ if (!existsSync(entry)) {
24
+ throw new UsageError("Install @oracle/suitecloud-cli in this project (npm install --save-dev @oracle/suitecloud-cli), and accept Oracle's license yourself.");
25
+ }
26
+ return entry;
27
+ }
28
+ /** Runs the project's SuiteCloud CLI from the SDF project folder. */
29
+ function suitecloud(project, sdfDir, args, layered) {
30
+ const env = { ...process.env, ...layered };
31
+ // Hand the passkey to the CLI for this child process only.
32
+ if (env.SUITESCRIPT_VERIFY_PASSKEY) {
33
+ env.SUITECLOUD_CI_PASSKEY = env.SUITESCRIPT_VERIFY_PASSKEY;
34
+ }
35
+ if (env.SUITECLOUD_CI_PASSKEY)
36
+ env.SUITECLOUD_CI = "1";
37
+ const result = spawnSync(process.execPath, [suitecloudEntry(project), ...args], { cwd: sdfDir, env, stdio: "inherit" });
38
+ return result.status ?? 1;
39
+ }
40
+ /**
41
+ * A write to the sandbox needs a person at a terminal, on top of --approved:
42
+ * an agent's shell has none, so an agent cannot run it. The person types the
43
+ * action name to confirm.
44
+ */
45
+ async function confirmWrite(action, what) {
46
+ if (!process.stdin.isTTY) {
47
+ throw new UsageError(`${what} needs the user at a terminal. Ask the user to run this same command, with --approved, in their own terminal.`);
48
+ }
49
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
50
+ const answer = await rl.question(`${what}. Type ${action} to continue: `);
51
+ rl.close();
52
+ if (answer.trim() !== action) {
53
+ throw new UsageError("Not confirmed. Nothing was changed.");
54
+ }
55
+ }
56
+ /** The value after `--dir`, when given. */
57
+ function dirFlag(args) {
58
+ const flag = args.indexOf("--dir");
59
+ if (flag < 0)
60
+ return undefined;
61
+ const value = args[flag + 1];
62
+ if (!value)
63
+ throw new UsageError("--dir needs a path.");
64
+ return value;
65
+ }
66
+ /** Loads the layered configuration; a problem stops every command except the reporting ones. */
67
+ function configure(args, refuse) {
68
+ const config = loadConfig(process.cwd(), dirFlag(args));
69
+ if (refuse && config.problems.length > 0) {
70
+ throw new UsageError(`Refusing to run:\n${config.problems.map((p) => `- ${p.message}`).join("\n")}`);
71
+ }
72
+ return config;
73
+ }
74
+ /** Loads the configuration for `doctor` and `init`, which report problems instead of refusing. */
75
+ function reporting(args) {
76
+ const config = configure(args, false);
77
+ if (config.problems.length > 0) {
78
+ console.log("Configuration problems:");
79
+ for (const problem of config.problems)
80
+ console.log(`- ${problem.message}`);
81
+ console.log("");
82
+ }
83
+ return config;
84
+ }
85
+ function doctor(args) {
86
+ const config = reporting(args);
87
+ const checks = runChecks(process.cwd(), config);
88
+ console.log(formatChecks(checks));
89
+ return config.problems.length === 0 && checks.every((c) => c.ok) ? 0 : 1;
90
+ }
91
+ function initCommand(args) {
92
+ console.log(init(process.cwd(), reporting(args)));
93
+ return 0;
94
+ }
95
+ async function deploy(args) {
96
+ const project = process.cwd();
97
+ const config = configure(args, true);
98
+ const approved = args.includes("--approved");
99
+ if (approved)
100
+ await confirmWrite("DEPLOY", "This deploys to the sandbox");
101
+ else {
102
+ console.log("Dry run only. Deploying writes to the sandbox, so ask the user for approval, then rerun with --approved.");
103
+ }
104
+ return suitecloud(project, config.sdfDir, ["project:deploy", ...(approved ? [] : ["--dryrun"])], config.values);
105
+ }
106
+ export async function main(args) {
107
+ const [command, ...rest] = args;
108
+ try {
109
+ if (command === undefined || command === "help" || command === "--help") {
110
+ console.log(USAGE);
111
+ return 0;
112
+ }
113
+ if (command === "deploy")
114
+ return await deploy(rest);
115
+ if (command === "doctor")
116
+ return doctor(rest);
117
+ if (command === "init")
118
+ return initCommand(rest);
119
+ throw new UsageError(`Unknown command "${command}".\n\n${USAGE}`);
120
+ }
121
+ catch (error) {
122
+ if (error instanceof UsageError) {
123
+ console.error(error.message);
124
+ return 1;
125
+ }
126
+ throw error;
127
+ }
128
+ }
package/dist/config.js ADDED
@@ -0,0 +1,120 @@
1
+ import { spawnSync } from "node:child_process";
2
+ import { existsSync, readFileSync } from "node:fs";
3
+ import { isAbsolute, join, relative, resolve } from "node:path";
4
+ const prefix = "SUITESCRIPT_VERIFY_";
5
+ /** Layout keys: the only ones the committed `.env` may hold. Each is a path in the project. */
6
+ export const layoutKeys = [
7
+ "SUITESCRIPT_VERIFY_PROBES_DIR",
8
+ "SUITESCRIPT_VERIFY_RESULTS_DIR",
9
+ "SUITESCRIPT_VERIFY_SDF_DIR",
10
+ ];
11
+ const unquote = (raw) => {
12
+ const value = raw.trim();
13
+ const quote = value[0];
14
+ if (value.length >= 2 &&
15
+ (quote === '"' || quote === "'") &&
16
+ value.endsWith(quote)) {
17
+ return value.slice(1, -1);
18
+ }
19
+ return value;
20
+ };
21
+ /** Reads `NAME=value` lines; comments and other lines are ignored. */
22
+ export function parseEnv(text) {
23
+ const values = new Map();
24
+ for (const line of text.split(/\r?\n/)) {
25
+ const match = /^\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=(.*)$/.exec(line);
26
+ if (match)
27
+ values.set(match[1] ?? "", unquote(match[2] ?? ""));
28
+ }
29
+ return values;
30
+ }
31
+ function readEnvFile(path) {
32
+ return existsSync(path)
33
+ ? parseEnv(readFileSync(path, "utf8"))
34
+ : new Map();
35
+ }
36
+ const insideProject = (project, path) => {
37
+ const rel = relative(project, resolve(project, path));
38
+ return !isAbsolute(rel) && rel.split(/[\\/]/)[0] !== "..";
39
+ };
40
+ /** A value a person would not write as a folder: a PEM block, a JWT or a long token. */
41
+ const looksSecret = (value) => /-----BEGIN /.test(value) ||
42
+ /^eyJ[\w-]+\.[\w-]+\.[\w-]+$/.test(value) ||
43
+ /^[A-Za-z0-9+_=-]{32,}$/.test(value);
44
+ /** Checks the committed `.env` against the allowlist. Messages name the key, never the value. */
45
+ function checkCommitted(project, committed, file) {
46
+ const problems = [];
47
+ for (const [key, value] of committed) {
48
+ if (!layoutKeys.includes(key)) {
49
+ problems.push({
50
+ message: `${key} in ${file} is not a layout key. The committed file holds layout only (${layoutKeys.join(", ")}); put anything else in .env.local or the process environment.`,
51
+ });
52
+ }
53
+ else if (!insideProject(project, value)) {
54
+ problems.push({
55
+ message: `${key} in ${file} points outside the project. Use a path inside the project.`,
56
+ });
57
+ }
58
+ else if (looksSecret(value)) {
59
+ problems.push({
60
+ message: `${key} in ${file} looks like a secret, not a path. The committed file is public: move a secret to .env.local.`,
61
+ });
62
+ }
63
+ }
64
+ return problems;
65
+ }
66
+ /** `.env.local` holds credentials: it must be gitignored and never tracked. */
67
+ function checkLocalIgnored(project, localFile) {
68
+ const git = (args) => spawnSync("git", args, { cwd: project, encoding: "utf8" });
69
+ const rel = relative(project, localFile).replaceAll("\\", "/");
70
+ if (git(["rev-parse", "--is-inside-work-tree"]).status !== 0)
71
+ return [];
72
+ const problems = [];
73
+ if (git(["ls-files", "--error-unmatch", "--", rel]).status === 0) {
74
+ problems.push({
75
+ message: `${rel} is tracked by git, so its credentials are in the repository. Run git rm --cached ${rel} and rotate what it held.`,
76
+ });
77
+ }
78
+ if (git(["check-ignore", "-q", "--", rel]).status !== 0) {
79
+ problems.push({
80
+ message: `${rel} is not gitignored. Add it to .gitignore before saving any value: it holds credentials.`,
81
+ });
82
+ }
83
+ return problems;
84
+ }
85
+ /**
86
+ * Layers defaults, the committed `.env`, the gitignored `.env.local` and the
87
+ * process environment (later wins). The root of the env files is `dir`
88
+ * (default `verification`), relative to the project.
89
+ */
90
+ export function loadConfig(project, dir, processEnv = process.env) {
91
+ const root = resolve(project, dir ?? "verification");
92
+ const envFile = join(root, ".env");
93
+ const localFile = join(root, ".env.local");
94
+ const committed = readEnvFile(envFile);
95
+ const values = {};
96
+ const layers = [
97
+ committed,
98
+ readEnvFile(localFile),
99
+ Object.entries(processEnv),
100
+ ];
101
+ for (const layer of layers) {
102
+ for (const [key, value] of layer) {
103
+ if (key.startsWith(prefix) && value !== undefined)
104
+ values[key] = value;
105
+ }
106
+ }
107
+ const path = (key, fallback) => resolve(project, values[key] || fallback);
108
+ const shown = relative(project, envFile).replaceAll("\\", "/");
109
+ return {
110
+ probesDir: path("SUITESCRIPT_VERIFY_PROBES_DIR", join(root, "probes")),
111
+ problems: [
112
+ ...checkCommitted(project, committed, shown),
113
+ ...checkLocalIgnored(project, localFile),
114
+ ],
115
+ resultsDir: path("SUITESCRIPT_VERIFY_RESULTS_DIR", "docs/verification/results"),
116
+ root,
117
+ sdfDir: path("SUITESCRIPT_VERIFY_SDF_DIR", join(root, "sdf")),
118
+ values,
119
+ };
120
+ }
package/dist/doctor.js ADDED
@@ -0,0 +1,97 @@
1
+ import { createPrivateKey } from "node:crypto";
2
+ import { existsSync, readFileSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ const names = {
5
+ account: "SUITESCRIPT_VERIFY_ACCOUNT",
6
+ certificateId: "SUITESCRIPT_VERIFY_CERTIFICATE_ID",
7
+ clientId: "SUITESCRIPT_VERIFY_CLIENT_ID",
8
+ passkey: "SUITESCRIPT_VERIFY_PASSKEY",
9
+ privateKeyPath: "SUITESCRIPT_VERIFY_PRIVATE_KEY_PATH",
10
+ sdfCertificateId: "SUITESCRIPT_VERIFY_SDF_CERTIFICATE_ID",
11
+ sdfPrivateKeyPath: "SUITESCRIPT_VERIFY_SDF_PRIVATE_KEY_PATH",
12
+ };
13
+ /** `ec:<curve>`, `rsa:<bits>` or `other`, from a private key file. Never the key itself. */
14
+ function keyType(path) {
15
+ try {
16
+ const key = createPrivateKey(readFileSync(path));
17
+ const details = key.asymmetricKeyDetails;
18
+ if (key.asymmetricKeyType === "ec")
19
+ return `ec:${details?.namedCurve}`;
20
+ if (key.asymmetricKeyType === "rsa")
21
+ return `rsa:${details?.modulusLength}`;
22
+ }
23
+ catch {
24
+ // Unreadable or not a private key.
25
+ }
26
+ return "other";
27
+ }
28
+ /** A sandbox account id looks like `1234567_SB1`. Verification never runs in production. */
29
+ export const isSandboxAccount = (account) => /^\d+_SB\d*$/i.test(account);
30
+ /** Read-only checklist: each failing step says what to do next and who does it. */
31
+ export function runChecks(project, config, env = process.env) {
32
+ const checks = [];
33
+ const add = (name, ok, owner, next) => checks.push({ name, next, ok, owner });
34
+ const v = config.values;
35
+ add("SuiteCloud CLI (@oracle/suitecloud-cli) is installed in the project", existsSync(join(project, "node_modules", "@oracle", "suitecloud-cli", "src", "suitecloud.js")), "maintainer", "Run npm install --save-dev @oracle/suitecloud-cli, read Oracle's license and accept it yourself. This package never accepts it.");
36
+ const missing = [
37
+ names.account,
38
+ names.clientId,
39
+ names.certificateId,
40
+ names.sdfCertificateId,
41
+ ].filter((name) => !v[name]);
42
+ add("Account, client id and both certificate ids are set", missing.length === 0, "maintainer", `Missing: ${missing.join(", ")}. Create the integration record and mapping in your sandbox, then save the values in .env.local (never the committed .env).`);
43
+ add("The account is a sandbox", isSandboxAccount(v[names.account] ?? ""), "maintainer", `${names.account} must look like digits_SBn. Verification never runs in production.`);
44
+ for (const [label, key, expected, accepts] of [
45
+ [
46
+ "runner",
47
+ names.privateKeyPath,
48
+ "an EC P-256 key",
49
+ (t) => t === "ec:prime256v1",
50
+ ],
51
+ [
52
+ "SDF",
53
+ names.sdfPrivateKeyPath,
54
+ "an RSA key of 3072 or 4096 bits",
55
+ (t) => t === "rsa:3072" || t === "rsa:4096",
56
+ ],
57
+ ]) {
58
+ const path = v[key] ?? "";
59
+ const exists = path !== "" && existsSync(path);
60
+ add(`The ${label} private key exists (${key})`, exists, "maintainer", `Generate the key pair outside the repository and set ${key} in .env.local.`);
61
+ if (exists) {
62
+ add(`The ${label} private key is ${expected}`, accepts(keyType(path)), "maintainer", "Generate a key of that type and upload its new public certificate in a new mapping.");
63
+ }
64
+ }
65
+ const passkey = v[names.passkey] ?? env.SUITECLOUD_CI_PASSKEY ?? "";
66
+ add(`${names.passkey} (or SUITECLOUD_CI_PASSKEY) is set, 32 to 100 letters and digits`, /^[A-Za-z0-9]{32,100}$/.test(passkey), "maintainer", `Generate a random passkey and save it as ${names.passkey} in .env.local. Keep a copy somewhere safe: it encrypts the stored credentials. Never set it user-wide.`);
67
+ add("SUITECLOUD_FALLBACK_PASSKEY is not set", !env.SUITECLOUD_FALLBACK_PASSKEY, "maintainer", "Unset it: the CLI refuses to run with both passkeys.");
68
+ const projectJson = join(config.sdfDir, "project.json");
69
+ let authId = "";
70
+ if (existsSync(projectJson)) {
71
+ try {
72
+ authId =
73
+ JSON.parse(readFileSync(projectJson, "utf8")).defaultAuthId ?? "";
74
+ }
75
+ catch {
76
+ // A malformed project.json fails the step.
77
+ }
78
+ }
79
+ const absent = [config.probesDir, config.resultsDir, config.sdfDir].filter((dir) => !existsSync(dir));
80
+ add("Project files: the probe, results and SDF folders exist and the SDF project has a default auth id", absent.length === 0 && authId !== "", absent.length > 0 ? "agent" : "maintainer", absent.length > 0
81
+ ? "Run suitescript-verify init to create the missing folders."
82
+ : "Run account:setup:ci from the SDF folder once the steps above pass, so project.json gets a default auth id.");
83
+ return checks;
84
+ }
85
+ export function formatChecks(checks) {
86
+ const lines = checks.map((c) => c.ok
87
+ ? `ok ${c.name}`
88
+ : `FAIL ${c.name}\n ${c.owner === "maintainer" ? "Maintainer's own step" : "Agent can do this"}: ${c.next ?? ""}`);
89
+ const failed = checks.filter((c) => !c.ok);
90
+ if (failed.length === 0)
91
+ lines.push("\nAll checks passed.");
92
+ else {
93
+ const own = failed.filter((c) => c.owner === "maintainer").length;
94
+ lines.push(`\n${failed.length} check(s) failed.${own > 0 ? ` ${own} are the Maintainer's own: relay each next step to the maintainer and wait. Never accept the license, create credentials or touch an account for them.` : ""}`);
95
+ }
96
+ return lines.join("\n");
97
+ }
package/dist/init.js ADDED
@@ -0,0 +1,35 @@
1
+ import { existsSync, mkdirSync, writeFileSync } from "node:fs";
2
+ import { dirname, join, relative } from "node:path";
3
+ /**
4
+ * Scaffolds the folders and a committed `.env` of layout keys, then returns
5
+ * the to-do list for the person. Creates no credential and installs nothing.
6
+ */
7
+ export function init(project, config) {
8
+ const rel = (path) => relative(project, path).replaceAll("\\", "/");
9
+ const dirs = [config.probesDir, config.resultsDir, config.sdfDir];
10
+ for (const dir of dirs)
11
+ mkdirSync(dir, { recursive: true });
12
+ const envFile = join(config.root, ".env");
13
+ const created = !existsSync(envFile);
14
+ if (created) {
15
+ mkdirSync(dirname(envFile), { recursive: true });
16
+ writeFileSync(envFile, [
17
+ "# Layout only: this file is committed. Credentials go in .env.local, which is gitignored.",
18
+ `SUITESCRIPT_VERIFY_PROBES_DIR=${rel(config.probesDir)}`,
19
+ `SUITESCRIPT_VERIFY_RESULTS_DIR=${rel(config.resultsDir)}`,
20
+ `SUITESCRIPT_VERIFY_SDF_DIR=${rel(config.sdfDir)}`,
21
+ "",
22
+ ].join("\n"));
23
+ }
24
+ const local = rel(join(config.root, ".env.local"));
25
+ return [
26
+ `${created ? "Created" : "Kept"} ${rel(envFile)} and the folders ${dirs.map(rel).join(", ")}. No credentials were created.`,
27
+ "",
28
+ "To do (each step is yours, the maintainer's):",
29
+ "1. Install the SuiteCloud CLI: npm install --save-dev @oracle/suitecloud-cli. Read Oracle's license and accept it yourself; this package never does.",
30
+ `2. Add ${local} to .gitignore before saving any value: it holds credentials.`,
31
+ "3. In a sandbox, create the integration record, the role and the certificate mappings, and generate the private keys outside the repository.",
32
+ `4. Save the account, client id, certificate ids, key paths and a passkey in ${local} (see docs/verification/README.md).`,
33
+ "5. Run suitescript-verify doctor until every step passes.",
34
+ ].join("\n");
35
+ }
package/package.json CHANGED
@@ -1,6 +1,76 @@
1
1
  {
2
2
  "name": "suitescript-verify",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.0.0",
4
+ "description": "Run probes in a NetSuite sandbox to verify what SuiteScript modules do, with guardrails for agents.",
5
+ "keywords": [
6
+ "suitescript",
7
+ "netsuite",
8
+ "verification",
9
+ "probe",
10
+ "suitecloud"
11
+ ],
12
+ "homepage": "https://github.com/nemchik/suitescript-verify#readme",
13
+ "bugs": "https://github.com/nemchik/suitescript-verify/issues",
14
+ "repository": {
15
+ "type": "git",
16
+ "url": "git+https://github.com/nemchik/suitescript-verify.git"
17
+ },
18
+ "license": "MIT",
19
+ "author": "Eric Nemchik",
20
+ "type": "module",
21
+ "bin": {
22
+ "suitescript-verify": "./dist/bin/suitescript-verify.js"
23
+ },
24
+ "files": [
25
+ "dist",
26
+ "README.md",
27
+ "LICENSE"
28
+ ],
29
+ "scripts": {
30
+ "prebuild": "node -e \"require(\\\"node:fs\\\").rmSync(\\\"dist\\\",{recursive:true,force:true})\"",
31
+ "build": "tsc -p tsconfig.build.json",
32
+ "check": "biome check",
33
+ "preformat": "npm run typecheck",
34
+ "format": "biome check --write",
35
+ "lint": "biome lint",
36
+ "prepack": "npm run build",
37
+ "skills": "node scripts/skills.ts",
38
+ "skills:add": "node scripts/skills.ts add --skill \"*\" --agent universal --agent claude-code --yes",
39
+ "skills:check": "node scripts/skills.ts check",
40
+ "skills:install": "node scripts/skills.ts experimental_install && node scripts/link-claude-skills.ts",
41
+ "skills:list": "node scripts/skills.ts list",
42
+ "skills:update": "node scripts/skills.ts update --project --yes && node scripts/link-claude-skills.ts",
43
+ "pretest": "npm run build",
44
+ "test": "node --test test/*.test.ts",
45
+ "typecheck": "tsc"
46
+ },
47
+ "devDependencies": {
48
+ "@biomejs/biome": "2.6.6",
49
+ "@types/node": "24.19.2",
50
+ "git-pull-run": "2.1.0",
51
+ "lefthook": "2.2.2",
52
+ "skills": "1.7.6",
53
+ "typescript": "7.0.2"
54
+ },
55
+ "peerDependencies": {
56
+ "@oracle/suitecloud-cli": "^4.1.1"
57
+ },
58
+ "engines": {
59
+ "node": ">=24"
60
+ },
61
+ "devEngines": {
62
+ "runtime": {
63
+ "name": "node",
64
+ "version": "24.22.3",
65
+ "onFail": "error"
66
+ },
67
+ "packageManager": {
68
+ "name": "npm",
69
+ "version": "12.2.2",
70
+ "onFail": "warn"
71
+ }
72
+ },
73
+ "allowScripts": {
74
+ "lefthook": true
75
+ }
76
+ }