procedure-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/.claude/settings.local.json +23 -0
- package/.env.example +2 -0
- package/AGENTS.md +113 -0
- package/CLAUDE.md +136 -0
- package/CODE-FIXED.md +124 -0
- package/CODE-REVIEW.md +253 -0
- package/README.md +130 -0
- package/config/defaults.json +8 -0
- package/config/powerline-config.json +52 -0
- package/config/stacks/typescript-node.json +15 -0
- package/dist/app.d.ts +1 -0
- package/dist/app.js +131 -0
- package/dist/app.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +6 -0
- package/dist/cli.js.map +1 -0
- package/dist/components/banner.d.ts +1 -0
- package/dist/components/banner.js +11 -0
- package/dist/components/banner.js.map +1 -0
- package/dist/components/gutter-line.d.ts +5 -0
- package/dist/components/gutter-line.js +9 -0
- package/dist/components/gutter-line.js.map +1 -0
- package/dist/components/guttered-select.d.ts +25 -0
- package/dist/components/guttered-select.js +68 -0
- package/dist/components/guttered-select.js.map +1 -0
- package/dist/components/step-indicator.d.ts +7 -0
- package/dist/components/step-indicator.js +12 -0
- package/dist/components/step-indicator.js.map +1 -0
- package/dist/components/timeline.d.ts +13 -0
- package/dist/components/timeline.js +26 -0
- package/dist/components/timeline.js.map +1 -0
- package/dist/lib/fs.d.ts +3 -0
- package/dist/lib/fs.js +15 -0
- package/dist/lib/fs.js.map +1 -0
- package/dist/lib/git.d.ts +4 -0
- package/dist/lib/git.js +37 -0
- package/dist/lib/git.js.map +1 -0
- package/dist/lib/powerline.d.ts +1 -0
- package/dist/lib/powerline.js +30 -0
- package/dist/lib/powerline.js.map +1 -0
- package/dist/lib/template.d.ts +9 -0
- package/dist/lib/template.js +46 -0
- package/dist/lib/template.js.map +1 -0
- package/dist/lib/types.d.ts +44 -0
- package/dist/lib/types.js +2 -0
- package/dist/lib/types.js.map +1 -0
- package/dist/providers/openai.d.ts +1 -0
- package/dist/providers/openai.js +5 -0
- package/dist/providers/openai.js.map +1 -0
- package/dist/providers/zai.d.ts +1 -0
- package/dist/providers/zai.js +7 -0
- package/dist/providers/zai.js.map +1 -0
- package/dist/steps/architecture.d.ts +6 -0
- package/dist/steps/architecture.js +39 -0
- package/dist/steps/architecture.js.map +1 -0
- package/dist/steps/build-test.d.ts +7 -0
- package/dist/steps/build-test.js +38 -0
- package/dist/steps/build-test.js.map +1 -0
- package/dist/steps/generation.d.ts +7 -0
- package/dist/steps/generation.js +60 -0
- package/dist/steps/generation.js.map +1 -0
- package/dist/steps/powerline.d.ts +7 -0
- package/dist/steps/powerline.js +62 -0
- package/dist/steps/powerline.js.map +1 -0
- package/dist/steps/product-context.d.ts +7 -0
- package/dist/steps/product-context.js +90 -0
- package/dist/steps/product-context.js.map +1 -0
- package/dist/steps/project-info.d.ts +6 -0
- package/dist/steps/project-info.js +61 -0
- package/dist/steps/project-info.js.map +1 -0
- package/dist/steps/stack-style.d.ts +6 -0
- package/dist/steps/stack-style.js +67 -0
- package/dist/steps/stack-style.js.map +1 -0
- package/docs/GIAI-THICH-CLAUDE-MD.md +206 -0
- package/docs/PRD.md +130 -0
- package/docs/USER-STORIES.md +181 -0
- package/package.json +38 -0
- package/src/app.tsx +201 -0
- package/src/cli.tsx +6 -0
- package/src/components/banner.tsx +23 -0
- package/src/components/gutter-line.tsx +10 -0
- package/src/components/guttered-select.tsx +137 -0
- package/src/components/step-indicator.tsx +19 -0
- package/src/components/timeline.tsx +49 -0
- package/src/lib/fs.ts +18 -0
- package/src/lib/git.ts +41 -0
- package/src/lib/powerline.ts +48 -0
- package/src/lib/template.ts +59 -0
- package/src/lib/types.ts +68 -0
- package/src/providers/openai.ts +5 -0
- package/src/providers/zai.ts +7 -0
- package/src/steps/architecture.tsx +71 -0
- package/src/steps/build-test.tsx +78 -0
- package/src/steps/generation.tsx +146 -0
- package/src/steps/powerline.tsx +149 -0
- package/src/steps/product-context.tsx +182 -0
- package/src/steps/project-info.tsx +135 -0
- package/src/steps/stack-style.tsx +117 -0
- package/templates/.env.example.hbs +6 -0
- package/templates/CLAUDE.md.hbs +105 -0
- package/templates/README.md.hbs +26 -0
- package/templates/docs/PRD.md.hbs +29 -0
- package/templates/docs/USER-STORIES.md.hbs +46 -0
- package/tsconfig.json +17 -0
package/src/lib/types.ts
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
export type StepId =
|
|
2
|
+
| 'project-info'
|
|
3
|
+
| 'stack-style'
|
|
4
|
+
| 'build-test'
|
|
5
|
+
| 'architecture'
|
|
6
|
+
| 'product-context'
|
|
7
|
+
| 'generation'
|
|
8
|
+
| 'powerline';
|
|
9
|
+
|
|
10
|
+
export interface UserStoryScenario {
|
|
11
|
+
name: string;
|
|
12
|
+
given: string;
|
|
13
|
+
when: string;
|
|
14
|
+
then: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface UserStory {
|
|
18
|
+
title: string;
|
|
19
|
+
asA: string;
|
|
20
|
+
iWant: string;
|
|
21
|
+
soThat: string;
|
|
22
|
+
feature: string;
|
|
23
|
+
scenarios: UserStoryScenario[];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface EnvVar {
|
|
27
|
+
key: string;
|
|
28
|
+
comment: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface WizardAnswers {
|
|
32
|
+
// Step 1: Project Info
|
|
33
|
+
projectName: string;
|
|
34
|
+
description: string;
|
|
35
|
+
packageManager: string;
|
|
36
|
+
license: string;
|
|
37
|
+
|
|
38
|
+
// Step 2: Stack & Style
|
|
39
|
+
codeStyle: string[];
|
|
40
|
+
framework: string;
|
|
41
|
+
language: string;
|
|
42
|
+
|
|
43
|
+
// Step 3: Build & Test
|
|
44
|
+
buildCommand: string;
|
|
45
|
+
testCommand: string;
|
|
46
|
+
typecheckCommand: string;
|
|
47
|
+
lintCommand: string;
|
|
48
|
+
prCommand: string;
|
|
49
|
+
|
|
50
|
+
// Step 4: Architecture
|
|
51
|
+
architecture: string;
|
|
52
|
+
architectureNotes: string;
|
|
53
|
+
|
|
54
|
+
// Step 5: Product Context
|
|
55
|
+
problem: string;
|
|
56
|
+
users: string;
|
|
57
|
+
coreFeatures: string[];
|
|
58
|
+
nonGoals: string[];
|
|
59
|
+
techStack: string;
|
|
60
|
+
userStories: UserStory[];
|
|
61
|
+
envVars: EnvVar[];
|
|
62
|
+
|
|
63
|
+
// Step 6: Generation
|
|
64
|
+
generationSkipped: boolean;
|
|
65
|
+
|
|
66
|
+
// Step 7: Powerline
|
|
67
|
+
setupPowerline: boolean;
|
|
68
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import React, { useState } from "react";
|
|
2
|
+
import { Text } from "ink";
|
|
3
|
+
import { GutterLine } from "../components/gutter-line.js";
|
|
4
|
+
import { GutteredSelect } from "../components/guttered-select.js";
|
|
5
|
+
import type { WizardAnswers } from "../lib/types.js";
|
|
6
|
+
|
|
7
|
+
interface Props {
|
|
8
|
+
onComplete: (answers: Partial<WizardAnswers>) => void;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const ARCHITECTURE_OPTIONS = [
|
|
12
|
+
{ label: "Monorepo — Multiple packages in one repository", value: "Monorepo" },
|
|
13
|
+
{ label: "MVC — Model-View-Controller separation", value: "MVC" },
|
|
14
|
+
{ label: "Feature folders — Co-located components, hooks, utils per feature", value: "Feature folders" },
|
|
15
|
+
{ label: "Layered — Presentation > Business > Data access layers", value: "Layered" },
|
|
16
|
+
{ label: "Microservices — Independent deployable services", value: "Microservices" },
|
|
17
|
+
{ label: "Serverless — Function-based, event-driven architecture", value: "Serverless" },
|
|
18
|
+
{ label: "Modular monolith — Single deploy, strict module boundaries", value: "Modular monolith" },
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
const ARCHITECTURE_NOTES: Record<string, string> = {
|
|
22
|
+
Monorepo:
|
|
23
|
+
"Shared packages in packages/, apps in apps/. Use workspace protocol for cross-references.",
|
|
24
|
+
MVC:
|
|
25
|
+
"Controllers handle HTTP, models handle data, views handle presentation. Keep business logic in models.",
|
|
26
|
+
"Feature folders":
|
|
27
|
+
"Each feature owns its components, hooks, utils, and tests. Shared code in shared/ or lib/.",
|
|
28
|
+
Layered:
|
|
29
|
+
"Strict dependency direction: Presentation -> Business -> Data. No skipping layers.",
|
|
30
|
+
Microservices:
|
|
31
|
+
"Each service owns its data store. Communicate via APIs or message queues. Deploy independently.",
|
|
32
|
+
Serverless:
|
|
33
|
+
"One function per endpoint. Keep functions stateless. Use managed services for state and storage.",
|
|
34
|
+
"Modular monolith":
|
|
35
|
+
"Define explicit module boundaries with public APIs. No direct cross-module database access.",
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export default function Architecture({ onComplete }: Props) {
|
|
39
|
+
const [selected, setSelected] = useState<string | null>(null);
|
|
40
|
+
|
|
41
|
+
function handleChange(value: string) {
|
|
42
|
+
const notes = ARCHITECTURE_NOTES[value] ?? "";
|
|
43
|
+
setSelected(value);
|
|
44
|
+
// Use setTimeout to allow React to render the selection before completing
|
|
45
|
+
setTimeout(() => {
|
|
46
|
+
onComplete({ architecture: value, architectureNotes: notes });
|
|
47
|
+
}, 100);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (selected) {
|
|
51
|
+
return (
|
|
52
|
+
<>
|
|
53
|
+
<GutterLine>
|
|
54
|
+
<Text dimColor>Architecture: {selected}</Text>
|
|
55
|
+
</GutterLine>
|
|
56
|
+
<GutterLine>
|
|
57
|
+
<Text dimColor>Notes: {ARCHITECTURE_NOTES[selected]}</Text>
|
|
58
|
+
</GutterLine>
|
|
59
|
+
</>
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return (
|
|
64
|
+
<>
|
|
65
|
+
<GutterLine>
|
|
66
|
+
<Text bold>Architecture pattern:</Text>
|
|
67
|
+
</GutterLine>
|
|
68
|
+
<GutteredSelect options={ARCHITECTURE_OPTIONS} onChange={handleChange} />
|
|
69
|
+
</>
|
|
70
|
+
);
|
|
71
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import React, { useState } from "react";
|
|
2
|
+
import { Text } from "ink";
|
|
3
|
+
import { TextInput } from "@inkjs/ui";
|
|
4
|
+
import { GutterLine } from "../components/gutter-line.js";
|
|
5
|
+
import type { WizardAnswers } from "../lib/types.js";
|
|
6
|
+
|
|
7
|
+
interface Props {
|
|
8
|
+
initialValues?: Partial<WizardAnswers>;
|
|
9
|
+
onComplete: (answers: Partial<WizardAnswers>) => void;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
type Field =
|
|
13
|
+
| "buildCommand"
|
|
14
|
+
| "testCommand"
|
|
15
|
+
| "typecheckCommand"
|
|
16
|
+
| "lintCommand"
|
|
17
|
+
| "prCommand";
|
|
18
|
+
|
|
19
|
+
const FIELDS: { key: Field; label: string; fallback: string; required: boolean }[] = [
|
|
20
|
+
{ key: "buildCommand", label: "Build command", fallback: "npm run build", required: true },
|
|
21
|
+
{ key: "testCommand", label: "Test command", fallback: "npm run test", required: true },
|
|
22
|
+
{ key: "typecheckCommand", label: "Typecheck command", fallback: "tsc --noEmit", required: false },
|
|
23
|
+
{ key: "lintCommand", label: "Lint command", fallback: "npm run lint", required: false },
|
|
24
|
+
{ key: "prCommand", label: "Pre-PR command", fallback: "npm run lint && npm run test", required: false },
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
export default function BuildTest({ initialValues, onComplete }: Props) {
|
|
28
|
+
const [fieldIndex, setFieldIndex] = useState(0);
|
|
29
|
+
const [answers, setAnswers] = useState<Record<string, string>>({});
|
|
30
|
+
const [error, setError] = useState("");
|
|
31
|
+
|
|
32
|
+
const current = FIELDS[fieldIndex]!;
|
|
33
|
+
const preset = initialValues?.[current.key] as string | undefined;
|
|
34
|
+
const defaultVal = preset || current.fallback;
|
|
35
|
+
|
|
36
|
+
function handleSubmit(value: string) {
|
|
37
|
+
const val = value.trim() || defaultVal;
|
|
38
|
+
|
|
39
|
+
if (current.required && !val) {
|
|
40
|
+
setError(`${current.label} is required`);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
setError("");
|
|
45
|
+
const next = { ...answers, [current.key]: val };
|
|
46
|
+
setAnswers(next);
|
|
47
|
+
|
|
48
|
+
if (fieldIndex < FIELDS.length - 1) {
|
|
49
|
+
setFieldIndex(fieldIndex + 1);
|
|
50
|
+
} else {
|
|
51
|
+
onComplete(next as unknown as Partial<WizardAnswers>);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return (
|
|
56
|
+
<>
|
|
57
|
+
{FIELDS.slice(0, fieldIndex).map((f) => (
|
|
58
|
+
<GutterLine key={f.key}>
|
|
59
|
+
<Text dimColor>
|
|
60
|
+
{f.label}: {answers[f.key]}
|
|
61
|
+
</Text>
|
|
62
|
+
</GutterLine>
|
|
63
|
+
))}
|
|
64
|
+
|
|
65
|
+
<GutterLine>
|
|
66
|
+
<Text bold>{current.label}</Text>
|
|
67
|
+
<Text dimColor> ({defaultVal})</Text>
|
|
68
|
+
<Text bold>: </Text>
|
|
69
|
+
<TextInput placeholder={defaultVal} onSubmit={handleSubmit} />
|
|
70
|
+
</GutterLine>
|
|
71
|
+
{error && (
|
|
72
|
+
<GutterLine>
|
|
73
|
+
<Text color="red">{error}</Text>
|
|
74
|
+
</GutterLine>
|
|
75
|
+
)}
|
|
76
|
+
</>
|
|
77
|
+
);
|
|
78
|
+
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import React, { useEffect, useState } from "react";
|
|
2
|
+
import { Text } from "ink";
|
|
3
|
+
import { ConfirmInput, Spinner } from "@inkjs/ui";
|
|
4
|
+
import { GutterLine } from "../components/gutter-line.js";
|
|
5
|
+
import { scaffoldAll, checkConflicts } from "../lib/template.js";
|
|
6
|
+
import type { WizardAnswers } from "../lib/types.js";
|
|
7
|
+
|
|
8
|
+
interface Props {
|
|
9
|
+
answers: WizardAnswers;
|
|
10
|
+
onComplete: (partial: Partial<WizardAnswers>) => void;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const FILES_TO_GENERATE = [
|
|
14
|
+
"CLAUDE.md",
|
|
15
|
+
"README.md",
|
|
16
|
+
"docs/PRD.md",
|
|
17
|
+
"docs/USER-STORIES.md",
|
|
18
|
+
".env.example",
|
|
19
|
+
".gitignore",
|
|
20
|
+
];
|
|
21
|
+
|
|
22
|
+
export default function Generation({ answers, onComplete }: Props) {
|
|
23
|
+
const [phase, setPhase] = useState<
|
|
24
|
+
"summary" | "running" | "done" | "error"
|
|
25
|
+
>("summary");
|
|
26
|
+
const [errorMsg, setErrorMsg] = useState("");
|
|
27
|
+
|
|
28
|
+
useEffect(() => {
|
|
29
|
+
if (phase !== "running") return;
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
const targetDir = process.cwd();
|
|
33
|
+
scaffoldAll(targetDir, answers);
|
|
34
|
+
setPhase("done");
|
|
35
|
+
const timer = setTimeout(() => onComplete({ generationSkipped: false }), 1500);
|
|
36
|
+
return () => clearTimeout(timer);
|
|
37
|
+
} catch (err) {
|
|
38
|
+
setPhase("error");
|
|
39
|
+
setErrorMsg(err instanceof Error ? err.message : String(err));
|
|
40
|
+
}
|
|
41
|
+
}, [phase]);
|
|
42
|
+
|
|
43
|
+
function handleConfirm() {
|
|
44
|
+
setPhase("running");
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function handleCancel() {
|
|
48
|
+
onComplete({ generationSkipped: true });
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (phase === "summary") {
|
|
52
|
+
const techParts: string[] = [];
|
|
53
|
+
if (answers.language) techParts.push(answers.language);
|
|
54
|
+
if (answers.framework) techParts.push(answers.framework);
|
|
55
|
+
const stack = techParts.length > 0 ? techParts.join(", ") : "N/A";
|
|
56
|
+
|
|
57
|
+
const pmAndLicense = [answers.packageManager, answers.license]
|
|
58
|
+
.filter(Boolean)
|
|
59
|
+
.join(", ");
|
|
60
|
+
|
|
61
|
+
const conflicts = checkConflicts(process.cwd());
|
|
62
|
+
|
|
63
|
+
return (
|
|
64
|
+
<>
|
|
65
|
+
<GutterLine>
|
|
66
|
+
<Text>
|
|
67
|
+
Project: <Text bold>{answers.projectName || "untitled"}</Text>
|
|
68
|
+
{" — "}
|
|
69
|
+
{answers.description || "no description"}
|
|
70
|
+
</Text>
|
|
71
|
+
</GutterLine>
|
|
72
|
+
<GutterLine>
|
|
73
|
+
<Text>
|
|
74
|
+
Stack: {stack}
|
|
75
|
+
{pmAndLicense ? ` (${pmAndLicense})` : ""}
|
|
76
|
+
</Text>
|
|
77
|
+
</GutterLine>
|
|
78
|
+
<GutterLine>
|
|
79
|
+
<Text>
|
|
80
|
+
Build: {answers.buildCommand || "N/A"} | Test:{" "}
|
|
81
|
+
{answers.testCommand || "N/A"}
|
|
82
|
+
</Text>
|
|
83
|
+
</GutterLine>
|
|
84
|
+
<GutterLine>
|
|
85
|
+
<Text>Architecture: {answers.architecture || "N/A"}</Text>
|
|
86
|
+
</GutterLine>
|
|
87
|
+
<GutterLine>
|
|
88
|
+
<Text>
|
|
89
|
+
Features: {answers.coreFeatures.length} | Non-goals:{" "}
|
|
90
|
+
{answers.nonGoals.length}
|
|
91
|
+
</Text>
|
|
92
|
+
</GutterLine>
|
|
93
|
+
<GutterLine>
|
|
94
|
+
<Text> </Text>
|
|
95
|
+
</GutterLine>
|
|
96
|
+
<GutterLine>
|
|
97
|
+
<Text>Files to generate:</Text>
|
|
98
|
+
</GutterLine>
|
|
99
|
+
<GutterLine>
|
|
100
|
+
<Text> {FILES_TO_GENERATE.join(", ")}</Text>
|
|
101
|
+
</GutterLine>
|
|
102
|
+
{conflicts.length > 0 && (
|
|
103
|
+
<>
|
|
104
|
+
<GutterLine>
|
|
105
|
+
<Text> </Text>
|
|
106
|
+
</GutterLine>
|
|
107
|
+
<GutterLine>
|
|
108
|
+
<Text color="yellow">
|
|
109
|
+
{"⚠ Will overwrite: "}{conflicts.join(", ")}
|
|
110
|
+
</Text>
|
|
111
|
+
</GutterLine>
|
|
112
|
+
</>
|
|
113
|
+
)}
|
|
114
|
+
<GutterLine>
|
|
115
|
+
<Text> </Text>
|
|
116
|
+
</GutterLine>
|
|
117
|
+
<GutterLine>
|
|
118
|
+
<Text>Proceed? </Text>
|
|
119
|
+
<ConfirmInput onConfirm={handleConfirm} onCancel={handleCancel} />
|
|
120
|
+
</GutterLine>
|
|
121
|
+
</>
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (phase === "running") {
|
|
126
|
+
return (
|
|
127
|
+
<GutterLine>
|
|
128
|
+
<Spinner label="Generating project files..." />
|
|
129
|
+
</GutterLine>
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (phase === "error") {
|
|
134
|
+
return (
|
|
135
|
+
<GutterLine>
|
|
136
|
+
<Text color="red">Error generating files: {errorMsg}</Text>
|
|
137
|
+
</GutterLine>
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return (
|
|
142
|
+
<GutterLine>
|
|
143
|
+
<Text color="green">All project files generated successfully.</Text>
|
|
144
|
+
</GutterLine>
|
|
145
|
+
);
|
|
146
|
+
}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import React, { useState } from "react";
|
|
2
|
+
import { Text } from "ink";
|
|
3
|
+
import { ConfirmInput, Spinner } from "@inkjs/ui";
|
|
4
|
+
import { GutterLine } from "../components/gutter-line.js";
|
|
5
|
+
import { setupPowerline } from "../lib/powerline.js";
|
|
6
|
+
import { initGit } from "../lib/git.js";
|
|
7
|
+
import type { WizardAnswers } from "../lib/types.js";
|
|
8
|
+
|
|
9
|
+
interface Props {
|
|
10
|
+
answers: WizardAnswers;
|
|
11
|
+
onComplete: (answers: Partial<WizardAnswers>) => void;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
type Phase =
|
|
15
|
+
| "ask-powerline"
|
|
16
|
+
| "ask-git"
|
|
17
|
+
| "running"
|
|
18
|
+
| "done"
|
|
19
|
+
| "error";
|
|
20
|
+
|
|
21
|
+
interface SetupResult {
|
|
22
|
+
gitCommitted?: boolean;
|
|
23
|
+
gitWarning?: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export default function Powerline({ answers, onComplete }: Props) {
|
|
27
|
+
const [phase, setPhase] = useState<Phase>("ask-powerline");
|
|
28
|
+
const [wantPowerline, setWantPowerline] = useState(false);
|
|
29
|
+
const [wantGit, setWantGit] = useState(false);
|
|
30
|
+
const [errorMsg, setErrorMsg] = useState("");
|
|
31
|
+
const [setupResult, setSetupResult] = useState<SetupResult>({});
|
|
32
|
+
|
|
33
|
+
function runSetup(powerline: boolean, git: boolean) {
|
|
34
|
+
setPhase("running");
|
|
35
|
+
try {
|
|
36
|
+
const targetDir = process.cwd();
|
|
37
|
+
if (powerline) {
|
|
38
|
+
setupPowerline(targetDir);
|
|
39
|
+
}
|
|
40
|
+
if (git) {
|
|
41
|
+
const gitResult = initGit(targetDir);
|
|
42
|
+
setSetupResult({
|
|
43
|
+
gitCommitted: gitResult.committed,
|
|
44
|
+
gitWarning: gitResult.committed ? undefined : gitResult.reason,
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
setPhase("done");
|
|
48
|
+
setTimeout(() => onComplete({ setupPowerline: powerline }), 1000);
|
|
49
|
+
} catch (err) {
|
|
50
|
+
setPhase("error");
|
|
51
|
+
setErrorMsg(err instanceof Error ? err.message : String(err));
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (phase === "ask-powerline") {
|
|
56
|
+
return (
|
|
57
|
+
<>
|
|
58
|
+
<GutterLine>
|
|
59
|
+
<Text bold>Setup Claude Powerline status bar?</Text>
|
|
60
|
+
</GutterLine>
|
|
61
|
+
<GutterLine>
|
|
62
|
+
<ConfirmInput
|
|
63
|
+
onConfirm={() => {
|
|
64
|
+
setWantPowerline(true);
|
|
65
|
+
setPhase("ask-git");
|
|
66
|
+
}}
|
|
67
|
+
onCancel={() => {
|
|
68
|
+
setWantPowerline(false);
|
|
69
|
+
setPhase("ask-git");
|
|
70
|
+
}}
|
|
71
|
+
/>
|
|
72
|
+
</GutterLine>
|
|
73
|
+
</>
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (phase === "ask-git") {
|
|
78
|
+
return (
|
|
79
|
+
<>
|
|
80
|
+
{wantPowerline && (
|
|
81
|
+
<GutterLine>
|
|
82
|
+
<Text dimColor>Powerline: yes</Text>
|
|
83
|
+
</GutterLine>
|
|
84
|
+
)}
|
|
85
|
+
<GutterLine>
|
|
86
|
+
<Text bold>Initialize git repository?</Text>
|
|
87
|
+
</GutterLine>
|
|
88
|
+
<GutterLine>
|
|
89
|
+
<ConfirmInput
|
|
90
|
+
onConfirm={() => {
|
|
91
|
+
setWantGit(true);
|
|
92
|
+
runSetup(wantPowerline, true);
|
|
93
|
+
}}
|
|
94
|
+
onCancel={() => {
|
|
95
|
+
setWantGit(false);
|
|
96
|
+
runSetup(wantPowerline, false);
|
|
97
|
+
}}
|
|
98
|
+
/>
|
|
99
|
+
</GutterLine>
|
|
100
|
+
</>
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (phase === "running") {
|
|
105
|
+
return (
|
|
106
|
+
<GutterLine>
|
|
107
|
+
<Spinner label="Running setup tasks..." />
|
|
108
|
+
</GutterLine>
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (phase === "error") {
|
|
113
|
+
return (
|
|
114
|
+
<GutterLine>
|
|
115
|
+
<Text color="red">Setup error: {errorMsg}</Text>
|
|
116
|
+
</GutterLine>
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return (
|
|
121
|
+
<>
|
|
122
|
+
{wantPowerline && (
|
|
123
|
+
<GutterLine>
|
|
124
|
+
<Text color="green">Powerline configured.</Text>
|
|
125
|
+
</GutterLine>
|
|
126
|
+
)}
|
|
127
|
+
{wantGit && setupResult.gitCommitted && (
|
|
128
|
+
<GutterLine>
|
|
129
|
+
<Text color="green">Git repository initialized.</Text>
|
|
130
|
+
</GutterLine>
|
|
131
|
+
)}
|
|
132
|
+
{wantGit && !setupResult.gitCommitted && setupResult.gitWarning && (
|
|
133
|
+
<GutterLine>
|
|
134
|
+
<Text color="yellow">{"⚠ Git: "}{setupResult.gitWarning}</Text>
|
|
135
|
+
</GutterLine>
|
|
136
|
+
)}
|
|
137
|
+
{!wantPowerline && !wantGit && (
|
|
138
|
+
<GutterLine>
|
|
139
|
+
<Text dimColor>No extras setup.</Text>
|
|
140
|
+
</GutterLine>
|
|
141
|
+
)}
|
|
142
|
+
<GutterLine>
|
|
143
|
+
<Text color="green" bold>
|
|
144
|
+
Setup complete!
|
|
145
|
+
</Text>
|
|
146
|
+
</GutterLine>
|
|
147
|
+
</>
|
|
148
|
+
);
|
|
149
|
+
}
|