@shahmarasy/prodo 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.
Files changed (120) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +157 -0
  3. package/bin/prodo.cjs +6 -0
  4. package/dist/agent-command-installer.d.ts +4 -0
  5. package/dist/agent-command-installer.js +158 -0
  6. package/dist/agents.d.ts +15 -0
  7. package/dist/agents.js +47 -0
  8. package/dist/artifact-registry.d.ts +11 -0
  9. package/dist/artifact-registry.js +49 -0
  10. package/dist/artifacts.d.ts +9 -0
  11. package/dist/artifacts.js +514 -0
  12. package/dist/cli.d.ts +9 -0
  13. package/dist/cli.js +305 -0
  14. package/dist/consistency.d.ts +8 -0
  15. package/dist/consistency.js +268 -0
  16. package/dist/constants.d.ts +7 -0
  17. package/dist/constants.js +64 -0
  18. package/dist/doctor.d.ts +1 -0
  19. package/dist/doctor.js +123 -0
  20. package/dist/errors.d.ts +3 -0
  21. package/dist/errors.js +10 -0
  22. package/dist/hook-executor.d.ts +1 -0
  23. package/dist/hook-executor.js +175 -0
  24. package/dist/init-tui.d.ts +21 -0
  25. package/dist/init-tui.js +161 -0
  26. package/dist/init.d.ts +10 -0
  27. package/dist/init.js +307 -0
  28. package/dist/markdown.d.ts +11 -0
  29. package/dist/markdown.js +66 -0
  30. package/dist/normalize.d.ts +7 -0
  31. package/dist/normalize.js +73 -0
  32. package/dist/normalized-brief.d.ts +39 -0
  33. package/dist/normalized-brief.js +170 -0
  34. package/dist/output-index.d.ts +13 -0
  35. package/dist/output-index.js +55 -0
  36. package/dist/paths.d.ts +16 -0
  37. package/dist/paths.js +76 -0
  38. package/dist/preset-loader.d.ts +4 -0
  39. package/dist/preset-loader.js +210 -0
  40. package/dist/project-config.d.ts +14 -0
  41. package/dist/project-config.js +69 -0
  42. package/dist/providers/index.d.ts +2 -0
  43. package/dist/providers/index.js +12 -0
  44. package/dist/providers/mock-provider.d.ts +7 -0
  45. package/dist/providers/mock-provider.js +168 -0
  46. package/dist/providers/openai-provider.d.ts +11 -0
  47. package/dist/providers/openai-provider.js +69 -0
  48. package/dist/registry.d.ts +13 -0
  49. package/dist/registry.js +115 -0
  50. package/dist/settings.d.ts +6 -0
  51. package/dist/settings.js +34 -0
  52. package/dist/template-resolver.d.ts +11 -0
  53. package/dist/template-resolver.js +28 -0
  54. package/dist/templates.d.ts +33 -0
  55. package/dist/templates.js +428 -0
  56. package/dist/types.d.ts +35 -0
  57. package/dist/types.js +5 -0
  58. package/dist/utils.d.ts +6 -0
  59. package/dist/utils.js +53 -0
  60. package/dist/validate.d.ts +9 -0
  61. package/dist/validate.js +226 -0
  62. package/dist/validator.d.ts +5 -0
  63. package/dist/validator.js +80 -0
  64. package/dist/version.d.ts +1 -0
  65. package/dist/version.js +30 -0
  66. package/dist/workflow-commands.d.ts +7 -0
  67. package/dist/workflow-commands.js +28 -0
  68. package/package.json +45 -0
  69. package/presets/fintech/preset.json +1 -0
  70. package/presets/fintech/prompts/prd.md +3 -0
  71. package/presets/marketplace/preset.json +1 -0
  72. package/presets/marketplace/prompts/prd.md +3 -0
  73. package/presets/saas/preset.json +1 -0
  74. package/presets/saas/prompts/prd.md +3 -0
  75. package/src/agent-command-installer.ts +174 -0
  76. package/src/agents.ts +56 -0
  77. package/src/artifact-registry.ts +69 -0
  78. package/src/artifacts.ts +606 -0
  79. package/src/cli.ts +322 -0
  80. package/src/consistency.ts +303 -0
  81. package/src/constants.ts +72 -0
  82. package/src/doctor.ts +137 -0
  83. package/src/errors.ts +7 -0
  84. package/src/hook-executor.ts +196 -0
  85. package/src/init-tui.ts +193 -0
  86. package/src/init.ts +375 -0
  87. package/src/markdown.ts +73 -0
  88. package/src/normalize.ts +89 -0
  89. package/src/normalized-brief.ts +206 -0
  90. package/src/output-index.ts +59 -0
  91. package/src/paths.ts +72 -0
  92. package/src/preset-loader.ts +237 -0
  93. package/src/project-config.ts +78 -0
  94. package/src/providers/index.ts +12 -0
  95. package/src/providers/mock-provider.ts +188 -0
  96. package/src/providers/openai-provider.ts +87 -0
  97. package/src/registry.ts +119 -0
  98. package/src/settings.ts +34 -0
  99. package/src/template-resolver.ts +33 -0
  100. package/src/templates.ts +440 -0
  101. package/src/types.ts +46 -0
  102. package/src/utils.ts +50 -0
  103. package/src/validate.ts +246 -0
  104. package/src/validator.ts +96 -0
  105. package/src/version.ts +24 -0
  106. package/src/workflow-commands.ts +31 -0
  107. package/templates/artifacts/prd.md +219 -0
  108. package/templates/artifacts/stories.md +49 -0
  109. package/templates/artifacts/techspec.md +42 -0
  110. package/templates/artifacts/wireframe.html +260 -0
  111. package/templates/artifacts/wireframe.md +22 -0
  112. package/templates/artifacts/workflow.md +22 -0
  113. package/templates/artifacts/workflow.mmd +6 -0
  114. package/templates/commands/prodo-normalize.md +24 -0
  115. package/templates/commands/prodo-prd.md +24 -0
  116. package/templates/commands/prodo-stories.md +24 -0
  117. package/templates/commands/prodo-techspec.md +24 -0
  118. package/templates/commands/prodo-validate.md +24 -0
  119. package/templates/commands/prodo-wireframe.md +24 -0
  120. package/templates/commands/prodo-workflow.md +24 -0
package/dist/doctor.js ADDED
@@ -0,0 +1,123 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.runDoctor = runDoctor;
4
+ const node_child_process_1 = require("node:child_process");
5
+ const paths_1 = require("./paths");
6
+ const utils_1 = require("./utils");
7
+ const version_1 = require("./version");
8
+ function termWidth() {
9
+ return Math.max(80, process.stdout.columns ?? 100);
10
+ }
11
+ function stripAnsi(input) {
12
+ return input.replace(/\u001B\[[0-9;]*m/g, "");
13
+ }
14
+ function color(input, code) {
15
+ if (!process.stdout.isTTY)
16
+ return input;
17
+ return `${code}${input}\u001B[0m`;
18
+ }
19
+ function center(input, width) {
20
+ const visible = stripAnsi(input).length;
21
+ const left = Math.max(0, Math.floor((width - visible) / 2));
22
+ return `${" ".repeat(left)}${input}`;
23
+ }
24
+ function iconFor(status) {
25
+ if (status === "available")
26
+ return color("✔", "\u001B[32m");
27
+ if (status === "not_found")
28
+ return color("✖", "\u001B[31m");
29
+ return color("•", "\u001B[33m");
30
+ }
31
+ function labelFor(status) {
32
+ if (status === "available")
33
+ return color("available", "\u001B[32m");
34
+ if (status === "not_found")
35
+ return color("not found", "\u001B[31m");
36
+ return color("IDE-based", "\u001B[2;33m");
37
+ }
38
+ function renderRows(rows) {
39
+ const leftWidth = Math.max(...rows.map((row) => row.name.length), 10);
40
+ return rows.map((row) => {
41
+ const left = row.name.padEnd(leftWidth, " ");
42
+ const status = `${labelFor(row.status)}${row.detail ? ` (${row.detail})` : ""}`;
43
+ return ` ${iconFor(row.status)} ${left} ${status}`;
44
+ });
45
+ }
46
+ async function commandExists(command) {
47
+ return new Promise((resolve) => {
48
+ const lookup = process.platform === "win32" ? "where" : "which";
49
+ const child = (0, node_child_process_1.spawn)(lookup, [command], { stdio: "ignore" });
50
+ child.on("error", () => resolve(false));
51
+ child.on("close", (code) => resolve(code === 0));
52
+ });
53
+ }
54
+ async function firstAvailable(commands) {
55
+ for (const command of commands) {
56
+ if (await commandExists(command))
57
+ return true;
58
+ }
59
+ return false;
60
+ }
61
+ function renderLogo(width) {
62
+ const cyan = "\u001B[38;5;45m";
63
+ const blue = "\u001B[38;5;39m";
64
+ const logo = [
65
+ "██████╗ ██████╗ ██████╗ ██████╗ ██████╗ ",
66
+ "██╔══██╗██╔══██╗██╔═══██╗██╔══██╗██╔═══██╗",
67
+ "██████╔╝██████╔╝██║ ██║██║ ██║██║ ██║",
68
+ "██╔═══╝ ██╔══██╗██║ ██║██║ ██║██║ ██║",
69
+ "██║ ██║ ██║╚██████╔╝██████╔╝╚██████╔╝"
70
+ ];
71
+ const painted = logo.map((line, idx) => color(line, idx % 2 === 0 ? cyan : blue));
72
+ return painted.map((line) => center(line, width)).join("\n");
73
+ }
74
+ async function runDoctor(cwd, out) {
75
+ const width = termWidth();
76
+ const version = await (0, version_1.readCliVersion)(cwd);
77
+ const isInitialized = await (0, utils_1.fileExists)((0, paths_1.prodoPath)(cwd));
78
+ const codex = await firstAvailable(["codex"]);
79
+ const gemini = await firstAvailable(["gemini", "gemini-cli"]);
80
+ const claude = await firstAvailable(["claude", "claude-cli"]);
81
+ const git = await firstAvailable(["git"]);
82
+ const node = await firstAvailable(["node"]);
83
+ const vscode = await firstAvailable(["code"]);
84
+ const coreRows = [
85
+ { name: "Prodo CLI", status: "available", detail: `v${version}` },
86
+ {
87
+ name: "Project initialized",
88
+ status: isInitialized ? "available" : "not_found",
89
+ detail: isInitialized ? ".prodo found" : ".prodo missing"
90
+ }
91
+ ];
92
+ const aiRows = [
93
+ { name: "Codex CLI", status: codex ? "available" : "not_found", detail: codex ? "available" : "not found" },
94
+ { name: "Gemini CLI", status: gemini ? "available" : "not_found", detail: gemini ? "available" : "not found" },
95
+ { name: "Claude CLI", status: claude ? "available" : "not_found", detail: claude ? "available" : "not found" }
96
+ ];
97
+ const devRows = [
98
+ { name: "Git", status: git ? "available" : "not_found", detail: git ? "available" : "not found" },
99
+ { name: "Node.js", status: node ? "available" : "not_found", detail: node ? "available" : "not found" },
100
+ { name: "Visual Studio Code", status: vscode ? "available" : "not_found", detail: vscode ? "available" : "not found" },
101
+ { name: "Cursor", status: "ide", detail: "IDE-based, no CLI check" }
102
+ ];
103
+ out("");
104
+ out(renderLogo(width));
105
+ out("");
106
+ out(center(color("Prodo — Product Artifact Toolkit", "\u001B[1;37m"), width));
107
+ out(center(color("Crafted by Codex, guided by Shahmarasy intelligence", "\u001B[2;37m"), width));
108
+ out("");
109
+ out("Checking environment...");
110
+ out("");
111
+ out(color("Core", "\u001B[1m"));
112
+ for (const line of renderRows(coreRows))
113
+ out(line);
114
+ out("");
115
+ out(color("AI / Agents", "\u001B[1m"));
116
+ for (const line of renderRows(aiRows))
117
+ out(line);
118
+ out("");
119
+ out(color("Dev Tools", "\u001B[1m"));
120
+ for (const line of renderRows(devRows))
121
+ out(line);
122
+ out("");
123
+ }
@@ -0,0 +1,3 @@
1
+ export declare class UserError extends Error {
2
+ constructor(message: string);
3
+ }
package/dist/errors.js ADDED
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.UserError = void 0;
4
+ class UserError extends Error {
5
+ constructor(message) {
6
+ super(message);
7
+ this.name = "UserError";
8
+ }
9
+ }
10
+ exports.UserError = UserError;
@@ -0,0 +1 @@
1
+ export declare function runHookPhase(cwd: string, phaseKey: string, log: (message: string) => void): Promise<void>;
@@ -0,0 +1,175 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.runHookPhase = runHookPhase;
7
+ const promises_1 = __importDefault(require("node:fs/promises"));
8
+ const node_path_1 = __importDefault(require("node:path"));
9
+ const node_child_process_1 = require("node:child_process");
10
+ const js_yaml_1 = __importDefault(require("js-yaml"));
11
+ const errors_1 = require("./errors");
12
+ const utils_1 = require("./utils");
13
+ function hooksPath(cwd) {
14
+ return node_path_1.default.join(cwd, ".prodo", "hooks.yml");
15
+ }
16
+ async function runShellCommand(command, cwd, timeoutMs) {
17
+ const parsed = parseCommand(command);
18
+ if (!parsed) {
19
+ return { code: 1, stdout: "", stderr: "Invalid hook command syntax.", timedOut: false };
20
+ }
21
+ return new Promise((resolve) => {
22
+ const child = (0, node_child_process_1.spawn)(parsed.bin, parsed.args, { cwd, shell: false, stdio: ["ignore", "pipe", "pipe"] });
23
+ let stdout = "";
24
+ let stderr = "";
25
+ let timedOut = false;
26
+ const timer = setTimeout(() => {
27
+ timedOut = true;
28
+ child.kill();
29
+ }, Math.max(1000, timeoutMs));
30
+ child.stdout.on("data", (chunk) => {
31
+ stdout += chunk.toString();
32
+ });
33
+ child.stderr.on("data", (chunk) => {
34
+ stderr += chunk.toString();
35
+ });
36
+ child.on("close", (code) => {
37
+ clearTimeout(timer);
38
+ resolve({ code: code ?? 1, stdout, stderr, timedOut });
39
+ });
40
+ });
41
+ }
42
+ function parseCommand(command) {
43
+ const src = command.trim();
44
+ if (!src)
45
+ return null;
46
+ const out = [];
47
+ let current = "";
48
+ let quote = null;
49
+ let escaping = false;
50
+ for (let i = 0; i < src.length; i += 1) {
51
+ const ch = src[i];
52
+ if (escaping) {
53
+ current += ch;
54
+ escaping = false;
55
+ continue;
56
+ }
57
+ if (ch === "\\") {
58
+ escaping = true;
59
+ continue;
60
+ }
61
+ if (quote) {
62
+ if (ch === quote) {
63
+ quote = null;
64
+ }
65
+ else {
66
+ current += ch;
67
+ }
68
+ continue;
69
+ }
70
+ if (ch === "'" || ch === '"') {
71
+ quote = ch;
72
+ continue;
73
+ }
74
+ if (/\s/.test(ch)) {
75
+ if (current.length > 0) {
76
+ out.push(current);
77
+ current = "";
78
+ }
79
+ continue;
80
+ }
81
+ current += ch;
82
+ }
83
+ if (escaping || quote)
84
+ return null;
85
+ if (current.length > 0)
86
+ out.push(current);
87
+ if (out.length === 0)
88
+ return null;
89
+ return { bin: out[0], args: out.slice(1) };
90
+ }
91
+ function toPositiveInt(value, fallback) {
92
+ if (typeof value !== "number" || !Number.isFinite(value))
93
+ return fallback;
94
+ const normalized = Math.floor(value);
95
+ return normalized > 0 ? normalized : fallback;
96
+ }
97
+ function toNonNegativeInt(value, fallback) {
98
+ if (typeof value !== "number" || !Number.isFinite(value))
99
+ return fallback;
100
+ const normalized = Math.floor(value);
101
+ return normalized >= 0 ? normalized : fallback;
102
+ }
103
+ async function sleep(ms) {
104
+ await new Promise((resolve) => setTimeout(resolve, ms));
105
+ }
106
+ async function evaluateCondition(condition, cwd) {
107
+ const trimmed = condition.trim();
108
+ if (!trimmed)
109
+ return true;
110
+ const result = await runShellCommand(trimmed, cwd, 10_000);
111
+ return !result.timedOut && result.code === 0;
112
+ }
113
+ async function readHooks(cwd) {
114
+ const file = hooksPath(cwd);
115
+ if (!(await (0, utils_1.fileExists)(file)))
116
+ return null;
117
+ try {
118
+ const raw = await promises_1.default.readFile(file, "utf8");
119
+ const parsed = js_yaml_1.default.load(raw);
120
+ return parsed ?? null;
121
+ }
122
+ catch {
123
+ return null;
124
+ }
125
+ }
126
+ async function runHookPhase(cwd, phaseKey, log) {
127
+ const config = await readHooks(cwd);
128
+ const phaseHooks = config?.hooks?.[phaseKey];
129
+ if (!Array.isArray(phaseHooks) || phaseHooks.length === 0)
130
+ return;
131
+ for (const hook of phaseHooks) {
132
+ if (hook?.enabled === false)
133
+ continue;
134
+ const command = typeof hook?.command === "string" ? hook.command.trim() : "";
135
+ if (!command)
136
+ continue;
137
+ if (typeof hook.condition === "string" && hook.condition.trim()) {
138
+ const pass = await evaluateCondition(hook.condition, cwd);
139
+ if (!pass) {
140
+ log(`[Hook:skipped:${phaseKey}] condition=false for ${command}`);
141
+ continue;
142
+ }
143
+ }
144
+ const label = hook.extension || hook.description || command;
145
+ if (hook.optional) {
146
+ log(`[Hook:optional:${phaseKey}] ${label}`);
147
+ if (hook.prompt)
148
+ log(` Prompt: ${hook.prompt}`);
149
+ log(` To run manually: ${command}`);
150
+ continue;
151
+ }
152
+ const timeoutMs = toPositiveInt(hook.timeout_ms, 30_000);
153
+ const retries = toNonNegativeInt(hook.retry, 0);
154
+ const retryDelayMs = toNonNegativeInt(hook.retry_delay_ms, 500);
155
+ const attempts = 1 + retries;
156
+ let lastDetail = "";
157
+ for (let attempt = 1; attempt <= attempts; attempt += 1) {
158
+ log(`[Hook:mandatory:${phaseKey}] Running (attempt ${attempt}/${attempts}): ${command}`);
159
+ const result = await runShellCommand(command, cwd, timeoutMs);
160
+ if (!result.timedOut && result.code === 0) {
161
+ lastDetail = "";
162
+ break;
163
+ }
164
+ const stderr = result.stderr.trim();
165
+ const stdout = result.stdout.trim();
166
+ lastDetail = result.timedOut ? `Timed out after ${timeoutMs}ms` : stderr || stdout || "unknown error";
167
+ if (attempt < attempts) {
168
+ await sleep(retryDelayMs);
169
+ }
170
+ }
171
+ if (lastDetail) {
172
+ throw new errors_1.UserError(`Mandatory hook failed (${phaseKey}): ${command}\n${lastDetail}`);
173
+ }
174
+ }
175
+ }
@@ -0,0 +1,21 @@
1
+ import { type SupportedAi } from "./agent-command-installer";
2
+ export type InitSelections = {
3
+ ai?: SupportedAi;
4
+ script: "sh" | "ps";
5
+ lang: "tr" | "en";
6
+ interactive: boolean;
7
+ };
8
+ type GatherInitUiOptions = {
9
+ projectRoot: string;
10
+ aiInput?: string;
11
+ langInput?: string;
12
+ };
13
+ export declare function gatherInitSelections(options: GatherInitUiOptions): Promise<InitSelections>;
14
+ export declare function finishInitInteractive(summary: {
15
+ projectRoot: string;
16
+ settingsPath: string;
17
+ ai?: SupportedAi;
18
+ script: "sh" | "ps";
19
+ lang: "tr" | "en";
20
+ }): Promise<void>;
21
+ export {};
@@ -0,0 +1,161 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.gatherInitSelections = gatherInitSelections;
7
+ exports.finishInitInteractive = finishInitInteractive;
8
+ const node_path_1 = __importDefault(require("node:path"));
9
+ const agent_command_installer_1 = require("./agent-command-installer");
10
+ const errors_1 = require("./errors");
11
+ const utils_1 = require("./utils");
12
+ const dynamicImport = new Function("specifier", "return import(specifier)");
13
+ async function loadClack() {
14
+ return (await dynamicImport("@clack/prompts"));
15
+ }
16
+ function isInteractiveTerminal() {
17
+ return Boolean(process.stdin.isTTY && process.stdout.isTTY && !process.env.CI);
18
+ }
19
+ function color(text, code) {
20
+ return `${code}${text}\u001B[0m`;
21
+ }
22
+ function stripAnsi(input) {
23
+ return input.replace(/\u001B\[[0-9;]*m/g, "");
24
+ }
25
+ function centerLine(line, width) {
26
+ const visible = stripAnsi(line).length;
27
+ const left = Math.max(0, Math.floor((width - visible) / 2));
28
+ return `${" ".repeat(left)}${line}`;
29
+ }
30
+ function centerBlock(lines, width) {
31
+ return lines.map((line) => centerLine(line, width)).join("\n");
32
+ }
33
+ function terminalWidth() {
34
+ const columns = process.stdout.columns ?? 100;
35
+ return Math.max(80, columns);
36
+ }
37
+ function renderLogo() {
38
+ const cyan = "\u001B[38;5;45m";
39
+ const blue = "\u001B[38;5;39m";
40
+ const lines = [
41
+ "██████╗ ██████╗ ██████╗ ██████╗ ██████╗ ",
42
+ "██╔══██╗██╔══██╗██╔═══██╗██╔══██╗██╔═══██╗",
43
+ "██████╔╝██████╔╝██║ ██║██║ ██║██║ ██║",
44
+ "██╔═══╝ ██╔══██╗██║ ██║██║ ██║██║ ██║",
45
+ "██║ ██║ ██║╚██████╔╝██████╔╝╚██████╔╝"
46
+ ];
47
+ return lines.map((line, idx) => color(line, idx % 2 === 0 ? cyan : blue)).join("\n");
48
+ }
49
+ function renderProjectBox(projectName, projectRoot) {
50
+ const content = [`Project ${projectName}`, `Directory ${projectRoot}`];
51
+ const innerWidth = Math.max(...content.map((line) => line.length)) + 2;
52
+ const top = `┌${"─".repeat(innerWidth)}┐`;
53
+ const rows = content.map((line) => `│ ${line.padEnd(innerWidth - 1)}│`);
54
+ const bottom = `└${"─".repeat(innerWidth)}┘`;
55
+ return [top, ...rows, bottom].join("\n");
56
+ }
57
+ async function detectAi(projectRoot) {
58
+ if (await (0, utils_1.fileExists)(node_path_1.default.join(projectRoot, ".agents")))
59
+ return "codex";
60
+ if (await (0, utils_1.fileExists)(node_path_1.default.join(projectRoot, ".gemini")))
61
+ return "gemini-cli";
62
+ if (await (0, utils_1.fileExists)(node_path_1.default.join(projectRoot, ".claude")))
63
+ return "claude-cli";
64
+ return undefined;
65
+ }
66
+ function normalizeLang(lang) {
67
+ if ((lang ?? "").trim().toLowerCase().startsWith("tr"))
68
+ return "tr";
69
+ return "en";
70
+ }
71
+ function modelChoiceFromAi(ai) {
72
+ if (ai === "codex")
73
+ return "codex";
74
+ if (ai === "gemini-cli")
75
+ return "gemini";
76
+ return "auto-detect";
77
+ }
78
+ async function gatherInitSelections(options) {
79
+ const clack = await loadClack();
80
+ const defaultLang = normalizeLang(options.langInput);
81
+ const fallbackScript = process.platform === "win32" ? "ps" : "sh";
82
+ const parsedAi = (0, agent_command_installer_1.resolveAi)(options.aiInput);
83
+ if (!isInteractiveTerminal()) {
84
+ return {
85
+ ai: parsedAi,
86
+ script: fallbackScript,
87
+ lang: defaultLang,
88
+ interactive: false
89
+ };
90
+ }
91
+ const detectedAi = await detectAi(options.projectRoot);
92
+ const initialModel = modelChoiceFromAi(parsedAi ?? detectedAi);
93
+ const projectName = node_path_1.default.basename(options.projectRoot) || ".";
94
+ const width = terminalWidth();
95
+ const subtitle = color("Prodo — Product Artifact Toolkit", "\u001B[1;37m");
96
+ const signature = color("Crafted by Codex, guided by Shahmarasy intelligence", "\u001B[38;5;244m");
97
+ const hero = [
98
+ "",
99
+ centerBlock(renderLogo().split("\n"), width),
100
+ "",
101
+ centerLine(subtitle, width),
102
+ centerLine(signature, width),
103
+ ""
104
+ ].join("\n");
105
+ clack.intro(hero);
106
+ clack.note(renderProjectBox(projectName, options.projectRoot), "Project Setup");
107
+ const model = await clack.select({
108
+ message: "Select model",
109
+ initialValue: initialModel,
110
+ options: [
111
+ { value: "codex", label: "codex", hint: "Native Codex flow" },
112
+ { value: "gemini", label: "gemini", hint: "Gemini CLI command set" },
113
+ { value: "auto-detect", label: "auto-detect", hint: "Detect from local agent directories" }
114
+ ]
115
+ });
116
+ if (clack.isCancel(model)) {
117
+ clack.cancel("Initialization cancelled.");
118
+ throw new errors_1.UserError("Initialization cancelled.");
119
+ }
120
+ const script = await clack.select({
121
+ message: "Select script type",
122
+ initialValue: fallbackScript,
123
+ options: [
124
+ { value: "sh", label: "sh", hint: "Command profile (metadata)" },
125
+ { value: "ps", label: "ps", hint: "Command profile (metadata)" }
126
+ ]
127
+ });
128
+ if (clack.isCancel(script)) {
129
+ clack.cancel("Initialization cancelled.");
130
+ throw new errors_1.UserError("Initialization cancelled.");
131
+ }
132
+ const lang = await clack.select({
133
+ message: "Select language",
134
+ initialValue: defaultLang,
135
+ options: [
136
+ { value: "tr", label: "tr", hint: "Turkish" },
137
+ { value: "en", label: "en", hint: "English" }
138
+ ]
139
+ });
140
+ if (clack.isCancel(lang)) {
141
+ clack.cancel("Initialization cancelled.");
142
+ throw new errors_1.UserError("Initialization cancelled.");
143
+ }
144
+ let selectedAi;
145
+ if (model === "codex")
146
+ selectedAi = "codex";
147
+ else if (model === "gemini")
148
+ selectedAi = "gemini-cli";
149
+ else
150
+ selectedAi = detectedAi;
151
+ return {
152
+ ai: selectedAi,
153
+ script,
154
+ lang,
155
+ interactive: true
156
+ };
157
+ }
158
+ function finishInitInteractive(summary) {
159
+ const aiText = summary.ai ?? "none";
160
+ return loadClack().then((clack) => clack.outro(`Scaffold complete.\nAI: ${aiText}\nScript: ${summary.script}\nLanguage: ${summary.lang}\nSettings: ${summary.settingsPath}\nNext: edit brief.md`));
161
+ }
package/dist/init.d.ts ADDED
@@ -0,0 +1,10 @@
1
+ import { type SupportedAi } from "./agent-command-installer";
2
+ export declare function runInit(cwd: string, options?: {
3
+ ai?: SupportedAi;
4
+ lang?: string;
5
+ preset?: string;
6
+ script?: "sh" | "ps";
7
+ }): Promise<{
8
+ installedAgentFiles: string[];
9
+ settingsPath: string;
10
+ }>;