fabis-ralph-loop 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/dist/cli.d.mts ADDED
@@ -0,0 +1 @@
1
+ export { };
package/dist/cli.mjs ADDED
@@ -0,0 +1,25 @@
1
+ #!/usr/bin/env node
2
+ import { defineCommand, runMain } from "citty";
3
+
4
+ //#region src/cli.ts
5
+ runMain(defineCommand({
6
+ meta: {
7
+ name: "fabis-ralph-loop",
8
+ description: "CLI for setting up and running Claude Ralph autonomous coding loops",
9
+ version: "0.1.0"
10
+ },
11
+ subCommands: {
12
+ init: () => import("./init.mjs").then((m) => m.default),
13
+ generate: () => import("./generate.mjs").then((m) => m.default),
14
+ start: () => import("./start.mjs").then((m) => m.default),
15
+ stop: () => import("./stop.mjs").then((m) => m.default),
16
+ restart: () => import("./restart.mjs").then((m) => m.default),
17
+ logs: () => import("./logs.mjs").then((m) => m.default),
18
+ run: () => import("./run.mjs").then((m) => m.default),
19
+ exec: () => import("./exec.mjs").then((m) => m.default)
20
+ }
21
+ }));
22
+
23
+ //#endregion
24
+ export { };
25
+ //# sourceMappingURL=cli.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.mjs","names":[],"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { defineCommand, runMain } from 'citty'\n\nconst main = defineCommand({\n meta: {\n name: 'fabis-ralph-loop',\n description: 'CLI for setting up and running Claude Ralph autonomous coding loops',\n version: '0.1.0',\n },\n subCommands: {\n init: () => import('./commands/init.js').then((m) => m.default),\n generate: () => import('./commands/generate.js').then((m) => m.default),\n start: () => import('./commands/start.js').then((m) => m.default),\n stop: () => import('./commands/stop.js').then((m) => m.default),\n restart: () => import('./commands/restart.js').then((m) => m.default),\n logs: () => import('./commands/logs.js').then((m) => m.default),\n run: () => import('./commands/run.js').then((m) => m.default),\n exec: () => import('./commands/exec.js').then((m) => m.default),\n },\n})\n\nrunMain(main)\n"],"mappings":";;;;AAqBA,QAlBa,cAAc;CACzB,MAAM;EACJ,MAAM;EACN,aAAa;EACb,SAAS;EACV;CACD,aAAa;EACX,YAAY,OAAO,cAAsB,MAAM,MAAM,EAAE,QAAQ;EAC/D,gBAAgB,OAAO,kBAA0B,MAAM,MAAM,EAAE,QAAQ;EACvE,aAAa,OAAO,eAAuB,MAAM,MAAM,EAAE,QAAQ;EACjE,YAAY,OAAO,cAAsB,MAAM,MAAM,EAAE,QAAQ;EAC/D,eAAe,OAAO,iBAAyB,MAAM,MAAM,EAAE,QAAQ;EACrE,YAAY,OAAO,cAAsB,MAAM,MAAM,EAAE,QAAQ;EAC/D,WAAW,OAAO,aAAqB,MAAM,MAAM,EAAE,QAAQ;EAC7D,YAAY,OAAO,cAAsB,MAAM,MAAM,EAAE,QAAQ;EAChE;CACF,CAAC,CAEW"}
package/dist/exec.mjs ADDED
@@ -0,0 +1,26 @@
1
+ import { t as loadRalphConfig } from "./loader.mjs";
2
+ import { t as execInContainer } from "./lifecycle.mjs";
3
+ import { defineCommand } from "citty";
4
+
5
+ //#region src/commands/exec.ts
6
+ var exec_default = defineCommand({
7
+ meta: {
8
+ name: "exec",
9
+ description: "Run arbitrary command inside container"
10
+ },
11
+ args: { command: {
12
+ type: "positional",
13
+ description: "Command to execute",
14
+ required: true
15
+ } },
16
+ async run({ args }) {
17
+ const config = await loadRalphConfig();
18
+ const rest = args._;
19
+ const command = [String(args.command), ...Array.isArray(rest) ? rest.map(String) : []];
20
+ await execInContainer(config.container.name, command);
21
+ }
22
+ });
23
+
24
+ //#endregion
25
+ export { exec_default as default };
26
+ //# sourceMappingURL=exec.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"exec.mjs","names":[],"sources":["../src/commands/exec.ts"],"sourcesContent":["import { defineCommand } from 'citty'\nimport { loadRalphConfig } from '../config/loader.js'\nimport { execInContainer } from '../container/lifecycle.js'\n\nexport default defineCommand({\n meta: {\n name: 'exec',\n description: 'Run arbitrary command inside container',\n },\n args: {\n command: {\n type: 'positional',\n description: 'Command to execute',\n required: true,\n },\n },\n async run({ args }) {\n const config = await loadRalphConfig()\n // args._ contains the rest of the arguments after the command\n const rest = (args as Record<string, unknown>)._\n const command = [String(args.command), ...(Array.isArray(rest) ? rest.map(String) : [])]\n await execInContainer(config.container.name, command)\n },\n})\n"],"mappings":";;;;;AAIA,mBAAe,cAAc;CAC3B,MAAM;EACJ,MAAM;EACN,aAAa;EACd;CACD,MAAM,EACJ,SAAS;EACP,MAAM;EACN,aAAa;EACb,UAAU;EACX,EACF;CACD,MAAM,IAAI,EAAE,QAAQ;EAClB,MAAM,SAAS,MAAM,iBAAiB;EAEtC,MAAM,OAAQ,KAAiC;EAC/C,MAAM,UAAU,CAAC,OAAO,KAAK,QAAQ,EAAE,GAAI,MAAM,QAAQ,KAAK,GAAG,KAAK,IAAI,OAAO,GAAG,EAAE,CAAE;AACxF,QAAM,gBAAgB,OAAO,UAAU,MAAM,QAAQ;;CAExD,CAAC"}
@@ -0,0 +1,34 @@
1
+ import { t as loadRalphConfig } from "./loader.mjs";
2
+ import { t as generateAll } from "./generators.mjs";
3
+ import { defineCommand } from "citty";
4
+
5
+ //#region src/commands/generate.ts
6
+ var generate_default = defineCommand({
7
+ meta: {
8
+ name: "generate",
9
+ description: "Regenerate all files from config (idempotent)"
10
+ },
11
+ args: {
12
+ "dry-run": {
13
+ type: "boolean",
14
+ description: "Preview what would be generated",
15
+ default: false
16
+ },
17
+ only: {
18
+ type: "string",
19
+ description: "Only generate specific type: container|prompt|skills"
20
+ }
21
+ },
22
+ async run({ args }) {
23
+ const config = await loadRalphConfig();
24
+ const only = args.only;
25
+ await generateAll(config, process.cwd(), {
26
+ dryRun: args["dry-run"],
27
+ only
28
+ });
29
+ }
30
+ });
31
+
32
+ //#endregion
33
+ export { generate_default as default };
34
+ //# sourceMappingURL=generate.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generate.mjs","names":[],"sources":["../src/commands/generate.ts"],"sourcesContent":["import { defineCommand } from 'citty'\nimport { loadRalphConfig } from '../config/loader.js'\nimport { generateAll } from '../generators/index.js'\n\nexport default defineCommand({\n meta: {\n name: 'generate',\n description: 'Regenerate all files from config (idempotent)',\n },\n args: {\n 'dry-run': {\n type: 'boolean',\n description: 'Preview what would be generated',\n default: false,\n },\n only: {\n type: 'string',\n description: 'Only generate specific type: container|prompt|skills',\n },\n },\n async run({ args }) {\n const config = await loadRalphConfig()\n const only = args.only as 'container' | 'prompt' | 'skills' | undefined\n await generateAll(config, process.cwd(), {\n dryRun: args['dry-run'],\n only,\n })\n },\n})\n"],"mappings":";;;;;AAIA,uBAAe,cAAc;CAC3B,MAAM;EACJ,MAAM;EACN,aAAa;EACd;CACD,MAAM;EACJ,WAAW;GACT,MAAM;GACN,aAAa;GACb,SAAS;GACV;EACD,MAAM;GACJ,MAAM;GACN,aAAa;GACd;EACF;CACD,MAAM,IAAI,EAAE,QAAQ;EAClB,MAAM,SAAS,MAAM,iBAAiB;EACtC,MAAM,OAAO,KAAK;AAClB,QAAM,YAAY,QAAQ,QAAQ,KAAK,EAAE;GACvC,QAAQ,KAAK;GACb;GACD,CAAC;;CAEL,CAAC"}
@@ -0,0 +1,222 @@
1
+ import { createRequire } from "node:module";
2
+ import { mkdir, readFile, readdir, rm, writeFile } from "node:fs/promises";
3
+ import { dirname, join } from "node:path";
4
+ import { consola } from "consola";
5
+ import { existsSync } from "node:fs";
6
+ import { fileURLToPath } from "node:url";
7
+ import ejs from "ejs";
8
+ import { tmpdir } from "node:os";
9
+ import { generate, writeGeneratedFiles } from "universal-ai-config";
10
+
11
+ //#region src/utils/template.ts
12
+ /**
13
+ * Resolve a bundled asset directory (templates, static, uac-templates).
14
+ * In dist (flat layout): assets are siblings of the compiled files.
15
+ * In src (nested layout via tsx): assets are siblings of the parent dir.
16
+ */
17
+ function resolveAssetDir(assetName, metaUrl) {
18
+ const dir = dirname(fileURLToPath(metaUrl));
19
+ const sibling = join(dir, assetName);
20
+ if (existsSync(sibling)) return sibling;
21
+ return join(dir, "..", assetName);
22
+ }
23
+ const TEMPLATES_DIR = resolveAssetDir("templates", import.meta.url);
24
+ async function renderTemplate(templateName, data) {
25
+ const template = await readFile(join(TEMPLATES_DIR, templateName), "utf8");
26
+ return ejs.render(template, data, { async: false });
27
+ }
28
+ const GENERATED_HEADER = `# Generated by fabis-ralph-loop — DO NOT EDIT MANUALLY
29
+ # Regenerate with: npx fabis-ralph-loop generate
30
+ `;
31
+
32
+ //#endregion
33
+ //#region src/utils/version.ts
34
+ function getPackageVersion() {
35
+ try {
36
+ return createRequire(import.meta.url)("../../package.json").version;
37
+ } catch {
38
+ return "latest";
39
+ }
40
+ }
41
+
42
+ //#endregion
43
+ //#region src/generators/dockerfile.ts
44
+ /**
45
+ * Detect whether the base image already includes Node.js.
46
+ */
47
+ function isNodeBaseImage(baseImage) {
48
+ return /^node[:/]/i.test(baseImage);
49
+ }
50
+ async function generateDockerfile(config) {
51
+ const user = config.container.user;
52
+ return renderTemplate("Dockerfile.ejs", {
53
+ generatedHeader: GENERATED_HEADER,
54
+ baseImage: config.container.baseImage,
55
+ systemPackages: config.container.systemPackages,
56
+ installNode: !isNodeBaseImage(config.container.baseImage),
57
+ playwright: config.container.playwright,
58
+ hooks: config.container.hooks,
59
+ user,
60
+ createUser: user === "sandbox",
61
+ homeDir: `/home/${user}`,
62
+ packageVersion: getPackageVersion()
63
+ });
64
+ }
65
+
66
+ //#endregion
67
+ //#region src/generators/compose.ts
68
+ async function generateCompose(config) {
69
+ const homeDir = `/home/${config.container.user}`;
70
+ const persistVolumes = {
71
+ "ralph-claude-config": `${homeDir}/.claude`,
72
+ ...Object.fromEntries(Object.entries(config.container.persistVolumes).map(([name, path]) => [name, path.replace("/home/sandbox", homeDir)]))
73
+ };
74
+ return renderTemplate("docker-compose.yml.ejs", {
75
+ generatedHeader: GENERATED_HEADER,
76
+ containerName: config.container.name,
77
+ shmSize: config.container.shmSize,
78
+ networkMode: config.container.networkMode,
79
+ capabilities: config.container.capabilities,
80
+ shadowVolumes: config.container.shadowVolumes,
81
+ persistVolumes,
82
+ extraVolumes: config.container.volumes,
83
+ env: config.container.env,
84
+ homeDir
85
+ });
86
+ }
87
+
88
+ //#endregion
89
+ //#region src/generators/entrypoint.ts
90
+ async function generateEntrypoint(config) {
91
+ const user = config.container.user;
92
+ return renderTemplate("entrypoint.ts.ejs", {
93
+ generatedHeader: GENERATED_HEADER.replace(/^# /gm, "// "),
94
+ agent: config.defaults.agent,
95
+ shadowVolumes: config.container.shadowVolumes,
96
+ entrypointSetup: config.container.hooks.entrypointSetup,
97
+ user,
98
+ homeDir: `/home/${user}`
99
+ });
100
+ }
101
+
102
+ //#endregion
103
+ //#region src/generators/prompt.ts
104
+ async function generatePrompt(config) {
105
+ return renderTemplate("ralph-prompt.md.ejs", {
106
+ generatedHeader: GENERATED_HEADER,
107
+ projectName: config.project.name,
108
+ projectDescription: config.project.description,
109
+ projectContext: config.project.context,
110
+ backpressureCommands: config.project.backpressureCommands,
111
+ openAppSkill: config.project.openAppSkill,
112
+ playwright: config.container.playwright,
113
+ completionSignal: config.defaults.completionSignal
114
+ });
115
+ }
116
+
117
+ //#endregion
118
+ //#region src/generators/skills.ts
119
+ const UAC_TEMPLATES_DIR = resolveAssetDir("uac-templates", import.meta.url);
120
+ function buildLevel1Variables(config) {
121
+ return {
122
+ backpressureCommands: config.project.backpressureCommands,
123
+ projectName: config.project.name,
124
+ projectContext: config.project.context,
125
+ openAppSkill: config.project.openAppSkill,
126
+ playwright: config.container.playwright,
127
+ config
128
+ };
129
+ }
130
+ async function discoverSkills() {
131
+ return (await readdir(join(UAC_TEMPLATES_DIR, "skills"), { withFileTypes: true })).filter((e) => e.isDirectory()).map((e) => e.name);
132
+ }
133
+ async function generateSkills(config, projectRoot) {
134
+ if (config.output.mode === "direct") await generateDirect(config, projectRoot);
135
+ else await generateUac(config, projectRoot);
136
+ }
137
+ async function generateDirect(config, projectRoot) {
138
+ const variables = buildLevel1Variables(config);
139
+ const skills = await discoverSkills();
140
+ const tempDir = join(tmpdir(), `ralph-skills-${Date.now()}`);
141
+ try {
142
+ for (const skill of skills) {
143
+ const template = await readFile(join(UAC_TEMPLATES_DIR, "skills", skill, "SKILL.md"), "utf8");
144
+ const rendered = ejs.render(template, variables);
145
+ const outDir = join(tempDir, "skills", skill);
146
+ await mkdir(outDir, { recursive: true });
147
+ await writeFile(join(outDir, "SKILL.md"), rendered, "utf8");
148
+ }
149
+ const files = await generate({
150
+ root: projectRoot,
151
+ targets: ["claude"],
152
+ types: ["skills"],
153
+ overrides: { templatesDir: tempDir }
154
+ });
155
+ await writeGeneratedFiles(files, projectRoot);
156
+ consola.info(`Generated ${files.length} skill file(s)`);
157
+ } finally {
158
+ await rm(tempDir, {
159
+ recursive: true,
160
+ force: true
161
+ });
162
+ }
163
+ }
164
+ async function generateUac(config, projectRoot) {
165
+ const variables = buildLevel1Variables(config);
166
+ const skills = await discoverSkills();
167
+ let count = 0;
168
+ for (const skill of skills) {
169
+ const template = await readFile(join(UAC_TEMPLATES_DIR, "skills", skill, "SKILL.md"), "utf8");
170
+ const rendered = ejs.render(template, variables);
171
+ const outDir = join(projectRoot, config.output.uacTemplatesDir, "skills", skill);
172
+ await mkdir(outDir, { recursive: true });
173
+ await writeFile(join(outDir, "SKILL.md"), rendered, "utf8");
174
+ count++;
175
+ }
176
+ consola.info(`Generated ${count} skill template(s) to ${config.output.uacTemplatesDir}/skills/`);
177
+ }
178
+
179
+ //#endregion
180
+ //#region src/generators/index.ts
181
+ async function generateAll(config, projectRoot, options = {}) {
182
+ const files = [];
183
+ if (!options.only || options.only === "container") {
184
+ await mkdir(join(projectRoot, ".ralph-container"), { recursive: true });
185
+ const dockerfile = await generateDockerfile(config);
186
+ files.push({
187
+ path: join(".ralph-container", "Dockerfile"),
188
+ content: dockerfile
189
+ });
190
+ const entrypoint = await generateEntrypoint(config);
191
+ files.push({
192
+ path: join(".ralph-container", "entrypoint.ts"),
193
+ content: entrypoint
194
+ });
195
+ const compose = await generateCompose(config);
196
+ files.push({
197
+ path: join(".ralph-container", "docker-compose.yml"),
198
+ content: compose
199
+ });
200
+ }
201
+ if (!options.only || options.only === "prompt") {
202
+ const prompt = await generatePrompt(config);
203
+ files.push({
204
+ path: join(".ralph-container", "ralph-prompt.md"),
205
+ content: prompt
206
+ });
207
+ }
208
+ if (options.dryRun) for (const file of files) consola.info(`[dry-run] Would write: ${file.path}`);
209
+ else for (const file of files) {
210
+ const fullPath = join(projectRoot, file.path);
211
+ await mkdir(join(fullPath, ".."), { recursive: true });
212
+ await writeFile(fullPath, file.content, "utf8");
213
+ consola.success(`Written: ${file.path}`);
214
+ }
215
+ if (!options.only || options.only === "skills") if (options.dryRun) consola.info("[dry-run] Would generate skills");
216
+ else await generateSkills(config, projectRoot);
217
+ return files;
218
+ }
219
+
220
+ //#endregion
221
+ export { generateAll as t };
222
+ //# sourceMappingURL=generators.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generators.mjs","names":[],"sources":["../src/utils/template.ts","../src/utils/version.ts","../src/generators/dockerfile.ts","../src/generators/compose.ts","../src/generators/entrypoint.ts","../src/generators/prompt.ts","../src/generators/skills.ts","../src/generators/index.ts"],"sourcesContent":["import { readFile } from 'node:fs/promises'\nimport { existsSync } from 'node:fs'\nimport { fileURLToPath } from 'node:url'\nimport { dirname, join } from 'node:path'\nimport ejs from 'ejs'\n\n/**\n * Resolve a bundled asset directory (templates, static, uac-templates).\n * In dist (flat layout): assets are siblings of the compiled files.\n * In src (nested layout via tsx): assets are siblings of the parent dir.\n */\nexport function resolveAssetDir(assetName: string, metaUrl: string): string {\n const dir = dirname(fileURLToPath(metaUrl))\n const sibling = join(dir, assetName)\n if (existsSync(sibling)) return sibling\n return join(dir, '..', assetName)\n}\n\nconst TEMPLATES_DIR = resolveAssetDir('templates', import.meta.url)\n\nexport async function renderTemplate(\n templateName: string,\n data: Record<string, unknown>,\n): Promise<string> {\n const templatePath = join(TEMPLATES_DIR, templateName)\n const template = await readFile(templatePath, 'utf8')\n return ejs.render(template, data, { async: false }) as string\n}\n\nexport const GENERATED_HEADER = `# Generated by fabis-ralph-loop — DO NOT EDIT MANUALLY\n# Regenerate with: npx fabis-ralph-loop generate\n`\n","import { createRequire } from 'node:module'\n\nexport function getPackageVersion(): string {\n try {\n const require = createRequire(import.meta.url)\n const pkg = require('../../package.json') as { version: string }\n return pkg.version\n } catch {\n return 'latest'\n }\n}\n","import { renderTemplate, GENERATED_HEADER } from '../utils/template.js'\nimport { getPackageVersion } from '../utils/version.js'\nimport type { ResolvedConfig } from '../config/schema.js'\n\n/**\n * Detect whether the base image already includes Node.js.\n */\nfunction isNodeBaseImage(baseImage: string): boolean {\n return /^node[:/]/i.test(baseImage)\n}\n\nexport async function generateDockerfile(config: ResolvedConfig): Promise<string> {\n const user = config.container.user\n return renderTemplate('Dockerfile.ejs', {\n generatedHeader: GENERATED_HEADER,\n baseImage: config.container.baseImage,\n systemPackages: config.container.systemPackages,\n installNode: !isNodeBaseImage(config.container.baseImage),\n playwright: config.container.playwright,\n hooks: config.container.hooks,\n user,\n createUser: user === 'sandbox',\n homeDir: `/home/${user}`,\n packageVersion: getPackageVersion(),\n })\n}\n","import { renderTemplate, GENERATED_HEADER } from '../utils/template.js'\nimport type { ResolvedConfig } from '../config/schema.js'\n\nexport async function generateCompose(config: ResolvedConfig): Promise<string> {\n const homeDir = `/home/${config.container.user}`\n\n // Ensure .claude config is always persisted with the correct home dir\n const persistVolumes: Record<string, string> = {\n 'ralph-claude-config': `${homeDir}/.claude`,\n ...Object.fromEntries(\n Object.entries(config.container.persistVolumes).map(([name, path]) => [\n name,\n path.replace('/home/sandbox', homeDir),\n ]),\n ),\n }\n\n return renderTemplate('docker-compose.yml.ejs', {\n generatedHeader: GENERATED_HEADER,\n containerName: config.container.name,\n shmSize: config.container.shmSize,\n networkMode: config.container.networkMode,\n capabilities: config.container.capabilities,\n shadowVolumes: config.container.shadowVolumes,\n persistVolumes,\n extraVolumes: config.container.volumes,\n env: config.container.env,\n homeDir,\n })\n}\n","import { renderTemplate, GENERATED_HEADER } from '../utils/template.js'\nimport type { ResolvedConfig } from '../config/schema.js'\n\nexport async function generateEntrypoint(config: ResolvedConfig): Promise<string> {\n const user = config.container.user\n return renderTemplate('entrypoint.ts.ejs', {\n generatedHeader: GENERATED_HEADER.replace(/^# /gm, '// '),\n agent: config.defaults.agent,\n shadowVolumes: config.container.shadowVolumes,\n entrypointSetup: config.container.hooks.entrypointSetup,\n user,\n homeDir: `/home/${user}`,\n })\n}\n","import { renderTemplate, GENERATED_HEADER } from '../utils/template.js'\nimport type { ResolvedConfig } from '../config/schema.js'\n\nexport async function generatePrompt(config: ResolvedConfig): Promise<string> {\n return renderTemplate('ralph-prompt.md.ejs', {\n generatedHeader: GENERATED_HEADER,\n projectName: config.project.name,\n projectDescription: config.project.description,\n projectContext: config.project.context,\n backpressureCommands: config.project.backpressureCommands,\n openAppSkill: config.project.openAppSkill,\n playwright: config.container.playwright,\n completionSignal: config.defaults.completionSignal,\n })\n}\n","import { readdir, readFile, writeFile, mkdir, rm } from 'node:fs/promises'\nimport { join } from 'node:path'\nimport { tmpdir } from 'node:os'\nimport ejs from 'ejs'\nimport { consola } from 'consola'\nimport { generate, writeGeneratedFiles } from 'universal-ai-config'\nimport type { ResolvedConfig } from '../config/schema.js'\nimport { resolveAssetDir } from '../utils/template.js'\n\nconst UAC_TEMPLATES_DIR = resolveAssetDir('uac-templates', import.meta.url)\n\nfunction buildLevel1Variables(config: ResolvedConfig): Record<string, unknown> {\n return {\n backpressureCommands: config.project.backpressureCommands,\n projectName: config.project.name,\n projectContext: config.project.context,\n openAppSkill: config.project.openAppSkill,\n playwright: config.container.playwright,\n config,\n }\n}\n\nasync function discoverSkills(): Promise<string[]> {\n const skillsDir = join(UAC_TEMPLATES_DIR, 'skills')\n const entries = await readdir(skillsDir, { withFileTypes: true })\n return entries.filter((e) => e.isDirectory()).map((e) => e.name)\n}\n\nexport async function generateSkills(config: ResolvedConfig, projectRoot: string): Promise<void> {\n if (config.output.mode === 'direct') {\n await generateDirect(config, projectRoot)\n } else {\n await generateUac(config, projectRoot)\n }\n}\n\nasync function generateDirect(config: ResolvedConfig, projectRoot: string): Promise<void> {\n const variables = buildLevel1Variables(config)\n const skills = await discoverSkills()\n\n // Render Level 1 EJS and write to a temp dir structured as UAC templates\n const tempDir = join(tmpdir(), `ralph-skills-${Date.now()}`)\n\n try {\n for (const skill of skills) {\n const templatePath = join(UAC_TEMPLATES_DIR, 'skills', skill, 'SKILL.md')\n const template = await readFile(templatePath, 'utf8')\n const rendered = ejs.render(template, variables) as string\n\n const outDir = join(tempDir, 'skills', skill)\n await mkdir(outDir, { recursive: true })\n await writeFile(join(outDir, 'SKILL.md'), rendered, 'utf8')\n }\n\n // Use UAC's generate() API for the second pass (handles Level 2 EJS + frontmatter mapping)\n const files = await generate({\n root: projectRoot,\n targets: ['claude'],\n types: ['skills'],\n overrides: { templatesDir: tempDir },\n })\n\n await writeGeneratedFiles(files, projectRoot)\n consola.info(`Generated ${files.length} skill file(s)`)\n } finally {\n await rm(tempDir, { recursive: true, force: true })\n }\n}\n\nasync function generateUac(config: ResolvedConfig, projectRoot: string): Promise<void> {\n const variables = buildLevel1Variables(config)\n const skills = await discoverSkills()\n let count = 0\n\n for (const skill of skills) {\n const templatePath = join(UAC_TEMPLATES_DIR, 'skills', skill, 'SKILL.md')\n const template = await readFile(templatePath, 'utf8')\n // Render Level 1 EJS — Level 2 <%% %> becomes <% %> in output\n const rendered = ejs.render(template, variables) as string\n\n const outDir = join(projectRoot, config.output.uacTemplatesDir, 'skills', skill)\n await mkdir(outDir, { recursive: true })\n await writeFile(join(outDir, 'SKILL.md'), rendered, 'utf8')\n count++\n }\n\n consola.info(`Generated ${count} skill template(s) to ${config.output.uacTemplatesDir}/skills/`)\n}\n","import { mkdir, writeFile } from 'node:fs/promises'\nimport { join } from 'node:path'\nimport { consola } from 'consola'\nimport { generateDockerfile } from './dockerfile.js'\nimport { generateCompose } from './compose.js'\nimport { generateEntrypoint } from './entrypoint.js'\nimport { generatePrompt } from './prompt.js'\nimport { generateSkills } from './skills.js'\nimport type { ResolvedConfig } from '../config/schema.js'\n\ninterface GenerateOptions {\n dryRun?: boolean\n only?: 'container' | 'prompt' | 'skills'\n}\n\ninterface GeneratedFile {\n path: string\n content: string\n}\n\nexport async function generateAll(\n config: ResolvedConfig,\n projectRoot: string,\n options: GenerateOptions = {},\n): Promise<GeneratedFile[]> {\n const files: GeneratedFile[] = []\n\n if (!options.only || options.only === 'container') {\n const containerDir = join(projectRoot, '.ralph-container')\n await mkdir(containerDir, { recursive: true })\n\n const dockerfile = await generateDockerfile(config)\n files.push({ path: join('.ralph-container', 'Dockerfile'), content: dockerfile })\n\n const entrypoint = await generateEntrypoint(config)\n files.push({ path: join('.ralph-container', 'entrypoint.ts'), content: entrypoint })\n\n const compose = await generateCompose(config)\n files.push({ path: join('.ralph-container', 'docker-compose.yml'), content: compose })\n }\n\n if (!options.only || options.only === 'prompt') {\n const prompt = await generatePrompt(config)\n files.push({ path: join('.ralph-container', 'ralph-prompt.md'), content: prompt })\n }\n\n if (options.dryRun) {\n for (const file of files) {\n consola.info(`[dry-run] Would write: ${file.path}`)\n }\n } else {\n for (const file of files) {\n const fullPath = join(projectRoot, file.path)\n await mkdir(join(fullPath, '..'), { recursive: true })\n await writeFile(fullPath, file.content, 'utf8')\n consola.success(`Written: ${file.path}`)\n }\n }\n\n if (!options.only || options.only === 'skills') {\n if (options.dryRun) {\n consola.info('[dry-run] Would generate skills')\n } else {\n await generateSkills(config, projectRoot)\n }\n }\n\n return files\n}\n"],"mappings":"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"}
@@ -0,0 +1,125 @@
1
+ import { z } from "zod";
2
+
3
+ //#region src/config/schema.d.ts
4
+ declare const backpressureCommandSchema: z.ZodObject<{
5
+ name: z.ZodString;
6
+ command: z.ZodString;
7
+ }, z.core.$strip>;
8
+ declare const ralphLoopConfigSchema: z.ZodObject<{
9
+ container: z.ZodPrefault<z.ZodObject<{
10
+ name: z.ZodString;
11
+ baseImage: z.ZodDefault<z.ZodString>;
12
+ user: z.ZodDefault<z.ZodString>;
13
+ systemPackages: z.ZodDefault<z.ZodArray<z.ZodString>>;
14
+ playwright: z.ZodDefault<z.ZodBoolean>;
15
+ networkMode: z.ZodDefault<z.ZodString>;
16
+ env: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodString>>;
17
+ shmSize: z.ZodDefault<z.ZodString>;
18
+ capabilities: z.ZodDefault<z.ZodArray<z.ZodString>>;
19
+ volumes: z.ZodDefault<z.ZodArray<z.ZodString>>;
20
+ shadowVolumes: z.ZodDefault<z.ZodArray<z.ZodString>>;
21
+ persistVolumes: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodString>>;
22
+ hooks: z.ZodPrefault<z.ZodObject<{
23
+ rootSetup: z.ZodDefault<z.ZodArray<z.ZodString>>;
24
+ userSetup: z.ZodDefault<z.ZodArray<z.ZodString>>;
25
+ entrypointSetup: z.ZodDefault<z.ZodArray<z.ZodString>>;
26
+ }, z.core.$strip>>;
27
+ }, z.core.$strip>>;
28
+ setup: z.ZodPrefault<z.ZodObject<{
29
+ preStartCommand: z.ZodDefault<z.ZodString>;
30
+ }, z.core.$strip>>;
31
+ defaults: z.ZodPrefault<z.ZodObject<{
32
+ agent: z.ZodDefault<z.ZodLiteral<"claude">>;
33
+ model: z.ZodDefault<z.ZodString>;
34
+ verbose: z.ZodDefault<z.ZodBoolean>;
35
+ sleepBetweenMs: z.ZodDefault<z.ZodNumber>;
36
+ completionSignal: z.ZodDefault<z.ZodString>;
37
+ }, z.core.$strip>>;
38
+ project: z.ZodObject<{
39
+ name: z.ZodString;
40
+ description: z.ZodDefault<z.ZodString>;
41
+ context: z.ZodDefault<z.ZodString>;
42
+ backpressureCommands: z.ZodDefault<z.ZodArray<z.ZodObject<{
43
+ name: z.ZodString;
44
+ command: z.ZodString;
45
+ }, z.core.$strip>>>;
46
+ openAppSkill: z.ZodDefault<z.ZodString>;
47
+ }, z.core.$strip>;
48
+ output: z.ZodPrefault<z.ZodObject<{
49
+ mode: z.ZodDefault<z.ZodEnum<{
50
+ direct: "direct";
51
+ uac: "uac";
52
+ }>>;
53
+ uacTemplatesDir: z.ZodDefault<z.ZodString>;
54
+ }, z.core.$strip>>;
55
+ }, z.core.$strip>;
56
+ type RalphLoopConfig = z.input<typeof ralphLoopConfigSchema>;
57
+ type ResolvedConfig = z.output<typeof ralphLoopConfigSchema>;
58
+ type BackpressureCommand = z.infer<typeof backpressureCommandSchema>;
59
+ //#endregion
60
+ //#region src/config/loader.d.ts
61
+ declare function loadRalphConfig(cwd?: string): Promise<ResolvedConfig>;
62
+ //#endregion
63
+ //#region src/generators/index.d.ts
64
+ interface GenerateOptions {
65
+ dryRun?: boolean;
66
+ only?: 'container' | 'prompt' | 'skills';
67
+ }
68
+ interface GeneratedFile {
69
+ path: string;
70
+ content: string;
71
+ }
72
+ declare function generateAll(config: ResolvedConfig, projectRoot: string, options?: GenerateOptions): Promise<GeneratedFile[]>;
73
+ //#endregion
74
+ //#region src/index.d.ts
75
+ /**
76
+ * Helper for defining a typed ralph-loop config.
77
+ */
78
+ declare function defineConfig(config: RalphLoopConfig): {
79
+ project: {
80
+ name: string;
81
+ description?: string | undefined;
82
+ context?: string | undefined;
83
+ backpressureCommands?: {
84
+ name: string;
85
+ command: string;
86
+ }[] | undefined;
87
+ openAppSkill?: string | undefined;
88
+ };
89
+ container?: {
90
+ name: string;
91
+ baseImage?: string | undefined;
92
+ user?: string | undefined;
93
+ systemPackages?: string[] | undefined;
94
+ playwright?: boolean | undefined;
95
+ networkMode?: string | undefined;
96
+ env?: Record<string, string> | undefined;
97
+ shmSize?: string | undefined;
98
+ capabilities?: string[] | undefined;
99
+ volumes?: string[] | undefined;
100
+ shadowVolumes?: string[] | undefined;
101
+ persistVolumes?: Record<string, string> | undefined;
102
+ hooks?: {
103
+ rootSetup?: string[] | undefined;
104
+ userSetup?: string[] | undefined;
105
+ entrypointSetup?: string[] | undefined;
106
+ } | undefined;
107
+ } | undefined;
108
+ setup?: {
109
+ preStartCommand?: string | undefined;
110
+ } | undefined;
111
+ defaults?: {
112
+ agent?: "claude" | undefined;
113
+ model?: string | undefined;
114
+ verbose?: boolean | undefined;
115
+ sleepBetweenMs?: number | undefined;
116
+ completionSignal?: string | undefined;
117
+ } | undefined;
118
+ output?: {
119
+ mode?: "direct" | "uac" | undefined;
120
+ uacTemplatesDir?: string | undefined;
121
+ } | undefined;
122
+ };
123
+ //#endregion
124
+ export { type BackpressureCommand, type RalphLoopConfig, type ResolvedConfig, defineConfig, generateAll, loadRalphConfig, ralphLoopConfigSchema };
125
+ //# sourceMappingURL=index.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/config/schema.ts","../src/config/loader.ts","../src/generators/index.ts","../src/index.ts"],"mappings":";;;cAEM,yBAAA,EAAyB,CAAA,CAAA,SAAA;;;;cAsDlB,qBAAA,EAAqB,CAAA,CAAA,SAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAQtB,eAAA,GAAkB,CAAA,CAAE,KAAA,QAAa,qBAAA;AAAA,KACjC,cAAA,GAAiB,CAAA,CAAE,MAAA,QAAc,qBAAA;AAAA,KACjC,mBAAA,GAAsB,CAAA,CAAE,KAAA,QAAa,yBAAA;;;iBC7D3B,eAAA,CAAgB,GAAA,YAAe,OAAA,CAAQ,cAAA;;;UCKnD,eAAA;EACR,MAAA;EACA,IAAA;AAAA;AAAA,UAGQ,aAAA;EACR,IAAA;EACA,OAAA;AAAA;AAAA,iBAGoB,WAAA,CACpB,MAAA,EAAQ,cAAA,EACR,WAAA,UACA,OAAA,GAAS,eAAA,GACR,OAAA,CAAQ,aAAA;;;;;;iBChBK,YAAA,CAAa,MAAA,EAAD,eAAA"}
package/dist/index.mjs ADDED
@@ -0,0 +1,14 @@
1
+ import { n as ralphLoopConfigSchema, t as loadRalphConfig } from "./loader.mjs";
2
+ import { t as generateAll } from "./generators.mjs";
3
+
4
+ //#region src/index.ts
5
+ /**
6
+ * Helper for defining a typed ralph-loop config.
7
+ */
8
+ function defineConfig(config) {
9
+ return config;
10
+ }
11
+
12
+ //#endregion
13
+ export { defineConfig, generateAll, loadRalphConfig, ralphLoopConfigSchema };
14
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../src/index.ts"],"sourcesContent":["export { ralphLoopConfigSchema } from './config/schema.js'\nexport type { RalphLoopConfig, ResolvedConfig, BackpressureCommand } from './config/schema.js'\nexport { loadRalphConfig } from './config/loader.js'\nexport { generateAll } from './generators/index.js'\n\n/**\n * Helper for defining a typed ralph-loop config.\n */\nexport function defineConfig(config: import('./config/schema.js').RalphLoopConfig) {\n return config\n}\n"],"mappings":";;;;;;;AAQA,SAAgB,aAAa,QAAsD;AACjF,QAAO"}
package/dist/init.mjs ADDED
@@ -0,0 +1,55 @@
1
+ import { t as loadRalphConfig } from "./loader.mjs";
2
+ import { t as generateAll } from "./generators.mjs";
3
+ import { writeFile } from "node:fs/promises";
4
+ import { consola } from "consola";
5
+ import { existsSync } from "node:fs";
6
+ import { defineCommand } from "citty";
7
+
8
+ //#region src/commands/init.ts
9
+ const SAMPLE_CONFIG = `import { defineConfig } from 'fabis-ralph-loop'
10
+
11
+ export default defineConfig({
12
+ container: {
13
+ name: 'my-ralph-container',
14
+ baseImage: 'node:22-bookworm',
15
+ // playwright: true,
16
+ // shadowVolumes: ['/workspace/node_modules'],
17
+ hooks: {
18
+ rootSetup: [
19
+ // 'RUN npm install -g pnpm@10',
20
+ ],
21
+ userSetup: [
22
+ // 'RUN corepack enable',
23
+ ],
24
+ },
25
+ },
26
+ project: {
27
+ name: 'My Project',
28
+ description: '',
29
+ context: '- **Monorepo** managed with npm\\n- **TypeScript strict mode** everywhere',
30
+ },
31
+ output: {
32
+ mode: 'direct',
33
+ },
34
+ })
35
+ `;
36
+ var init_default = defineCommand({
37
+ meta: {
38
+ name: "init",
39
+ description: "Scaffold ralph-loop config and generate all files"
40
+ },
41
+ async run() {
42
+ const configPath = "fabis-ralph-loop.config.ts";
43
+ if (existsSync(configPath)) consola.warn(`${configPath} already exists. Regenerating files from existing config.`);
44
+ else {
45
+ await writeFile(configPath, SAMPLE_CONFIG, "utf8");
46
+ consola.success(`Created ${configPath}`);
47
+ }
48
+ await generateAll(await loadRalphConfig(), process.cwd());
49
+ consola.success("Init complete. Edit fabis-ralph-loop.config.ts and run `fabis-ralph-loop generate` to regenerate.");
50
+ }
51
+ });
52
+
53
+ //#endregion
54
+ export { init_default as default };
55
+ //# sourceMappingURL=init.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"init.mjs","names":[],"sources":["../src/commands/init.ts"],"sourcesContent":["import { defineCommand } from 'citty'\nimport { writeFile } from 'node:fs/promises'\nimport { existsSync } from 'node:fs'\nimport { consola } from 'consola'\nimport { loadRalphConfig } from '../config/loader.js'\nimport { generateAll } from '../generators/index.js'\n\nconst SAMPLE_CONFIG = `import { defineConfig } from 'fabis-ralph-loop'\n\nexport default defineConfig({\n container: {\n name: 'my-ralph-container',\n baseImage: 'node:22-bookworm',\n // playwright: true,\n // shadowVolumes: ['/workspace/node_modules'],\n hooks: {\n rootSetup: [\n // 'RUN npm install -g pnpm@10',\n ],\n userSetup: [\n // 'RUN corepack enable',\n ],\n },\n },\n project: {\n name: 'My Project',\n description: '',\n context: '- **Monorepo** managed with npm\\\\n- **TypeScript strict mode** everywhere',\n },\n output: {\n mode: 'direct',\n },\n})\n`\n\nexport default defineCommand({\n meta: {\n name: 'init',\n description: 'Scaffold ralph-loop config and generate all files',\n },\n async run() {\n const configPath = 'fabis-ralph-loop.config.ts'\n\n if (existsSync(configPath)) {\n consola.warn(`${configPath} already exists. Regenerating files from existing config.`)\n } else {\n await writeFile(configPath, SAMPLE_CONFIG, 'utf8')\n consola.success(`Created ${configPath}`)\n }\n\n // Load config and generate\n const config = await loadRalphConfig()\n await generateAll(config, process.cwd())\n\n consola.success(\n 'Init complete. Edit fabis-ralph-loop.config.ts and run `fabis-ralph-loop generate` to regenerate.',\n )\n },\n})\n"],"mappings":";;;;;;;;AAOA,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4BtB,mBAAe,cAAc;CAC3B,MAAM;EACJ,MAAM;EACN,aAAa;EACd;CACD,MAAM,MAAM;EACV,MAAM,aAAa;AAEnB,MAAI,WAAW,WAAW,CACxB,SAAQ,KAAK,GAAG,WAAW,2DAA2D;OACjF;AACL,SAAM,UAAU,YAAY,eAAe,OAAO;AAClD,WAAQ,QAAQ,WAAW,aAAa;;AAK1C,QAAM,YADS,MAAM,iBAAiB,EACZ,QAAQ,KAAK,CAAC;AAExC,UAAQ,QACN,oGACD;;CAEJ,CAAC"}