gui4cli 0.0.0 → 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/README.md +74 -2
  2. package/bin/gui4cli.js +16 -0
  3. package/dist/cli.d.ts +1 -0
  4. package/dist/cli.js +86 -0
  5. package/dist/config/load.d.ts +13 -0
  6. package/dist/config/load.js +66 -0
  7. package/dist/detect/ast.d.ts +6 -0
  8. package/dist/detect/ast.js +62 -0
  9. package/dist/detect/commander.d.ts +2 -0
  10. package/dist/detect/commander.js +45 -0
  11. package/dist/detect/flags.d.ts +25 -0
  12. package/dist/detect/flags.js +77 -0
  13. package/dist/detect/help.d.ts +3 -0
  14. package/dist/detect/help.js +53 -0
  15. package/dist/detect/index.d.ts +2 -0
  16. package/dist/detect/index.js +61 -0
  17. package/dist/detect/jsdoc.d.ts +2 -0
  18. package/dist/detect/jsdoc.js +22 -0
  19. package/dist/detect/yargs.d.ts +2 -0
  20. package/dist/detect/yargs.js +75 -0
  21. package/dist/errors.d.ts +5 -0
  22. package/dist/errors.js +17 -0
  23. package/dist/generate/project.d.ts +10 -0
  24. package/dist/generate/project.js +77 -0
  25. package/dist/generate/window-app.d.ts +2 -0
  26. package/dist/generate/window-app.js +282 -0
  27. package/dist/generate/window.d.ts +9 -0
  28. package/dist/generate/window.js +56 -0
  29. package/dist/last-run/store.d.ts +4 -0
  30. package/dist/last-run/store.js +23 -0
  31. package/dist/resolve/target.d.ts +4 -0
  32. package/dist/resolve/target.js +54 -0
  33. package/dist/run/argv.d.ts +5 -0
  34. package/dist/run/argv.js +59 -0
  35. package/dist/run/kill.d.ts +1 -0
  36. package/dist/run/kill.js +18 -0
  37. package/dist/schema/form.d.ts +139 -0
  38. package/dist/schema/form.js +51 -0
  39. package/dist/window/env.d.ts +1 -0
  40. package/dist/window/env.js +14 -0
  41. package/dist/window/open.d.ts +2 -0
  42. package/dist/window/open.js +44 -0
  43. package/dist/window/resolve.d.ts +6 -0
  44. package/dist/window/resolve.js +49 -0
  45. package/package.json +26 -4
  46. package/bin/argui.js +0 -4
@@ -0,0 +1,59 @@
1
+ export function valuesFromDefaults(fields) {
2
+ const values = {};
3
+ for (const field of fields) {
4
+ if (field.default !== undefined) {
5
+ values[field.name] = coerce(field, field.default);
6
+ }
7
+ else if (field.type === "boolean") {
8
+ values[field.name] = false;
9
+ }
10
+ else {
11
+ values[field.name] = "";
12
+ }
13
+ }
14
+ return values;
15
+ }
16
+ export function buildArgv(fields, values) {
17
+ const argv = [];
18
+ for (const field of fields) {
19
+ const value = values[field.name];
20
+ if (field.positional) {
21
+ if (value !== undefined && value !== "" && value !== false) {
22
+ argv.push(String(value));
23
+ }
24
+ continue;
25
+ }
26
+ if (field.type === "boolean") {
27
+ if (value === true)
28
+ argv.push(field.longFlag);
29
+ continue;
30
+ }
31
+ if (value === undefined || value === "")
32
+ continue;
33
+ argv.push(field.longFlag, String(value));
34
+ }
35
+ return argv;
36
+ }
37
+ export function previewCommand(nodePath, target, argv, cwd) {
38
+ const quoted = [nodePath, target, ...argv].map(quote).join(" ");
39
+ return `${quoted}\n(in ${cwd})`;
40
+ }
41
+ export function missingRequired(fields, values) {
42
+ return fields
43
+ .filter((field) => field.required && field.type !== "boolean")
44
+ .filter((field) => {
45
+ const value = values[field.name];
46
+ return value === undefined || value === "";
47
+ })
48
+ .map((field) => field.label);
49
+ }
50
+ function coerce(field, value) {
51
+ if (field.type === "number")
52
+ return typeof value === "number" ? value : Number(value);
53
+ if (field.type === "boolean")
54
+ return Boolean(value);
55
+ return String(value);
56
+ }
57
+ function quote(part) {
58
+ return /[\s"]/.test(part) ? `"${part.replaceAll('"', '\\"')}"` : part;
59
+ }
@@ -0,0 +1 @@
1
+ export declare function killProcessTree(pid: number): void;
@@ -0,0 +1,18 @@
1
+ import { spawn } from "node:child_process";
2
+ export function killProcessTree(pid) {
3
+ if (process.platform === "win32") {
4
+ spawn("taskkill", ["/pid", String(pid), "/T", "/F"], { stdio: "ignore", windowsHide: true });
5
+ return;
6
+ }
7
+ try {
8
+ process.kill(-pid, "SIGTERM");
9
+ }
10
+ catch {
11
+ try {
12
+ process.kill(pid, "SIGTERM");
13
+ }
14
+ catch {
15
+ // Already gone.
16
+ }
17
+ }
18
+ }
@@ -0,0 +1,139 @@
1
+ import { z } from "zod";
2
+ export declare const fieldTypeSchema: z.ZodEnum<["string", "number", "boolean", "choice", "file", "directory"]>;
3
+ export type FieldType = z.infer<typeof fieldTypeSchema>;
4
+ export declare const fieldSchema: z.ZodObject<{
5
+ name: z.ZodString;
6
+ longFlag: z.ZodString;
7
+ shortFlag: z.ZodOptional<z.ZodString>;
8
+ type: z.ZodEnum<["string", "number", "boolean", "choice", "file", "directory"]>;
9
+ label: z.ZodString;
10
+ help: z.ZodOptional<z.ZodString>;
11
+ required: z.ZodBoolean;
12
+ default: z.ZodOptional<z.ZodUnion<[z.ZodString, z.ZodNumber, z.ZodBoolean]>>;
13
+ choices: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
14
+ positional: z.ZodOptional<z.ZodBoolean>;
15
+ }, "strip", z.ZodTypeAny, {
16
+ type: "string" | "number" | "boolean" | "choice" | "file" | "directory";
17
+ name: string;
18
+ longFlag: string;
19
+ label: string;
20
+ required: boolean;
21
+ shortFlag?: string | undefined;
22
+ help?: string | undefined;
23
+ default?: string | number | boolean | undefined;
24
+ choices?: string[] | undefined;
25
+ positional?: boolean | undefined;
26
+ }, {
27
+ type: "string" | "number" | "boolean" | "choice" | "file" | "directory";
28
+ name: string;
29
+ longFlag: string;
30
+ label: string;
31
+ required: boolean;
32
+ shortFlag?: string | undefined;
33
+ help?: string | undefined;
34
+ default?: string | number | boolean | undefined;
35
+ choices?: string[] | undefined;
36
+ positional?: boolean | undefined;
37
+ }>;
38
+ export type Field = z.infer<typeof fieldSchema>;
39
+ export declare const detectSourceSchema: z.ZodEnum<["commander", "yargs", "jsdoc", "config", "help", "merged"]>;
40
+ export type DetectSource = z.infer<typeof detectSourceSchema>;
41
+ export declare const formSpecSchema: z.ZodObject<{
42
+ title: z.ZodString;
43
+ description: z.ZodOptional<z.ZodString>;
44
+ target: z.ZodString;
45
+ cwd: z.ZodString;
46
+ source: z.ZodEnum<["commander", "yargs", "jsdoc", "config", "help", "merged"]>;
47
+ fields: z.ZodArray<z.ZodObject<{
48
+ name: z.ZodString;
49
+ longFlag: z.ZodString;
50
+ shortFlag: z.ZodOptional<z.ZodString>;
51
+ type: z.ZodEnum<["string", "number", "boolean", "choice", "file", "directory"]>;
52
+ label: z.ZodString;
53
+ help: z.ZodOptional<z.ZodString>;
54
+ required: z.ZodBoolean;
55
+ default: z.ZodOptional<z.ZodUnion<[z.ZodString, z.ZodNumber, z.ZodBoolean]>>;
56
+ choices: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
57
+ positional: z.ZodOptional<z.ZodBoolean>;
58
+ }, "strip", z.ZodTypeAny, {
59
+ type: "string" | "number" | "boolean" | "choice" | "file" | "directory";
60
+ name: string;
61
+ longFlag: string;
62
+ label: string;
63
+ required: boolean;
64
+ shortFlag?: string | undefined;
65
+ help?: string | undefined;
66
+ default?: string | number | boolean | undefined;
67
+ choices?: string[] | undefined;
68
+ positional?: boolean | undefined;
69
+ }, {
70
+ type: "string" | "number" | "boolean" | "choice" | "file" | "directory";
71
+ name: string;
72
+ longFlag: string;
73
+ label: string;
74
+ required: boolean;
75
+ shortFlag?: string | undefined;
76
+ help?: string | undefined;
77
+ default?: string | number | boolean | undefined;
78
+ choices?: string[] | undefined;
79
+ positional?: boolean | undefined;
80
+ }>, "many">;
81
+ window: z.ZodOptional<z.ZodObject<{
82
+ width: z.ZodOptional<z.ZodNumber>;
83
+ height: z.ZodOptional<z.ZodNumber>;
84
+ }, "strip", z.ZodTypeAny, {
85
+ width?: number | undefined;
86
+ height?: number | undefined;
87
+ }, {
88
+ width?: number | undefined;
89
+ height?: number | undefined;
90
+ }>>;
91
+ }, "strip", z.ZodTypeAny, {
92
+ title: string;
93
+ target: string;
94
+ cwd: string;
95
+ source: "help" | "commander" | "yargs" | "jsdoc" | "config" | "merged";
96
+ fields: {
97
+ type: "string" | "number" | "boolean" | "choice" | "file" | "directory";
98
+ name: string;
99
+ longFlag: string;
100
+ label: string;
101
+ required: boolean;
102
+ shortFlag?: string | undefined;
103
+ help?: string | undefined;
104
+ default?: string | number | boolean | undefined;
105
+ choices?: string[] | undefined;
106
+ positional?: boolean | undefined;
107
+ }[];
108
+ description?: string | undefined;
109
+ window?: {
110
+ width?: number | undefined;
111
+ height?: number | undefined;
112
+ } | undefined;
113
+ }, {
114
+ title: string;
115
+ target: string;
116
+ cwd: string;
117
+ source: "help" | "commander" | "yargs" | "jsdoc" | "config" | "merged";
118
+ fields: {
119
+ type: "string" | "number" | "boolean" | "choice" | "file" | "directory";
120
+ name: string;
121
+ longFlag: string;
122
+ label: string;
123
+ required: boolean;
124
+ shortFlag?: string | undefined;
125
+ help?: string | undefined;
126
+ default?: string | number | boolean | undefined;
127
+ choices?: string[] | undefined;
128
+ positional?: boolean | undefined;
129
+ }[];
130
+ description?: string | undefined;
131
+ window?: {
132
+ width?: number | undefined;
133
+ height?: number | undefined;
134
+ } | undefined;
135
+ }>;
136
+ export type FormSpec = z.infer<typeof formSpecSchema>;
137
+ export declare const formValuesSchema: z.ZodRecord<z.ZodString, z.ZodUnion<[z.ZodString, z.ZodNumber, z.ZodBoolean]>>;
138
+ export type FormValues = z.infer<typeof formValuesSchema>;
139
+ export declare function labelFromName(name: string): string;
@@ -0,0 +1,51 @@
1
+ import { z } from "zod";
2
+ export const fieldTypeSchema = z.enum([
3
+ "string",
4
+ "number",
5
+ "boolean",
6
+ "choice",
7
+ "file",
8
+ "directory",
9
+ ]);
10
+ export const fieldSchema = z.object({
11
+ name: z.string().min(1),
12
+ longFlag: z.string().min(1),
13
+ shortFlag: z.string().optional(),
14
+ type: fieldTypeSchema,
15
+ label: z.string().min(1),
16
+ help: z.string().optional(),
17
+ required: z.boolean(),
18
+ default: z.union([z.string(), z.number(), z.boolean()]).optional(),
19
+ choices: z.array(z.string()).optional(),
20
+ positional: z.boolean().optional(),
21
+ });
22
+ export const detectSourceSchema = z.enum([
23
+ "commander",
24
+ "yargs",
25
+ "jsdoc",
26
+ "config",
27
+ "help",
28
+ "merged",
29
+ ]);
30
+ export const formSpecSchema = z.object({
31
+ title: z.string().min(1),
32
+ description: z.string().optional(),
33
+ target: z.string().min(1),
34
+ cwd: z.string().min(1),
35
+ source: detectSourceSchema,
36
+ fields: z.array(fieldSchema),
37
+ window: z
38
+ .object({
39
+ width: z.number().int().positive().optional(),
40
+ height: z.number().int().positive().optional(),
41
+ })
42
+ .optional(),
43
+ });
44
+ export const formValuesSchema = z.record(z.string(), z.union([z.string(), z.number(), z.boolean()]));
45
+ export function labelFromName(name) {
46
+ return name
47
+ .replace(/^--?/, "")
48
+ .replace(/[-_]/g, " ")
49
+ .replace(/([a-z])([A-Z])/g, "$1 $2")
50
+ .replace(/\b\w/g, (ch) => ch.toUpperCase());
51
+ }
@@ -0,0 +1 @@
1
+ export declare function windowProcessEnv(source?: NodeJS.ProcessEnv): NodeJS.ProcessEnv;
@@ -0,0 +1,14 @@
1
+ const INSPECTOR_KEYS = [
2
+ "VSCODE_INSPECTOR_OPTIONS",
3
+ "NODE_INSPECT_RESUME_ON_START",
4
+ "NODE_OPTIONS",
5
+ "NODE_DEBUG",
6
+ "NODE_UNIQUE_ID",
7
+ ];
8
+ export function windowProcessEnv(source = process.env) {
9
+ const env = { ...source };
10
+ for (const key of INSPECTOR_KEYS) {
11
+ delete env[key];
12
+ }
13
+ return env;
14
+ }
@@ -0,0 +1,2 @@
1
+ import type { FormSpec } from "../schema/form.js";
2
+ export declare function openWindow(dir: string, spec: FormSpec): Promise<number>;
@@ -0,0 +1,44 @@
1
+ import { spawn } from "node:child_process";
2
+ import { Gui4CliError } from "../errors.js";
3
+ import { windowProcessEnv } from "./env.js";
4
+ import { resolveWindowdLaunch } from "./resolve.js";
5
+ const QUICK_EXIT_MS = 5000;
6
+ export async function openWindow(dir, spec) {
7
+ const first = await spawnWindowd(dir, spec);
8
+ if (first.elapsedMs < QUICK_EXIT_MS && first.code === 0) {
9
+ process.stderr.write("The window closed right away. Opening it again…\n");
10
+ return (await spawnWindowd(dir, spec)).code;
11
+ }
12
+ return first.code;
13
+ }
14
+ async function spawnWindowd(dir, spec) {
15
+ const launch = resolveWindowdLaunch();
16
+ const width = String(spec.window?.width ?? 960);
17
+ const height = String(spec.window?.height ?? 620);
18
+ const artifacts = process.env.GUI4CLI_WINDOW_LOGS;
19
+ const args = [
20
+ ...launch.prefixArgs,
21
+ launch.cli,
22
+ "--title",
23
+ spec.title,
24
+ "--width",
25
+ width,
26
+ "--height",
27
+ height,
28
+ ...(artifacts ? ["--artifacts", artifacts, "--debug"] : []),
29
+ ];
30
+ const started = Date.now();
31
+ return new Promise((resolve, reject) => {
32
+ const child = spawn(launch.command, args, {
33
+ cwd: dir,
34
+ stdio: "inherit",
35
+ env: windowProcessEnv(),
36
+ });
37
+ child.on("error", (error) => {
38
+ reject(new Gui4CliError("Could not open the desktop window.", `${error.message}\nwindowd is installed. If this keeps failing, install Bun from https://bun.sh and run pnpm dev again.`));
39
+ });
40
+ child.on("close", (code) => {
41
+ resolve({ code: code ?? 0, elapsedMs: Date.now() - started });
42
+ });
43
+ });
44
+ }
@@ -0,0 +1,6 @@
1
+ export type WindowdLaunch = {
2
+ command: string;
3
+ prefixArgs: string[];
4
+ cli: string;
5
+ };
6
+ export declare function resolveWindowdLaunch(): WindowdLaunch;
@@ -0,0 +1,49 @@
1
+ import { existsSync } from "node:fs";
2
+ import { createRequire } from "node:module";
3
+ import { delimiter, dirname, join } from "node:path";
4
+ import { Gui4CliError } from "../errors.js";
5
+ const require = createRequire(import.meta.url);
6
+ export function resolveWindowdLaunch() {
7
+ let windowdRoot;
8
+ try {
9
+ windowdRoot = dirname(require.resolve("windowd/package.json"));
10
+ }
11
+ catch {
12
+ throw new Gui4CliError("Could not find the windowd package.", "From this repo run: pnpm install");
13
+ }
14
+ const cli = join(windowdRoot, "bin", "cli.ts");
15
+ if (!existsSync(cli)) {
16
+ throw new Gui4CliError("windowd is installed but its launcher file is missing.", `Expected ${cli}`);
17
+ }
18
+ const bun = findOnPath("bun");
19
+ if (bun) {
20
+ return { command: bun, prefixArgs: [], cli };
21
+ }
22
+ const tsxCli = resolveTsxCli();
23
+ return { command: process.execPath, prefixArgs: [tsxCli], cli };
24
+ }
25
+ function resolveTsxCli() {
26
+ try {
27
+ const tsxRoot = dirname(require.resolve("tsx/package.json"));
28
+ const cli = join(tsxRoot, "dist", "cli.mjs");
29
+ if (existsSync(cli))
30
+ return cli;
31
+ }
32
+ catch {
33
+ // Fall through.
34
+ }
35
+ throw new Gui4CliError("Could not start the desktop window.", "windowd needs Bun (https://bun.sh) or tsx. From this repo run: pnpm install");
36
+ }
37
+ function findOnPath(name) {
38
+ const exts = process.platform === "win32" ? [".exe", ".cmd", ""] : [""];
39
+ for (const dir of (process.env.PATH ?? "").split(delimiter)) {
40
+ if (!dir)
41
+ continue;
42
+ for (const ext of exts) {
43
+ const candidate = join(dir, `${name}${ext}`);
44
+ if (existsSync(candidate))
45
+ return candidate;
46
+ }
47
+ }
48
+ return undefined;
49
+ }
package/package.json CHANGED
@@ -1,17 +1,27 @@
1
1
  {
2
2
  "name": "gui4cli",
3
- "version": "0.0.0",
4
- "description": "Turn a Node.js CLI script into a desktop form app — inputs, run button, live output. Placeholder release; the CLI is not ready yet.",
3
+ "version": "0.0.2",
4
+ "description": "Turn a Node.js CLI script into a desktop form app — inputs, run button, live output.",
5
5
  "type": "module",
6
6
  "bin": {
7
- "gui4cli": "bin/argui.js",
8
- "argui": "bin/argui.js"
7
+ "gui4cli": "bin/gui4cli.js",
8
+ "argui": "bin/gui4cli.js"
9
9
  },
10
10
  "files": [
11
11
  "bin",
12
+ "dist",
12
13
  "README.md",
13
14
  "LICENSE"
14
15
  ],
16
+ "scripts": {
17
+ "dev": "tsx src/cli.ts fixtures/resize.js",
18
+ "detect": "tsx src/cli.ts fixtures/resize.js --json",
19
+ "build": "tsc",
20
+ "typecheck": "tsc --noEmit",
21
+ "test": "vitest run",
22
+ "prepare": "tsc",
23
+ "prepublishOnly": "node scripts/publish-gate.mjs && pnpm test && pnpm build"
24
+ },
15
25
  "keywords": [
16
26
  "cli",
17
27
  "desktop",
@@ -24,5 +34,17 @@
24
34
  "author": "Catalyst Forge LLC",
25
35
  "engines": {
26
36
  "node": ">=20"
37
+ },
38
+ "dependencies": {
39
+ "windowd": "^0.7.0",
40
+ "zod": "^3.25.0"
41
+ },
42
+ "devDependencies": {
43
+ "@types/node": "^24.0.0",
44
+ "commander": "^14.0.0",
45
+ "tsx": "^4.20.0",
46
+ "typescript": "^5.9.0",
47
+ "vitest": "^3.2.0",
48
+ "yargs": "^18.0.0"
27
49
  }
28
50
  }
package/bin/argui.js DELETED
@@ -1,4 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- console.log("ArgUI is reserved but not ready yet.");
4
- process.exit(0);