kratos-framework 1.27.55
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/README.md +487 -0
- package/bin/kratos-framework.js +190 -0
- package/kratos-install.sh +920 -0
- package/package.json +33 -0
|
@@ -0,0 +1,920 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
5
|
+
# Kratos Framework Installer
|
|
6
|
+
# Installs, updates, validates, and reports on KRATOS installations.
|
|
7
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
8
|
+
|
|
9
|
+
readonly VERSION="1.27.4"
|
|
10
|
+
readonly DEFAULT_GITHUB_REPO="https://github.com/jlouage/Kratos-framework.git"
|
|
11
|
+
readonly GITHUB_REPO="${KRATOS_REPO_URL:-$DEFAULT_GITHUB_REPO}"
|
|
12
|
+
readonly MANIFEST_REL="_kratos/_config/manifest.yaml"
|
|
13
|
+
|
|
14
|
+
# Temp dir tracking for cleanup
|
|
15
|
+
TEMP_CLONE_DIR=""
|
|
16
|
+
|
|
17
|
+
# ─── Colors & Formatting ────────────────────────────────────────────────────
|
|
18
|
+
|
|
19
|
+
if [[ -t 1 ]]; then
|
|
20
|
+
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[0;33m'
|
|
21
|
+
BLUE='\033[0;34m'; CYAN='\033[0;36m'; BOLD='\033[1m'; DIM='\033[2m'
|
|
22
|
+
RESET='\033[0m'
|
|
23
|
+
else
|
|
24
|
+
RED=''; GREEN=''; YELLOW=''; BLUE=''; CYAN=''; BOLD=''; DIM=''; RESET=''
|
|
25
|
+
fi
|
|
26
|
+
|
|
27
|
+
info() { printf "${BLUE}ℹ${RESET} %s\n" "$*"; }
|
|
28
|
+
success() { printf "${GREEN}✔${RESET} %s\n" "$*"; }
|
|
29
|
+
warn() { printf "${YELLOW}⚠${RESET} %s\n" "$*"; }
|
|
30
|
+
error() { printf "${RED}✖${RESET} %s\n" "$*" >&2; }
|
|
31
|
+
step() { printf "${CYAN}→${RESET} %s\n" "$*"; }
|
|
32
|
+
detail() { printf "${DIM} %s${RESET}\n" "$*"; }
|
|
33
|
+
|
|
34
|
+
# ─── Globals (set by argument parsing) ──────────────────────────────────────
|
|
35
|
+
|
|
36
|
+
CMD=""
|
|
37
|
+
SOURCE_FLAG=""
|
|
38
|
+
TARGET=""
|
|
39
|
+
OPT_YES=false
|
|
40
|
+
OPT_MINIMAL=false
|
|
41
|
+
OPT_DRY_RUN=false
|
|
42
|
+
OPT_VERBOSE=false
|
|
43
|
+
|
|
44
|
+
# ─── Utility Functions ──────────────────────────────────────────────────────
|
|
45
|
+
|
|
46
|
+
cleanup() {
|
|
47
|
+
if [[ -n "$TEMP_CLONE_DIR" && -d "$TEMP_CLONE_DIR" ]]; then
|
|
48
|
+
rm -rf "$TEMP_CLONE_DIR"
|
|
49
|
+
[[ "$OPT_VERBOSE" == true ]] && detail "Cleaned up temp dir: $TEMP_CLONE_DIR" || true
|
|
50
|
+
fi
|
|
51
|
+
}
|
|
52
|
+
trap cleanup EXIT
|
|
53
|
+
|
|
54
|
+
clone_from_github() {
|
|
55
|
+
if ! command -v git &>/dev/null; then
|
|
56
|
+
error "git is required to clone from GitHub but was not found."
|
|
57
|
+
error "Install git or provide a local source with --source."
|
|
58
|
+
exit 1
|
|
59
|
+
fi
|
|
60
|
+
TEMP_CLONE_DIR="$(mktemp -d "${TMPDIR:-/tmp}/kratos-framework-XXXXXX")"
|
|
61
|
+
info "Cloning KRATOS from GitHub..." >&2
|
|
62
|
+
if git clone --depth 1 "$GITHUB_REPO" "$TEMP_CLONE_DIR" 2>/dev/null; then
|
|
63
|
+
success "Cloned to temporary directory" >&2
|
|
64
|
+
else
|
|
65
|
+
error "Failed to clone from $GITHUB_REPO"
|
|
66
|
+
exit 1
|
|
67
|
+
fi
|
|
68
|
+
echo "$TEMP_CLONE_DIR"
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
resolve_source() {
|
|
72
|
+
local resolved=""
|
|
73
|
+
|
|
74
|
+
# 1. --source flag
|
|
75
|
+
if [[ -n "$SOURCE_FLAG" ]]; then
|
|
76
|
+
resolved="$SOURCE_FLAG"
|
|
77
|
+
[[ "$OPT_VERBOSE" == true ]] && detail "Source from --source flag: $resolved" >&2
|
|
78
|
+
# 2. $KRATOS_SOURCE env var
|
|
79
|
+
elif [[ -n "${KRATOS_SOURCE:-}" ]]; then
|
|
80
|
+
resolved="$KRATOS_SOURCE"
|
|
81
|
+
[[ "$OPT_VERBOSE" == true ]] && detail "Source from \$KRATOS_SOURCE: $resolved" >&2
|
|
82
|
+
# 3. Self-detect: script's own directory
|
|
83
|
+
elif [[ -d "$(dirname "$(realpath "$0")")/_kratos" ]]; then
|
|
84
|
+
resolved="$(dirname "$(realpath "$0")")"
|
|
85
|
+
[[ "$OPT_VERBOSE" == true ]] && detail "Source from script location: $resolved" >&2
|
|
86
|
+
# 4. GitHub clone
|
|
87
|
+
else
|
|
88
|
+
resolved="$(clone_from_github)"
|
|
89
|
+
fi
|
|
90
|
+
|
|
91
|
+
echo "$resolved"
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
validate_source() {
|
|
95
|
+
local src="$1"
|
|
96
|
+
if [[ ! -f "$src/$MANIFEST_REL" ]]; then
|
|
97
|
+
error "Invalid KRATOS source: $src"
|
|
98
|
+
error "Expected $MANIFEST_REL but it was not found."
|
|
99
|
+
exit 1
|
|
100
|
+
fi
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
copy_if_missing() {
|
|
104
|
+
local src="$1" dst="$2"
|
|
105
|
+
if [[ -e "$dst" ]]; then
|
|
106
|
+
[[ "$OPT_VERBOSE" == true ]] && detail "Skipped (exists): $dst"
|
|
107
|
+
return 0
|
|
108
|
+
fi
|
|
109
|
+
if [[ "$OPT_DRY_RUN" == true ]]; then
|
|
110
|
+
detail "[dry-run] Would copy: $dst"
|
|
111
|
+
return 0
|
|
112
|
+
fi
|
|
113
|
+
mkdir -p "$(dirname "$dst")"
|
|
114
|
+
cp "$src" "$dst"
|
|
115
|
+
[[ "$OPT_VERBOSE" == true ]] && detail "Copied: $dst" || true
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
copy_with_backup() {
|
|
119
|
+
local src="$1" dst="$2" backup_dir="$3"
|
|
120
|
+
if [[ ! -e "$dst" ]]; then
|
|
121
|
+
if [[ "$OPT_DRY_RUN" == true ]]; then
|
|
122
|
+
detail "[dry-run] Would copy: $dst"
|
|
123
|
+
return 0
|
|
124
|
+
fi
|
|
125
|
+
mkdir -p "$(dirname "$dst")"
|
|
126
|
+
cp "$src" "$dst"
|
|
127
|
+
[[ "$OPT_VERBOSE" == true ]] && detail "Copied (new): $dst" || true
|
|
128
|
+
return 0
|
|
129
|
+
fi
|
|
130
|
+
# Compare files — skip if identical
|
|
131
|
+
if cmp -s "$src" "$dst"; then
|
|
132
|
+
[[ "$OPT_VERBOSE" == true ]] && detail "Unchanged: $dst" || true
|
|
133
|
+
return 0
|
|
134
|
+
fi
|
|
135
|
+
if [[ "$OPT_DRY_RUN" == true ]]; then
|
|
136
|
+
detail "[dry-run] Would update (with backup): $dst"
|
|
137
|
+
return 0
|
|
138
|
+
fi
|
|
139
|
+
# Back up the existing file
|
|
140
|
+
local rel_path="${dst#$TARGET/}"
|
|
141
|
+
local backup_path="$backup_dir/$rel_path"
|
|
142
|
+
mkdir -p "$(dirname "$backup_path")"
|
|
143
|
+
cp "$dst" "$backup_path"
|
|
144
|
+
cp "$src" "$dst"
|
|
145
|
+
[[ "$OPT_VERBOSE" == true ]] && detail "Updated (backed up): $dst" || true
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
append_if_missing() {
|
|
149
|
+
local file="$1" marker="$2" content="$3"
|
|
150
|
+
if [[ -f "$file" ]] && grep -qF "$marker" "$file"; then
|
|
151
|
+
[[ "$OPT_VERBOSE" == true ]] && detail "Already in $(basename "$file"): $marker"
|
|
152
|
+
return 0
|
|
153
|
+
fi
|
|
154
|
+
if [[ "$OPT_DRY_RUN" == true ]]; then
|
|
155
|
+
detail "[dry-run] Would append to $(basename "$file"): $marker"
|
|
156
|
+
return 0
|
|
157
|
+
fi
|
|
158
|
+
# Add a newline before appending if file exists and doesn't end with newline
|
|
159
|
+
if [[ -f "$file" ]] && [[ -s "$file" ]] && [[ "$(tail -c 1 "$file")" != "" ]]; then
|
|
160
|
+
printf '\n' >> "$file"
|
|
161
|
+
fi
|
|
162
|
+
printf '%s\n' "$content" >> "$file"
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
prompt_value() {
|
|
166
|
+
local prompt_text="$1" default="$2"
|
|
167
|
+
if [[ "$OPT_YES" == true ]]; then
|
|
168
|
+
echo "$default"
|
|
169
|
+
return 0
|
|
170
|
+
fi
|
|
171
|
+
local value
|
|
172
|
+
printf "${BOLD}%s${RESET} [%s]: " "$prompt_text" "$default" >&2
|
|
173
|
+
read -r value
|
|
174
|
+
echo "${value:-$default}"
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
extract_yaml_value() {
|
|
178
|
+
local file="$1" key="$2"
|
|
179
|
+
grep "^${key}:" "$file" 2>/dev/null | sed "s/^${key}:[[:space:]]*//" | sed 's/^"//;s/"$//' || echo ""
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
count_files() {
|
|
183
|
+
local dir="$1" pattern="${2:-*}"
|
|
184
|
+
find "$dir" -name "$pattern" -type f 2>/dev/null | wc -l | tr -d ' '
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
set_global_value() {
|
|
188
|
+
local file="$1" key="$2" value="$3"
|
|
189
|
+
if [[ ! -f "$file" ]]; then
|
|
190
|
+
return 0
|
|
191
|
+
fi
|
|
192
|
+
if grep -q "^${key}:" "$file"; then
|
|
193
|
+
if [[ "$(uname)" == "Darwin" ]]; then
|
|
194
|
+
sed -i '' "s/^${key}:.*/${key}: \"${value}\"/" "$file"
|
|
195
|
+
else
|
|
196
|
+
sed -i "s/^${key}:.*/${key}: \"${value}\"/" "$file"
|
|
197
|
+
fi
|
|
198
|
+
else
|
|
199
|
+
printf '%s: "%s"\n' "$key" "$value" >> "$file"
|
|
200
|
+
fi
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
# ─── cmd_init ───────────────────────────────────────────────────────────────
|
|
204
|
+
|
|
205
|
+
cmd_init() {
|
|
206
|
+
local source
|
|
207
|
+
source="$(resolve_source)"
|
|
208
|
+
validate_source "$source"
|
|
209
|
+
|
|
210
|
+
local src_version
|
|
211
|
+
src_version="$(extract_yaml_value "$source/_kratos/_config/global.yaml" "framework_version")"
|
|
212
|
+
|
|
213
|
+
printf "\n${BOLD}Kratos Framework Installer v%s${RESET}\n" "$VERSION"
|
|
214
|
+
printf " Source: %s\n" "$source"
|
|
215
|
+
printf " Target: %s\n" "$TARGET"
|
|
216
|
+
printf " Version: %s\n\n" "${src_version:-unknown}"
|
|
217
|
+
|
|
218
|
+
if [[ "$OPT_DRY_RUN" == true ]]; then
|
|
219
|
+
warn "Dry-run mode — no files will be written"
|
|
220
|
+
echo ""
|
|
221
|
+
fi
|
|
222
|
+
|
|
223
|
+
if [[ -d "$TARGET/_kratos" ]]; then
|
|
224
|
+
warn "Target already contains _kratos/ — use 'update' to refresh framework files."
|
|
225
|
+
if [[ "$OPT_YES" != true ]]; then
|
|
226
|
+
printf "Continue with init anyway? [y/N]: "
|
|
227
|
+
local confirm; read -r confirm
|
|
228
|
+
[[ "$confirm" =~ ^[Yy] ]] || { info "Aborted."; exit 0; }
|
|
229
|
+
fi
|
|
230
|
+
fi
|
|
231
|
+
|
|
232
|
+
# Step 1: Create target docs directories
|
|
233
|
+
step "Creating documentation directories..."
|
|
234
|
+
for dir in planning-artifacts implementation-artifacts test-artifacts creative-artifacts; do
|
|
235
|
+
if [[ "$OPT_DRY_RUN" == true ]]; then
|
|
236
|
+
detail "[dry-run] Would create: docs/$dir/"
|
|
237
|
+
else
|
|
238
|
+
mkdir -p "$TARGET/docs/$dir"
|
|
239
|
+
fi
|
|
240
|
+
done
|
|
241
|
+
|
|
242
|
+
# Step 2: Copy framework files (full or minimal profile)
|
|
243
|
+
step "Copying framework files..."
|
|
244
|
+
if [[ "$OPT_DRY_RUN" == true ]]; then
|
|
245
|
+
if [[ "$OPT_MINIMAL" == true ]]; then
|
|
246
|
+
detail "[dry-run] Would copy minimal KRATOS profile"
|
|
247
|
+
else
|
|
248
|
+
detail "[dry-run] Would copy _kratos/ (excluding checkpoints and .resolved/*.yaml)"
|
|
249
|
+
fi
|
|
250
|
+
else
|
|
251
|
+
mkdir -p "$TARGET/_kratos"
|
|
252
|
+
if [[ "$OPT_MINIMAL" == true ]]; then
|
|
253
|
+
local minimal_paths=(
|
|
254
|
+
"_config"
|
|
255
|
+
"_memory/config.yaml"
|
|
256
|
+
"core"
|
|
257
|
+
"dev"
|
|
258
|
+
"lifecycle/config.yaml"
|
|
259
|
+
"lifecycle/module-help.csv"
|
|
260
|
+
"lifecycle/workflows/4-implementation/dev-story"
|
|
261
|
+
"lifecycle/workflows/4-implementation/code-review"
|
|
262
|
+
"lifecycle/workflows/quick-flow"
|
|
263
|
+
)
|
|
264
|
+
for entry in "${minimal_paths[@]}"; do
|
|
265
|
+
local src_path="$source/_kratos/$entry"
|
|
266
|
+
local dst_path="$TARGET/_kratos/$entry"
|
|
267
|
+
[[ -e "$src_path" ]] || continue
|
|
268
|
+
if [[ -d "$src_path" ]]; then
|
|
269
|
+
mkdir -p "$dst_path"
|
|
270
|
+
rsync -a \
|
|
271
|
+
--exclude='_memory/checkpoints/*.yaml' \
|
|
272
|
+
--exclude='_memory/checkpoints/completed/*.yaml' \
|
|
273
|
+
--exclude='.resolved/*.yaml' \
|
|
274
|
+
--exclude='_memory/*-sidecar/*.md' \
|
|
275
|
+
--exclude='_memory/*-sidecar/*.yaml' \
|
|
276
|
+
"$src_path/" "$dst_path/"
|
|
277
|
+
else
|
|
278
|
+
mkdir -p "$(dirname "$dst_path")"
|
|
279
|
+
cp "$src_path" "$dst_path"
|
|
280
|
+
fi
|
|
281
|
+
done
|
|
282
|
+
else
|
|
283
|
+
rsync -a \
|
|
284
|
+
--exclude='_memory/checkpoints/*.yaml' \
|
|
285
|
+
--exclude='_memory/checkpoints/completed/*.yaml' \
|
|
286
|
+
--exclude='.resolved/*.yaml' \
|
|
287
|
+
--exclude='_memory/*-sidecar/*.md' \
|
|
288
|
+
--exclude='_memory/*-sidecar/*.yaml' \
|
|
289
|
+
"$source/_kratos/" "$TARGET/_kratos/"
|
|
290
|
+
fi
|
|
291
|
+
fi
|
|
292
|
+
|
|
293
|
+
# Step 3: Create memory directories with .gitkeep
|
|
294
|
+
step "Creating memory directories..."
|
|
295
|
+
local sidecar_dirs=("checkpoints" "checkpoints/completed")
|
|
296
|
+
if [[ "$OPT_MINIMAL" != true ]]; then
|
|
297
|
+
sidecar_dirs+=(
|
|
298
|
+
"architect-sidecar"
|
|
299
|
+
"devops-sidecar"
|
|
300
|
+
"orchestrator-sidecar"
|
|
301
|
+
"pm-sidecar"
|
|
302
|
+
"security-sidecar"
|
|
303
|
+
"sm-sidecar"
|
|
304
|
+
"storyteller-sidecar"
|
|
305
|
+
"tech-writer-sidecar"
|
|
306
|
+
"test-architect-sidecar"
|
|
307
|
+
)
|
|
308
|
+
fi
|
|
309
|
+
for dir in "${sidecar_dirs[@]}"; do
|
|
310
|
+
if [[ "$OPT_DRY_RUN" == true ]]; then
|
|
311
|
+
detail "[dry-run] Would create: _kratos/_memory/$dir/"
|
|
312
|
+
else
|
|
313
|
+
mkdir -p "$TARGET/_kratos/_memory/$dir"
|
|
314
|
+
touch "$TARGET/_kratos/_memory/$dir/.gitkeep"
|
|
315
|
+
fi
|
|
316
|
+
done
|
|
317
|
+
|
|
318
|
+
# Step 4: Create .resolved directories with .gitkeep
|
|
319
|
+
step "Creating .resolved directories..."
|
|
320
|
+
local resolved_dirs=("core" "lifecycle")
|
|
321
|
+
if [[ "$OPT_MINIMAL" != true ]]; then
|
|
322
|
+
resolved_dirs+=("creative" "testing")
|
|
323
|
+
fi
|
|
324
|
+
for mod in "${resolved_dirs[@]}"; do
|
|
325
|
+
if [[ "$OPT_DRY_RUN" == true ]]; then
|
|
326
|
+
detail "[dry-run] Would create: _kratos/$mod/.resolved/"
|
|
327
|
+
else
|
|
328
|
+
mkdir -p "$TARGET/_kratos/$mod/.resolved"
|
|
329
|
+
touch "$TARGET/_kratos/$mod/.resolved/.gitkeep"
|
|
330
|
+
fi
|
|
331
|
+
done
|
|
332
|
+
|
|
333
|
+
# Step 5: Customize global.yaml
|
|
334
|
+
step "Configuring global.yaml..."
|
|
335
|
+
local project_name user_name
|
|
336
|
+
local dir_name
|
|
337
|
+
dir_name="$(basename "$TARGET")"
|
|
338
|
+
project_name="$(prompt_value "Project name" "$dir_name")"
|
|
339
|
+
user_name="$(prompt_value "User name" "$(whoami)")"
|
|
340
|
+
|
|
341
|
+
if [[ "$OPT_DRY_RUN" == true ]]; then
|
|
342
|
+
detail "[dry-run] Would set project_name=$project_name, user_name=$user_name"
|
|
343
|
+
else
|
|
344
|
+
local global_file="$TARGET/_kratos/_config/global.yaml"
|
|
345
|
+
if [[ -f "$global_file" ]]; then
|
|
346
|
+
set_global_value "$global_file" "project_name" "$project_name"
|
|
347
|
+
set_global_value "$global_file" "user_name" "$user_name"
|
|
348
|
+
set_global_value "$global_file" "install_profile" "$([[ "$OPT_MINIMAL" == true ]] && echo minimal || echo full)"
|
|
349
|
+
fi
|
|
350
|
+
fi
|
|
351
|
+
|
|
352
|
+
# Step 6: Copy CLAUDE.md (skip if exists)
|
|
353
|
+
step "Setting up CLAUDE.md..."
|
|
354
|
+
if [[ -f "$source/CLAUDE.md" ]]; then
|
|
355
|
+
copy_if_missing "$source/CLAUDE.md" "$TARGET/CLAUDE.md"
|
|
356
|
+
fi
|
|
357
|
+
|
|
358
|
+
# Step 7: Copy slash commands to .claude/commands/
|
|
359
|
+
step "Installing slash commands..."
|
|
360
|
+
if [[ -d "$source/.claude/commands" ]]; then
|
|
361
|
+
if [[ "$OPT_DRY_RUN" == true ]]; then
|
|
362
|
+
local cmd_count
|
|
363
|
+
if [[ "$OPT_MINIMAL" == true ]]; then
|
|
364
|
+
cmd_count="10"
|
|
365
|
+
else
|
|
366
|
+
cmd_count="$(find "$source/.claude/commands" -name 'kratos*.md' -type f | wc -l | tr -d ' ')"
|
|
367
|
+
fi
|
|
368
|
+
detail "[dry-run] Would copy $cmd_count slash commands to .claude/commands/"
|
|
369
|
+
else
|
|
370
|
+
mkdir -p "$TARGET/.claude/commands"
|
|
371
|
+
if [[ "$OPT_MINIMAL" == true ]]; then
|
|
372
|
+
local minimal_commands=(
|
|
373
|
+
"kratos-help.md"
|
|
374
|
+
"kratos-build-configs.md"
|
|
375
|
+
"kratos-resume.md"
|
|
376
|
+
"kratos-quick-spec.md"
|
|
377
|
+
"kratos-quick-dev.md"
|
|
378
|
+
"kratos-dev-story.md"
|
|
379
|
+
"kratos-code-review.md"
|
|
380
|
+
"kratos-agent-senior-frontend.md"
|
|
381
|
+
"kratos-agent-senior-backend.md"
|
|
382
|
+
"kratos-agent-senior-fullstack.md"
|
|
383
|
+
)
|
|
384
|
+
for basename_cmd in "${minimal_commands[@]}"; do
|
|
385
|
+
local cmd_file="$source/.claude/commands/$basename_cmd"
|
|
386
|
+
[[ -f "$cmd_file" ]] || continue
|
|
387
|
+
copy_if_missing "$cmd_file" "$TARGET/.claude/commands/$basename_cmd"
|
|
388
|
+
done
|
|
389
|
+
else
|
|
390
|
+
for cmd_file in "$source/.claude/commands"/kratos*.md; do
|
|
391
|
+
[[ -f "$cmd_file" ]] || continue
|
|
392
|
+
local basename_cmd
|
|
393
|
+
basename_cmd="$(basename "$cmd_file")"
|
|
394
|
+
copy_if_missing "$cmd_file" "$TARGET/.claude/commands/$basename_cmd"
|
|
395
|
+
done
|
|
396
|
+
fi
|
|
397
|
+
fi
|
|
398
|
+
fi
|
|
399
|
+
|
|
400
|
+
# Step 8: Append KRATOS entries to .gitignore
|
|
401
|
+
step "Updating .gitignore..."
|
|
402
|
+
local gitignore_block
|
|
403
|
+
gitignore_block="$(cat <<'GITIGNORE'
|
|
404
|
+
|
|
405
|
+
# Kratos Framework — runtime artifacts
|
|
406
|
+
_kratos/_memory/checkpoints/*.yaml
|
|
407
|
+
_kratos/_memory/*-sidecar/*.md
|
|
408
|
+
_kratos/_memory/*-sidecar/*.yaml
|
|
409
|
+
_kratos/**/.resolved/*.yaml
|
|
410
|
+
!_kratos/**/.resolved/.gitkeep
|
|
411
|
+
GITIGNORE
|
|
412
|
+
)"
|
|
413
|
+
append_if_missing "$TARGET/.gitignore" "# Kratos Framework — runtime artifacts" "$gitignore_block"
|
|
414
|
+
|
|
415
|
+
# Summary
|
|
416
|
+
echo ""
|
|
417
|
+
success "Kratos Framework installed successfully!"
|
|
418
|
+
echo ""
|
|
419
|
+
printf " ${BOLD}Project:${RESET} %s\n" "$project_name"
|
|
420
|
+
printf " ${BOLD}User:${RESET} %s\n" "$user_name"
|
|
421
|
+
printf " ${BOLD}Version:${RESET} %s\n" "${src_version:-1.0.0}"
|
|
422
|
+
printf " ${BOLD}Target:${RESET} %s\n" "$TARGET"
|
|
423
|
+
echo ""
|
|
424
|
+
info "Next steps:"
|
|
425
|
+
detail "1. cd $TARGET"
|
|
426
|
+
detail "2. Run /kratos-build-configs in the Claude Code terminal to generate resolved configs"
|
|
427
|
+
if [[ "$OPT_MINIMAL" == true ]]; then
|
|
428
|
+
detail "3. Run /kratos-quick-spec or /kratos-agent-senior-fullstack to start with the lightweight profile"
|
|
429
|
+
detail "4. Minimal profile installed: add more commands or workflows later by running a full update from your fork"
|
|
430
|
+
else
|
|
431
|
+
detail "3. Run /kratos in the Claude Code terminal to start the orchestrator"
|
|
432
|
+
fi
|
|
433
|
+
echo ""
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
# ─── cmd_update ─────────────────────────────────────────────────────────────
|
|
437
|
+
|
|
438
|
+
cmd_update() {
|
|
439
|
+
local source
|
|
440
|
+
source="$(resolve_source)"
|
|
441
|
+
validate_source "$source"
|
|
442
|
+
|
|
443
|
+
if [[ ! -d "$TARGET/_kratos" ]]; then
|
|
444
|
+
error "No KRATOS installation found at $TARGET"
|
|
445
|
+
error "Run 'init' first to install KRATOS."
|
|
446
|
+
exit 1
|
|
447
|
+
fi
|
|
448
|
+
|
|
449
|
+
local src_version
|
|
450
|
+
src_version="$(extract_yaml_value "$source/_kratos/_config/global.yaml" "framework_version")"
|
|
451
|
+
local cur_version
|
|
452
|
+
cur_version="$(extract_yaml_value "$TARGET/_kratos/_config/global.yaml" "framework_version")"
|
|
453
|
+
|
|
454
|
+
printf "\n${BOLD}Kratos Framework Updater v%s${RESET}\n" "$VERSION"
|
|
455
|
+
printf " Source: %s (v%s)\n" "$source" "${src_version:-unknown}"
|
|
456
|
+
printf " Target: %s (v%s)\n\n" "$TARGET" "${cur_version:-unknown}"
|
|
457
|
+
|
|
458
|
+
if [[ "$OPT_DRY_RUN" == true ]]; then
|
|
459
|
+
warn "Dry-run mode — no files will be written"
|
|
460
|
+
echo ""
|
|
461
|
+
fi
|
|
462
|
+
|
|
463
|
+
if [[ "$OPT_YES" != true ]]; then
|
|
464
|
+
printf "Proceed with update? [y/N]: "
|
|
465
|
+
local confirm; read -r confirm
|
|
466
|
+
[[ "$confirm" =~ ^[Yy] ]] || { info "Aborted."; exit 0; }
|
|
467
|
+
fi
|
|
468
|
+
|
|
469
|
+
local timestamp
|
|
470
|
+
timestamp="$(date +%Y%m%d-%H%M%S)"
|
|
471
|
+
local backup_dir="$TARGET/_kratos/_backups/$timestamp"
|
|
472
|
+
|
|
473
|
+
# Update framework structure — these directories get fully refreshed
|
|
474
|
+
# NEVER touch: global.yaml, _memory/, .resolved/, CLAUDE.md
|
|
475
|
+
local update_targets=(
|
|
476
|
+
"core/engine"
|
|
477
|
+
"core/agents"
|
|
478
|
+
"core/tasks"
|
|
479
|
+
"core/workflows"
|
|
480
|
+
"lifecycle/agents"
|
|
481
|
+
"lifecycle/workflows"
|
|
482
|
+
"lifecycle/templates"
|
|
483
|
+
"lifecycle/checklists"
|
|
484
|
+
"lifecycle/teams"
|
|
485
|
+
"dev/agents"
|
|
486
|
+
"dev/skills"
|
|
487
|
+
"dev/knowledge"
|
|
488
|
+
"creative/agents"
|
|
489
|
+
"creative/workflows"
|
|
490
|
+
"creative/teams"
|
|
491
|
+
"creative/data"
|
|
492
|
+
"testing/agents"
|
|
493
|
+
"testing/workflows"
|
|
494
|
+
"testing/knowledge"
|
|
495
|
+
"_config/manifest.yaml"
|
|
496
|
+
"_config/agent-manifest.csv"
|
|
497
|
+
"_config/workflow-manifest.csv"
|
|
498
|
+
"_config/kratos-help.csv"
|
|
499
|
+
"_config/skill-manifest.csv"
|
|
500
|
+
"_config/task-manifest.csv"
|
|
501
|
+
)
|
|
502
|
+
|
|
503
|
+
step "Updating framework files..."
|
|
504
|
+
local updated=0 skipped=0 changed=0
|
|
505
|
+
|
|
506
|
+
for entry in "${update_targets[@]}"; do
|
|
507
|
+
local src_path="$source/_kratos/$entry"
|
|
508
|
+
local dst_path="$TARGET/_kratos/$entry"
|
|
509
|
+
|
|
510
|
+
if [[ ! -e "$src_path" ]]; then
|
|
511
|
+
[[ "$OPT_VERBOSE" == true ]] && detail "Source missing, skipped: $entry"
|
|
512
|
+
continue
|
|
513
|
+
fi
|
|
514
|
+
|
|
515
|
+
# Show progress for each update target
|
|
516
|
+
local entry_label="${entry}"
|
|
517
|
+
[[ "$OPT_VERBOSE" != true ]] && detail "Processing: $entry_label"
|
|
518
|
+
|
|
519
|
+
if [[ -f "$src_path" ]]; then
|
|
520
|
+
# Single file
|
|
521
|
+
copy_with_backup "$src_path" "$dst_path" "$backup_dir"
|
|
522
|
+
updated=$((updated + 1))
|
|
523
|
+
elif [[ -d "$src_path" ]]; then
|
|
524
|
+
# Directory — update each file
|
|
525
|
+
while IFS= read -r -d '' file; do
|
|
526
|
+
local rel="${file#$src_path/}"
|
|
527
|
+
copy_with_backup "$file" "$dst_path/$rel" "$backup_dir"
|
|
528
|
+
updated=$((updated + 1))
|
|
529
|
+
done < <(find "$src_path" -type f -print0) || true
|
|
530
|
+
fi
|
|
531
|
+
done
|
|
532
|
+
|
|
533
|
+
detail "Processed $updated file(s) across ${#update_targets[@]} targets"
|
|
534
|
+
|
|
535
|
+
# Update slash commands (add new ones, update existing with backup)
|
|
536
|
+
step "Updating slash commands..."
|
|
537
|
+
if [[ -d "$source/.claude/commands" ]]; then
|
|
538
|
+
mkdir -p "$TARGET/.claude/commands"
|
|
539
|
+
for cmd_file in "$source/.claude/commands"/kratos*.md; do
|
|
540
|
+
[[ -f "$cmd_file" ]] || continue
|
|
541
|
+
local basename_cmd
|
|
542
|
+
basename_cmd="$(basename "$cmd_file")"
|
|
543
|
+
copy_with_backup "$cmd_file" "$TARGET/.claude/commands/$basename_cmd" "$backup_dir"
|
|
544
|
+
done
|
|
545
|
+
fi
|
|
546
|
+
|
|
547
|
+
# Update version in global.yaml and CLAUDE.md
|
|
548
|
+
if [[ -n "$src_version" && "$src_version" != "$cur_version" ]]; then
|
|
549
|
+
step "Updating framework version: $cur_version → $src_version"
|
|
550
|
+
if [[ "$OPT_DRY_RUN" != true ]]; then
|
|
551
|
+
local global_file="$TARGET/_kratos/_config/global.yaml"
|
|
552
|
+
set_global_value "$global_file" "framework_version" "$src_version"
|
|
553
|
+
# Update version in CLAUDE.md heading
|
|
554
|
+
local claude_file="$TARGET/CLAUDE.md"
|
|
555
|
+
if [[ -f "$claude_file" ]]; then
|
|
556
|
+
if [[ "$(uname)" == "Darwin" ]]; then
|
|
557
|
+
sed -i '' "s/^# Kratos Framework v.*/# Kratos Framework v$src_version/" "$claude_file"
|
|
558
|
+
else
|
|
559
|
+
sed -i "s/^# Kratos Framework v.*/# Kratos Framework v$src_version/" "$claude_file"
|
|
560
|
+
fi
|
|
561
|
+
[[ "$OPT_VERBOSE" == true ]] && detail "Updated CLAUDE.md version heading" || true
|
|
562
|
+
fi
|
|
563
|
+
fi
|
|
564
|
+
fi
|
|
565
|
+
|
|
566
|
+
# Summary
|
|
567
|
+
echo ""
|
|
568
|
+
if [[ -d "$backup_dir" ]]; then
|
|
569
|
+
local backup_count
|
|
570
|
+
backup_count="$(find "$backup_dir" -type f | wc -l | tr -d ' ')"
|
|
571
|
+
success "Update complete! $backup_count file(s) backed up to:"
|
|
572
|
+
detail "$backup_dir"
|
|
573
|
+
else
|
|
574
|
+
success "Update complete! All files were already up to date."
|
|
575
|
+
fi
|
|
576
|
+
echo ""
|
|
577
|
+
info "Run /kratos-build-configs in the Claude Code terminal to regenerate resolved configs."
|
|
578
|
+
echo ""
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
# ─── cmd_validate ───────────────────────────────────────────────────────────
|
|
582
|
+
|
|
583
|
+
cmd_validate() {
|
|
584
|
+
if [[ ! -d "$TARGET/_kratos" ]]; then
|
|
585
|
+
error "No KRATOS installation found at $TARGET"
|
|
586
|
+
exit 1
|
|
587
|
+
fi
|
|
588
|
+
|
|
589
|
+
printf "\n${BOLD}Kratos Framework Validation${RESET}\n"
|
|
590
|
+
printf " Target: %s\n\n" "$TARGET"
|
|
591
|
+
|
|
592
|
+
local pass=0 fail=0
|
|
593
|
+
|
|
594
|
+
check() {
|
|
595
|
+
local label="$1" condition="$2"
|
|
596
|
+
if eval "$condition"; then
|
|
597
|
+
printf " ${GREEN}✔${RESET} %s\n" "$label"
|
|
598
|
+
pass=$((pass + 1))
|
|
599
|
+
else
|
|
600
|
+
printf " ${RED}✖${RESET} %s\n" "$label"
|
|
601
|
+
fail=$((fail + 1))
|
|
602
|
+
fi
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
# Manifest
|
|
606
|
+
check "manifest.yaml exists" "[[ -f '$TARGET/$MANIFEST_REL' ]]"
|
|
607
|
+
|
|
608
|
+
# Global config fields
|
|
609
|
+
local global="$TARGET/_kratos/_config/global.yaml"
|
|
610
|
+
check "global.yaml exists" "[[ -f '$global' ]]"
|
|
611
|
+
if [[ -f "$global" ]]; then
|
|
612
|
+
check "global.yaml has project_name" "grep -q '^project_name:' '$global'"
|
|
613
|
+
check "global.yaml has user_name" "grep -q '^user_name:' '$global'"
|
|
614
|
+
check "global.yaml has framework_version" "grep -q '^framework_version:' '$global'"
|
|
615
|
+
fi
|
|
616
|
+
|
|
617
|
+
local install_profile
|
|
618
|
+
install_profile="$(extract_yaml_value "$global" "install_profile")"
|
|
619
|
+
[[ -n "$install_profile" ]] || install_profile="full"
|
|
620
|
+
|
|
621
|
+
# Module directories
|
|
622
|
+
local modules=(core lifecycle dev)
|
|
623
|
+
if [[ "$install_profile" != "minimal" ]]; then
|
|
624
|
+
modules+=(creative testing)
|
|
625
|
+
fi
|
|
626
|
+
for mod in "${modules[@]}"; do
|
|
627
|
+
check "Module: $mod" "[[ -d '$TARGET/_kratos/$mod' ]]"
|
|
628
|
+
done
|
|
629
|
+
|
|
630
|
+
# .resolved directories
|
|
631
|
+
local resolved_modules=(core lifecycle)
|
|
632
|
+
if [[ "$install_profile" != "minimal" ]]; then
|
|
633
|
+
resolved_modules+=(creative testing)
|
|
634
|
+
fi
|
|
635
|
+
for mod in "${resolved_modules[@]}"; do
|
|
636
|
+
check ".resolved: $mod" "[[ -d '$TARGET/_kratos/$mod/.resolved' ]]"
|
|
637
|
+
done
|
|
638
|
+
|
|
639
|
+
# Sidecar directories
|
|
640
|
+
local sidecar_dirs=("checkpoints" "checkpoints/completed")
|
|
641
|
+
if [[ "$install_profile" != "minimal" ]]; then
|
|
642
|
+
sidecar_dirs+=(
|
|
643
|
+
"architect-sidecar" "devops-sidecar" "orchestrator-sidecar"
|
|
644
|
+
"pm-sidecar" "security-sidecar" "sm-sidecar"
|
|
645
|
+
"storyteller-sidecar" "tech-writer-sidecar" "test-architect-sidecar"
|
|
646
|
+
)
|
|
647
|
+
fi
|
|
648
|
+
for dir in "${sidecar_dirs[@]}"; do
|
|
649
|
+
check "Sidecar: $dir" "[[ -d '$TARGET/_kratos/_memory/$dir' ]]"
|
|
650
|
+
done
|
|
651
|
+
|
|
652
|
+
# CLAUDE.md
|
|
653
|
+
check "CLAUDE.md exists" "[[ -f '$TARGET/CLAUDE.md' ]]"
|
|
654
|
+
|
|
655
|
+
# Slash commands
|
|
656
|
+
check "Slash commands directory" "[[ -d '$TARGET/.claude/commands' ]]"
|
|
657
|
+
if [[ -d "$TARGET/.claude/commands" ]]; then
|
|
658
|
+
local cmd_count
|
|
659
|
+
cmd_count="$(find "$TARGET/.claude/commands" -name 'kratos*.md' -type f 2>/dev/null | wc -l | tr -d ' ')"
|
|
660
|
+
check "Slash commands present (found: $cmd_count)" "[[ $cmd_count -gt 0 ]]"
|
|
661
|
+
fi
|
|
662
|
+
|
|
663
|
+
# Docs directories
|
|
664
|
+
for dir in planning-artifacts implementation-artifacts test-artifacts creative-artifacts; do
|
|
665
|
+
check "Docs: $dir" "[[ -d '$TARGET/docs/$dir' ]]"
|
|
666
|
+
done
|
|
667
|
+
|
|
668
|
+
# Version
|
|
669
|
+
local version
|
|
670
|
+
version="$(extract_yaml_value "$TARGET/_kratos/_config/global.yaml" "framework_version")"
|
|
671
|
+
|
|
672
|
+
echo ""
|
|
673
|
+
if [[ $fail -eq 0 ]]; then
|
|
674
|
+
success "All $pass checks passed (v${version:-unknown})"
|
|
675
|
+
echo ""
|
|
676
|
+
return 0
|
|
677
|
+
else
|
|
678
|
+
error "$fail of $((pass + fail)) checks failed"
|
|
679
|
+
echo ""
|
|
680
|
+
exit 1
|
|
681
|
+
fi
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
# ─── cmd_status ─────────────────────────────────────────────────────────────
|
|
685
|
+
|
|
686
|
+
cmd_status() {
|
|
687
|
+
if [[ ! -d "$TARGET/_kratos" ]]; then
|
|
688
|
+
error "No KRATOS installation found at $TARGET"
|
|
689
|
+
exit 1
|
|
690
|
+
fi
|
|
691
|
+
|
|
692
|
+
local global="$TARGET/_kratos/_config/global.yaml"
|
|
693
|
+
local version project_name user_name
|
|
694
|
+
version="$(extract_yaml_value "$global" "framework_version")"
|
|
695
|
+
project_name="$(extract_yaml_value "$global" "project_name")"
|
|
696
|
+
user_name="$(extract_yaml_value "$global" "user_name")"
|
|
697
|
+
|
|
698
|
+
printf "\n${BOLD}Kratos Framework Status${RESET}\n\n"
|
|
699
|
+
local install_profile
|
|
700
|
+
install_profile="$(extract_yaml_value "$global" "install_profile")"
|
|
701
|
+
[[ -n "$install_profile" ]] || install_profile="full"
|
|
702
|
+
|
|
703
|
+
printf " ${BOLD}Version:${RESET} %s\n" "${version:-unknown}"
|
|
704
|
+
printf " ${BOLD}Project:${RESET} %s\n" "${project_name:-unknown}"
|
|
705
|
+
printf " ${BOLD}User:${RESET} %s\n" "${user_name:-unknown}"
|
|
706
|
+
printf " ${BOLD}Profile:${RESET} %s\n" "$install_profile"
|
|
707
|
+
|
|
708
|
+
# Modules
|
|
709
|
+
local expected_modules=(core lifecycle dev)
|
|
710
|
+
if [[ "$install_profile" != "minimal" ]]; then
|
|
711
|
+
expected_modules+=(creative testing)
|
|
712
|
+
fi
|
|
713
|
+
local installed_modules=()
|
|
714
|
+
for mod in "${expected_modules[@]}"; do
|
|
715
|
+
[[ -d "$TARGET/_kratos/$mod" ]] && installed_modules+=("$mod")
|
|
716
|
+
done
|
|
717
|
+
printf " ${BOLD}Modules:${RESET} %s (%s)\n" "${#installed_modules[@]}" "${installed_modules[*]}"
|
|
718
|
+
|
|
719
|
+
# Slash commands
|
|
720
|
+
local cmd_count=0
|
|
721
|
+
if [[ -d "$TARGET/.claude/commands" ]]; then
|
|
722
|
+
cmd_count="$(find "$TARGET/.claude/commands" -name 'kratos*.md' -type f 2>/dev/null | wc -l | tr -d ' ')"
|
|
723
|
+
fi
|
|
724
|
+
printf " ${BOLD}Commands:${RESET} %s slash commands\n" "$cmd_count"
|
|
725
|
+
|
|
726
|
+
# Sidecar status
|
|
727
|
+
local sidecar_count=0
|
|
728
|
+
local sidecar_populated=0
|
|
729
|
+
for dir in "$TARGET/_kratos/_memory"/*-sidecar; do
|
|
730
|
+
[[ -d "$dir" ]] || continue
|
|
731
|
+
((sidecar_count++))
|
|
732
|
+
local file_count
|
|
733
|
+
file_count="$(find "$dir" -type f ! -name '.gitkeep' 2>/dev/null | wc -l | tr -d ' ')"
|
|
734
|
+
[[ $file_count -gt 0 ]] && ((sidecar_populated++))
|
|
735
|
+
done
|
|
736
|
+
printf " ${BOLD}Sidecars:${RESET} %s directories (%s with data)\n" "$sidecar_count" "$sidecar_populated"
|
|
737
|
+
|
|
738
|
+
# .resolved population
|
|
739
|
+
local resolved_count=0
|
|
740
|
+
local resolved_populated=0
|
|
741
|
+
local resolved_mods=(core lifecycle)
|
|
742
|
+
if [[ "$install_profile" != "minimal" ]]; then
|
|
743
|
+
resolved_mods+=(creative testing)
|
|
744
|
+
fi
|
|
745
|
+
for mod in "${resolved_mods[@]}"; do
|
|
746
|
+
local rdir="$TARGET/_kratos/$mod/.resolved"
|
|
747
|
+
[[ -d "$rdir" ]] || continue
|
|
748
|
+
((resolved_count++))
|
|
749
|
+
local yaml_count
|
|
750
|
+
yaml_count="$(find "$rdir" -name '*.yaml' -type f 2>/dev/null | wc -l | tr -d ' ')"
|
|
751
|
+
[[ $yaml_count -gt 0 ]] && ((resolved_populated++))
|
|
752
|
+
done
|
|
753
|
+
printf " ${BOLD}.resolved:${RESET} %s directories (%s populated)\n" "$resolved_count" "$resolved_populated"
|
|
754
|
+
|
|
755
|
+
# Checkpoints
|
|
756
|
+
local checkpoint_count=0
|
|
757
|
+
if [[ -d "$TARGET/_kratos/_memory/checkpoints" ]]; then
|
|
758
|
+
checkpoint_count="$(find "$TARGET/_kratos/_memory/checkpoints" -name '*.yaml' -type f 2>/dev/null | wc -l | tr -d ' ')"
|
|
759
|
+
fi
|
|
760
|
+
printf " ${BOLD}Checkpoints:${RESET} %s\n" "$checkpoint_count"
|
|
761
|
+
|
|
762
|
+
echo ""
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
# ─── Usage & Argument Parsing ───────────────────────────────────────────────
|
|
766
|
+
|
|
767
|
+
usage() {
|
|
768
|
+
cat <<EOF
|
|
769
|
+
${BOLD}Kratos Framework Installer v${VERSION}${RESET}
|
|
770
|
+
|
|
771
|
+
Usage: kratos-install.sh <command> [options] [target]
|
|
772
|
+
|
|
773
|
+
${BOLD}Commands:${RESET}
|
|
774
|
+
init Install KRATOS into a project
|
|
775
|
+
update Update framework files (preserves config and memory)
|
|
776
|
+
validate Check installation integrity
|
|
777
|
+
status Show installation info
|
|
778
|
+
|
|
779
|
+
${BOLD}Options:${RESET}
|
|
780
|
+
--source <path> Local KRATOS source (or clones from GitHub if omitted)
|
|
781
|
+
--yes Skip confirmation prompts
|
|
782
|
+
--minimal Install the lightweight profile (core, quick flow, senior dev agents)
|
|
783
|
+
--dry-run Show what would be done without making changes
|
|
784
|
+
--verbose Show detailed progress
|
|
785
|
+
--help Show this help message
|
|
786
|
+
|
|
787
|
+
${BOLD}Environment:${RESET}
|
|
788
|
+
KRATOS_REPO_URL Override the Git clone source for your fork or private mirror
|
|
789
|
+
|
|
790
|
+
${BOLD}Examples:${RESET}
|
|
791
|
+
kratos-install.sh init ~/my-new-project
|
|
792
|
+
kratos-install.sh init --source ~/local-kratos ~/my-project
|
|
793
|
+
kratos-install.sh update ~/my-existing-project
|
|
794
|
+
kratos-install.sh validate .
|
|
795
|
+
kratos-install.sh status .
|
|
796
|
+
|
|
797
|
+
${BOLD}Source resolution order:${RESET}
|
|
798
|
+
1. --source flag
|
|
799
|
+
2. \$KRATOS_SOURCE environment variable
|
|
800
|
+
3. Script's own directory (if it contains _kratos/)
|
|
801
|
+
4. GitHub clone ($GITHUB_REPO)
|
|
802
|
+
EOF
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
parse_args() {
|
|
806
|
+
if [[ $# -eq 0 ]]; then
|
|
807
|
+
usage
|
|
808
|
+
exit 0
|
|
809
|
+
fi
|
|
810
|
+
|
|
811
|
+
# First argument is the command
|
|
812
|
+
case "$1" in
|
|
813
|
+
init|update|validate|status)
|
|
814
|
+
CMD="$1"
|
|
815
|
+
shift
|
|
816
|
+
;;
|
|
817
|
+
--help|-h|help)
|
|
818
|
+
usage
|
|
819
|
+
exit 0
|
|
820
|
+
;;
|
|
821
|
+
--version|-v)
|
|
822
|
+
echo "kratos-install.sh v${VERSION}"
|
|
823
|
+
exit 0
|
|
824
|
+
;;
|
|
825
|
+
*)
|
|
826
|
+
error "Unknown command: $1"
|
|
827
|
+
echo ""
|
|
828
|
+
usage
|
|
829
|
+
exit 1
|
|
830
|
+
;;
|
|
831
|
+
esac
|
|
832
|
+
|
|
833
|
+
# Parse remaining options
|
|
834
|
+
while [[ $# -gt 0 ]]; do
|
|
835
|
+
case "$1" in
|
|
836
|
+
--source)
|
|
837
|
+
if [[ -z "${2:-}" ]]; then
|
|
838
|
+
error "--source requires a path argument"
|
|
839
|
+
exit 1
|
|
840
|
+
fi
|
|
841
|
+
SOURCE_FLAG="$2"
|
|
842
|
+
shift 2
|
|
843
|
+
;;
|
|
844
|
+
--yes|-y)
|
|
845
|
+
OPT_YES=true
|
|
846
|
+
shift
|
|
847
|
+
;;
|
|
848
|
+
--minimal)
|
|
849
|
+
OPT_MINIMAL=true
|
|
850
|
+
shift
|
|
851
|
+
;;
|
|
852
|
+
--dry-run)
|
|
853
|
+
OPT_DRY_RUN=true
|
|
854
|
+
shift
|
|
855
|
+
;;
|
|
856
|
+
--verbose)
|
|
857
|
+
OPT_VERBOSE=true
|
|
858
|
+
shift
|
|
859
|
+
;;
|
|
860
|
+
--help|-h)
|
|
861
|
+
usage
|
|
862
|
+
exit 0
|
|
863
|
+
;;
|
|
864
|
+
-*)
|
|
865
|
+
error "Unknown option: $1"
|
|
866
|
+
exit 1
|
|
867
|
+
;;
|
|
868
|
+
*)
|
|
869
|
+
if [[ -n "$TARGET" ]]; then
|
|
870
|
+
error "Unexpected argument: $1"
|
|
871
|
+
exit 1
|
|
872
|
+
fi
|
|
873
|
+
TARGET="$1"
|
|
874
|
+
shift
|
|
875
|
+
;;
|
|
876
|
+
esac
|
|
877
|
+
done
|
|
878
|
+
|
|
879
|
+
# Default target to current directory
|
|
880
|
+
if [[ -z "$TARGET" ]]; then
|
|
881
|
+
TARGET="."
|
|
882
|
+
fi
|
|
883
|
+
|
|
884
|
+
# Resolve target to absolute path
|
|
885
|
+
if [[ "$TARGET" == "." ]]; then
|
|
886
|
+
TARGET="$(pwd)"
|
|
887
|
+
else
|
|
888
|
+
# Create target dir for init if it doesn't exist
|
|
889
|
+
if [[ "$CMD" == "init" && ! -d "$TARGET" ]]; then
|
|
890
|
+
if [[ "$OPT_DRY_RUN" == true ]]; then
|
|
891
|
+
detail "[dry-run] Would create target directory: $TARGET"
|
|
892
|
+
# Resolve to absolute path without cd
|
|
893
|
+
case "$TARGET" in
|
|
894
|
+
/*) ;; # already absolute
|
|
895
|
+
*) TARGET="$(pwd)/$TARGET" ;;
|
|
896
|
+
esac
|
|
897
|
+
else
|
|
898
|
+
mkdir -p "$TARGET"
|
|
899
|
+
TARGET="$(cd "$TARGET" && pwd)"
|
|
900
|
+
fi
|
|
901
|
+
else
|
|
902
|
+
TARGET="$(cd "$TARGET" 2>/dev/null && pwd || echo "$TARGET")"
|
|
903
|
+
fi
|
|
904
|
+
fi
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
# ─── Main ───────────────────────────────────────────────────────────────────
|
|
908
|
+
|
|
909
|
+
main() {
|
|
910
|
+
parse_args "$@"
|
|
911
|
+
|
|
912
|
+
case "$CMD" in
|
|
913
|
+
init) cmd_init ;;
|
|
914
|
+
update) cmd_update ;;
|
|
915
|
+
validate) cmd_validate ;;
|
|
916
|
+
status) cmd_status ;;
|
|
917
|
+
esac
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
main "$@"
|