meto-cli 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/CHANGELOG.md +24 -0
- package/LICENSE +21 -0
- package/README.md +106 -0
- package/dist/cli/git.d.ts +11 -0
- package/dist/cli/git.d.ts.map +1 -0
- package/dist/cli/git.js +34 -0
- package/dist/cli/git.js.map +1 -0
- package/dist/cli/github.d.ts +35 -0
- package/dist/cli/github.d.ts.map +1 -0
- package/dist/cli/github.js +117 -0
- package/dist/cli/github.js.map +1 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +192 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/interruption.d.ts +32 -0
- package/dist/cli/interruption.d.ts.map +1 -0
- package/dist/cli/interruption.js +66 -0
- package/dist/cli/interruption.js.map +1 -0
- package/dist/cli/preflight.d.ts +44 -0
- package/dist/cli/preflight.d.ts.map +1 -0
- package/dist/cli/preflight.js +79 -0
- package/dist/cli/preflight.js.map +1 -0
- package/dist/cli/prompts.d.ts +7 -0
- package/dist/cli/prompts.d.ts.map +1 -0
- package/dist/cli/prompts.js +148 -0
- package/dist/cli/prompts.js.map +1 -0
- package/dist/cli/renderer.d.ts +45 -0
- package/dist/cli/renderer.d.ts.map +1 -0
- package/dist/cli/renderer.js +118 -0
- package/dist/cli/renderer.js.map +1 -0
- package/dist/cli/scaffold.d.ts +18 -0
- package/dist/cli/scaffold.d.ts.map +1 -0
- package/dist/cli/scaffold.js +48 -0
- package/dist/cli/scaffold.js.map +1 -0
- package/dist/cli/stacks.d.ts +40 -0
- package/dist/cli/stacks.d.ts.map +1 -0
- package/dist/cli/stacks.js +294 -0
- package/dist/cli/stacks.js.map +1 -0
- package/dist/cli/supabase.d.ts +24 -0
- package/dist/cli/supabase.d.ts.map +1 -0
- package/dist/cli/supabase.js +72 -0
- package/dist/cli/supabase.js.map +1 -0
- package/dist/cli/tree.d.ts +9 -0
- package/dist/cli/tree.d.ts.map +1 -0
- package/dist/cli/tree.js +70 -0
- package/dist/cli/tree.js.map +1 -0
- package/dist/cli/types.d.ts +33 -0
- package/dist/cli/types.d.ts.map +1 -0
- package/dist/cli/types.js +2 -0
- package/dist/cli/types.js.map +1 -0
- package/package.json +47 -0
- package/templates/.claude/agent-memory/lom-developer/MEMORY.md +20 -0
- package/templates/.claude/agent-memory/lom-pm/MEMORY.md +22 -0
- package/templates/.claude/agent-memory/lom-tester/MEMORY.md +18 -0
- package/templates/.claude/agents/developer-agent.md +56 -0
- package/templates/.claude/agents/pm-agent.md +72 -0
- package/templates/.claude/agents/tester-agent.md +62 -0
- package/templates/CLAUDE.md +63 -0
- package/templates/ai/backlog/epics.md +1 -0
- package/templates/ai/context/decisions.md +19 -0
- package/templates/ai/context/product-vision.md +19 -0
- package/templates/ai/context/tech-stack.md +8 -0
- package/templates/ai/context/test-log.md +7 -0
- package/templates/ai/tasks/tasks-backlog.md +1 -0
- package/templates/ai/tasks/tasks-done.md +1 -0
- package/templates/ai/tasks/tasks-in-progress.md +1 -0
- package/templates/ai/tasks/tasks-in-testing.md +1 -0
- package/templates/ai/tasks/tasks-todo.md +1 -0
- package/templates/ai/workflows/commit-conventions.md +10 -0
- package/templates/ai/workflows/definition-of-done.md +5 -0
- package/templates/gitignore +16 -0
- package/templates/src/.gitkeep +0 -0
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { rm } from "node:fs/promises";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
import * as p from "@clack/prompts";
|
|
4
|
+
/**
|
|
5
|
+
* Manages SIGINT cleanup during scaffold writing.
|
|
6
|
+
*
|
|
7
|
+
* When armed with an output directory, a SIGINT will remove that directory
|
|
8
|
+
* and print a cleanup message. When not armed, SIGINT exits cleanly
|
|
9
|
+
* (the default behavior from clack's prompt cancellation).
|
|
10
|
+
*/
|
|
11
|
+
export class InterruptionHandler {
|
|
12
|
+
outputDir;
|
|
13
|
+
boundHandler;
|
|
14
|
+
constructor() {
|
|
15
|
+
this.outputDir = undefined;
|
|
16
|
+
this.boundHandler = () => {
|
|
17
|
+
this.handleInterrupt();
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Registers the SIGINT handler.
|
|
22
|
+
*/
|
|
23
|
+
install() {
|
|
24
|
+
process.on("SIGINT", this.boundHandler);
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Removes the SIGINT handler.
|
|
28
|
+
*/
|
|
29
|
+
uninstall() {
|
|
30
|
+
process.removeListener("SIGINT", this.boundHandler);
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Arms the handler with the output directory that should be cleaned up
|
|
34
|
+
* if SIGINT is received. Call this just before scaffold writing begins.
|
|
35
|
+
*/
|
|
36
|
+
arm(outputDirectory) {
|
|
37
|
+
this.outputDir = resolve(outputDirectory);
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Disarms the handler. Call this after scaffold writing completes
|
|
41
|
+
* successfully so that a late SIGINT does not remove the user's project.
|
|
42
|
+
*/
|
|
43
|
+
disarm() {
|
|
44
|
+
this.outputDir = undefined;
|
|
45
|
+
}
|
|
46
|
+
handleInterrupt() {
|
|
47
|
+
if (this.outputDir !== undefined) {
|
|
48
|
+
const dir = this.outputDir;
|
|
49
|
+
this.outputDir = undefined;
|
|
50
|
+
rm(dir, { recursive: true, force: true })
|
|
51
|
+
.then(() => {
|
|
52
|
+
p.log.warning("Interrupted. Cleaned up partial scaffold.");
|
|
53
|
+
process.exit(0);
|
|
54
|
+
})
|
|
55
|
+
.catch(() => {
|
|
56
|
+
p.log.warning("Interrupted. Could not clean up partial scaffold.");
|
|
57
|
+
process.exit(0);
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
p.cancel("Interrupted.");
|
|
62
|
+
process.exit(0);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=interruption.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"interruption.js","sourceRoot":"","sources":["../../src/cli/interruption.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,EAAE,EAAE,MAAM,kBAAkB,CAAC;AACtC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,KAAK,CAAC,MAAM,gBAAgB,CAAC;AAEpC;;;;;;GAMG;AACH,MAAM,OAAO,mBAAmB;IACtB,SAAS,CAAqB;IACrB,YAAY,CAAa;IAE1C;QACE,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;QAC3B,IAAI,CAAC,YAAY,GAAG,GAAG,EAAE;YACvB,IAAI,CAAC,eAAe,EAAE,CAAC;QACzB,CAAC,CAAC;IACJ,CAAC;IAED;;OAEG;IACH,OAAO;QACL,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,IAAI,CAAC,YAAY,CAAC,CAAC;IAC1C,CAAC;IAED;;OAEG;IACH,SAAS;QACP,OAAO,CAAC,cAAc,CAAC,QAAQ,EAAE,IAAI,CAAC,YAAY,CAAC,CAAC;IACtD,CAAC;IAED;;;OAGG;IACH,GAAG,CAAC,eAAuB;QACzB,IAAI,CAAC,SAAS,GAAG,OAAO,CAAC,eAAe,CAAC,CAAC;IAC5C,CAAC;IAED;;;OAGG;IACH,MAAM;QACJ,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;IAC7B,CAAC;IAEO,eAAe;QACrB,IAAI,IAAI,CAAC,SAAS,KAAK,SAAS,EAAE,CAAC;YACjC,MAAM,GAAG,GAAG,IAAI,CAAC,SAAS,CAAC;YAC3B,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;YAE3B,EAAE,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;iBACtC,IAAI,CAAC,GAAG,EAAE;gBACT,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,2CAA2C,CAAC,CAAC;gBAC3D,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YAClB,CAAC,CAAC;iBACD,KAAK,CAAC,GAAG,EAAE;gBACV,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,mDAAmD,CAAC,CAAC;gBACnE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YAClB,CAAC,CAAC,CAAC;QACP,CAAC;aAAM,CAAC;YACN,CAAC,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC;YACzB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;IACH,CAAC;CACF"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Result of running pre-flight environment checks.
|
|
3
|
+
*/
|
|
4
|
+
export interface PreflightResult {
|
|
5
|
+
/** Whether git is available on the system PATH. */
|
|
6
|
+
gitAvailable: boolean;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Parses the major version number from a Node.js version string.
|
|
10
|
+
* Expects the format "vMAJOR.MINOR.PATCH" (e.g. "v18.12.0").
|
|
11
|
+
* Returns NaN if the format is unrecognized.
|
|
12
|
+
*/
|
|
13
|
+
export declare function parseNodeMajorVersion(versionString: string): number;
|
|
14
|
+
/**
|
|
15
|
+
* Checks whether the current Node.js version meets the minimum requirement.
|
|
16
|
+
* Returns an error message string if the version is too old, or undefined if OK.
|
|
17
|
+
*/
|
|
18
|
+
export declare function checkNodeVersion(versionString: string, minimumMajor: number): string | undefined;
|
|
19
|
+
/**
|
|
20
|
+
* Checks whether git is available by running `git --version`.
|
|
21
|
+
* Resolves to true if git is found, false otherwise.
|
|
22
|
+
*/
|
|
23
|
+
export declare function checkGitAvailable(): Promise<boolean>;
|
|
24
|
+
/**
|
|
25
|
+
* Checks whether the parent directory of the given output path exists and is writable.
|
|
26
|
+
* Returns an error message string if the check fails, or undefined if OK.
|
|
27
|
+
*/
|
|
28
|
+
export declare function checkWritePermission(outputPath: string): Promise<string | undefined>;
|
|
29
|
+
/**
|
|
30
|
+
* Runs pre-flight environment checks before prompts begin.
|
|
31
|
+
*
|
|
32
|
+
* - Validates Node.js version (exits with code 1 if < 18)
|
|
33
|
+
* - Checks git availability (stores warning, does not exit)
|
|
34
|
+
*
|
|
35
|
+
* Returns a PreflightResult with the check outcomes.
|
|
36
|
+
*/
|
|
37
|
+
export declare function runPreflightChecks(): Promise<PreflightResult>;
|
|
38
|
+
/**
|
|
39
|
+
* Error thrown when a blocking pre-flight check fails.
|
|
40
|
+
*/
|
|
41
|
+
export declare class PreflightError extends Error {
|
|
42
|
+
constructor(message: string);
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=preflight.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"preflight.d.ts","sourceRoot":"","sources":["../../src/cli/preflight.ts"],"names":[],"mappings":"AAIA;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,mDAAmD;IACnD,YAAY,EAAE,OAAO,CAAC;CACvB;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,aAAa,EAAE,MAAM,GAAG,MAAM,CAMnE;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAC9B,aAAa,EAAE,MAAM,EACrB,YAAY,EAAE,MAAM,GACnB,MAAM,GAAG,SAAS,CAMpB;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,IAAI,OAAO,CAAC,OAAO,CAAC,CAMpD;AAED;;;GAGG;AACH,wBAAsB,oBAAoB,CACxC,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAY7B;AAED;;;;;;;GAOG;AACH,wBAAsB,kBAAkB,IAAI,OAAO,CAAC,eAAe,CAAC,CASnE;AAED;;GAEG;AACH,qBAAa,cAAe,SAAQ,KAAK;gBAC3B,OAAO,EAAE,MAAM;CAI5B"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { access, constants } from "node:fs/promises";
|
|
3
|
+
import { dirname, resolve } from "node:path";
|
|
4
|
+
/**
|
|
5
|
+
* Parses the major version number from a Node.js version string.
|
|
6
|
+
* Expects the format "vMAJOR.MINOR.PATCH" (e.g. "v18.12.0").
|
|
7
|
+
* Returns NaN if the format is unrecognized.
|
|
8
|
+
*/
|
|
9
|
+
export function parseNodeMajorVersion(versionString) {
|
|
10
|
+
const match = /^v(\d+)/.exec(versionString);
|
|
11
|
+
if (!match) {
|
|
12
|
+
return NaN;
|
|
13
|
+
}
|
|
14
|
+
return parseInt(match[1], 10);
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Checks whether the current Node.js version meets the minimum requirement.
|
|
18
|
+
* Returns an error message string if the version is too old, or undefined if OK.
|
|
19
|
+
*/
|
|
20
|
+
export function checkNodeVersion(versionString, minimumMajor) {
|
|
21
|
+
const major = parseNodeMajorVersion(versionString);
|
|
22
|
+
if (Number.isNaN(major) || major < minimumMajor) {
|
|
23
|
+
return `Meto requires Node.js ${minimumMajor} or later. You are running ${versionString}.`;
|
|
24
|
+
}
|
|
25
|
+
return undefined;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Checks whether git is available by running `git --version`.
|
|
29
|
+
* Resolves to true if git is found, false otherwise.
|
|
30
|
+
*/
|
|
31
|
+
export function checkGitAvailable() {
|
|
32
|
+
return new Promise((promiseResolve) => {
|
|
33
|
+
execFile("git", ["--version"], (error) => {
|
|
34
|
+
promiseResolve(error === null);
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Checks whether the parent directory of the given output path exists and is writable.
|
|
40
|
+
* Returns an error message string if the check fails, or undefined if OK.
|
|
41
|
+
*/
|
|
42
|
+
export async function checkWritePermission(outputPath) {
|
|
43
|
+
const absolutePath = resolve(outputPath);
|
|
44
|
+
const parentDir = dirname(absolutePath);
|
|
45
|
+
try {
|
|
46
|
+
await access(parentDir, constants.W_OK);
|
|
47
|
+
return undefined;
|
|
48
|
+
}
|
|
49
|
+
catch (err) {
|
|
50
|
+
const reason = err instanceof Error ? err.message : "permission denied";
|
|
51
|
+
return `Cannot write to ${absolutePath}: ${reason}`;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Runs pre-flight environment checks before prompts begin.
|
|
56
|
+
*
|
|
57
|
+
* - Validates Node.js version (exits with code 1 if < 18)
|
|
58
|
+
* - Checks git availability (stores warning, does not exit)
|
|
59
|
+
*
|
|
60
|
+
* Returns a PreflightResult with the check outcomes.
|
|
61
|
+
*/
|
|
62
|
+
export async function runPreflightChecks() {
|
|
63
|
+
const nodeError = checkNodeVersion(process.version, 18);
|
|
64
|
+
if (nodeError !== undefined) {
|
|
65
|
+
throw new PreflightError(nodeError);
|
|
66
|
+
}
|
|
67
|
+
const gitAvailable = await checkGitAvailable();
|
|
68
|
+
return { gitAvailable };
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Error thrown when a blocking pre-flight check fails.
|
|
72
|
+
*/
|
|
73
|
+
export class PreflightError extends Error {
|
|
74
|
+
constructor(message) {
|
|
75
|
+
super(message);
|
|
76
|
+
this.name = "PreflightError";
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
//# sourceMappingURL=preflight.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"preflight.js","sourceRoot":"","sources":["../../src/cli/preflight.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC9C,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAU7C;;;;GAIG;AACH,MAAM,UAAU,qBAAqB,CAAC,aAAqB;IACzD,MAAM,KAAK,GAAG,SAAS,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;IAC5C,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,OAAO,GAAG,CAAC;IACb,CAAC;IACD,OAAO,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;AAChC,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,gBAAgB,CAC9B,aAAqB,EACrB,YAAoB;IAEpB,MAAM,KAAK,GAAG,qBAAqB,CAAC,aAAa,CAAC,CAAC;IACnD,IAAI,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,KAAK,GAAG,YAAY,EAAE,CAAC;QAChD,OAAO,yBAAyB,YAAY,8BAA8B,aAAa,GAAG,CAAC;IAC7F,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,iBAAiB;IAC/B,OAAO,IAAI,OAAO,CAAC,CAAC,cAAc,EAAE,EAAE;QACpC,QAAQ,CAAC,KAAK,EAAE,CAAC,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,EAAE;YACvC,cAAc,CAAC,KAAK,KAAK,IAAI,CAAC,CAAC;QACjC,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,oBAAoB,CACxC,UAAkB;IAElB,MAAM,YAAY,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;IACzC,MAAM,SAAS,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC;IAExC,IAAI,CAAC;QACH,MAAM,MAAM,CAAC,SAAS,EAAE,SAAS,CAAC,IAAI,CAAC,CAAC;QACxC,OAAO,SAAS,CAAC;IACnB,CAAC;IAAC,OAAO,GAAY,EAAE,CAAC;QACtB,MAAM,MAAM,GACV,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,mBAAmB,CAAC;QAC3D,OAAO,mBAAmB,YAAY,KAAK,MAAM,EAAE,CAAC;IACtD,CAAC;AACH,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB;IACtC,MAAM,SAAS,GAAG,gBAAgB,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;IACxD,IAAI,SAAS,KAAK,SAAS,EAAE,CAAC;QAC5B,MAAM,IAAI,cAAc,CAAC,SAAS,CAAC,CAAC;IACtC,CAAC;IAED,MAAM,YAAY,GAAG,MAAM,iBAAiB,EAAE,CAAC;IAE/C,OAAO,EAAE,YAAY,EAAE,CAAC;AAC1B,CAAC;AAED;;GAEG;AACH,MAAM,OAAO,cAAe,SAAQ,KAAK;IACvC,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,gBAAgB,CAAC;IAC/B,CAAC;CACF"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { ProjectBrief } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Collects the project brief from the user via interactive prompts.
|
|
4
|
+
* Returns the completed brief. Exits the process if the user cancels.
|
|
5
|
+
*/
|
|
6
|
+
export declare function collectProjectBrief(): Promise<ProjectBrief>;
|
|
7
|
+
//# sourceMappingURL=prompts.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prompts.d.ts","sourceRoot":"","sources":["../../src/cli/prompts.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAa,MAAM,YAAY,CAAC;AAoC1D;;;GAGG;AACH,wBAAsB,mBAAmB,IAAI,OAAO,CAAC,YAAY,CAAC,CA4HjE"}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import * as p from "@clack/prompts";
|
|
2
|
+
/**
|
|
3
|
+
* Validates that a string is non-empty after trimming.
|
|
4
|
+
*/
|
|
5
|
+
function requireNonEmpty(value) {
|
|
6
|
+
if (value.trim().length === 0) {
|
|
7
|
+
return "This field is required.";
|
|
8
|
+
}
|
|
9
|
+
return undefined;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Validates a project name: non-empty, kebab-case friendly.
|
|
13
|
+
*/
|
|
14
|
+
function validateProjectName(value) {
|
|
15
|
+
const trimmed = value.trim();
|
|
16
|
+
if (trimmed.length === 0) {
|
|
17
|
+
return "Project name is required.";
|
|
18
|
+
}
|
|
19
|
+
if (!/^[a-z0-9][a-z0-9-]*$/.test(trimmed)) {
|
|
20
|
+
return "Use lowercase letters, numbers, and hyphens only (e.g. my-project).";
|
|
21
|
+
}
|
|
22
|
+
return undefined;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Handles cancellation by printing a message and exiting.
|
|
26
|
+
*/
|
|
27
|
+
function handleCancel(value) {
|
|
28
|
+
if (p.isCancel(value)) {
|
|
29
|
+
p.cancel("Project setup cancelled.");
|
|
30
|
+
process.exit(0);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Collects the project brief from the user via interactive prompts.
|
|
35
|
+
* Returns the completed brief. Exits the process if the user cancels.
|
|
36
|
+
*/
|
|
37
|
+
export async function collectProjectBrief() {
|
|
38
|
+
const projectName = await p.text({
|
|
39
|
+
message: "What is your project name?",
|
|
40
|
+
placeholder: "my-awesome-project",
|
|
41
|
+
validate: validateProjectName,
|
|
42
|
+
});
|
|
43
|
+
handleCancel(projectName);
|
|
44
|
+
const description = await p.text({
|
|
45
|
+
message: "Describe your project in one line.",
|
|
46
|
+
placeholder: "A task management app for remote teams",
|
|
47
|
+
validate: requireNonEmpty,
|
|
48
|
+
});
|
|
49
|
+
handleCancel(description);
|
|
50
|
+
const targetUsers = await p.text({
|
|
51
|
+
message: "Who are the target users?",
|
|
52
|
+
placeholder: "Developers, small teams, freelancers",
|
|
53
|
+
validate: requireNonEmpty,
|
|
54
|
+
});
|
|
55
|
+
handleCancel(targetUsers);
|
|
56
|
+
const techStack = await p.select({
|
|
57
|
+
message: "Choose a tech stack.",
|
|
58
|
+
options: [
|
|
59
|
+
{
|
|
60
|
+
value: "nextjs-supabase",
|
|
61
|
+
label: "Next.js + Supabase",
|
|
62
|
+
hint: "Full-stack web app with auth, database, and edge functions",
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
value: "react-native",
|
|
66
|
+
label: "React Native",
|
|
67
|
+
hint: "Cross-platform mobile app",
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
value: "nodejs-cli",
|
|
71
|
+
label: "Node.js CLI",
|
|
72
|
+
hint: "Command-line tool distributed via npm",
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
value: "custom",
|
|
76
|
+
label: "Custom",
|
|
77
|
+
hint: "Describe your own stack",
|
|
78
|
+
},
|
|
79
|
+
],
|
|
80
|
+
});
|
|
81
|
+
if (p.isCancel(techStack)) {
|
|
82
|
+
p.cancel("Project setup cancelled.");
|
|
83
|
+
process.exit(0);
|
|
84
|
+
}
|
|
85
|
+
let customStack;
|
|
86
|
+
if (techStack === "custom") {
|
|
87
|
+
const customStackInput = await p.text({
|
|
88
|
+
message: "Describe your tech stack.",
|
|
89
|
+
placeholder: "Django + PostgreSQL + React frontend",
|
|
90
|
+
validate: requireNonEmpty,
|
|
91
|
+
});
|
|
92
|
+
handleCancel(customStackInput);
|
|
93
|
+
customStack = customStackInput.trim();
|
|
94
|
+
}
|
|
95
|
+
const defaultDeferred = "To be defined by @meto-pm";
|
|
96
|
+
const problemStatement = await p.text({
|
|
97
|
+
message: "What problem does this project solve?",
|
|
98
|
+
placeholder: "Users struggle with X because Y, leading to Z",
|
|
99
|
+
defaultValue: defaultDeferred,
|
|
100
|
+
});
|
|
101
|
+
handleCancel(problemStatement);
|
|
102
|
+
const successCriteria = await p.text({
|
|
103
|
+
message: "How will you measure success?",
|
|
104
|
+
placeholder: "User can do X in under Y minutes, Z% adoption in first month",
|
|
105
|
+
defaultValue: defaultDeferred,
|
|
106
|
+
});
|
|
107
|
+
handleCancel(successCriteria);
|
|
108
|
+
const valueProposition = await p.text({
|
|
109
|
+
message: "What is the core value proposition? (one line)",
|
|
110
|
+
placeholder: "The fastest way to do X without compromising on Y",
|
|
111
|
+
defaultValue: defaultDeferred,
|
|
112
|
+
});
|
|
113
|
+
handleCancel(valueProposition);
|
|
114
|
+
const outOfScope = await p.text({
|
|
115
|
+
message: "What is out of scope for v1?",
|
|
116
|
+
placeholder: "Multi-tenancy, mobile app, internationalization",
|
|
117
|
+
defaultValue: defaultDeferred,
|
|
118
|
+
});
|
|
119
|
+
handleCancel(outOfScope);
|
|
120
|
+
const defaultConventions = "TypeScript strict mode, no any types, no console.log in production code";
|
|
121
|
+
const codeConventions = await p.text({
|
|
122
|
+
message: "Any code conventions or standards?",
|
|
123
|
+
placeholder: "TypeScript strict, ESLint, Prettier, conventional commits",
|
|
124
|
+
defaultValue: defaultConventions,
|
|
125
|
+
});
|
|
126
|
+
handleCancel(codeConventions);
|
|
127
|
+
const outputDirectory = await p.text({
|
|
128
|
+
message: "Output directory?",
|
|
129
|
+
defaultValue: `./${projectName}`,
|
|
130
|
+
placeholder: `./${projectName}`,
|
|
131
|
+
validate: requireNonEmpty,
|
|
132
|
+
});
|
|
133
|
+
handleCancel(outputDirectory);
|
|
134
|
+
return {
|
|
135
|
+
projectName: projectName.trim(),
|
|
136
|
+
description: description.trim(),
|
|
137
|
+
targetUsers: targetUsers.trim(),
|
|
138
|
+
techStack,
|
|
139
|
+
customStack,
|
|
140
|
+
problemStatement: problemStatement.trim(),
|
|
141
|
+
successCriteria: successCriteria.trim(),
|
|
142
|
+
valueProposition: valueProposition.trim(),
|
|
143
|
+
outOfScope: outOfScope.trim(),
|
|
144
|
+
codeConventions: codeConventions.trim(),
|
|
145
|
+
outputDirectory: outputDirectory.trim(),
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
//# sourceMappingURL=prompts.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prompts.js","sourceRoot":"","sources":["../../src/cli/prompts.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { ProjectBrief } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Map of template tokens to their replacement values.
|
|
4
|
+
* Tokens in templates look like {{TOKEN_NAME}}.
|
|
5
|
+
*/
|
|
6
|
+
export type TokenMap = Record<string, string>;
|
|
7
|
+
/**
|
|
8
|
+
* A single rendered file ready to be written to disk.
|
|
9
|
+
*/
|
|
10
|
+
export interface RenderedFile {
|
|
11
|
+
/** Relative path from the output root (preserves folder structure) */
|
|
12
|
+
relativePath: string;
|
|
13
|
+
/** File content after token replacement */
|
|
14
|
+
content: string;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Builds a token replacement map from the project brief.
|
|
18
|
+
* Only tokens that the brief can fill are included.
|
|
19
|
+
* Remaining tokens (e.g. {{PROBLEM_STATEMENT}}) are left as-is
|
|
20
|
+
* for the PM agent to populate later.
|
|
21
|
+
*/
|
|
22
|
+
export declare function buildTokenMap(brief: ProjectBrief): TokenMap;
|
|
23
|
+
/**
|
|
24
|
+
* Replaces all `{{TOKEN}}` occurrences in content using the provided map.
|
|
25
|
+
* Tokens not present in the map are left untouched.
|
|
26
|
+
*/
|
|
27
|
+
export declare function replaceTokens(content: string, tokens: TokenMap): string;
|
|
28
|
+
/**
|
|
29
|
+
* Resolves the absolute path to the /templates/ directory.
|
|
30
|
+
* Uses the package root (two levels up from dist/cli/renderer.js).
|
|
31
|
+
*/
|
|
32
|
+
export declare function resolveTemplatesDir(): string;
|
|
33
|
+
/**
|
|
34
|
+
* Renders all template files by replacing tokens with values from the brief.
|
|
35
|
+
*
|
|
36
|
+
* Reads every file under the templates directory, performs token replacement,
|
|
37
|
+
* and returns an array of RenderedFile objects with their relative paths
|
|
38
|
+
* and rendered content.
|
|
39
|
+
*
|
|
40
|
+
* @param templatesDir - Absolute path to the templates directory
|
|
41
|
+
* @param tokens - Token map for replacements
|
|
42
|
+
* @returns Array of rendered files ready to be written to disk
|
|
43
|
+
*/
|
|
44
|
+
export declare function renderTemplates(templatesDir: string, tokens: TokenMap): Promise<RenderedFile[]>;
|
|
45
|
+
//# sourceMappingURL=renderer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"renderer.d.ts","sourceRoot":"","sources":["../../src/cli/renderer.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE/C;;;GAGG;AACH,MAAM,MAAM,QAAQ,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AAE9C;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,sEAAsE;IACtE,YAAY,EAAE,MAAM,CAAC;IACrB,2CAA2C;IAC3C,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,YAAY,GAAG,QAAQ,CAkB3D;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,GAAG,MAAM,CAOvE;AAqDD;;;GAGG;AACH,wBAAgB,mBAAmB,IAAI,MAAM,CAK5C;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,eAAe,CACnC,YAAY,EAAE,MAAM,EACpB,MAAM,EAAE,QAAQ,GACf,OAAO,CAAC,YAAY,EAAE,CAAC,CAgBzB"}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { readdir, readFile } from "node:fs/promises";
|
|
2
|
+
import { basename, dirname, join, relative } from "node:path";
|
|
3
|
+
import { getDefinitionOfDone, getStackDescription, getStarterEpics, } from "./stacks.js";
|
|
4
|
+
/**
|
|
5
|
+
* Builds a token replacement map from the project brief.
|
|
6
|
+
* Only tokens that the brief can fill are included.
|
|
7
|
+
* Remaining tokens (e.g. {{PROBLEM_STATEMENT}}) are left as-is
|
|
8
|
+
* for the PM agent to populate later.
|
|
9
|
+
*/
|
|
10
|
+
export function buildTokenMap(brief) {
|
|
11
|
+
return {
|
|
12
|
+
PROJECT_NAME: brief.projectName,
|
|
13
|
+
PRODUCT_VISION: brief.description,
|
|
14
|
+
TECH_STACK: getStackDescription(brief.techStack, brief.customStack),
|
|
15
|
+
TARGET_USERS: brief.targetUsers,
|
|
16
|
+
PROBLEM_STATEMENT: brief.problemStatement,
|
|
17
|
+
SUCCESS_CRITERIA: brief.successCriteria,
|
|
18
|
+
VALUE_PROPOSITION: brief.valueProposition,
|
|
19
|
+
OUT_OF_SCOPE: brief.outOfScope,
|
|
20
|
+
CODE_CONVENTIONS: brief.codeConventions,
|
|
21
|
+
DEFINITION_OF_DONE: getDefinitionOfDone(brief.techStack, brief.customStack),
|
|
22
|
+
STARTER_EPICS: getStarterEpics(brief.techStack, brief.projectName, brief.customStack),
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Replaces all `{{TOKEN}}` occurrences in content using the provided map.
|
|
27
|
+
* Tokens not present in the map are left untouched.
|
|
28
|
+
*/
|
|
29
|
+
export function replaceTokens(content, tokens) {
|
|
30
|
+
return content.replace(/\{\{([A-Z_]+)\}\}/g, (match, tokenName) => {
|
|
31
|
+
if (tokenName in tokens) {
|
|
32
|
+
return tokens[tokenName];
|
|
33
|
+
}
|
|
34
|
+
return match;
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Renames used in templates to work around npm pack exclusions.
|
|
39
|
+
* npm strips `.gitignore` files from tarballs, so we store them
|
|
40
|
+
* without the dot prefix and restore it at render time.
|
|
41
|
+
*
|
|
42
|
+
* Key: filename as stored in templates/ (no dot)
|
|
43
|
+
* Value: filename as written to the output directory (with dot)
|
|
44
|
+
*/
|
|
45
|
+
const TEMPLATE_RENAMES = {
|
|
46
|
+
gitignore: ".gitignore",
|
|
47
|
+
};
|
|
48
|
+
/**
|
|
49
|
+
* Applies template filename renames to a relative path.
|
|
50
|
+
* Only the basename is checked — directory components are left untouched.
|
|
51
|
+
*/
|
|
52
|
+
function applyTemplateRenames(relativePath) {
|
|
53
|
+
const name = basename(relativePath);
|
|
54
|
+
if (name in TEMPLATE_RENAMES) {
|
|
55
|
+
const dir = dirname(relativePath);
|
|
56
|
+
const renamed = TEMPLATE_RENAMES[name];
|
|
57
|
+
return dir === "." ? renamed : join(dir, renamed);
|
|
58
|
+
}
|
|
59
|
+
return relativePath;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Recursively collects all file paths under a directory.
|
|
63
|
+
* Returns paths relative to the root directory.
|
|
64
|
+
* Includes files inside hidden folders (e.g. `.claude/`).
|
|
65
|
+
*/
|
|
66
|
+
async function collectFiles(rootDir) {
|
|
67
|
+
const results = [];
|
|
68
|
+
async function walk(dir) {
|
|
69
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
70
|
+
for (const entry of entries) {
|
|
71
|
+
const fullPath = join(dir, entry.name);
|
|
72
|
+
if (entry.isDirectory()) {
|
|
73
|
+
await walk(fullPath);
|
|
74
|
+
}
|
|
75
|
+
else if (entry.isFile()) {
|
|
76
|
+
results.push(relative(rootDir, fullPath));
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
await walk(rootDir);
|
|
81
|
+
return results;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Resolves the absolute path to the /templates/ directory.
|
|
85
|
+
* Uses the package root (two levels up from dist/cli/renderer.js).
|
|
86
|
+
*/
|
|
87
|
+
export function resolveTemplatesDir() {
|
|
88
|
+
const currentFileUrl = new URL(import.meta.url);
|
|
89
|
+
// At runtime: dist/cli/renderer.js -> package root is ../../
|
|
90
|
+
const packageRoot = new URL("../../", currentFileUrl);
|
|
91
|
+
return join(decodeURIComponent(packageRoot.pathname), "templates");
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Renders all template files by replacing tokens with values from the brief.
|
|
95
|
+
*
|
|
96
|
+
* Reads every file under the templates directory, performs token replacement,
|
|
97
|
+
* and returns an array of RenderedFile objects with their relative paths
|
|
98
|
+
* and rendered content.
|
|
99
|
+
*
|
|
100
|
+
* @param templatesDir - Absolute path to the templates directory
|
|
101
|
+
* @param tokens - Token map for replacements
|
|
102
|
+
* @returns Array of rendered files ready to be written to disk
|
|
103
|
+
*/
|
|
104
|
+
export async function renderTemplates(templatesDir, tokens) {
|
|
105
|
+
const filePaths = await collectFiles(templatesDir);
|
|
106
|
+
const rendered = [];
|
|
107
|
+
for (const relativePath of filePaths) {
|
|
108
|
+
const absolutePath = join(templatesDir, relativePath);
|
|
109
|
+
const content = await readFile(absolutePath, "utf-8");
|
|
110
|
+
const renderedContent = replaceTokens(content, tokens);
|
|
111
|
+
rendered.push({
|
|
112
|
+
relativePath: applyTemplateRenames(relativePath),
|
|
113
|
+
content: renderedContent,
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
return rendered;
|
|
117
|
+
}
|
|
118
|
+
//# sourceMappingURL=renderer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"renderer.js","sourceRoot":"","sources":["../../src/cli/renderer.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { RenderedFile } from "./renderer.js";
|
|
2
|
+
/**
|
|
3
|
+
* Error thrown when the output directory already exists and is non-empty.
|
|
4
|
+
*/
|
|
5
|
+
export declare class DirectoryNotEmptyError extends Error {
|
|
6
|
+
constructor(dirPath: string);
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Writes all rendered template files to the output directory.
|
|
10
|
+
*
|
|
11
|
+
* Creates the output directory and any nested subdirectories as needed.
|
|
12
|
+
* Throws DirectoryNotEmptyError if the target directory already has content.
|
|
13
|
+
*
|
|
14
|
+
* @param outputDir - Absolute or relative path for the scaffold output
|
|
15
|
+
* @param files - Array of rendered files to write
|
|
16
|
+
*/
|
|
17
|
+
export declare function writeScaffold(outputDir: string, files: RenderedFile[]): Promise<void>;
|
|
18
|
+
//# sourceMappingURL=scaffold.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scaffold.d.ts","sourceRoot":"","sources":["../../src/cli/scaffold.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAElD;;GAEG;AACH,qBAAa,sBAAuB,SAAQ,KAAK;gBACnC,OAAO,EAAE,MAAM;CAO5B;AAgBD;;;;;;;;GAQG;AACH,wBAAsB,aAAa,CACjC,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,YAAY,EAAE,GACpB,OAAO,CAAC,IAAI,CAAC,CAcf"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { mkdir, readdir, writeFile } from "node:fs/promises";
|
|
2
|
+
import { dirname, join, resolve } from "node:path";
|
|
3
|
+
/**
|
|
4
|
+
* Error thrown when the output directory already exists and is non-empty.
|
|
5
|
+
*/
|
|
6
|
+
export class DirectoryNotEmptyError extends Error {
|
|
7
|
+
constructor(dirPath) {
|
|
8
|
+
super(`Directory "${dirPath}" already exists and is not empty. ` +
|
|
9
|
+
"Choose a different output directory or remove the existing one.");
|
|
10
|
+
this.name = "DirectoryNotEmptyError";
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Checks whether a directory exists and contains files.
|
|
15
|
+
* Returns true if the directory exists and has at least one entry.
|
|
16
|
+
*/
|
|
17
|
+
async function isNonEmptyDirectory(dirPath) {
|
|
18
|
+
try {
|
|
19
|
+
const entries = await readdir(dirPath);
|
|
20
|
+
return entries.length > 0;
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
// Directory does not exist — that's fine
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Writes all rendered template files to the output directory.
|
|
29
|
+
*
|
|
30
|
+
* Creates the output directory and any nested subdirectories as needed.
|
|
31
|
+
* Throws DirectoryNotEmptyError if the target directory already has content.
|
|
32
|
+
*
|
|
33
|
+
* @param outputDir - Absolute or relative path for the scaffold output
|
|
34
|
+
* @param files - Array of rendered files to write
|
|
35
|
+
*/
|
|
36
|
+
export async function writeScaffold(outputDir, files) {
|
|
37
|
+
const absoluteOutputDir = resolve(outputDir);
|
|
38
|
+
if (await isNonEmptyDirectory(absoluteOutputDir)) {
|
|
39
|
+
throw new DirectoryNotEmptyError(absoluteOutputDir);
|
|
40
|
+
}
|
|
41
|
+
for (const file of files) {
|
|
42
|
+
const targetPath = join(absoluteOutputDir, file.relativePath);
|
|
43
|
+
const targetDir = dirname(targetPath);
|
|
44
|
+
await mkdir(targetDir, { recursive: true });
|
|
45
|
+
await writeFile(targetPath, file.content, "utf-8");
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=scaffold.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scaffold.js","sourceRoot":"","sources":["../../src/cli/scaffold.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC7D,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAGnD;;GAEG;AACH,MAAM,OAAO,sBAAuB,SAAQ,KAAK;IAC/C,YAAY,OAAe;QACzB,KAAK,CACH,cAAc,OAAO,qCAAqC;YACxD,iEAAiE,CACpE,CAAC;QACF,IAAI,CAAC,IAAI,GAAG,wBAAwB,CAAC;IACvC,CAAC;CACF;AAED;;;GAGG;AACH,KAAK,UAAU,mBAAmB,CAAC,OAAe;IAChD,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,OAAO,CAAC,CAAC;QACvC,OAAO,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC;IAC5B,CAAC;IAAC,MAAM,CAAC;QACP,yCAAyC;QACzC,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,SAAiB,EACjB,KAAqB;IAErB,MAAM,iBAAiB,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC;IAE7C,IAAI,MAAM,mBAAmB,CAAC,iBAAiB,CAAC,EAAE,CAAC;QACjD,MAAM,IAAI,sBAAsB,CAAC,iBAAiB,CAAC,CAAC;IACtD,CAAC;IAED,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,UAAU,GAAG,IAAI,CAAC,iBAAiB,EAAE,IAAI,CAAC,YAAY,CAAC,CAAC;QAC9D,MAAM,SAAS,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;QAEtC,MAAM,KAAK,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAC5C,MAAM,SAAS,CAAC,UAAU,EAAE,IAAI,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;IACrD,CAAC;AACH,CAAC"}
|