planmode 0.1.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/package.json ADDED
@@ -0,0 +1,46 @@
1
+ {
2
+ "name": "planmode",
3
+ "version": "0.1.0",
4
+ "description": "The open source package manager for AI plans, rules, and prompts.",
5
+ "type": "module",
6
+ "bin": {
7
+ "planmode": "./dist/index.js"
8
+ },
9
+ "scripts": {
10
+ "build": "tsup",
11
+ "dev": "tsup --watch",
12
+ "test": "vitest run",
13
+ "test:watch": "vitest",
14
+ "lint": "eslint src/",
15
+ "typecheck": "tsc --noEmit"
16
+ },
17
+ "dependencies": {
18
+ "commander": "^13.1.0",
19
+ "handlebars": "^4.7.8",
20
+ "simple-git": "^3.27.0",
21
+ "yaml": "^2.7.0"
22
+ },
23
+ "devDependencies": {
24
+ "@types/node": "^22.13.0",
25
+ "tsup": "^8.4.0",
26
+ "typescript": "^5.7.0",
27
+ "vitest": "^3.0.0"
28
+ },
29
+ "engines": {
30
+ "node": ">=20"
31
+ },
32
+ "license": "MIT",
33
+ "repository": {
34
+ "type": "git",
35
+ "url": "https://github.com/kaihannonen/planmode.org"
36
+ },
37
+ "keywords": [
38
+ "planmode",
39
+ "ai",
40
+ "plans",
41
+ "prompts",
42
+ "rules",
43
+ "claude",
44
+ "package-manager"
45
+ ]
46
+ }
@@ -0,0 +1,61 @@
1
+ import { Command } from "commander";
2
+ import { fetchPackageMetadata } from "../lib/registry.js";
3
+ import { logger } from "../lib/logger.js";
4
+
5
+ export const infoCommand = new Command("info")
6
+ .description("Show detailed info about a package")
7
+ .argument("<package>", "Package name")
8
+ .action(async (packageName: string) => {
9
+ try {
10
+ const meta = await fetchPackageMetadata(packageName);
11
+
12
+ logger.blank();
13
+ logger.bold(`${meta.name}@${meta.latest_version}`);
14
+ logger.blank();
15
+
16
+ console.log(` Description: ${meta.description}`);
17
+ console.log(` Type: ${meta.type}`);
18
+ console.log(` Author: ${meta.author}`);
19
+ console.log(` License: ${meta.license}`);
20
+ console.log(` Category: ${meta.category}`);
21
+ console.log(` Downloads: ${meta.downloads.toLocaleString()}`);
22
+ console.log(` Repository: ${meta.repository}`);
23
+
24
+ if (meta.models && meta.models.length > 0) {
25
+ console.log(` Models: ${meta.models.join(", ")}`);
26
+ }
27
+
28
+ if (meta.tags && meta.tags.length > 0) {
29
+ console.log(` Tags: ${meta.tags.join(", ")}`);
30
+ }
31
+
32
+ console.log(` Versions: ${meta.versions.join(", ")}`);
33
+
34
+ if (meta.dependencies) {
35
+ if (meta.dependencies.rules && meta.dependencies.rules.length > 0) {
36
+ console.log(` Dep (rules): ${meta.dependencies.rules.join(", ")}`);
37
+ }
38
+ if (meta.dependencies.plans && meta.dependencies.plans.length > 0) {
39
+ console.log(` Dep (plans): ${meta.dependencies.plans.join(", ")}`);
40
+ }
41
+ }
42
+
43
+ if (meta.variables) {
44
+ logger.blank();
45
+ logger.bold(" Variables:");
46
+ for (const [name, def] of Object.entries(meta.variables)) {
47
+ const required = def.required ? " (required)" : "";
48
+ const defaultVal = def.default !== undefined ? ` [default: ${def.default}]` : "";
49
+ console.log(` ${name}: ${def.type}${required}${defaultVal} — ${def.description}`);
50
+ if (def.options) {
51
+ console.log(` options: ${def.options.join(", ")}`);
52
+ }
53
+ }
54
+ }
55
+
56
+ logger.blank();
57
+ } catch (err) {
58
+ logger.error((err as Error).message);
59
+ process.exit(1);
60
+ }
61
+ });
@@ -0,0 +1,85 @@
1
+ import { Command } from "commander";
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+ import { stringify } from "yaml";
5
+ import { logger } from "../lib/logger.js";
6
+ import type { PackageType, Category } from "../types/index.js";
7
+
8
+ async function prompt(question: string): Promise<string> {
9
+ const { createInterface } = await import("node:readline");
10
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
11
+ return new Promise((resolve) => {
12
+ rl.question(question, (answer) => {
13
+ rl.close();
14
+ resolve(answer.trim());
15
+ });
16
+ });
17
+ }
18
+
19
+ export const initCommand = new Command("init")
20
+ .description("Initialize a new package in the current directory")
21
+ .action(async () => {
22
+ try {
23
+ logger.blank();
24
+ logger.bold("Initialize a new Planmode package");
25
+ logger.blank();
26
+
27
+ const name = await prompt("Package name: ");
28
+ if (!name) {
29
+ logger.error("Package name is required.");
30
+ process.exit(1);
31
+ }
32
+
33
+ const typeInput = await prompt("Type (plan/rule/prompt) [plan]: ");
34
+ const type = (typeInput || "plan") as PackageType;
35
+
36
+ const description = await prompt("Description: ");
37
+ const author = await prompt("Author (GitHub username): ");
38
+ const license = await prompt("License [MIT]: ") || "MIT";
39
+ const tagsInput = await prompt("Tags (comma-separated): ");
40
+ const tags = tagsInput ? tagsInput.split(",").map((t) => t.trim().toLowerCase()) : [];
41
+ const category = (await prompt("Category (frontend/backend/devops/database/testing/mobile/ai-ml/security/other) [other]: ") || "other") as Category;
42
+
43
+ // Build manifest
44
+ const manifest: Record<string, unknown> = {
45
+ name,
46
+ version: "1.0.0",
47
+ type,
48
+ description,
49
+ author,
50
+ license,
51
+ };
52
+
53
+ if (tags.length > 0) manifest["tags"] = tags;
54
+ manifest["category"] = category;
55
+
56
+ const contentFile = `${type}.md`;
57
+ manifest["content_file"] = contentFile;
58
+
59
+ // Write planmode.yaml
60
+ const yamlContent = stringify(manifest);
61
+ fs.writeFileSync(path.join(process.cwd(), "planmode.yaml"), yamlContent, "utf-8");
62
+ logger.success("Created planmode.yaml");
63
+
64
+ // Write stub content file
65
+ const stubs: Record<string, string> = {
66
+ plan: `# ${name}\n\n1. First step\n2. Second step\n3. Third step\n`,
67
+ rule: `- Rule one\n- Rule two\n- Rule three\n`,
68
+ prompt: `Write your prompt here.\n\nUse {{variable_name}} for template variables.\n`,
69
+ };
70
+
71
+ fs.writeFileSync(
72
+ path.join(process.cwd(), contentFile),
73
+ stubs[type] ?? stubs["plan"]!,
74
+ "utf-8",
75
+ );
76
+ logger.success(`Created ${contentFile}`);
77
+
78
+ logger.blank();
79
+ logger.info(`Edit ${contentFile}, then run \`planmode publish\` when ready.`);
80
+ logger.blank();
81
+ } catch (err) {
82
+ logger.error((err as Error).message);
83
+ process.exit(1);
84
+ }
85
+ });
@@ -0,0 +1,29 @@
1
+ import { Command } from "commander";
2
+ import { installPackage } from "../lib/installer.js";
3
+ import { logger } from "../lib/logger.js";
4
+
5
+ export const installCommand = new Command("install")
6
+ .description("Install a package into the current project")
7
+ .argument("<package>", "Package name (e.g., nextjs-tailwind-starter)")
8
+ .option("-v, --version <version>", "Install specific version")
9
+ .option("--rule", "Force install as a rule to .claude/rules/")
10
+ .option("--no-input", "Fail if any required variable is missing")
11
+ .action(
12
+ async (
13
+ packageName: string,
14
+ options: { version?: string; rule?: boolean; input?: boolean },
15
+ ) => {
16
+ try {
17
+ logger.blank();
18
+ await installPackage(packageName, {
19
+ version: options.version,
20
+ forceRule: options.rule,
21
+ noInput: options.input === false,
22
+ });
23
+ logger.blank();
24
+ } catch (err) {
25
+ logger.error((err as Error).message);
26
+ process.exit(1);
27
+ }
28
+ },
29
+ );
@@ -0,0 +1,27 @@
1
+ import { Command } from "commander";
2
+ import { readLockfile } from "../lib/lockfile.js";
3
+ import { logger } from "../lib/logger.js";
4
+
5
+ export const listCommand = new Command("list")
6
+ .description("List all installed packages")
7
+ .action(() => {
8
+ const lockfile = readLockfile();
9
+ const entries = Object.entries(lockfile.packages);
10
+
11
+ if (entries.length === 0) {
12
+ logger.info("No packages installed. Run `planmode install <package>` to get started.");
13
+ return;
14
+ }
15
+
16
+ logger.blank();
17
+ logger.table(
18
+ ["name", "type", "version", "location"],
19
+ entries.map(([name, entry]) => [
20
+ name,
21
+ entry.type,
22
+ entry.version,
23
+ entry.installed_to,
24
+ ]),
25
+ );
26
+ logger.blank();
27
+ });
@@ -0,0 +1,56 @@
1
+ import { Command } from "commander";
2
+ import { execSync } from "node:child_process";
3
+ import { setGitHubToken, getGitHubToken } from "../lib/config.js";
4
+ import { logger } from "../lib/logger.js";
5
+
6
+ export const loginCommand = new Command("login")
7
+ .description("Configure GitHub authentication")
8
+ .option("--token <token>", "GitHub personal access token")
9
+ .option("--gh", "Read token from GitHub CLI (gh auth token)")
10
+ .action(async (options: { token?: string; gh?: boolean }) => {
11
+ let token: string | undefined;
12
+
13
+ if (options.token) {
14
+ token = options.token;
15
+ } else if (options.gh) {
16
+ try {
17
+ token = execSync("gh auth token", { encoding: "utf-8" }).trim();
18
+ } catch {
19
+ logger.error("Failed to read token from GitHub CLI. Make sure `gh` is installed and authenticated.");
20
+ process.exit(1);
21
+ }
22
+ } else {
23
+ // Interactive prompt via stdin
24
+ const { createInterface } = await import("node:readline");
25
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
26
+ token = await new Promise<string>((resolve) => {
27
+ rl.question("GitHub personal access token: ", (answer) => {
28
+ rl.close();
29
+ resolve(answer.trim());
30
+ });
31
+ });
32
+ }
33
+
34
+ if (!token) {
35
+ logger.error("No token provided.");
36
+ process.exit(1);
37
+ }
38
+
39
+ // Validate token
40
+ logger.info("Validating token...");
41
+ const response = await fetch("https://api.github.com/user", {
42
+ headers: {
43
+ Authorization: `Bearer ${token}`,
44
+ "User-Agent": "planmode-cli",
45
+ },
46
+ });
47
+
48
+ if (!response.ok) {
49
+ logger.error("Invalid token. GitHub API returned: " + response.status);
50
+ process.exit(1);
51
+ }
52
+
53
+ const user = (await response.json()) as { login: string };
54
+ setGitHubToken(token);
55
+ logger.success(`Authenticated as ${user.login}`);
56
+ });
@@ -0,0 +1,204 @@
1
+ import { Command } from "commander";
2
+ import { readManifest, validateManifest } from "../lib/manifest.js";
3
+ import { getGitHubToken } from "../lib/config.js";
4
+ import { getRemoteUrl, getHeadSha, createTag, pushTag } from "../lib/git.js";
5
+ import { logger } from "../lib/logger.js";
6
+
7
+ export const publishCommand = new Command("publish")
8
+ .description("Publish the current directory as a package to the registry")
9
+ .action(async () => {
10
+ try {
11
+ const cwd = process.cwd();
12
+
13
+ // Check auth
14
+ const token = getGitHubToken();
15
+ if (!token) {
16
+ logger.error("Not authenticated. Run `planmode login` first.");
17
+ process.exit(1);
18
+ }
19
+
20
+ // Read and validate manifest
21
+ logger.info("Reading planmode.yaml...");
22
+ const manifest = readManifest(cwd);
23
+ const errors = validateManifest(manifest, true);
24
+ if (errors.length > 0) {
25
+ logger.error("Invalid manifest:");
26
+ for (const err of errors) {
27
+ console.log(` - ${err}`);
28
+ }
29
+ process.exit(1);
30
+ }
31
+
32
+ // Check git remote
33
+ const remoteUrl = await getRemoteUrl(cwd);
34
+ if (!remoteUrl) {
35
+ logger.error("No git remote found. Push your code to GitHub first.");
36
+ process.exit(1);
37
+ }
38
+
39
+ const sha = await getHeadSha(cwd);
40
+ const tag = `v${manifest.version}`;
41
+
42
+ // Create and push tag
43
+ logger.info(`Creating tag ${tag}...`);
44
+ try {
45
+ await createTag(cwd, tag);
46
+ } catch {
47
+ logger.dim(`Tag ${tag} already exists, using existing`);
48
+ }
49
+
50
+ try {
51
+ await pushTag(cwd, tag);
52
+ logger.success(`Pushed tag ${tag}`);
53
+ } catch {
54
+ logger.dim(`Tag ${tag} already pushed`);
55
+ }
56
+
57
+ // Fork registry and create PR via GitHub API
58
+ logger.info("Submitting to registry...");
59
+
60
+ const headers = {
61
+ Authorization: `Bearer ${token}`,
62
+ Accept: "application/vnd.github.v3+json",
63
+ "User-Agent": "planmode-cli",
64
+ "Content-Type": "application/json",
65
+ };
66
+
67
+ // Fork the registry repo (idempotent)
68
+ await fetch("https://api.github.com/repos/planmode/registry/forks", {
69
+ method: "POST",
70
+ headers,
71
+ });
72
+
73
+ // Get authenticated user
74
+ const userRes = await fetch("https://api.github.com/user", { headers });
75
+ const user = (await userRes.json()) as { login: string };
76
+
77
+ // Create metadata files content
78
+ const metadataContent = JSON.stringify(
79
+ {
80
+ name: manifest.name,
81
+ description: manifest.description,
82
+ author: manifest.author,
83
+ license: manifest.license,
84
+ repository: remoteUrl
85
+ .replace(/^https?:\/\//, "")
86
+ .replace(/\.git$/, ""),
87
+ category: manifest.category ?? "other",
88
+ tags: manifest.tags ?? [],
89
+ type: manifest.type,
90
+ models: manifest.models ?? [],
91
+ latest_version: manifest.version,
92
+ versions: [manifest.version],
93
+ downloads: 0,
94
+ created_at: new Date().toISOString(),
95
+ updated_at: new Date().toISOString(),
96
+ dependencies: manifest.dependencies,
97
+ variables: manifest.variables,
98
+ },
99
+ null,
100
+ 2,
101
+ );
102
+
103
+ const versionContent = JSON.stringify(
104
+ {
105
+ version: manifest.version,
106
+ published_at: new Date().toISOString(),
107
+ source: {
108
+ repository: remoteUrl
109
+ .replace(/^https?:\/\//, "")
110
+ .replace(/\.git$/, ""),
111
+ tag,
112
+ sha,
113
+ },
114
+ files: ["planmode.yaml", manifest.content_file ?? "inline"],
115
+ content_hash: `sha256:${sha.slice(0, 16)}`,
116
+ },
117
+ null,
118
+ 2,
119
+ );
120
+
121
+ // Create branch on fork
122
+ const branchName = `add-${manifest.name}-${manifest.version}`;
123
+
124
+ // Get main branch ref
125
+ const refRes = await fetch(
126
+ `https://api.github.com/repos/${user.login}/registry/git/ref/heads/main`,
127
+ { headers },
128
+ );
129
+
130
+ if (!refRes.ok) {
131
+ logger.error("Failed to access registry fork. Make sure the fork exists.");
132
+ process.exit(1);
133
+ }
134
+
135
+ const refData = (await refRes.json()) as { object: { sha: string } };
136
+ const baseSha = refData.object.sha;
137
+
138
+ // Create branch
139
+ await fetch(`https://api.github.com/repos/${user.login}/registry/git/refs`, {
140
+ method: "POST",
141
+ headers,
142
+ body: JSON.stringify({
143
+ ref: `refs/heads/${branchName}`,
144
+ sha: baseSha,
145
+ }),
146
+ });
147
+
148
+ // Create metadata.json
149
+ await fetch(
150
+ `https://api.github.com/repos/${user.login}/registry/contents/packages/${manifest.name}/metadata.json`,
151
+ {
152
+ method: "PUT",
153
+ headers,
154
+ body: JSON.stringify({
155
+ message: `Add ${manifest.name}@${manifest.version}`,
156
+ content: Buffer.from(metadataContent).toString("base64"),
157
+ branch: branchName,
158
+ }),
159
+ },
160
+ );
161
+
162
+ // Create version file
163
+ await fetch(
164
+ `https://api.github.com/repos/${user.login}/registry/contents/packages/${manifest.name}/versions/${manifest.version}.json`,
165
+ {
166
+ method: "PUT",
167
+ headers,
168
+ body: JSON.stringify({
169
+ message: `Add ${manifest.name}@${manifest.version} version metadata`,
170
+ content: Buffer.from(versionContent).toString("base64"),
171
+ branch: branchName,
172
+ }),
173
+ },
174
+ );
175
+
176
+ // Create PR
177
+ const prRes = await fetch("https://api.github.com/repos/planmode/registry/pulls", {
178
+ method: "POST",
179
+ headers,
180
+ body: JSON.stringify({
181
+ title: `Add ${manifest.name}@${manifest.version}`,
182
+ head: `${user.login}:${branchName}`,
183
+ base: "main",
184
+ body: `## New package: ${manifest.name}\n\n- **Type:** ${manifest.type}\n- **Version:** ${manifest.version}\n- **Description:** ${manifest.description}\n- **Author:** ${manifest.author}\n\nSubmitted via \`planmode publish\`.`,
185
+ }),
186
+ });
187
+
188
+ if (prRes.ok) {
189
+ const pr = (await prRes.json()) as { html_url: string };
190
+ logger.blank();
191
+ logger.success(`Published ${manifest.name}@${manifest.version}`);
192
+ logger.info(`PR: ${pr.html_url}`);
193
+ } else {
194
+ const err = await prRes.text();
195
+ logger.error(`Failed to create PR: ${err}`);
196
+ process.exit(1);
197
+ }
198
+
199
+ logger.blank();
200
+ } catch (err) {
201
+ logger.error((err as Error).message);
202
+ process.exit(1);
203
+ }
204
+ });
@@ -0,0 +1,87 @@
1
+ import { Command } from "commander";
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+ import { parseManifest, readPackageContent } from "../lib/manifest.js";
5
+ import { renderTemplate, collectVariableValues, resolveVariable } from "../lib/template.js";
6
+ import { logger } from "../lib/logger.js";
7
+
8
+ export const runCommand = new Command("run")
9
+ .description("Run a templated prompt and output to stdout")
10
+ .argument("<prompt>", "Prompt package name")
11
+ .option("--no-input", "Fail if any required variable is missing")
12
+ .option("--json", "Output as JSON")
13
+ .allowUnknownOption(true)
14
+ .action(async (promptName: string, options: { input?: boolean; json?: boolean }, cmd: Command) => {
15
+ try {
16
+ // Parse dynamic --var flags from raw args
17
+ const vars: Record<string, string> = {};
18
+ const rawArgs = cmd.args.slice(0);
19
+ for (let i = 0; i < rawArgs.length; i++) {
20
+ const arg = rawArgs[i]!;
21
+ if (arg.startsWith("--") && arg !== "--no-input" && arg !== "--json") {
22
+ const key = arg.slice(2);
23
+ const value = rawArgs[i + 1];
24
+ if (value && !value.startsWith("--")) {
25
+ vars[key] = value;
26
+ i++;
27
+ }
28
+ }
29
+ }
30
+
31
+ // Look for prompt locally first
32
+ const localPath = path.join(process.cwd(), "prompts", `${promptName}.md`);
33
+ const localManifestPath = path.join(process.cwd(), "prompts", promptName, "planmode.yaml");
34
+
35
+ let content: string;
36
+ let manifest: ReturnType<typeof parseManifest> | undefined;
37
+
38
+ if (fs.existsSync(localManifestPath)) {
39
+ const raw = fs.readFileSync(localManifestPath, "utf-8");
40
+ manifest = parseManifest(raw);
41
+ const dir = path.join(process.cwd(), "prompts", promptName);
42
+ content = readPackageContent(dir, manifest);
43
+ } else if (fs.existsSync(localPath)) {
44
+ content = fs.readFileSync(localPath, "utf-8");
45
+ } else {
46
+ logger.error(`Prompt '${promptName}' not found locally. Install it first: planmode install ${promptName}`);
47
+ process.exit(1);
48
+ }
49
+
50
+ // Resolve variables
51
+ if (manifest?.variables && Object.keys(manifest.variables).length > 0) {
52
+ const values: Record<string, string | number | boolean> = {};
53
+
54
+ // First pass: resolve non-resolved variables
55
+ for (const [name, def] of Object.entries(manifest.variables)) {
56
+ if (def.type === "resolved") continue;
57
+ if (vars[name] !== undefined) {
58
+ values[name] = vars[name]!;
59
+ } else if (def.default !== undefined) {
60
+ values[name] = def.default;
61
+ } else if (def.required && options.input === false) {
62
+ logger.error(`Missing required variable: --${name}`);
63
+ process.exit(1);
64
+ }
65
+ }
66
+
67
+ // Second pass: resolve dynamic variables
68
+ for (const [name, def] of Object.entries(manifest.variables)) {
69
+ if (def.type !== "resolved") continue;
70
+ values[name] = await resolveVariable(def, values);
71
+ }
72
+
73
+ content = renderTemplate(content, values);
74
+
75
+ if (options.json) {
76
+ console.log(JSON.stringify({ rendered: content, variables: values }, null, 2));
77
+ return;
78
+ }
79
+ }
80
+
81
+ // Output rendered content
82
+ process.stdout.write(content);
83
+ } catch (err) {
84
+ logger.error((err as Error).message);
85
+ process.exit(1);
86
+ }
87
+ });
@@ -0,0 +1,45 @@
1
+ import { Command } from "commander";
2
+ import { searchPackages } from "../lib/registry.js";
3
+ import { logger } from "../lib/logger.js";
4
+
5
+ export const searchCommand = new Command("search")
6
+ .description("Search the registry for packages")
7
+ .argument("<query>", "Search query")
8
+ .option("--type <type>", "Filter by type (prompt, rule, plan)")
9
+ .option("--category <category>", "Filter by category")
10
+ .option("--json", "Output as JSON")
11
+ .action(async (query: string, options: { type?: string; category?: string; json?: boolean }) => {
12
+ try {
13
+ const results = await searchPackages(query, {
14
+ type: options.type,
15
+ category: options.category,
16
+ });
17
+
18
+ if (results.length === 0) {
19
+ logger.info("No packages found matching your query.");
20
+ return;
21
+ }
22
+
23
+ if (options.json) {
24
+ console.log(JSON.stringify(results, null, 2));
25
+ return;
26
+ }
27
+
28
+ logger.blank();
29
+ logger.table(
30
+ ["name", "type", "version", "description"],
31
+ results.map((pkg) => [
32
+ pkg.name,
33
+ pkg.type,
34
+ pkg.version,
35
+ pkg.description.length > 50
36
+ ? pkg.description.slice(0, 50) + "..."
37
+ : pkg.description,
38
+ ]),
39
+ );
40
+ logger.blank();
41
+ } catch (err) {
42
+ logger.error((err as Error).message);
43
+ process.exit(1);
44
+ }
45
+ });
@@ -0,0 +1,17 @@
1
+ import { Command } from "commander";
2
+ import { uninstallPackage } from "../lib/installer.js";
3
+ import { logger } from "../lib/logger.js";
4
+
5
+ export const uninstallCommand = new Command("uninstall")
6
+ .description("Remove an installed package")
7
+ .argument("<package>", "Package name")
8
+ .action(async (packageName: string) => {
9
+ try {
10
+ logger.blank();
11
+ await uninstallPackage(packageName);
12
+ logger.blank();
13
+ } catch (err) {
14
+ logger.error((err as Error).message);
15
+ process.exit(1);
16
+ }
17
+ });