@thuanha-groove/gck 0.0.1 → 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -15516,7 +15516,7 @@ var init_metadata = __esm(() => {
15516
15516
  TrackedFileSchema = objectType({
15517
15517
  path: stringType(),
15518
15518
  checksum: stringType().regex(/^[a-f0-9]{64}$/, "Invalid SHA-256 checksum"),
15519
- ownership: enumType(["ck", "user", "ck-modified"]),
15519
+ ownership: enumType(["gck", "user", "gck-modified"]),
15520
15520
  installedVersion: stringType(),
15521
15521
  baseChecksum: stringType().regex(/^[a-f0-9]{64}$/, "Invalid SHA-256 checksum").optional(),
15522
15522
  sourceTimestamp: stringType().datetime({ offset: true }).optional(),
@@ -15733,7 +15733,7 @@ var init_claudekit_api = __esm(() => {
15733
15733
  });
15734
15734
  });
15735
15735
 
15736
- // src/types/ck-config.ts
15736
+ // src/types/gck-config.ts
15737
15737
  function normalizeMigrateProviderToken(token) {
15738
15738
  const trimmed = token.trim();
15739
15739
  const unwrapped = trimmed.startsWith('"') && trimmed.endsWith('"') || trimmed.startsWith("'") && trimmed.endsWith("'") ? trimmed.slice(1, -1) : trimmed;
@@ -15804,7 +15804,7 @@ function normalizeCkConfigInput(value) {
15804
15804
  return normalized;
15805
15805
  }
15806
15806
  var PlanValidationModeSchema, PlanFocusAreaSchema, PlanResolutionOrderSchema, ProjectTypeSchema, PackageManagerSchema, FrameworkSchema, LEGACY_GEMINI_MODEL_ALIASES, GeminiModelSchema, StatuslineModeSchema, StatuslineSectionIdSchema, StatuslineSectionConfigSchema, StatuslineThemeSchema, StatuslineLayoutSchema, CodingLevelSchema, PlanResolutionSchema, PlanValidationSchema, CkPlanConfigSchema, CkDocsConfigSchema, CkPathsConfigSchema, CkLocaleConfigSchema, CkTrustConfigSchema, CkProjectConfigSchema, CkGeminiConfigSchema, CkSkillsConfigSchema, MigrateScopeConfigSchema, UpdatePipelineSchema, ResolvedModelConfigSchema, ModelTierMapSchema, CkModelTaxonomySchema, CkAssertionSchema, CkHooksConfigSchema, CkSimplifyConfigSchema, CkConfigSchema, DEFAULT_CK_CONFIG;
15807
- var init_ck_config = __esm(() => {
15807
+ var init_gck_config = __esm(() => {
15808
15808
  init_zod();
15809
15809
  PlanValidationModeSchema = enumType(["prompt", "auto", "strict", "none"]);
15810
15810
  PlanFocusAreaSchema = enumType([
@@ -16110,7 +16110,7 @@ var init_types3 = __esm(() => {
16110
16110
  init_errors2();
16111
16111
  init_claudekit_data();
16112
16112
  init_claudekit_api();
16113
- init_ck_config();
16113
+ init_gck_config();
16114
16114
  init_statusline_section_defaults();
16115
16115
  });
16116
16116
 
@@ -45076,7 +45076,7 @@ var init_config_manager = __esm(() => {
45076
45076
  init_types3();
45077
45077
  });
45078
45078
 
45079
- // src/domains/config/ck-config-manager.ts
45079
+ // src/domains/config/gck-config-manager.ts
45080
45080
  import { existsSync as existsSync11 } from "node:fs";
45081
45081
  import { mkdir as mkdir7, readFile as readFile8, writeFile as writeFile6 } from "node:fs/promises";
45082
45082
  import { homedir as homedir10 } from "node:os";
@@ -45281,7 +45281,7 @@ class CkConfigManager {
45281
45281
  }
45282
45282
  }
45283
45283
  var CK_CONFIG_FILE = ".ck.json", DANGEROUS_KEYS;
45284
- var init_ck_config_manager = __esm(() => {
45284
+ var init_gck_config_manager = __esm(() => {
45285
45285
  init_logger();
45286
45286
  init_types3();
45287
45287
  DANGEROUS_KEYS = ["__proto__", "constructor", "prototype"];
@@ -45354,7 +45354,7 @@ function getCliVersion2() {
45354
45354
  function getCliUserAgent() {
45355
45355
  return `${CLAUDEKIT_CLI_NPM_PACKAGE_NAME}/${getCliVersion2()}`;
45356
45356
  }
45357
- var CLAUDEKIT_CLI_NPM_PACKAGE_NAME = "gck", CLAUDEKIT_CLI_NPM_PACKAGE_URL, CLAUDEKIT_CLI_GLOBAL_INSTALL_COMMAND, CLAUDEKIT_CLI_INSTALL_COMMANDS, DEFAULT_NETWORK_TIMEOUT_MS = 3000;
45357
+ var CLAUDEKIT_CLI_NPM_PACKAGE_NAME = "@thuanha-groove/gck", CLAUDEKIT_CLI_NPM_PACKAGE_URL, CLAUDEKIT_CLI_GLOBAL_INSTALL_COMMAND, CLAUDEKIT_CLI_INSTALL_COMMANDS, DEFAULT_NETWORK_TIMEOUT_MS = 3000;
45358
45358
  var init_claudekit_constants = __esm(() => {
45359
45359
  CLAUDEKIT_CLI_NPM_PACKAGE_URL = `https://www.npmjs.com/package/${CLAUDEKIT_CLI_NPM_PACKAGE_NAME}`;
45360
45360
  CLAUDEKIT_CLI_GLOBAL_INSTALL_COMMAND = `npm install -g ${CLAUDEKIT_CLI_NPM_PACKAGE_NAME}`;
@@ -46179,7 +46179,7 @@ var init_types4 = __esm(() => {
46179
46179
  // src/domains/config/index.ts
46180
46180
  var init_config = __esm(() => {
46181
46181
  init_config_manager();
46182
- init_ck_config_manager();
46182
+ init_gck_config_manager();
46183
46183
  init_settings_merger();
46184
46184
  init_config_generator();
46185
46185
  init_config_validator();
@@ -49360,1154 +49360,311 @@ var init_agents_routes = __esm(() => {
49360
49360
  home2 = homedir14();
49361
49361
  });
49362
49362
 
49363
- // src/schemas/ck-config.schema.json
49364
- var ck_config_schema_default;
49365
- var init_ck_config_schema = __esm(() => {
49366
- ck_config_schema_default = {
49367
- $schema: "https://json-schema.org/draft-07/schema#",
49368
- $id: "https://claudekit.cc/schemas/ck-config.schema.json",
49369
- title: "ClaudeKit Configuration",
49370
- description: "Configuration schema for ClaudeKit Engineer .ck.json files",
49371
- type: "object",
49372
- properties: {
49373
- $schema: {
49374
- type: "string",
49375
- description: "JSON Schema reference for editor autocompletion"
49376
- },
49377
- codingLevel: {
49378
- type: "integer",
49379
- minimum: -1,
49380
- maximum: 5,
49381
- default: -1,
49382
- description: "User coding experience level: -1=auto, 0=beginner, 1=intermediate, 2=advanced, 3=expert, 4=tech-lead, 5=god-mode"
49383
- },
49384
- privacyBlock: {
49385
- type: "boolean",
49386
- default: true,
49387
- description: "Enable privacy block hook to protect sensitive files (.env, credentials, etc.)"
49388
- },
49389
- docs: {
49390
- type: "object",
49391
- description: "Documentation settings",
49392
- properties: {
49393
- maxLoc: {
49394
- type: "integer",
49395
- minimum: 100,
49396
- maximum: 5000,
49397
- default: 800,
49398
- description: "Maximum lines of code per documentation file"
49399
- }
49400
- },
49401
- additionalProperties: false
49402
- },
49403
- plan: {
49404
- type: "object",
49405
- description: "Plan naming and validation settings",
49406
- properties: {
49407
- namingFormat: {
49408
- type: "string",
49409
- default: "{date}-{issue}-{slug}",
49410
- description: "Format for plan directory names. Variables: {date}, {issue}, {slug}"
49411
- },
49412
- dateFormat: {
49413
- type: "string",
49414
- default: "YYMMDD-HHmm",
49415
- description: "Date format for plan names (moment.js format)"
49416
- },
49417
- issuePrefix: {
49418
- type: "string",
49419
- default: "GH-",
49420
- description: "Prefix for issue references (e.g., GH-, JIRA-)"
49421
- },
49422
- reportsDir: {
49423
- type: "string",
49424
- default: "reports",
49425
- description: "Subdirectory name for reports within plans"
49426
- },
49427
- resolution: {
49428
- type: "object",
49429
- description: "Plan resolution settings",
49430
- properties: {
49431
- order: {
49432
- type: "array",
49433
- items: {
49434
- type: "string",
49435
- enum: ["session", "branch", "directory"]
49436
- },
49437
- default: ["session", "branch"],
49438
- description: "Order of resolution strategies for plan context"
49439
- },
49440
- branchPattern: {
49441
- type: "string",
49442
- default: "(?:feat|fix|chore|refactor|docs)/(?:[^/]+/)?(.+)",
49443
- description: "Regex pattern to extract plan slug from branch name"
49444
- }
49445
- },
49446
- additionalProperties: false
49447
- },
49448
- validation: {
49449
- type: "object",
49450
- description: "Plan validation settings",
49451
- properties: {
49452
- mode: {
49453
- type: "string",
49454
- enum: ["prompt", "auto", "strict", "none"],
49455
- default: "prompt",
49456
- description: "Validation mode: prompt=ask questions, auto=auto-validate, strict=require all, none=skip"
49457
- },
49458
- minQuestions: {
49459
- type: "integer",
49460
- minimum: 0,
49461
- maximum: 20,
49462
- default: 3,
49463
- description: "Minimum validation questions to ask"
49464
- },
49465
- maxQuestions: {
49466
- type: "integer",
49467
- minimum: 1,
49468
- maximum: 20,
49469
- default: 8,
49470
- description: "Maximum validation questions to ask"
49471
- },
49472
- focusAreas: {
49473
- type: "array",
49474
- items: {
49475
- type: "string",
49476
- enum: [
49477
- "assumptions",
49478
- "risks",
49479
- "tradeoffs",
49480
- "architecture",
49481
- "security",
49482
- "performance",
49483
- "testing",
49484
- "dependencies"
49485
- ]
49486
- },
49487
- default: ["assumptions", "risks", "tradeoffs", "architecture"],
49488
- description: "Focus areas for validation questions"
49489
- }
49490
- },
49491
- additionalProperties: false
49492
- }
49493
- },
49494
- additionalProperties: false
49495
- },
49496
- paths: {
49497
- type: "object",
49498
- description: "Directory paths configuration",
49499
- properties: {
49500
- docs: {
49501
- type: "string",
49502
- default: "docs",
49503
- description: "Path to documentation directory"
49504
- },
49505
- plans: {
49506
- type: "string",
49507
- default: "plans",
49508
- description: "Path to plans directory"
49509
- },
49510
- globalPlans: {
49511
- type: "string",
49512
- default: "plans",
49513
- description: "Path to global plans directory (relative to ~/.claude or absolute)"
49514
- }
49515
- },
49516
- additionalProperties: false
49517
- },
49518
- locale: {
49519
- type: "object",
49520
- description: "Language and locale settings",
49521
- properties: {
49522
- thinkingLanguage: {
49523
- type: ["string", "null"],
49524
- default: null,
49525
- description: "Language for Claude's thinking (null=English)"
49526
- },
49527
- responseLanguage: {
49528
- type: ["string", "null"],
49529
- default: null,
49530
- description: "Language for Claude's responses (null=match user)"
49531
- }
49532
- },
49533
- additionalProperties: false
49534
- },
49535
- trust: {
49536
- type: "object",
49537
- description: "Trust mode settings for auto-approval",
49538
- properties: {
49539
- passphrase: {
49540
- type: ["string", "null"],
49541
- default: null,
49542
- description: "Passphrase to enable trust mode (null=disabled)"
49543
- },
49544
- enabled: {
49545
- type: "boolean",
49546
- default: false,
49547
- description: "Whether trust mode is currently enabled"
49548
- }
49549
- },
49550
- additionalProperties: false
49551
- },
49552
- project: {
49553
- type: "object",
49554
- description: "Project type detection settings",
49555
- properties: {
49556
- type: {
49557
- type: "string",
49558
- enum: ["auto", "library", "application", "monorepo", "cli", "api", "web", "mobile"],
49559
- default: "auto",
49560
- description: "Project type (auto=detect from package.json)"
49561
- },
49562
- packageManager: {
49563
- type: "string",
49564
- enum: ["auto", "npm", "yarn", "pnpm", "bun"],
49565
- default: "auto",
49566
- description: "Package manager (auto=detect from lockfile)"
49567
- },
49568
- framework: {
49569
- type: "string",
49570
- enum: [
49571
- "auto",
49572
- "react",
49573
- "vue",
49574
- "angular",
49575
- "svelte",
49576
- "nextjs",
49577
- "nuxt",
49578
- "express",
49579
- "nestjs",
49580
- "fastify",
49581
- "none"
49582
- ],
49583
- default: "auto",
49584
- description: "Framework (auto=detect from dependencies)"
49585
- }
49586
- },
49587
- additionalProperties: false
49588
- },
49589
- gemini: {
49590
- type: "object",
49591
- description: "Gemini CLI configuration",
49592
- properties: {
49593
- model: {
49594
- type: "string",
49595
- enum: [
49596
- "gemini-2.5-flash",
49597
- "gemini-2.5-pro",
49598
- "gemini-3-pro-preview",
49599
- "gemini-3-flash-preview"
49600
- ],
49601
- default: "gemini-3-flash-preview",
49602
- description: "Gemini model to use for CLI commands. Legacy gemini-3.0-* ids are auto-mapped to the current preview aliases when loading config."
49603
- }
49604
- },
49605
- additionalProperties: false
49606
- },
49607
- skills: {
49608
- type: "object",
49609
- description: "Per-skill configuration",
49610
- properties: {
49611
- research: {
49612
- type: "object",
49613
- properties: {
49614
- useGemini: {
49615
- type: "boolean",
49616
- default: true,
49617
- description: "Use Gemini CLI for research (false=use WebSearch)"
49363
+ // src/domains/web-server/routes/command-routes.ts
49364
+ import { existsSync as existsSync15 } from "node:fs";
49365
+ import { readFile as readFile11, readdir as readdir8 } from "node:fs/promises";
49366
+ import { homedir as homedir15 } from "node:os";
49367
+ import { basename as basename9, isAbsolute as isAbsolute5, join as join29, relative as relative8, resolve as resolve13 } from "node:path";
49368
+ async function buildCommandTree(dir, baseDir) {
49369
+ let dirents;
49370
+ try {
49371
+ dirents = await readdir8(dir, { withFileTypes: true });
49372
+ } catch {
49373
+ return [];
49374
+ }
49375
+ const nodes = [];
49376
+ const sorted = [...dirents].sort((a, b) => {
49377
+ const aDir = a.isDirectory();
49378
+ const bDir = b.isDirectory();
49379
+ if (aDir !== bDir)
49380
+ return aDir ? -1 : 1;
49381
+ return a.name.localeCompare(b.name);
49382
+ });
49383
+ for (const entry of sorted) {
49384
+ const name = entry.name;
49385
+ const isDir = entry.isDirectory();
49386
+ const fullPath = join29(dir, name);
49387
+ const relPath = relative8(baseDir, fullPath);
49388
+ if (isDir) {
49389
+ const children = await buildCommandTree(fullPath, baseDir);
49390
+ if (children.length > 0) {
49391
+ nodes.push({ name, path: relPath, children });
49392
+ }
49393
+ } else if (name.endsWith(".md")) {
49394
+ let description;
49395
+ try {
49396
+ const content = await readFile11(fullPath, "utf-8");
49397
+ const { frontmatter } = parseFrontmatter(content);
49398
+ description = frontmatter.description;
49399
+ } catch {}
49400
+ const commandName = basename9(name, ".md");
49401
+ nodes.push({ name: commandName, path: relPath, description });
49402
+ }
49403
+ }
49404
+ return nodes;
49405
+ }
49406
+ function registerCommandRoutes(app) {
49407
+ app.get("/api/commands", async (_req, res) => {
49408
+ const commandsDir = join29(homedir15(), ".claude", "commands");
49409
+ if (!existsSync15(commandsDir)) {
49410
+ res.json({ tree: [] });
49411
+ return;
49412
+ }
49413
+ try {
49414
+ const tree = await buildCommandTree(commandsDir, commandsDir);
49415
+ res.json({ tree });
49416
+ } catch {
49417
+ res.status(500).json({ error: "Failed to scan commands directory" });
49418
+ }
49419
+ });
49420
+ app.get("/api/commands/detail/:slug", async (req, res) => {
49421
+ const slug = String(req.params.slug ?? "");
49422
+ const rawPath = slug.replace(/--/g, "/");
49423
+ if (!rawPath) {
49424
+ res.status(400).json({ error: "Missing command path" });
49425
+ return;
49426
+ }
49427
+ if (rawPath.includes("..") || rawPath.includes("\x00")) {
49428
+ res.status(400).json({ error: "Invalid path" });
49429
+ return;
49430
+ }
49431
+ const commandsDir = join29(homedir15(), ".claude", "commands");
49432
+ const safePath = resolve13(commandsDir, rawPath);
49433
+ const rel = relative8(commandsDir, safePath);
49434
+ if (rel.startsWith("..") || isAbsolute5(rel)) {
49435
+ res.status(403).json({ error: "Access denied" });
49436
+ return;
49437
+ }
49438
+ const filePath = safePath.endsWith(".md") ? safePath : `${safePath}.md`;
49439
+ if (!existsSync15(filePath)) {
49440
+ res.status(404).json({ error: "Command not found" });
49441
+ return;
49442
+ }
49443
+ try {
49444
+ const content = await readFile11(filePath, "utf-8");
49445
+ const { frontmatter } = parseFrontmatter(content);
49446
+ const commandName = basename9(filePath, ".md");
49447
+ res.json({
49448
+ name: commandName,
49449
+ path: rawPath,
49450
+ content,
49451
+ description: frontmatter.description
49452
+ });
49453
+ } catch {
49454
+ res.status(500).json({ error: "Failed to read command" });
49455
+ }
49456
+ });
49457
+ }
49458
+ var init_command_routes = __esm(() => {
49459
+ init_frontmatter_parser();
49460
+ });
49461
+
49462
+ // src/services/claude-data/types.ts
49463
+ var HistoryEntrySchema, UserPreferencesSchema, TipsHistorySchema, FeatureFlagsSchema, HookLogEntrySchema;
49464
+ var init_types5 = __esm(() => {
49465
+ init_zod();
49466
+ HistoryEntrySchema = objectType({
49467
+ display: stringType(),
49468
+ pastedContents: recordType(unknownType()).optional(),
49469
+ timestamp: numberType(),
49470
+ project: stringType()
49471
+ });
49472
+ UserPreferencesSchema = objectType({
49473
+ numStartups: numberType().optional(),
49474
+ theme: enumType(["dark", "light", "dark-daltonized"]).optional(),
49475
+ installMethod: stringType().optional(),
49476
+ autoUpdates: booleanType().optional(),
49477
+ firstStartTime: stringType().optional(),
49478
+ hasSeenTasksHint: booleanType().optional(),
49479
+ hasSeenStashHint: booleanType().optional(),
49480
+ promptQueueUseCount: numberType().optional()
49481
+ });
49482
+ TipsHistorySchema = recordType(stringType(), numberType());
49483
+ FeatureFlagsSchema = objectType({
49484
+ statsigGates: recordType(stringType(), booleanType()).optional(),
49485
+ growthBookFeatures: recordType(stringType(), unknownType()).optional()
49486
+ });
49487
+ HookLogEntrySchema = objectType({
49488
+ ts: stringType().min(1).refine((value) => Number.isFinite(Date.parse(value)), {
49489
+ message: "Invalid timestamp"
49490
+ }),
49491
+ hook: stringType().min(1),
49492
+ event: stringType().min(1).optional(),
49493
+ tool: stringType().min(1).optional(),
49494
+ target: stringType().min(1).optional(),
49495
+ note: stringType().min(1).optional(),
49496
+ dur: numberType().finite().nonnegative().optional(),
49497
+ status: stringType().min(1),
49498
+ exit: numberType().int().optional(),
49499
+ error: stringType().min(1).optional()
49500
+ }).passthrough();
49501
+ });
49502
+
49503
+ // src/services/claude-data/history-parser.ts
49504
+ import { createReadStream, statSync as statSync4 } from "node:fs";
49505
+ import { homedir as homedir16 } from "node:os";
49506
+ import { join as join30 } from "node:path";
49507
+ import { createInterface as createInterface2 } from "node:readline";
49508
+ function getHistoryPath() {
49509
+ return process.env.CLAUDE_HISTORY_PATH ?? join30(homedir16(), ".claude", "history.jsonl");
49510
+ }
49511
+ function emptyResult(parseTimeMs, error) {
49512
+ return { projects: [], totalEntries: 0, errorCount: 0, parseTimeMs, error };
49513
+ }
49514
+ async function parseHistoryFile(filePath) {
49515
+ const path = filePath ?? getHistoryPath();
49516
+ const existingLock = locks.get(path);
49517
+ if (existingLock)
49518
+ return existingLock;
49519
+ const start = Date.now();
49520
+ let mtime;
49521
+ try {
49522
+ const stats = statSync4(path);
49523
+ mtime = stats.mtimeMs;
49524
+ const cached = cache.get(path);
49525
+ if (cached && cached.mtime === mtime) {
49526
+ return cached.result;
49527
+ }
49528
+ } catch (err) {
49529
+ const msg = err instanceof Error ? err.message : "File not found";
49530
+ return emptyResult(Date.now() - start, `Cannot read history.jsonl: ${msg}`);
49531
+ }
49532
+ const parsePromise = (async () => {
49533
+ try {
49534
+ const projectMap = new Map;
49535
+ let totalEntries = 0;
49536
+ let errorCount = 0;
49537
+ try {
49538
+ const fileStream = createReadStream(path);
49539
+ const rl = createInterface2({ input: fileStream, crlfDelay: Number.POSITIVE_INFINITY });
49540
+ for await (const line of rl) {
49541
+ if (!line.trim())
49542
+ continue;
49543
+ totalEntries++;
49544
+ try {
49545
+ const entry = JSON.parse(line);
49546
+ if (!entry.project)
49547
+ continue;
49548
+ const existing = projectMap.get(entry.project);
49549
+ if (existing) {
49550
+ existing.interactionCount++;
49551
+ if (entry.timestamp > existing.lastUsed) {
49552
+ existing.lastUsed = entry.timestamp;
49618
49553
  }
49619
- },
49620
- additionalProperties: true
49621
- }
49622
- },
49623
- additionalProperties: {
49624
- type: "object",
49625
- description: "Custom skill configuration"
49626
- }
49627
- },
49628
- assertions: {
49629
- type: "array",
49630
- items: {
49631
- type: "object",
49632
- properties: {
49633
- pattern: {
49634
- type: "string",
49635
- description: "Glob pattern or regex for files to match"
49636
- },
49637
- rule: {
49638
- type: "string",
49639
- description: "Assertion rule to enforce"
49640
- },
49641
- message: {
49642
- type: "string",
49643
- description: "Error message when assertion fails"
49644
- }
49645
- }
49646
- },
49647
- default: [],
49648
- description: "Code assertions and rules to enforce"
49649
- },
49650
- statusline: {
49651
- type: "string",
49652
- enum: ["full", "compact", "minimal", "none"],
49653
- default: "full",
49654
- description: "Statusline display mode: full=all features, compact=2-3 lines, minimal=single text line, none=disabled"
49655
- },
49656
- statuslineColors: {
49657
- type: "boolean",
49658
- default: true,
49659
- description: "Enable ANSI colors in statusline output. Set false for plain text. Respects NO_COLOR/FORCE_COLOR env vars."
49660
- },
49661
- statuslineQuota: {
49662
- type: "boolean",
49663
- default: true,
49664
- description: "Show the cosmetic 5h / wk quota chips in the statusline. Disable to keep the statusline layout without usage-window percentages."
49665
- },
49666
- statuslineLayout: {
49667
- type: "object",
49668
- description: "Advanced statusline layout config for the visual builder. When absent, uses hardcoded defaults (backward compatible). Takes precedence over the statusline field when both are set.",
49669
- properties: {
49670
- baseMode: {
49671
- type: "string",
49672
- enum: ["full", "compact", "minimal", "none"],
49673
- default: "full",
49674
- description: "Starting template mode for the layout"
49675
- },
49676
- lines: {
49677
- type: "array",
49678
- description: "Layout lines. Each inner array is one terminal row containing section IDs in left-to-right order. Sections not listed in any line are hidden.",
49679
- items: {
49680
- type: "array",
49681
- items: {
49682
- type: "string",
49683
- enum: [
49684
- "model",
49685
- "context",
49686
- "quota",
49687
- "directory",
49688
- "git",
49689
- "cost",
49690
- "changes",
49691
- "agents",
49692
- "todos"
49693
- ]
49694
- }
49695
- },
49696
- maxItems: 10
49697
- },
49698
- sectionConfig: {
49699
- type: "object",
49700
- description: "Per-section visual customization keyed by section ID.",
49701
- additionalProperties: {
49702
- type: "object",
49703
- properties: {
49704
- icon: {
49705
- type: "string",
49706
- description: "Custom emoji/icon override",
49707
- maxLength: 20
49708
- },
49709
- label: {
49710
- type: "string",
49711
- description: "Custom label override",
49712
- maxLength: 50
49713
- },
49714
- color: {
49715
- type: "string",
49716
- description: "ANSI named color (e.g. red, cyan, green). Hex codes not supported.",
49717
- maxLength: 30,
49718
- pattern: "^[a-zA-Z]+$"
49719
- },
49720
- maxWidth: {
49721
- type: "integer",
49722
- minimum: 10,
49723
- description: "Maximum character width for this section",
49724
- maximum: 500
49725
- }
49726
- },
49727
- additionalProperties: false
49728
- }
49729
- },
49730
- theme: {
49731
- type: "object",
49732
- description: "Color theme overrides",
49733
- properties: {
49734
- name: {
49735
- type: "string",
49736
- description: "Theme name (informational)",
49737
- maxLength: 50
49738
- },
49739
- contextLow: {
49740
- type: "string",
49741
- default: "green",
49742
- description: "Context bar color when usage <50%",
49743
- maxLength: 30,
49744
- pattern: "^[a-zA-Z]+$"
49745
- },
49746
- contextMid: {
49747
- type: "string",
49748
- default: "yellow",
49749
- description: "Context bar color when usage 50-75%",
49750
- maxLength: 30,
49751
- pattern: "^[a-zA-Z]+$"
49752
- },
49753
- contextHigh: {
49754
- type: "string",
49755
- default: "red",
49756
- description: "Context bar color when usage >75%",
49757
- maxLength: 30,
49758
- pattern: "^[a-zA-Z]+$"
49759
- },
49760
- accent: {
49761
- type: "string",
49762
- default: "cyan",
49763
- description: "Accent color for highlights",
49764
- maxLength: 30,
49765
- pattern: "^[a-zA-Z]+$"
49766
- },
49767
- muted: {
49768
- type: "string",
49769
- default: "dim",
49770
- description: "Muted text color",
49771
- maxLength: 30,
49772
- pattern: "^[a-zA-Z]+$"
49773
- },
49774
- separator: {
49775
- type: "string",
49776
- default: "dim",
49777
- description: "Separator color",
49778
- maxLength: 30,
49779
- pattern: "^[a-zA-Z]+$"
49780
- },
49781
- quotaLow: {
49782
- type: "string",
49783
- description: "Quota section color when all windows <85%",
49784
- maxLength: 30,
49785
- pattern: "^[a-zA-Z]+$"
49786
- },
49787
- quotaHigh: {
49788
- type: "string",
49789
- description: "Quota section color when any window >=85%",
49790
- maxLength: 30,
49791
- pattern: "^[a-zA-Z]+$"
49792
- }
49793
- },
49794
- additionalProperties: false
49795
- },
49796
- responsiveBreakpoint: {
49797
- type: "number",
49798
- minimum: 0.5,
49799
- maximum: 1,
49800
- default: 0.85,
49801
- description: "Terminal width fraction at which compact mode activates"
49802
- },
49803
- maxAgentRows: {
49804
- type: "integer",
49805
- minimum: 1,
49806
- maximum: 10,
49807
- default: 4,
49808
- description: "Maximum rows to show in agents section"
49809
- },
49810
- todoTruncation: {
49811
- type: "integer",
49812
- minimum: 20,
49813
- maximum: 100,
49814
- default: 50,
49815
- description: "Max characters before truncating todo text"
49816
- }
49817
- },
49818
- additionalProperties: false
49819
- },
49820
- hooks: {
49821
- type: "object",
49822
- description: "Toggle individual hooks on/off. Default: all enabled.",
49823
- properties: {
49824
- "session-init": {
49825
- type: "boolean",
49826
- default: true,
49827
- description: "SessionStart hook - project detection and env setup"
49828
- },
49829
- "subagent-init": {
49830
- type: "boolean",
49831
- default: true,
49832
- description: "SubagentStart hook - injects context to subagents"
49833
- },
49834
- "session-state": {
49835
- type: "boolean",
49836
- default: true,
49837
- description: "Stop/SubagentStop/PostToolUse hook - persists session state for handoff and statusline context"
49838
- },
49839
- "cook-after-plan-reminder": {
49840
- type: "boolean",
49841
- default: true,
49842
- description: "SubagentStop hook - reminds agents to continue implementation after planning"
49843
- },
49844
- "descriptive-name": {
49845
- type: "boolean",
49846
- default: true,
49847
- description: "PreToolUse hook - injects language-aware descriptive file naming guidance"
49848
- },
49849
- "dev-rules-reminder": {
49850
- type: "boolean",
49851
- default: true,
49852
- description: "UserPromptSubmit hook - injects dev rules context"
49853
- },
49854
- "plan-format-kanban": {
49855
- type: "boolean",
49856
- default: true,
49857
- description: "PostToolUse hook - keeps plan kanban metadata synchronized after edits"
49858
- },
49859
- "usage-quota-cache-refresh": {
49860
- type: "boolean",
49861
- default: true,
49862
- description: "Lifecycle hook - refreshes cached usage quota information"
49863
- },
49864
- "usage-context-awareness": {
49865
- type: "boolean",
49866
- default: true,
49867
- description: "UserPromptSubmit/PostToolUse hook - inject usage-limit awareness into prompt context"
49868
- },
49869
- "context-tracking": {
49870
- type: "boolean",
49871
- default: true,
49872
- description: "Context window tracking injection (% used, token counts) in dev-rules-reminder"
49873
- },
49874
- "scout-block": {
49875
- type: "boolean",
49876
- default: true,
49877
- description: "PreToolUse hook - blocks heavy directories"
49878
- },
49879
- "privacy-block": {
49880
- type: "boolean",
49881
- default: true,
49882
- description: "PreToolUse hook - blocks sensitive files"
49883
- },
49884
- "simplify-gate": {
49885
- type: "boolean",
49886
- default: true,
49887
- description: "UserPromptSubmit hook - hard-blocks ship/merge/pr/deploy/publish and soft-warns commit/finalize/release when live diff exceeds simplify thresholds. Bypass via CK_SIMPLIFY_DISABLED=1."
49888
- }
49889
- },
49890
- additionalProperties: false
49891
- },
49892
- simplify: {
49893
- type: "object",
49894
- description: "Signal-driven simplifier orchestration. Live git diff thresholds + verb-based gate.",
49895
- properties: {
49896
- threshold: {
49897
- type: "object",
49898
- description: "LOC/file thresholds that trip the gate. Nested object stays open for forward-compat fields.",
49899
- properties: {
49900
- locDelta: {
49901
- type: "integer",
49902
- minimum: 0,
49903
- default: 400,
49904
- description: "Total LOC change (across all files) above which the gate fires."
49905
- },
49906
- fileCount: {
49907
- type: "integer",
49908
- minimum: 0,
49909
- default: 8,
49910
- description: "Number of changed files above which the gate fires."
49911
- },
49912
- singleFileLoc: {
49913
- type: "integer",
49914
- minimum: 0,
49915
- default: 200,
49916
- description: "Per-file LOC change above which the gate fires."
49917
- }
49918
- }
49919
- },
49920
- gate: {
49921
- type: "object",
49922
- description: "UserPromptSubmit gate behavior. Nested object stays open for forward-compat fields.",
49923
- properties: {
49924
- enabled: {
49925
- type: "boolean",
49926
- default: false,
49927
- description: "Master toggle. Off by default (opt-in). Set true to activate; env CK_SIMPLIFY_DISABLED=1 always bypasses."
49928
- },
49929
- hardVerbs: {
49930
- type: "array",
49931
- items: { type: "string" },
49932
- default: ["ship", "merge", "pr", "deploy", "publish"],
49933
- description: "Verbs that hard-block (continue:false) when thresholds breached."
49934
- },
49935
- softVerbs: {
49936
- type: "array",
49937
- items: { type: "string" },
49938
- default: ["commit", "finalize", "release"],
49939
- description: "Verbs that soft-warn (additionalContext) when thresholds breached."
49940
- }
49941
- }
49942
- }
49943
- },
49944
- additionalProperties: false
49945
- },
49946
- updatePipeline: {
49947
- type: "object",
49948
- description: "3-step update pipeline: CLI update -> kit init -> provider migrate (each independent)",
49949
- properties: {
49950
- autoInitAfterUpdate: {
49951
- type: "boolean",
49952
- default: false,
49953
- description: "Automatically run ck init after ck update when kit has new version"
49954
- },
49955
- autoMigrateAfterUpdate: {
49956
- type: "boolean",
49957
- default: false,
49958
- description: "Automatically run ck migrate after ck update for detected providers (independent of init)"
49959
- },
49960
- migrateProviders: {
49961
- oneOf: [
49962
- {
49963
- type: "string",
49964
- const: "auto"
49965
- },
49966
- {
49967
- type: "array",
49968
- items: {
49969
- type: "string"
49970
- }
49971
- }
49972
- ],
49973
- default: "auto",
49974
- description: "Which providers to auto-migrate. 'auto' scans filesystem."
49975
- }
49976
- },
49977
- additionalProperties: false
49978
- },
49979
- modelTaxonomy: {
49980
- type: "object",
49981
- description: "Custom model mappings for portable migration. Keys are provider names (e.g., 'codex', 'gemini-cli'), values map tiers to model config.",
49982
- additionalProperties: {
49983
- type: "object",
49984
- properties: {
49985
- heavy: {
49986
- type: "object",
49987
- properties: {
49988
- model: {
49989
- type: "string",
49990
- description: "Target model name"
49991
- },
49992
- effort: {
49993
- type: "string",
49994
- description: "Effort/reasoning level (provider-specific)"
49995
- }
49996
- },
49997
- required: ["model"]
49998
- },
49999
- balanced: {
50000
- type: "object",
50001
- properties: {
50002
- model: {
50003
- type: "string",
50004
- description: "Target model name"
50005
- },
50006
- effort: {
50007
- type: "string",
50008
- description: "Effort/reasoning level (provider-specific)"
50009
- }
50010
- },
50011
- required: ["model"]
50012
- },
50013
- light: {
50014
- type: "object",
50015
- properties: {
50016
- model: {
50017
- type: "string",
50018
- description: "Target model name"
50019
- },
50020
- effort: {
50021
- type: "string",
50022
- description: "Effort/reasoning level (provider-specific)"
50023
- }
50024
- },
50025
- required: ["model"]
49554
+ } else {
49555
+ projectMap.set(entry.project, {
49556
+ path: entry.project,
49557
+ lastUsed: entry.timestamp,
49558
+ interactionCount: 1
49559
+ });
50026
49560
  }
49561
+ } catch {
49562
+ errorCount++;
50027
49563
  }
50028
49564
  }
49565
+ } catch (err) {
49566
+ const msg = err instanceof Error ? err.message : "Parse error";
49567
+ return emptyResult(Date.now() - start, `Failed to parse history.jsonl: ${msg}`);
50029
49568
  }
50030
- },
50031
- additionalProperties: true
50032
- };
49569
+ const result = {
49570
+ projects: Array.from(projectMap.values()).sort((a, b) => b.lastUsed - a.lastUsed),
49571
+ totalEntries,
49572
+ errorCount,
49573
+ parseTimeMs: Date.now() - start
49574
+ };
49575
+ cache.set(path, { mtime, result });
49576
+ return result;
49577
+ } finally {
49578
+ locks.delete(path);
49579
+ }
49580
+ })();
49581
+ locks.set(path, parsePromise);
49582
+ return parsePromise;
49583
+ }
49584
+ var cache, locks;
49585
+ var init_history_parser = __esm(() => {
49586
+ cache = new Map;
49587
+ locks = new Map;
50033
49588
  });
50034
49589
 
50035
- // src/domains/web-server/routes/ck-config-routes.ts
50036
- import { existsSync as existsSync15 } from "node:fs";
50037
- import { readFile as readFile11 } from "node:fs/promises";
50038
- import { join as join29 } from "node:path";
50039
- async function resolveProjectDir(projectId) {
50040
- if (!projectId)
50041
- return null;
50042
- if (projectId.startsWith("discovered-")) {
50043
- const encodedPath = projectId.slice("discovered-".length);
50044
- return Buffer.from(encodedPath, "base64url").toString("utf-8");
49590
+ // src/services/claude-data/session-parser.ts
49591
+ import { existsSync as existsSync16 } from "node:fs";
49592
+ import { readFile as readFile12, readdir as readdir9, stat as stat5 } from "node:fs/promises";
49593
+ import { join as join31 } from "node:path";
49594
+ function formatTimestamp(date) {
49595
+ const now = new Date;
49596
+ const isToday = date.toDateString() === now.toDateString();
49597
+ const hours = date.getHours().toString().padStart(2, "0");
49598
+ const minutes = date.getMinutes().toString().padStart(2, "0");
49599
+ if (isToday) {
49600
+ return `Today ${hours}:${minutes}`;
50045
49601
  }
50046
- await Promise.resolve().then(() => init_projects_registry());
50047
- const project = await ProjectsRegistryManager.getProject(projectId);
50048
- return project?.path ?? null;
49602
+ const months = [
49603
+ "Jan",
49604
+ "Feb",
49605
+ "Mar",
49606
+ "Apr",
49607
+ "May",
49608
+ "Jun",
49609
+ "Jul",
49610
+ "Aug",
49611
+ "Sep",
49612
+ "Oct",
49613
+ "Nov",
49614
+ "Dec"
49615
+ ];
49616
+ return `${months[date.getMonth()]} ${date.getDate()} ${hours}:${minutes}`;
50049
49617
  }
50050
- function registerCkConfigRoutes(app) {
50051
- app.get("/api/ck-config", async (req, res) => {
50052
- try {
50053
- const { projectId, scope = "merged" } = req.query;
50054
- const projectDir = await resolveProjectDir(projectId);
50055
- if (scope === "global") {
50056
- const config = await CkConfigManager.loadScope("global", null);
50057
- res.json({
50058
- config: config || {},
50059
- sources: {},
50060
- globalPath: CkConfigManager.getGlobalConfigPath(),
50061
- projectPath: null
50062
- });
50063
- return;
50064
- }
50065
- if (scope === "project") {
50066
- if (!projectDir) {
50067
- res.status(400).json({ error: "projectId required for project scope" });
50068
- return;
49618
+ function formatDuration(startMs, endMs) {
49619
+ const diffMs = endMs - startMs;
49620
+ const minutes = Math.floor(diffMs / 60000);
49621
+ const hours = Math.floor(minutes / 60);
49622
+ const remainingMinutes = minutes % 60;
49623
+ if (hours === 0) {
49624
+ return `${remainingMinutes}min`;
49625
+ }
49626
+ return `${hours}h ${remainingMinutes}min`;
49627
+ }
49628
+ function extractContent(message) {
49629
+ if (!message?.content)
49630
+ return "";
49631
+ if (typeof message.content === "string") {
49632
+ return message.content;
49633
+ }
49634
+ const textBlock = message.content.find((b) => b.type === "text");
49635
+ return textBlock?.text || "";
49636
+ }
49637
+ async function parseSession(filePath) {
49638
+ try {
49639
+ const content = await readFile12(filePath, "utf-8");
49640
+ const lines = content.split(`
49641
+ `).filter((l) => l.trim());
49642
+ if (lines.length === 0)
49643
+ return null;
49644
+ let summary = "";
49645
+ let sessionId = "";
49646
+ let firstTimestamp = null;
49647
+ let lastTimestamp = null;
49648
+ for (const line of lines) {
49649
+ try {
49650
+ const event = JSON.parse(line);
49651
+ if (event.type === "summary" && event.summary && !summary) {
49652
+ summary = event.summary;
50069
49653
  }
50070
- const config = await CkConfigManager.loadScope("project", projectDir);
50071
- res.json({
50072
- config: config || {},
50073
- sources: {},
50074
- globalPath: CkConfigManager.getGlobalConfigPath(),
50075
- projectPath: CkConfigManager.getProjectConfigPath(projectDir)
50076
- });
50077
- return;
50078
- }
50079
- const result = await CkConfigManager.loadFull(projectDir);
50080
- res.json(result);
50081
- } catch (error) {
50082
- logger.error(`Failed to load ck-config: ${error}`);
50083
- res.status(500).json({ error: "Failed to load configuration" });
50084
- }
50085
- });
50086
- app.put("/api/ck-config", async (req, res) => {
50087
- try {
50088
- const { scope, projectId, config } = req.body;
50089
- if (!scope || !["global", "project"].includes(scope)) {
50090
- res.status(400).json({ error: "Invalid scope. Must be 'global' or 'project'" });
50091
- return;
50092
- }
50093
- if (!config || typeof config !== "object") {
50094
- res.status(400).json({ error: "Invalid config payload" });
50095
- return;
50096
- }
50097
- const parseResult = CkConfigSchema.safeParse(normalizeCkConfigInput(config));
50098
- if (!parseResult.success) {
50099
- res.status(400).json({
50100
- error: "Config validation failed",
50101
- details: parseResult.error.issues
50102
- });
50103
- return;
50104
- }
50105
- let projectDir = null;
50106
- if (scope === "project") {
50107
- if (!projectId) {
50108
- res.status(400).json({ error: "projectId required for project scope" });
50109
- return;
49654
+ if (event.sessionId && !sessionId) {
49655
+ sessionId = event.sessionId;
50110
49656
  }
50111
- projectDir = await resolveProjectDir(projectId);
50112
- if (!projectDir) {
50113
- res.status(404).json({ error: "Project not found" });
50114
- return;
49657
+ if (event.timestamp) {
49658
+ const ts = new Date(event.timestamp);
49659
+ if (!firstTimestamp)
49660
+ firstTimestamp = ts;
49661
+ lastTimestamp = ts;
50115
49662
  }
50116
- }
50117
- const savedPath = await CkConfigManager.saveFull(parseResult.data, scope, projectDir);
50118
- let savedConfig = await CkConfigManager.loadScope(scope, projectDir);
50119
- if (!savedConfig) {
50120
- try {
50121
- const rawContent = await readFile11(savedPath, "utf-8");
50122
- const rawConfig = JSON.parse(rawContent);
50123
- if (rawConfig && typeof rawConfig === "object" && !Array.isArray(rawConfig)) {
50124
- savedConfig = normalizeCkConfigInput(rawConfig);
50125
- }
50126
- } catch {
50127
- savedConfig = null;
50128
- }
50129
- }
50130
- res.json({
50131
- success: true,
50132
- path: savedPath,
50133
- scope,
50134
- config: savedConfig || {}
50135
- });
50136
- } catch (error) {
50137
- logger.error(`Failed to save ck-config: ${error}`);
50138
- res.status(500).json({ error: "Failed to save configuration" });
50139
- }
50140
- });
50141
- app.get("/api/ck-config/schema", (_req, res) => {
50142
- res.json(ck_config_schema_default);
50143
- });
50144
- app.patch("/api/ck-config/field", async (req, res) => {
50145
- try {
50146
- const { scope, projectId, fieldPath, value } = req.body;
50147
- if (!scope || !["global", "project"].includes(scope)) {
50148
- res.status(400).json({ error: "Invalid scope" });
50149
- return;
50150
- }
50151
- if (!fieldPath || typeof fieldPath !== "string") {
50152
- res.status(400).json({ error: "fieldPath required" });
50153
- return;
50154
- }
50155
- let projectDir = null;
50156
- if (scope === "project") {
50157
- if (!projectId) {
50158
- res.status(400).json({ error: "projectId required for project scope" });
50159
- return;
50160
- }
50161
- projectDir = await resolveProjectDir(projectId);
50162
- if (!projectDir) {
50163
- res.status(404).json({ error: "Project not found" });
50164
- return;
50165
- }
50166
- }
50167
- await CkConfigManager.updateField(fieldPath, value, scope, projectDir);
50168
- res.json({ success: true, fieldPath, value, scope });
50169
- } catch (error) {
50170
- if (error instanceof ZodError) {
50171
- res.status(400).json({ error: "Validation failed", issues: error.issues });
50172
- return;
50173
- }
50174
- logger.error(`Failed to update ck-config field: ${error}`);
50175
- res.status(500).json({ error: "Failed to update field" });
50176
- }
50177
- });
50178
- app.get("/api/metadata/global", async (_req, res) => {
50179
- try {
50180
- const metadataPath = join29(PathResolver.getGlobalKitDir(), "metadata.json");
50181
- let metadata = {};
50182
- if (existsSync15(metadataPath)) {
50183
- const content = await readFile11(metadataPath, "utf-8");
50184
- try {
50185
- metadata = JSON.parse(content);
50186
- } catch (err) {
50187
- logger.warning(`Invalid JSON in metadata.json: ${err}`);
50188
- }
50189
- }
50190
- res.json(metadata);
50191
- } catch (error) {
50192
- logger.error(`Failed to load global metadata: ${error}`);
50193
- res.status(500).json({ error: "Failed to load metadata" });
50194
- }
50195
- });
50196
- }
50197
- var init_ck_config_routes = __esm(() => {
50198
- init_config();
50199
- init_ck_config_schema();
50200
- init_logger();
50201
- init_path_resolver();
50202
- init_types3();
50203
- init_zod();
50204
- });
50205
-
50206
- // src/domains/web-server/routes/command-routes.ts
50207
- import { existsSync as existsSync16 } from "node:fs";
50208
- import { readFile as readFile12, readdir as readdir8 } from "node:fs/promises";
50209
- import { homedir as homedir15 } from "node:os";
50210
- import { basename as basename9, isAbsolute as isAbsolute5, join as join30, relative as relative8, resolve as resolve13 } from "node:path";
50211
- async function buildCommandTree(dir, baseDir) {
50212
- let dirents;
50213
- try {
50214
- dirents = await readdir8(dir, { withFileTypes: true });
50215
- } catch {
50216
- return [];
50217
- }
50218
- const nodes = [];
50219
- const sorted = [...dirents].sort((a, b) => {
50220
- const aDir = a.isDirectory();
50221
- const bDir = b.isDirectory();
50222
- if (aDir !== bDir)
50223
- return aDir ? -1 : 1;
50224
- return a.name.localeCompare(b.name);
50225
- });
50226
- for (const entry of sorted) {
50227
- const name = entry.name;
50228
- const isDir = entry.isDirectory();
50229
- const fullPath = join30(dir, name);
50230
- const relPath = relative8(baseDir, fullPath);
50231
- if (isDir) {
50232
- const children = await buildCommandTree(fullPath, baseDir);
50233
- if (children.length > 0) {
50234
- nodes.push({ name, path: relPath, children });
50235
- }
50236
- } else if (name.endsWith(".md")) {
50237
- let description;
50238
- try {
50239
- const content = await readFile12(fullPath, "utf-8");
50240
- const { frontmatter } = parseFrontmatter(content);
50241
- description = frontmatter.description;
50242
- } catch {}
50243
- const commandName = basename9(name, ".md");
50244
- nodes.push({ name: commandName, path: relPath, description });
50245
- }
50246
- }
50247
- return nodes;
50248
- }
50249
- function registerCommandRoutes(app) {
50250
- app.get("/api/commands", async (_req, res) => {
50251
- const commandsDir = join30(homedir15(), ".claude", "commands");
50252
- if (!existsSync16(commandsDir)) {
50253
- res.json({ tree: [] });
50254
- return;
50255
- }
50256
- try {
50257
- const tree = await buildCommandTree(commandsDir, commandsDir);
50258
- res.json({ tree });
50259
- } catch {
50260
- res.status(500).json({ error: "Failed to scan commands directory" });
50261
- }
50262
- });
50263
- app.get("/api/commands/detail/:slug", async (req, res) => {
50264
- const slug = String(req.params.slug ?? "");
50265
- const rawPath = slug.replace(/--/g, "/");
50266
- if (!rawPath) {
50267
- res.status(400).json({ error: "Missing command path" });
50268
- return;
50269
- }
50270
- if (rawPath.includes("..") || rawPath.includes("\x00")) {
50271
- res.status(400).json({ error: "Invalid path" });
50272
- return;
50273
- }
50274
- const commandsDir = join30(homedir15(), ".claude", "commands");
50275
- const safePath = resolve13(commandsDir, rawPath);
50276
- const rel = relative8(commandsDir, safePath);
50277
- if (rel.startsWith("..") || isAbsolute5(rel)) {
50278
- res.status(403).json({ error: "Access denied" });
50279
- return;
50280
- }
50281
- const filePath = safePath.endsWith(".md") ? safePath : `${safePath}.md`;
50282
- if (!existsSync16(filePath)) {
50283
- res.status(404).json({ error: "Command not found" });
50284
- return;
50285
- }
50286
- try {
50287
- const content = await readFile12(filePath, "utf-8");
50288
- const { frontmatter } = parseFrontmatter(content);
50289
- const commandName = basename9(filePath, ".md");
50290
- res.json({
50291
- name: commandName,
50292
- path: rawPath,
50293
- content,
50294
- description: frontmatter.description
50295
- });
50296
- } catch {
50297
- res.status(500).json({ error: "Failed to read command" });
50298
- }
50299
- });
50300
- }
50301
- var init_command_routes = __esm(() => {
50302
- init_frontmatter_parser();
50303
- });
50304
-
50305
- // src/services/claude-data/types.ts
50306
- var HistoryEntrySchema, UserPreferencesSchema, TipsHistorySchema, FeatureFlagsSchema, HookLogEntrySchema;
50307
- var init_types5 = __esm(() => {
50308
- init_zod();
50309
- HistoryEntrySchema = objectType({
50310
- display: stringType(),
50311
- pastedContents: recordType(unknownType()).optional(),
50312
- timestamp: numberType(),
50313
- project: stringType()
50314
- });
50315
- UserPreferencesSchema = objectType({
50316
- numStartups: numberType().optional(),
50317
- theme: enumType(["dark", "light", "dark-daltonized"]).optional(),
50318
- installMethod: stringType().optional(),
50319
- autoUpdates: booleanType().optional(),
50320
- firstStartTime: stringType().optional(),
50321
- hasSeenTasksHint: booleanType().optional(),
50322
- hasSeenStashHint: booleanType().optional(),
50323
- promptQueueUseCount: numberType().optional()
50324
- });
50325
- TipsHistorySchema = recordType(stringType(), numberType());
50326
- FeatureFlagsSchema = objectType({
50327
- statsigGates: recordType(stringType(), booleanType()).optional(),
50328
- growthBookFeatures: recordType(stringType(), unknownType()).optional()
50329
- });
50330
- HookLogEntrySchema = objectType({
50331
- ts: stringType().min(1).refine((value) => Number.isFinite(Date.parse(value)), {
50332
- message: "Invalid timestamp"
50333
- }),
50334
- hook: stringType().min(1),
50335
- event: stringType().min(1).optional(),
50336
- tool: stringType().min(1).optional(),
50337
- target: stringType().min(1).optional(),
50338
- note: stringType().min(1).optional(),
50339
- dur: numberType().finite().nonnegative().optional(),
50340
- status: stringType().min(1),
50341
- exit: numberType().int().optional(),
50342
- error: stringType().min(1).optional()
50343
- }).passthrough();
50344
- });
50345
-
50346
- // src/services/claude-data/history-parser.ts
50347
- import { createReadStream, statSync as statSync4 } from "node:fs";
50348
- import { homedir as homedir16 } from "node:os";
50349
- import { join as join31 } from "node:path";
50350
- import { createInterface as createInterface2 } from "node:readline";
50351
- function getHistoryPath() {
50352
- return process.env.CLAUDE_HISTORY_PATH ?? join31(homedir16(), ".claude", "history.jsonl");
50353
- }
50354
- function emptyResult(parseTimeMs, error) {
50355
- return { projects: [], totalEntries: 0, errorCount: 0, parseTimeMs, error };
50356
- }
50357
- async function parseHistoryFile(filePath) {
50358
- const path = filePath ?? getHistoryPath();
50359
- const existingLock = locks.get(path);
50360
- if (existingLock)
50361
- return existingLock;
50362
- const start = Date.now();
50363
- let mtime;
50364
- try {
50365
- const stats = statSync4(path);
50366
- mtime = stats.mtimeMs;
50367
- const cached = cache.get(path);
50368
- if (cached && cached.mtime === mtime) {
50369
- return cached.result;
50370
- }
50371
- } catch (err) {
50372
- const msg = err instanceof Error ? err.message : "File not found";
50373
- return emptyResult(Date.now() - start, `Cannot read history.jsonl: ${msg}`);
50374
- }
50375
- const parsePromise = (async () => {
50376
- try {
50377
- const projectMap = new Map;
50378
- let totalEntries = 0;
50379
- let errorCount = 0;
50380
- try {
50381
- const fileStream = createReadStream(path);
50382
- const rl = createInterface2({ input: fileStream, crlfDelay: Number.POSITIVE_INFINITY });
50383
- for await (const line of rl) {
50384
- if (!line.trim())
50385
- continue;
50386
- totalEntries++;
50387
- try {
50388
- const entry = JSON.parse(line);
50389
- if (!entry.project)
50390
- continue;
50391
- const existing = projectMap.get(entry.project);
50392
- if (existing) {
50393
- existing.interactionCount++;
50394
- if (entry.timestamp > existing.lastUsed) {
50395
- existing.lastUsed = entry.timestamp;
50396
- }
50397
- } else {
50398
- projectMap.set(entry.project, {
50399
- path: entry.project,
50400
- lastUsed: entry.timestamp,
50401
- interactionCount: 1
50402
- });
50403
- }
50404
- } catch {
50405
- errorCount++;
50406
- }
50407
- }
50408
- } catch (err) {
50409
- const msg = err instanceof Error ? err.message : "Parse error";
50410
- return emptyResult(Date.now() - start, `Failed to parse history.jsonl: ${msg}`);
50411
- }
50412
- const result = {
50413
- projects: Array.from(projectMap.values()).sort((a, b) => b.lastUsed - a.lastUsed),
50414
- totalEntries,
50415
- errorCount,
50416
- parseTimeMs: Date.now() - start
50417
- };
50418
- cache.set(path, { mtime, result });
50419
- return result;
50420
- } finally {
50421
- locks.delete(path);
50422
- }
50423
- })();
50424
- locks.set(path, parsePromise);
50425
- return parsePromise;
50426
- }
50427
- var cache, locks;
50428
- var init_history_parser = __esm(() => {
50429
- cache = new Map;
50430
- locks = new Map;
50431
- });
50432
-
50433
- // src/services/claude-data/session-parser.ts
50434
- import { existsSync as existsSync17 } from "node:fs";
50435
- import { readFile as readFile13, readdir as readdir9, stat as stat5 } from "node:fs/promises";
50436
- import { join as join32 } from "node:path";
50437
- function formatTimestamp(date) {
50438
- const now = new Date;
50439
- const isToday = date.toDateString() === now.toDateString();
50440
- const hours = date.getHours().toString().padStart(2, "0");
50441
- const minutes = date.getMinutes().toString().padStart(2, "0");
50442
- if (isToday) {
50443
- return `Today ${hours}:${minutes}`;
50444
- }
50445
- const months = [
50446
- "Jan",
50447
- "Feb",
50448
- "Mar",
50449
- "Apr",
50450
- "May",
50451
- "Jun",
50452
- "Jul",
50453
- "Aug",
50454
- "Sep",
50455
- "Oct",
50456
- "Nov",
50457
- "Dec"
50458
- ];
50459
- return `${months[date.getMonth()]} ${date.getDate()} ${hours}:${minutes}`;
50460
- }
50461
- function formatDuration(startMs, endMs) {
50462
- const diffMs = endMs - startMs;
50463
- const minutes = Math.floor(diffMs / 60000);
50464
- const hours = Math.floor(minutes / 60);
50465
- const remainingMinutes = minutes % 60;
50466
- if (hours === 0) {
50467
- return `${remainingMinutes}min`;
50468
- }
50469
- return `${hours}h ${remainingMinutes}min`;
50470
- }
50471
- function extractContent(message) {
50472
- if (!message?.content)
50473
- return "";
50474
- if (typeof message.content === "string") {
50475
- return message.content;
50476
- }
50477
- const textBlock = message.content.find((b) => b.type === "text");
50478
- return textBlock?.text || "";
50479
- }
50480
- async function parseSession(filePath) {
50481
- try {
50482
- const content = await readFile13(filePath, "utf-8");
50483
- const lines = content.split(`
50484
- `).filter((l) => l.trim());
50485
- if (lines.length === 0)
50486
- return null;
50487
- let summary = "";
50488
- let sessionId = "";
50489
- let firstTimestamp = null;
50490
- let lastTimestamp = null;
50491
- for (const line of lines) {
50492
- try {
50493
- const event = JSON.parse(line);
50494
- if (event.type === "summary" && event.summary && !summary) {
50495
- summary = event.summary;
50496
- }
50497
- if (event.sessionId && !sessionId) {
50498
- sessionId = event.sessionId;
50499
- }
50500
- if (event.timestamp) {
50501
- const ts = new Date(event.timestamp);
50502
- if (!firstTimestamp)
50503
- firstTimestamp = ts;
50504
- lastTimestamp = ts;
50505
- }
50506
- if (event.type === "user" && event.message?.role === "user" && !summary) {
50507
- const text = extractContent(event.message);
50508
- summary = text.replace(/<[^>]+>/g, "").slice(0, 100);
50509
- if (text.length > 100)
50510
- summary += "...";
49663
+ if (event.type === "user" && event.message?.role === "user" && !summary) {
49664
+ const text = extractContent(event.message);
49665
+ summary = text.replace(/<[^>]+>/g, "").slice(0, 100);
49666
+ if (text.length > 100)
49667
+ summary += "...";
50511
49668
  }
50512
49669
  } catch {}
50513
49670
  }
@@ -50524,13 +49681,13 @@ async function parseSession(filePath) {
50524
49681
  }
50525
49682
  }
50526
49683
  async function getProjectSessions(projectDir, limit = 10) {
50527
- if (!existsSync17(projectDir))
49684
+ if (!existsSync16(projectDir))
50528
49685
  return [];
50529
49686
  try {
50530
49687
  const files = await readdir9(projectDir);
50531
49688
  const jsonlFiles = files.filter((f) => f.endsWith(".jsonl"));
50532
49689
  const fileStats = await Promise.all(jsonlFiles.map(async (file) => {
50533
- const filePath = join32(projectDir, file);
49690
+ const filePath = join31(projectDir, file);
50534
49691
  const fileStat = await stat5(filePath).catch(() => null);
50535
49692
  return { file, filePath, mtime: fileStat?.mtime || new Date(0) };
50536
49693
  }));
@@ -50545,24 +49702,24 @@ async function getProjectSessions(projectDir, limit = 10) {
50545
49702
  var init_session_parser = () => {};
50546
49703
 
50547
49704
  // src/services/claude-data/skill-scanner.ts
50548
- import { existsSync as existsSync18 } from "node:fs";
50549
- import { readFile as readFile14, readdir as readdir10, stat as stat6 } from "node:fs/promises";
49705
+ import { existsSync as existsSync17 } from "node:fs";
49706
+ import { readFile as readFile13, readdir as readdir10, stat as stat6 } from "node:fs/promises";
50550
49707
  import { homedir as homedir17 } from "node:os";
50551
- import { join as join33 } from "node:path";
49708
+ import { join as join32 } from "node:path";
50552
49709
  async function getCkSkillMetadata(scope) {
50553
- const metaPath = scope === "global" ? join33(homedir17(), ".claude", "metadata.json") : join33(process.cwd(), ".claude", "metadata.json");
50554
- if (!existsSync18(metaPath))
49710
+ const metaPath = scope === "global" ? join32(homedir17(), ".claude", "metadata.json") : join32(process.cwd(), ".claude", "metadata.json");
49711
+ if (!existsSync17(metaPath))
50555
49712
  return null;
50556
49713
  const result = new Map;
50557
49714
  try {
50558
- const content = await readFile14(metaPath, "utf-8");
49715
+ const content = await readFile13(metaPath, "utf-8");
50559
49716
  const data = JSON.parse(content);
50560
49717
  for (const [kitName, kitData] of Object.entries(data.kits || {})) {
50561
49718
  const kit = kitData;
50562
49719
  if (!Array.isArray(kit.files))
50563
49720
  continue;
50564
49721
  for (const file of kit.files) {
50565
- if (file.ownership !== "ck")
49722
+ if (file.ownership !== "gck")
50566
49723
  continue;
50567
49724
  const parts = file.path.split("/");
50568
49725
  if (parts.length < 3 || parts[0] !== "skills")
@@ -50586,14 +49743,14 @@ async function getCkSkillMetadata(scope) {
50586
49743
  return result.size > 0 ? result : null;
50587
49744
  }
50588
49745
  async function getSkillMetadata(skillPath) {
50589
- const skillMdPath = join33(skillPath, "SKILL.md");
49746
+ const skillMdPath = join32(skillPath, "SKILL.md");
50590
49747
  try {
50591
49748
  await stat6(skillMdPath);
50592
49749
  } catch {
50593
49750
  return null;
50594
49751
  }
50595
49752
  try {
50596
- const content = await readFile14(skillMdPath, "utf-8");
49753
+ const content = await readFile13(skillMdPath, "utf-8");
50597
49754
  const { data } = import_gray_matter4.default(content, { engines: { javascript: { parse: () => ({}) } } });
50598
49755
  return data;
50599
49756
  } catch {
@@ -50619,7 +49776,7 @@ function inferCategory(name, metadata) {
50619
49776
  return "other";
50620
49777
  }
50621
49778
  async function scanSkills() {
50622
- if (!existsSync18(skillsDir))
49779
+ if (!existsSync17(skillsDir))
50623
49780
  return [];
50624
49781
  try {
50625
49782
  const entries = await readdir10(skillsDir);
@@ -50634,9 +49791,9 @@ async function scanSkills() {
50634
49791
  continue;
50635
49792
  if (ckMeta && !ckMeta.has(entry))
50636
49793
  continue;
50637
- const entryPath = join33(skillsDir, entry);
50638
- const skillMdPath = join33(entryPath, "SKILL.md");
50639
- if (!existsSync18(skillMdPath))
49794
+ const entryPath = join32(skillsDir, entry);
49795
+ const skillMdPath = join32(entryPath, "SKILL.md");
49796
+ if (!existsSync17(skillMdPath))
50640
49797
  continue;
50641
49798
  const frontmatter = await getSkillMetadata(entryPath);
50642
49799
  const meta = ckMeta?.get(entry);
@@ -50680,24 +49837,24 @@ async function scanSkills() {
50680
49837
  var import_gray_matter4, skillsDir, SKIP_DIRS2;
50681
49838
  var init_skill_scanner = __esm(() => {
50682
49839
  import_gray_matter4 = __toESM(require_gray_matter(), 1);
50683
- skillsDir = join33(homedir17(), ".claude", "skills");
49840
+ skillsDir = join32(homedir17(), ".claude", "skills");
50684
49841
  SKIP_DIRS2 = [".venv", "scripts", "__pycache__", "node_modules", ".git", "common"];
50685
49842
  });
50686
49843
 
50687
49844
  // src/services/claude-data/settings-reader.ts
50688
- import { existsSync as existsSync19 } from "node:fs";
50689
- import { copyFile as copyFile2, mkdir as mkdir9, readFile as readFile15, rename as rename5, rm as rm4, writeFile as writeFile10 } from "node:fs/promises";
49845
+ import { existsSync as existsSync18 } from "node:fs";
49846
+ import { copyFile as copyFile2, mkdir as mkdir9, readFile as readFile14, rename as rename5, rm as rm4, writeFile as writeFile10 } from "node:fs/promises";
50690
49847
  import { homedir as homedir18 } from "node:os";
50691
- import { join as join34 } from "node:path";
49848
+ import { join as join33 } from "node:path";
50692
49849
  function getSettingsPath() {
50693
- return join34(claudeDir, settingsFilename);
49850
+ return join33(claudeDir, settingsFilename);
50694
49851
  }
50695
49852
  async function readSettings() {
50696
49853
  const settingsPath = getSettingsPath();
50697
49854
  try {
50698
- if (!existsSync19(settingsPath))
49855
+ if (!existsSync18(settingsPath))
50699
49856
  return null;
50700
- const content = await readFile15(settingsPath, "utf-8");
49857
+ const content = await readFile14(settingsPath, "utf-8");
50701
49858
  return JSON.parse(content);
50702
49859
  } catch {
50703
49860
  return null;
@@ -50710,9 +49867,9 @@ async function backupAndSaveSettings(settings) {
50710
49867
  const settingsPath = getSettingsPath();
50711
49868
  await mkdir9(claudeDir, { recursive: true });
50712
49869
  let backupPath = null;
50713
- if (existsSync19(settingsPath)) {
49870
+ if (existsSync18(settingsPath)) {
50714
49871
  await mkdir9(settingsBackupDir, { recursive: true });
50715
- backupPath = join34(settingsBackupDir, `${getBackupTimestamp()}-${settingsFilename}`);
49872
+ backupPath = join33(settingsBackupDir, `${getBackupTimestamp()}-${settingsFilename}`);
50716
49873
  await copyFile2(settingsPath, backupPath);
50717
49874
  }
50718
49875
  const tempPath = `${settingsPath}.tmp-${Date.now()}`;
@@ -50749,13 +49906,13 @@ function countMcpServers(settings) {
50749
49906
  }
50750
49907
  var claudeDir, settingsFilename = "settings.json", settingsBackupDir;
50751
49908
  var init_settings_reader = __esm(() => {
50752
- claudeDir = join34(homedir18(), ".claude");
50753
- settingsBackupDir = join34(claudeDir, ".ck-backups", "settings");
49909
+ claudeDir = join33(homedir18(), ".claude");
49910
+ settingsBackupDir = join33(claudeDir, ".ck-backups", "settings");
50754
49911
  });
50755
49912
 
50756
49913
  // src/services/claude-data/hook-log-reader.ts
50757
49914
  import { open as open4 } from "node:fs/promises";
50758
- import { join as join35 } from "node:path";
49915
+ import { join as join34 } from "node:path";
50759
49916
  function createEmptySummary() {
50760
49917
  return {
50761
49918
  total: 0,
@@ -50803,9 +49960,9 @@ async function resolveProjectPath(projectId) {
50803
49960
  }
50804
49961
  function getHookLogPath(scope, basePath) {
50805
49962
  if (scope === "global") {
50806
- return join35(basePath, "hooks", ".logs", "hook-log.jsonl");
49963
+ return join34(basePath, "hooks", ".logs", "hook-log.jsonl");
50807
49964
  }
50808
- return join35(basePath, ".claude", "hooks", ".logs", "hook-log.jsonl");
49965
+ return join34(basePath, ".claude", "hooks", ".logs", "hook-log.jsonl");
50809
49966
  }
50810
49967
  function parseEntry(line) {
50811
49968
  try {
@@ -50916,409 +50073,1252 @@ var init_hook_log_reader = __esm(() => {
50916
50073
  };
50917
50074
  });
50918
50075
 
50919
- // src/services/claude-data/project-scanner.ts
50920
- import { homedir as homedir19 } from "node:os";
50921
- import { basename as basename10, join as join36, normalize as normalize4 } from "node:path";
50922
- function decodePath(encoded) {
50923
- const decoded = encoded.replace(/^-/, "/").replace(/-/g, "/");
50924
- const normalized = normalize4(decoded);
50925
- if (normalized.includes("..")) {
50926
- throw new Error(`Invalid path: contains traversal pattern - ${encoded}`);
50927
- }
50928
- return normalized;
50929
- }
50930
- function encodePath(path) {
50931
- return path.replace(/\//g, "-").replace(/^-/, "-");
50932
- }
50933
- var projectsDir;
50934
- var init_project_scanner = __esm(() => {
50935
- projectsDir = join36(homedir19(), ".claude", "projects");
50936
- });
50937
-
50938
- // src/services/claude-data/project-discovery.ts
50939
- import { constants, access } from "node:fs/promises";
50940
- import { basename as basename11 } from "node:path";
50941
- async function discoverProjectsFromHistory() {
50942
- const result = await parseHistoryFile();
50943
- return result.projects;
50944
- }
50945
- async function pathExists5(path) {
50946
- try {
50947
- await access(path, constants.F_OK);
50948
- return true;
50949
- } catch {
50950
- return false;
50076
+ // src/services/claude-data/project-scanner.ts
50077
+ import { homedir as homedir19 } from "node:os";
50078
+ import { basename as basename10, join as join35, normalize as normalize4 } from "node:path";
50079
+ function decodePath(encoded) {
50080
+ const decoded = encoded.replace(/^-/, "/").replace(/-/g, "/");
50081
+ const normalized = normalize4(decoded);
50082
+ if (normalized.includes("..")) {
50083
+ throw new Error(`Invalid path: contains traversal pattern - ${encoded}`);
50084
+ }
50085
+ return normalized;
50086
+ }
50087
+ function encodePath(path) {
50088
+ return path.replace(/\//g, "-").replace(/^-/, "-");
50089
+ }
50090
+ var projectsDir;
50091
+ var init_project_scanner = __esm(() => {
50092
+ projectsDir = join35(homedir19(), ".claude", "projects");
50093
+ });
50094
+
50095
+ // src/services/claude-data/project-discovery.ts
50096
+ import { constants, access } from "node:fs/promises";
50097
+ import { basename as basename11 } from "node:path";
50098
+ async function discoverProjectsFromHistory() {
50099
+ const result = await parseHistoryFile();
50100
+ return result.projects;
50101
+ }
50102
+ async function pathExists5(path) {
50103
+ try {
50104
+ await access(path, constants.F_OK);
50105
+ return true;
50106
+ } catch {
50107
+ return false;
50108
+ }
50109
+ }
50110
+ async function mergeProjectDiscovery(sessionProjects, filterNonExistent = false, historyPath) {
50111
+ const start = Date.now();
50112
+ let error;
50113
+ try {
50114
+ const historyResult = await parseHistoryFile(historyPath);
50115
+ if (historyResult.error) {
50116
+ error = historyResult.error;
50117
+ }
50118
+ const historyMap = new Map;
50119
+ for (const p of historyResult.projects) {
50120
+ historyMap.set(p.path, p);
50121
+ }
50122
+ const projectMap = new Map;
50123
+ for (const sp of sessionProjects) {
50124
+ const existing = projectMap.get(sp.path);
50125
+ if (!existing) {
50126
+ projectMap.set(sp.path, {
50127
+ path: sp.path,
50128
+ name: basename11(sp.path),
50129
+ lastUsed: sp.lastUsed ?? null,
50130
+ source: "session",
50131
+ exists: true
50132
+ });
50133
+ }
50134
+ }
50135
+ for (const [path, hp] of historyMap) {
50136
+ const existing = projectMap.get(path);
50137
+ if (existing) {
50138
+ existing.source = "both";
50139
+ existing.interactionCount = hp.interactionCount;
50140
+ if (hp.lastUsed > (existing.lastUsed ?? 0)) {
50141
+ existing.lastUsed = hp.lastUsed;
50142
+ }
50143
+ } else {
50144
+ projectMap.set(path, {
50145
+ path,
50146
+ name: basename11(path),
50147
+ lastUsed: hp.lastUsed,
50148
+ source: "history",
50149
+ exists: false,
50150
+ interactionCount: hp.interactionCount
50151
+ });
50152
+ }
50153
+ }
50154
+ const projects = Array.from(projectMap.values());
50155
+ await Promise.all(projects.filter((p) => p.source === "history").map(async (p) => {
50156
+ p.exists = await pathExists5(p.path);
50157
+ }));
50158
+ let result = projects;
50159
+ if (filterNonExistent) {
50160
+ result = result.filter((p) => p.exists);
50161
+ }
50162
+ result.sort((a, b) => (b.lastUsed ?? 0) - (a.lastUsed ?? 0));
50163
+ return {
50164
+ projects: result,
50165
+ totalFromSessions: sessionProjects.length,
50166
+ totalFromHistory: historyResult.projects.length,
50167
+ parseTimeMs: Date.now() - start,
50168
+ error
50169
+ };
50170
+ } catch (err) {
50171
+ const msg = err instanceof Error ? err.message : "Unknown error";
50172
+ return {
50173
+ projects: [],
50174
+ totalFromSessions: sessionProjects.length,
50175
+ totalFromHistory: 0,
50176
+ parseTimeMs: Date.now() - start,
50177
+ error: `Failed to merge project discovery: ${msg}`
50178
+ };
50179
+ }
50180
+ }
50181
+ var init_project_discovery = __esm(() => {
50182
+ init_history_parser();
50183
+ });
50184
+
50185
+ // src/services/claude-data/user-preferences.ts
50186
+ import { readFile as readFile15, stat as stat7 } from "node:fs/promises";
50187
+ import { homedir as homedir20 } from "node:os";
50188
+ import { join as join36 } from "node:path";
50189
+ function getPreferencesPath() {
50190
+ return join36(homedir20(), ".claude.json");
50191
+ }
50192
+ async function readUserPreferences(filePath) {
50193
+ const path = filePath ?? getPreferencesPath();
50194
+ const existingLock = locks2.get(path);
50195
+ if (existingLock)
50196
+ return existingLock;
50197
+ const start = Date.now();
50198
+ try {
50199
+ const { mtimeMs } = await stat7(path);
50200
+ const cached = cache2.get(path);
50201
+ if (cached && cached.mtime === mtimeMs) {
50202
+ return cached.result;
50203
+ }
50204
+ } catch {
50205
+ return emptyResult2(Date.now() - start, "Cannot read user preferences - file not found");
50206
+ }
50207
+ const parsePromise = (async () => {
50208
+ try {
50209
+ try {
50210
+ const content = await readFile15(path, "utf8");
50211
+ const raw = JSON.parse(content);
50212
+ const result = {
50213
+ preferences: UserPreferencesSchema.parse(raw),
50214
+ tipsHistory: TipsHistorySchema.parse(raw.tipsHistory ?? {}),
50215
+ featureFlags: {
50216
+ statsigGates: raw.cachedStatsigGates ?? {},
50217
+ growthBookFeatures: raw.cachedGrowthBookFeatures ?? {}
50218
+ },
50219
+ rawSize: Buffer.byteLength(content),
50220
+ parseTimeMs: Date.now() - start
50221
+ };
50222
+ const { mtimeMs } = await stat7(path);
50223
+ cache2.set(path, { mtime: mtimeMs, result });
50224
+ return result;
50225
+ } catch (err) {
50226
+ const errorMsg = err instanceof Error ? `Failed to parse user preferences: ${err.message}` : "Failed to parse user preferences";
50227
+ return emptyResult2(Date.now() - start, errorMsg);
50228
+ }
50229
+ } finally {
50230
+ locks2.delete(path);
50231
+ }
50232
+ })();
50233
+ locks2.set(path, parsePromise);
50234
+ return parsePromise;
50235
+ }
50236
+ function emptyResult2(parseTimeMs, error) {
50237
+ return {
50238
+ preferences: {},
50239
+ tipsHistory: {},
50240
+ featureFlags: { statsigGates: {}, growthBookFeatures: {} },
50241
+ rawSize: 0,
50242
+ parseTimeMs,
50243
+ error
50244
+ };
50245
+ }
50246
+ function getEffectiveTheme(prefs) {
50247
+ return prefs.preferences.theme ?? "dark";
50248
+ }
50249
+ function getUsageSummary(prefs) {
50250
+ const tipsCounts = Object.values(prefs.tipsHistory);
50251
+ return {
50252
+ totalSessions: prefs.preferences.numStartups ?? 0,
50253
+ promptQueueUsage: prefs.preferences.promptQueueUseCount ?? 0,
50254
+ firstUsed: prefs.preferences.firstStartTime ? new Date(prefs.preferences.firstStartTime) : null,
50255
+ tipsShown: tipsCounts.reduce((a, b) => a + b, 0)
50256
+ };
50257
+ }
50258
+ var cache2, locks2;
50259
+ var init_user_preferences = __esm(() => {
50260
+ init_types5();
50261
+ cache2 = new Map;
50262
+ locks2 = new Map;
50263
+ });
50264
+
50265
+ // src/services/claude-data/index.ts
50266
+ var init_claude_data = __esm(() => {
50267
+ init_types5();
50268
+ init_history_parser();
50269
+ init_session_parser();
50270
+ init_skill_scanner();
50271
+ init_settings_reader();
50272
+ init_hook_log_reader();
50273
+ init_project_scanner();
50274
+ init_project_discovery();
50275
+ init_user_preferences();
50276
+ });
50277
+
50278
+ // src/domains/web-server/routes/dashboard-routes.ts
50279
+ import { existsSync as existsSync19 } from "node:fs";
50280
+ import { readFile as readFile16, readdir as readdir11 } from "node:fs/promises";
50281
+ import { homedir as homedir21 } from "node:os";
50282
+ import { join as join37 } from "node:path";
50283
+ async function readAgents() {
50284
+ const agentsDir = join37(claudeDir2, "agents");
50285
+ if (!existsSync19(agentsDir))
50286
+ return [];
50287
+ try {
50288
+ const files = await readdir11(agentsDir);
50289
+ const mdFiles = files.filter((f) => f.endsWith(".md"));
50290
+ const agents = await Promise.all(mdFiles.map(async (file) => {
50291
+ try {
50292
+ const { frontmatter: fm } = await parseFrontmatterFile(join37(agentsDir, file));
50293
+ return {
50294
+ name: fm.name || file.replace(/\.md$/, ""),
50295
+ model: fm.model || "unset",
50296
+ description: fm.description || "",
50297
+ color: fm.color
50298
+ };
50299
+ } catch {
50300
+ return null;
50301
+ }
50302
+ }));
50303
+ return agents.filter((a) => a !== null);
50304
+ } catch {
50305
+ return [];
50306
+ }
50307
+ }
50308
+ async function countMdFilesRecursive(dir, depth = 0) {
50309
+ if (!existsSync19(dir) || depth > 10)
50310
+ return 0;
50311
+ try {
50312
+ const entries = await readdir11(dir, { withFileTypes: true });
50313
+ const counts = await Promise.all(entries.map(async (entry) => {
50314
+ if (entry.isDirectory()) {
50315
+ return countMdFilesRecursive(join37(dir, entry.name), depth + 1);
50316
+ }
50317
+ return entry.name.endsWith(".md") ? 1 : 0;
50318
+ }));
50319
+ return counts.reduce((sum, c) => sum + c, 0);
50320
+ } catch {
50321
+ return 0;
50322
+ }
50323
+ }
50324
+ async function countSkills2() {
50325
+ const skillsDir = join37(claudeDir2, "skills");
50326
+ if (!existsSync19(skillsDir))
50327
+ return 0;
50328
+ try {
50329
+ const entries = await readdir11(skillsDir);
50330
+ let count = 0;
50331
+ for (const entry of entries) {
50332
+ if (existsSync19(join37(skillsDir, entry, "SKILL.md")))
50333
+ count++;
50334
+ }
50335
+ return count;
50336
+ } catch {
50337
+ return 0;
50338
+ }
50339
+ }
50340
+ async function countAllMcpServers() {
50341
+ const settings = await readSettings();
50342
+ const baseCount = settings ? countMcpServers(settings) : 0;
50343
+ const seen = new Set;
50344
+ if (settings?.mcpServers && typeof settings.mcpServers === "object") {
50345
+ for (const name of Object.keys(settings.mcpServers)) {
50346
+ seen.add(name);
50347
+ }
50348
+ }
50349
+ let claudeJsonCount = 0;
50350
+ try {
50351
+ const claudeJsonPath = join37(homedir21(), ".claude.json");
50352
+ if (existsSync19(claudeJsonPath)) {
50353
+ const content = await readFile16(claudeJsonPath, "utf-8");
50354
+ const data = JSON.parse(content);
50355
+ if (data.mcpServers && typeof data.mcpServers === "object") {
50356
+ for (const name of Object.keys(data.mcpServers)) {
50357
+ if (!seen.has(name)) {
50358
+ seen.add(name);
50359
+ claudeJsonCount++;
50360
+ }
50361
+ }
50362
+ }
50363
+ }
50364
+ } catch {}
50365
+ let mcpJsonCount = 0;
50366
+ const mcpJsonPath = join37(claudeDir2, ".mcp.json");
50367
+ try {
50368
+ if (existsSync19(mcpJsonPath)) {
50369
+ const content = await readFile16(mcpJsonPath, "utf-8");
50370
+ const data = JSON.parse(content);
50371
+ if (data.mcpServers && typeof data.mcpServers === "object") {
50372
+ for (const name of Object.keys(data.mcpServers)) {
50373
+ if (!seen.has(name)) {
50374
+ seen.add(name);
50375
+ mcpJsonCount++;
50376
+ }
50377
+ }
50378
+ }
50379
+ }
50380
+ } catch {}
50381
+ return baseCount + claudeJsonCount + mcpJsonCount;
50382
+ }
50383
+ function classifyModel(model) {
50384
+ const lower = model.toLowerCase();
50385
+ if (lower === "unset" || !lower)
50386
+ return "unset";
50387
+ if (lower.includes("opus"))
50388
+ return "opus";
50389
+ if (lower.includes("haiku"))
50390
+ return "haiku";
50391
+ if (lower.includes("sonnet"))
50392
+ return "sonnet";
50393
+ return "unset";
50394
+ }
50395
+ function registerDashboardRoutes(app) {
50396
+ app.get("/api/dashboard/stats", async (_req, res) => {
50397
+ try {
50398
+ const [agents, commandCount, skillCount, mcpCount] = await Promise.all([
50399
+ readAgents(),
50400
+ countMdFilesRecursive(join37(claudeDir2, "commands")),
50401
+ countSkills2(),
50402
+ countAllMcpServers()
50403
+ ]);
50404
+ const modelDistribution = { opus: 0, sonnet: 0, haiku: 0, unset: 0 };
50405
+ for (const agent of agents) {
50406
+ const tier = classifyModel(agent.model);
50407
+ modelDistribution[tier]++;
50408
+ }
50409
+ res.json({
50410
+ agents: agents.length,
50411
+ commands: commandCount,
50412
+ skills: skillCount,
50413
+ mcpServers: mcpCount,
50414
+ modelDistribution
50415
+ });
50416
+ } catch (error) {
50417
+ res.status(500).json({ error: "Failed to load dashboard stats" });
50418
+ }
50419
+ });
50420
+ app.get("/api/agents/list", async (_req, res) => {
50421
+ try {
50422
+ const agents = await readAgents();
50423
+ res.json({ agents: agents.slice(0, 6) });
50424
+ } catch (error) {
50425
+ res.status(500).json({ error: "Failed to load agents" });
50426
+ }
50427
+ });
50428
+ app.get("/api/suggestions", async (_req, res) => {
50429
+ try {
50430
+ const agents = await readAgents();
50431
+ const suggestions = [];
50432
+ const unsetCount = agents.filter((a) => classifyModel(a.model) === "unset").length;
50433
+ if (unsetCount > 0) {
50434
+ suggestions.push({
50435
+ type: "warning",
50436
+ message: `${unsetCount} agent${unsetCount > 1 ? "s" : ""} have no model set`,
50437
+ target: "agents"
50438
+ });
50439
+ }
50440
+ const skillCount = await countSkills2();
50441
+ if (skillCount === 0) {
50442
+ suggestions.push({
50443
+ type: "info",
50444
+ message: "No skills installed — browse the Skills Marketplace",
50445
+ target: "skills"
50446
+ });
50447
+ }
50448
+ const settings = await readSettings();
50449
+ if (!settings) {
50450
+ suggestions.push({
50451
+ type: "warning",
50452
+ message: "No ~/.claude/settings.json found — run gck init to create one"
50453
+ });
50454
+ }
50455
+ if (agents.length === 0) {
50456
+ suggestions.push({
50457
+ type: "info",
50458
+ message: "No agents configured in ~/.claude/agents/",
50459
+ target: "agents"
50460
+ });
50461
+ }
50462
+ if (suggestions.length === 0) {
50463
+ suggestions.push({
50464
+ type: "success",
50465
+ message: "Everything looks good!"
50466
+ });
50467
+ }
50468
+ res.json({ suggestions });
50469
+ } catch (error) {
50470
+ res.status(500).json({ error: "Failed to load suggestions" });
50471
+ }
50472
+ });
50473
+ }
50474
+ var claudeDir2;
50475
+ var init_dashboard_routes = __esm(() => {
50476
+ init_frontmatter_parser();
50477
+ init_claude_data();
50478
+ claudeDir2 = join37(homedir21(), ".claude");
50479
+ });
50480
+
50481
+ // src/schemas/gck-config.schema.json
50482
+ var gck_config_schema_default;
50483
+ var init_gck_config_schema = __esm(() => {
50484
+ gck_config_schema_default = {
50485
+ $schema: "https://json-schema.org/draft-07/schema#",
50486
+ $id: "https://claudekit.cc/schemas/gck-config.schema.json",
50487
+ title: "ClaudeKit Configuration",
50488
+ description: "Configuration schema for ClaudeKit Engineer .ck.json files",
50489
+ type: "object",
50490
+ properties: {
50491
+ $schema: {
50492
+ type: "string",
50493
+ description: "JSON Schema reference for editor autocompletion"
50494
+ },
50495
+ codingLevel: {
50496
+ type: "integer",
50497
+ minimum: -1,
50498
+ maximum: 5,
50499
+ default: -1,
50500
+ description: "User coding experience level: -1=auto, 0=beginner, 1=intermediate, 2=advanced, 3=expert, 4=tech-lead, 5=god-mode"
50501
+ },
50502
+ privacyBlock: {
50503
+ type: "boolean",
50504
+ default: true,
50505
+ description: "Enable privacy block hook to protect sensitive files (.env, credentials, etc.)"
50506
+ },
50507
+ docs: {
50508
+ type: "object",
50509
+ description: "Documentation settings",
50510
+ properties: {
50511
+ maxLoc: {
50512
+ type: "integer",
50513
+ minimum: 100,
50514
+ maximum: 5000,
50515
+ default: 800,
50516
+ description: "Maximum lines of code per documentation file"
50517
+ }
50518
+ },
50519
+ additionalProperties: false
50520
+ },
50521
+ plan: {
50522
+ type: "object",
50523
+ description: "Plan naming and validation settings",
50524
+ properties: {
50525
+ namingFormat: {
50526
+ type: "string",
50527
+ default: "{date}-{issue}-{slug}",
50528
+ description: "Format for plan directory names. Variables: {date}, {issue}, {slug}"
50529
+ },
50530
+ dateFormat: {
50531
+ type: "string",
50532
+ default: "YYMMDD-HHmm",
50533
+ description: "Date format for plan names (moment.js format)"
50534
+ },
50535
+ issuePrefix: {
50536
+ type: "string",
50537
+ default: "GH-",
50538
+ description: "Prefix for issue references (e.g., GH-, JIRA-)"
50539
+ },
50540
+ reportsDir: {
50541
+ type: "string",
50542
+ default: "reports",
50543
+ description: "Subdirectory name for reports within plans"
50544
+ },
50545
+ resolution: {
50546
+ type: "object",
50547
+ description: "Plan resolution settings",
50548
+ properties: {
50549
+ order: {
50550
+ type: "array",
50551
+ items: {
50552
+ type: "string",
50553
+ enum: ["session", "branch", "directory"]
50554
+ },
50555
+ default: ["session", "branch"],
50556
+ description: "Order of resolution strategies for plan context"
50557
+ },
50558
+ branchPattern: {
50559
+ type: "string",
50560
+ default: "(?:feat|fix|chore|refactor|docs)/(?:[^/]+/)?(.+)",
50561
+ description: "Regex pattern to extract plan slug from branch name"
50562
+ }
50563
+ },
50564
+ additionalProperties: false
50565
+ },
50566
+ validation: {
50567
+ type: "object",
50568
+ description: "Plan validation settings",
50569
+ properties: {
50570
+ mode: {
50571
+ type: "string",
50572
+ enum: ["prompt", "auto", "strict", "none"],
50573
+ default: "prompt",
50574
+ description: "Validation mode: prompt=ask questions, auto=auto-validate, strict=require all, none=skip"
50575
+ },
50576
+ minQuestions: {
50577
+ type: "integer",
50578
+ minimum: 0,
50579
+ maximum: 20,
50580
+ default: 3,
50581
+ description: "Minimum validation questions to ask"
50582
+ },
50583
+ maxQuestions: {
50584
+ type: "integer",
50585
+ minimum: 1,
50586
+ maximum: 20,
50587
+ default: 8,
50588
+ description: "Maximum validation questions to ask"
50589
+ },
50590
+ focusAreas: {
50591
+ type: "array",
50592
+ items: {
50593
+ type: "string",
50594
+ enum: [
50595
+ "assumptions",
50596
+ "risks",
50597
+ "tradeoffs",
50598
+ "architecture",
50599
+ "security",
50600
+ "performance",
50601
+ "testing",
50602
+ "dependencies"
50603
+ ]
50604
+ },
50605
+ default: ["assumptions", "risks", "tradeoffs", "architecture"],
50606
+ description: "Focus areas for validation questions"
50607
+ }
50608
+ },
50609
+ additionalProperties: false
50610
+ }
50611
+ },
50612
+ additionalProperties: false
50613
+ },
50614
+ paths: {
50615
+ type: "object",
50616
+ description: "Directory paths configuration",
50617
+ properties: {
50618
+ docs: {
50619
+ type: "string",
50620
+ default: "docs",
50621
+ description: "Path to documentation directory"
50622
+ },
50623
+ plans: {
50624
+ type: "string",
50625
+ default: "plans",
50626
+ description: "Path to plans directory"
50627
+ },
50628
+ globalPlans: {
50629
+ type: "string",
50630
+ default: "plans",
50631
+ description: "Path to global plans directory (relative to ~/.claude or absolute)"
50632
+ }
50633
+ },
50634
+ additionalProperties: false
50635
+ },
50636
+ locale: {
50637
+ type: "object",
50638
+ description: "Language and locale settings",
50639
+ properties: {
50640
+ thinkingLanguage: {
50641
+ type: ["string", "null"],
50642
+ default: null,
50643
+ description: "Language for Claude's thinking (null=English)"
50644
+ },
50645
+ responseLanguage: {
50646
+ type: ["string", "null"],
50647
+ default: null,
50648
+ description: "Language for Claude's responses (null=match user)"
50649
+ }
50650
+ },
50651
+ additionalProperties: false
50652
+ },
50653
+ trust: {
50654
+ type: "object",
50655
+ description: "Trust mode settings for auto-approval",
50656
+ properties: {
50657
+ passphrase: {
50658
+ type: ["string", "null"],
50659
+ default: null,
50660
+ description: "Passphrase to enable trust mode (null=disabled)"
50661
+ },
50662
+ enabled: {
50663
+ type: "boolean",
50664
+ default: false,
50665
+ description: "Whether trust mode is currently enabled"
50666
+ }
50667
+ },
50668
+ additionalProperties: false
50669
+ },
50670
+ project: {
50671
+ type: "object",
50672
+ description: "Project type detection settings",
50673
+ properties: {
50674
+ type: {
50675
+ type: "string",
50676
+ enum: ["auto", "library", "application", "monorepo", "cli", "api", "web", "mobile"],
50677
+ default: "auto",
50678
+ description: "Project type (auto=detect from package.json)"
50679
+ },
50680
+ packageManager: {
50681
+ type: "string",
50682
+ enum: ["auto", "npm", "yarn", "pnpm", "bun"],
50683
+ default: "auto",
50684
+ description: "Package manager (auto=detect from lockfile)"
50685
+ },
50686
+ framework: {
50687
+ type: "string",
50688
+ enum: [
50689
+ "auto",
50690
+ "react",
50691
+ "vue",
50692
+ "angular",
50693
+ "svelte",
50694
+ "nextjs",
50695
+ "nuxt",
50696
+ "express",
50697
+ "nestjs",
50698
+ "fastify",
50699
+ "none"
50700
+ ],
50701
+ default: "auto",
50702
+ description: "Framework (auto=detect from dependencies)"
50703
+ }
50704
+ },
50705
+ additionalProperties: false
50706
+ },
50707
+ gemini: {
50708
+ type: "object",
50709
+ description: "Gemini CLI configuration",
50710
+ properties: {
50711
+ model: {
50712
+ type: "string",
50713
+ enum: [
50714
+ "gemini-2.5-flash",
50715
+ "gemini-2.5-pro",
50716
+ "gemini-3-pro-preview",
50717
+ "gemini-3-flash-preview"
50718
+ ],
50719
+ default: "gemini-3-flash-preview",
50720
+ description: "Gemini model to use for CLI commands. Legacy gemini-3.0-* ids are auto-mapped to the current preview aliases when loading config."
50721
+ }
50722
+ },
50723
+ additionalProperties: false
50724
+ },
50725
+ skills: {
50726
+ type: "object",
50727
+ description: "Per-skill configuration",
50728
+ properties: {
50729
+ research: {
50730
+ type: "object",
50731
+ properties: {
50732
+ useGemini: {
50733
+ type: "boolean",
50734
+ default: true,
50735
+ description: "Use Gemini CLI for research (false=use WebSearch)"
50736
+ }
50737
+ },
50738
+ additionalProperties: true
50739
+ }
50740
+ },
50741
+ additionalProperties: {
50742
+ type: "object",
50743
+ description: "Custom skill configuration"
50744
+ }
50745
+ },
50746
+ assertions: {
50747
+ type: "array",
50748
+ items: {
50749
+ type: "object",
50750
+ properties: {
50751
+ pattern: {
50752
+ type: "string",
50753
+ description: "Glob pattern or regex for files to match"
50754
+ },
50755
+ rule: {
50756
+ type: "string",
50757
+ description: "Assertion rule to enforce"
50758
+ },
50759
+ message: {
50760
+ type: "string",
50761
+ description: "Error message when assertion fails"
50762
+ }
50763
+ }
50764
+ },
50765
+ default: [],
50766
+ description: "Code assertions and rules to enforce"
50767
+ },
50768
+ statusline: {
50769
+ type: "string",
50770
+ enum: ["full", "compact", "minimal", "none"],
50771
+ default: "full",
50772
+ description: "Statusline display mode: full=all features, compact=2-3 lines, minimal=single text line, none=disabled"
50773
+ },
50774
+ statuslineColors: {
50775
+ type: "boolean",
50776
+ default: true,
50777
+ description: "Enable ANSI colors in statusline output. Set false for plain text. Respects NO_COLOR/FORCE_COLOR env vars."
50778
+ },
50779
+ statuslineQuota: {
50780
+ type: "boolean",
50781
+ default: true,
50782
+ description: "Show the cosmetic 5h / wk quota chips in the statusline. Disable to keep the statusline layout without usage-window percentages."
50783
+ },
50784
+ statuslineLayout: {
50785
+ type: "object",
50786
+ description: "Advanced statusline layout config for the visual builder. When absent, uses hardcoded defaults (backward compatible). Takes precedence over the statusline field when both are set.",
50787
+ properties: {
50788
+ baseMode: {
50789
+ type: "string",
50790
+ enum: ["full", "compact", "minimal", "none"],
50791
+ default: "full",
50792
+ description: "Starting template mode for the layout"
50793
+ },
50794
+ lines: {
50795
+ type: "array",
50796
+ description: "Layout lines. Each inner array is one terminal row containing section IDs in left-to-right order. Sections not listed in any line are hidden.",
50797
+ items: {
50798
+ type: "array",
50799
+ items: {
50800
+ type: "string",
50801
+ enum: [
50802
+ "model",
50803
+ "context",
50804
+ "quota",
50805
+ "directory",
50806
+ "git",
50807
+ "cost",
50808
+ "changes",
50809
+ "agents",
50810
+ "todos"
50811
+ ]
50812
+ }
50813
+ },
50814
+ maxItems: 10
50815
+ },
50816
+ sectionConfig: {
50817
+ type: "object",
50818
+ description: "Per-section visual customization keyed by section ID.",
50819
+ additionalProperties: {
50820
+ type: "object",
50821
+ properties: {
50822
+ icon: {
50823
+ type: "string",
50824
+ description: "Custom emoji/icon override",
50825
+ maxLength: 20
50826
+ },
50827
+ label: {
50828
+ type: "string",
50829
+ description: "Custom label override",
50830
+ maxLength: 50
50831
+ },
50832
+ color: {
50833
+ type: "string",
50834
+ description: "ANSI named color (e.g. red, cyan, green). Hex codes not supported.",
50835
+ maxLength: 30,
50836
+ pattern: "^[a-zA-Z]+$"
50837
+ },
50838
+ maxWidth: {
50839
+ type: "integer",
50840
+ minimum: 10,
50841
+ description: "Maximum character width for this section",
50842
+ maximum: 500
50843
+ }
50844
+ },
50845
+ additionalProperties: false
50846
+ }
50847
+ },
50848
+ theme: {
50849
+ type: "object",
50850
+ description: "Color theme overrides",
50851
+ properties: {
50852
+ name: {
50853
+ type: "string",
50854
+ description: "Theme name (informational)",
50855
+ maxLength: 50
50856
+ },
50857
+ contextLow: {
50858
+ type: "string",
50859
+ default: "green",
50860
+ description: "Context bar color when usage <50%",
50861
+ maxLength: 30,
50862
+ pattern: "^[a-zA-Z]+$"
50863
+ },
50864
+ contextMid: {
50865
+ type: "string",
50866
+ default: "yellow",
50867
+ description: "Context bar color when usage 50-75%",
50868
+ maxLength: 30,
50869
+ pattern: "^[a-zA-Z]+$"
50870
+ },
50871
+ contextHigh: {
50872
+ type: "string",
50873
+ default: "red",
50874
+ description: "Context bar color when usage >75%",
50875
+ maxLength: 30,
50876
+ pattern: "^[a-zA-Z]+$"
50877
+ },
50878
+ accent: {
50879
+ type: "string",
50880
+ default: "cyan",
50881
+ description: "Accent color for highlights",
50882
+ maxLength: 30,
50883
+ pattern: "^[a-zA-Z]+$"
50884
+ },
50885
+ muted: {
50886
+ type: "string",
50887
+ default: "dim",
50888
+ description: "Muted text color",
50889
+ maxLength: 30,
50890
+ pattern: "^[a-zA-Z]+$"
50891
+ },
50892
+ separator: {
50893
+ type: "string",
50894
+ default: "dim",
50895
+ description: "Separator color",
50896
+ maxLength: 30,
50897
+ pattern: "^[a-zA-Z]+$"
50898
+ },
50899
+ quotaLow: {
50900
+ type: "string",
50901
+ description: "Quota section color when all windows <85%",
50902
+ maxLength: 30,
50903
+ pattern: "^[a-zA-Z]+$"
50904
+ },
50905
+ quotaHigh: {
50906
+ type: "string",
50907
+ description: "Quota section color when any window >=85%",
50908
+ maxLength: 30,
50909
+ pattern: "^[a-zA-Z]+$"
50910
+ }
50911
+ },
50912
+ additionalProperties: false
50913
+ },
50914
+ responsiveBreakpoint: {
50915
+ type: "number",
50916
+ minimum: 0.5,
50917
+ maximum: 1,
50918
+ default: 0.85,
50919
+ description: "Terminal width fraction at which compact mode activates"
50920
+ },
50921
+ maxAgentRows: {
50922
+ type: "integer",
50923
+ minimum: 1,
50924
+ maximum: 10,
50925
+ default: 4,
50926
+ description: "Maximum rows to show in agents section"
50927
+ },
50928
+ todoTruncation: {
50929
+ type: "integer",
50930
+ minimum: 20,
50931
+ maximum: 100,
50932
+ default: 50,
50933
+ description: "Max characters before truncating todo text"
50934
+ }
50935
+ },
50936
+ additionalProperties: false
50937
+ },
50938
+ hooks: {
50939
+ type: "object",
50940
+ description: "Toggle individual hooks on/off. Default: all enabled.",
50941
+ properties: {
50942
+ "session-init": {
50943
+ type: "boolean",
50944
+ default: true,
50945
+ description: "SessionStart hook - project detection and env setup"
50946
+ },
50947
+ "subagent-init": {
50948
+ type: "boolean",
50949
+ default: true,
50950
+ description: "SubagentStart hook - injects context to subagents"
50951
+ },
50952
+ "session-state": {
50953
+ type: "boolean",
50954
+ default: true,
50955
+ description: "Stop/SubagentStop/PostToolUse hook - persists session state for handoff and statusline context"
50956
+ },
50957
+ "cook-after-plan-reminder": {
50958
+ type: "boolean",
50959
+ default: true,
50960
+ description: "SubagentStop hook - reminds agents to continue implementation after planning"
50961
+ },
50962
+ "descriptive-name": {
50963
+ type: "boolean",
50964
+ default: true,
50965
+ description: "PreToolUse hook - injects language-aware descriptive file naming guidance"
50966
+ },
50967
+ "dev-rules-reminder": {
50968
+ type: "boolean",
50969
+ default: true,
50970
+ description: "UserPromptSubmit hook - injects dev rules context"
50971
+ },
50972
+ "plan-format-kanban": {
50973
+ type: "boolean",
50974
+ default: true,
50975
+ description: "PostToolUse hook - keeps plan kanban metadata synchronized after edits"
50976
+ },
50977
+ "usage-quota-cache-refresh": {
50978
+ type: "boolean",
50979
+ default: true,
50980
+ description: "Lifecycle hook - refreshes cached usage quota information"
50981
+ },
50982
+ "usage-context-awareness": {
50983
+ type: "boolean",
50984
+ default: true,
50985
+ description: "UserPromptSubmit/PostToolUse hook - inject usage-limit awareness into prompt context"
50986
+ },
50987
+ "context-tracking": {
50988
+ type: "boolean",
50989
+ default: true,
50990
+ description: "Context window tracking injection (% used, token counts) in dev-rules-reminder"
50991
+ },
50992
+ "scout-block": {
50993
+ type: "boolean",
50994
+ default: true,
50995
+ description: "PreToolUse hook - blocks heavy directories"
50996
+ },
50997
+ "privacy-block": {
50998
+ type: "boolean",
50999
+ default: true,
51000
+ description: "PreToolUse hook - blocks sensitive files"
51001
+ },
51002
+ "simplify-gate": {
51003
+ type: "boolean",
51004
+ default: true,
51005
+ description: "UserPromptSubmit hook - hard-blocks ship/merge/pr/deploy/publish and soft-warns commit/finalize/release when live diff exceeds simplify thresholds. Bypass via CK_SIMPLIFY_DISABLED=1."
51006
+ }
51007
+ },
51008
+ additionalProperties: false
51009
+ },
51010
+ simplify: {
51011
+ type: "object",
51012
+ description: "Signal-driven simplifier orchestration. Live git diff thresholds + verb-based gate.",
51013
+ properties: {
51014
+ threshold: {
51015
+ type: "object",
51016
+ description: "LOC/file thresholds that trip the gate. Nested object stays open for forward-compat fields.",
51017
+ properties: {
51018
+ locDelta: {
51019
+ type: "integer",
51020
+ minimum: 0,
51021
+ default: 400,
51022
+ description: "Total LOC change (across all files) above which the gate fires."
51023
+ },
51024
+ fileCount: {
51025
+ type: "integer",
51026
+ minimum: 0,
51027
+ default: 8,
51028
+ description: "Number of changed files above which the gate fires."
51029
+ },
51030
+ singleFileLoc: {
51031
+ type: "integer",
51032
+ minimum: 0,
51033
+ default: 200,
51034
+ description: "Per-file LOC change above which the gate fires."
51035
+ }
51036
+ }
51037
+ },
51038
+ gate: {
51039
+ type: "object",
51040
+ description: "UserPromptSubmit gate behavior. Nested object stays open for forward-compat fields.",
51041
+ properties: {
51042
+ enabled: {
51043
+ type: "boolean",
51044
+ default: false,
51045
+ description: "Master toggle. Off by default (opt-in). Set true to activate; env CK_SIMPLIFY_DISABLED=1 always bypasses."
51046
+ },
51047
+ hardVerbs: {
51048
+ type: "array",
51049
+ items: { type: "string" },
51050
+ default: ["ship", "merge", "pr", "deploy", "publish"],
51051
+ description: "Verbs that hard-block (continue:false) when thresholds breached."
51052
+ },
51053
+ softVerbs: {
51054
+ type: "array",
51055
+ items: { type: "string" },
51056
+ default: ["commit", "finalize", "release"],
51057
+ description: "Verbs that soft-warn (additionalContext) when thresholds breached."
51058
+ }
51059
+ }
51060
+ }
51061
+ },
51062
+ additionalProperties: false
51063
+ },
51064
+ updatePipeline: {
51065
+ type: "object",
51066
+ description: "3-step update pipeline: CLI update -> kit init -> provider migrate (each independent)",
51067
+ properties: {
51068
+ autoInitAfterUpdate: {
51069
+ type: "boolean",
51070
+ default: false,
51071
+ description: "Automatically run ck init after ck update when kit has new version"
51072
+ },
51073
+ autoMigrateAfterUpdate: {
51074
+ type: "boolean",
51075
+ default: false,
51076
+ description: "Automatically run ck migrate after ck update for detected providers (independent of init)"
51077
+ },
51078
+ migrateProviders: {
51079
+ oneOf: [
51080
+ {
51081
+ type: "string",
51082
+ const: "auto"
51083
+ },
51084
+ {
51085
+ type: "array",
51086
+ items: {
51087
+ type: "string"
51088
+ }
51089
+ }
51090
+ ],
51091
+ default: "auto",
51092
+ description: "Which providers to auto-migrate. 'auto' scans filesystem."
51093
+ }
51094
+ },
51095
+ additionalProperties: false
51096
+ },
51097
+ modelTaxonomy: {
51098
+ type: "object",
51099
+ description: "Custom model mappings for portable migration. Keys are provider names (e.g., 'codex', 'gemini-cli'), values map tiers to model config.",
51100
+ additionalProperties: {
51101
+ type: "object",
51102
+ properties: {
51103
+ heavy: {
51104
+ type: "object",
51105
+ properties: {
51106
+ model: {
51107
+ type: "string",
51108
+ description: "Target model name"
51109
+ },
51110
+ effort: {
51111
+ type: "string",
51112
+ description: "Effort/reasoning level (provider-specific)"
51113
+ }
51114
+ },
51115
+ required: ["model"]
51116
+ },
51117
+ balanced: {
51118
+ type: "object",
51119
+ properties: {
51120
+ model: {
51121
+ type: "string",
51122
+ description: "Target model name"
51123
+ },
51124
+ effort: {
51125
+ type: "string",
51126
+ description: "Effort/reasoning level (provider-specific)"
51127
+ }
51128
+ },
51129
+ required: ["model"]
51130
+ },
51131
+ light: {
51132
+ type: "object",
51133
+ properties: {
51134
+ model: {
51135
+ type: "string",
51136
+ description: "Target model name"
51137
+ },
51138
+ effort: {
51139
+ type: "string",
51140
+ description: "Effort/reasoning level (provider-specific)"
51141
+ }
51142
+ },
51143
+ required: ["model"]
51144
+ }
51145
+ }
51146
+ }
51147
+ }
51148
+ },
51149
+ additionalProperties: true
51150
+ };
51151
+ });
51152
+
51153
+ // src/domains/web-server/routes/gck-config-routes.ts
51154
+ import { existsSync as existsSync20 } from "node:fs";
51155
+ import { readFile as readFile17 } from "node:fs/promises";
51156
+ import { join as join38 } from "node:path";
51157
+ async function resolveProjectDir(projectId) {
51158
+ if (!projectId)
51159
+ return null;
51160
+ if (projectId.startsWith("discovered-")) {
51161
+ const encodedPath = projectId.slice("discovered-".length);
51162
+ return Buffer.from(encodedPath, "base64url").toString("utf-8");
50951
51163
  }
51164
+ await Promise.resolve().then(() => init_projects_registry());
51165
+ const project = await ProjectsRegistryManager.getProject(projectId);
51166
+ return project?.path ?? null;
50952
51167
  }
50953
- async function mergeProjectDiscovery(sessionProjects, filterNonExistent = false, historyPath) {
50954
- const start = Date.now();
50955
- let error;
50956
- try {
50957
- const historyResult = await parseHistoryFile(historyPath);
50958
- if (historyResult.error) {
50959
- error = historyResult.error;
50960
- }
50961
- const historyMap = new Map;
50962
- for (const p of historyResult.projects) {
50963
- historyMap.set(p.path, p);
50964
- }
50965
- const projectMap = new Map;
50966
- for (const sp of sessionProjects) {
50967
- const existing = projectMap.get(sp.path);
50968
- if (!existing) {
50969
- projectMap.set(sp.path, {
50970
- path: sp.path,
50971
- name: basename11(sp.path),
50972
- lastUsed: sp.lastUsed ?? null,
50973
- source: "session",
50974
- exists: true
51168
+ function registerCkConfigRoutes(app) {
51169
+ app.get("/api/gck-config", async (req, res) => {
51170
+ try {
51171
+ const { projectId, scope = "merged" } = req.query;
51172
+ const projectDir = await resolveProjectDir(projectId);
51173
+ if (scope === "global") {
51174
+ const config = await CkConfigManager.loadScope("global", null);
51175
+ res.json({
51176
+ config: config || {},
51177
+ sources: {},
51178
+ globalPath: CkConfigManager.getGlobalConfigPath(),
51179
+ projectPath: null
50975
51180
  });
51181
+ return;
50976
51182
  }
50977
- }
50978
- for (const [path, hp] of historyMap) {
50979
- const existing = projectMap.get(path);
50980
- if (existing) {
50981
- existing.source = "both";
50982
- existing.interactionCount = hp.interactionCount;
50983
- if (hp.lastUsed > (existing.lastUsed ?? 0)) {
50984
- existing.lastUsed = hp.lastUsed;
51183
+ if (scope === "project") {
51184
+ if (!projectDir) {
51185
+ res.status(400).json({ error: "projectId required for project scope" });
51186
+ return;
50985
51187
  }
50986
- } else {
50987
- projectMap.set(path, {
50988
- path,
50989
- name: basename11(path),
50990
- lastUsed: hp.lastUsed,
50991
- source: "history",
50992
- exists: false,
50993
- interactionCount: hp.interactionCount
51188
+ const config = await CkConfigManager.loadScope("project", projectDir);
51189
+ res.json({
51190
+ config: config || {},
51191
+ sources: {},
51192
+ globalPath: CkConfigManager.getGlobalConfigPath(),
51193
+ projectPath: CkConfigManager.getProjectConfigPath(projectDir)
50994
51194
  });
51195
+ return;
50995
51196
  }
51197
+ const result = await CkConfigManager.loadFull(projectDir);
51198
+ res.json(result);
51199
+ } catch (error) {
51200
+ logger.error(`Failed to load gck-config: ${error}`);
51201
+ res.status(500).json({ error: "Failed to load configuration" });
50996
51202
  }
50997
- const projects = Array.from(projectMap.values());
50998
- await Promise.all(projects.filter((p) => p.source === "history").map(async (p) => {
50999
- p.exists = await pathExists5(p.path);
51000
- }));
51001
- let result = projects;
51002
- if (filterNonExistent) {
51003
- result = result.filter((p) => p.exists);
51004
- }
51005
- result.sort((a, b) => (b.lastUsed ?? 0) - (a.lastUsed ?? 0));
51006
- return {
51007
- projects: result,
51008
- totalFromSessions: sessionProjects.length,
51009
- totalFromHistory: historyResult.projects.length,
51010
- parseTimeMs: Date.now() - start,
51011
- error
51012
- };
51013
- } catch (err) {
51014
- const msg = err instanceof Error ? err.message : "Unknown error";
51015
- return {
51016
- projects: [],
51017
- totalFromSessions: sessionProjects.length,
51018
- totalFromHistory: 0,
51019
- parseTimeMs: Date.now() - start,
51020
- error: `Failed to merge project discovery: ${msg}`
51021
- };
51022
- }
51023
- }
51024
- var init_project_discovery = __esm(() => {
51025
- init_history_parser();
51026
- });
51027
-
51028
- // src/services/claude-data/user-preferences.ts
51029
- import { readFile as readFile16, stat as stat7 } from "node:fs/promises";
51030
- import { homedir as homedir20 } from "node:os";
51031
- import { join as join37 } from "node:path";
51032
- function getPreferencesPath() {
51033
- return join37(homedir20(), ".claude.json");
51034
- }
51035
- async function readUserPreferences(filePath) {
51036
- const path = filePath ?? getPreferencesPath();
51037
- const existingLock = locks2.get(path);
51038
- if (existingLock)
51039
- return existingLock;
51040
- const start = Date.now();
51041
- try {
51042
- const { mtimeMs } = await stat7(path);
51043
- const cached = cache2.get(path);
51044
- if (cached && cached.mtime === mtimeMs) {
51045
- return cached.result;
51046
- }
51047
- } catch {
51048
- return emptyResult2(Date.now() - start, "Cannot read user preferences - file not found");
51049
- }
51050
- const parsePromise = (async () => {
51203
+ });
51204
+ app.put("/api/gck-config", async (req, res) => {
51051
51205
  try {
51052
- try {
51053
- const content = await readFile16(path, "utf8");
51054
- const raw = JSON.parse(content);
51055
- const result = {
51056
- preferences: UserPreferencesSchema.parse(raw),
51057
- tipsHistory: TipsHistorySchema.parse(raw.tipsHistory ?? {}),
51058
- featureFlags: {
51059
- statsigGates: raw.cachedStatsigGates ?? {},
51060
- growthBookFeatures: raw.cachedGrowthBookFeatures ?? {}
51061
- },
51062
- rawSize: Buffer.byteLength(content),
51063
- parseTimeMs: Date.now() - start
51064
- };
51065
- const { mtimeMs } = await stat7(path);
51066
- cache2.set(path, { mtime: mtimeMs, result });
51067
- return result;
51068
- } catch (err) {
51069
- const errorMsg = err instanceof Error ? `Failed to parse user preferences: ${err.message}` : "Failed to parse user preferences";
51070
- return emptyResult2(Date.now() - start, errorMsg);
51206
+ const { scope, projectId, config } = req.body;
51207
+ if (!scope || !["global", "project"].includes(scope)) {
51208
+ res.status(400).json({ error: "Invalid scope. Must be 'global' or 'project'" });
51209
+ return;
51071
51210
  }
51072
- } finally {
51073
- locks2.delete(path);
51074
- }
51075
- })();
51076
- locks2.set(path, parsePromise);
51077
- return parsePromise;
51078
- }
51079
- function emptyResult2(parseTimeMs, error) {
51080
- return {
51081
- preferences: {},
51082
- tipsHistory: {},
51083
- featureFlags: { statsigGates: {}, growthBookFeatures: {} },
51084
- rawSize: 0,
51085
- parseTimeMs,
51086
- error
51087
- };
51088
- }
51089
- function getEffectiveTheme(prefs) {
51090
- return prefs.preferences.theme ?? "dark";
51091
- }
51092
- function getUsageSummary(prefs) {
51093
- const tipsCounts = Object.values(prefs.tipsHistory);
51094
- return {
51095
- totalSessions: prefs.preferences.numStartups ?? 0,
51096
- promptQueueUsage: prefs.preferences.promptQueueUseCount ?? 0,
51097
- firstUsed: prefs.preferences.firstStartTime ? new Date(prefs.preferences.firstStartTime) : null,
51098
- tipsShown: tipsCounts.reduce((a, b) => a + b, 0)
51099
- };
51100
- }
51101
- var cache2, locks2;
51102
- var init_user_preferences = __esm(() => {
51103
- init_types5();
51104
- cache2 = new Map;
51105
- locks2 = new Map;
51106
- });
51107
-
51108
- // src/services/claude-data/index.ts
51109
- var init_claude_data = __esm(() => {
51110
- init_types5();
51111
- init_history_parser();
51112
- init_session_parser();
51113
- init_skill_scanner();
51114
- init_settings_reader();
51115
- init_hook_log_reader();
51116
- init_project_scanner();
51117
- init_project_discovery();
51118
- init_user_preferences();
51119
- });
51120
-
51121
- // src/domains/web-server/routes/dashboard-routes.ts
51122
- import { existsSync as existsSync20 } from "node:fs";
51123
- import { readFile as readFile17, readdir as readdir11 } from "node:fs/promises";
51124
- import { homedir as homedir21 } from "node:os";
51125
- import { join as join38 } from "node:path";
51126
- async function readAgents() {
51127
- const agentsDir = join38(claudeDir2, "agents");
51128
- if (!existsSync20(agentsDir))
51129
- return [];
51130
- try {
51131
- const files = await readdir11(agentsDir);
51132
- const mdFiles = files.filter((f) => f.endsWith(".md"));
51133
- const agents = await Promise.all(mdFiles.map(async (file) => {
51134
- try {
51135
- const { frontmatter: fm } = await parseFrontmatterFile(join38(agentsDir, file));
51136
- return {
51137
- name: fm.name || file.replace(/\.md$/, ""),
51138
- model: fm.model || "unset",
51139
- description: fm.description || "",
51140
- color: fm.color
51141
- };
51142
- } catch {
51143
- return null;
51211
+ if (!config || typeof config !== "object") {
51212
+ res.status(400).json({ error: "Invalid config payload" });
51213
+ return;
51144
51214
  }
51145
- }));
51146
- return agents.filter((a) => a !== null);
51147
- } catch {
51148
- return [];
51149
- }
51150
- }
51151
- async function countMdFilesRecursive(dir, depth = 0) {
51152
- if (!existsSync20(dir) || depth > 10)
51153
- return 0;
51154
- try {
51155
- const entries = await readdir11(dir, { withFileTypes: true });
51156
- const counts = await Promise.all(entries.map(async (entry) => {
51157
- if (entry.isDirectory()) {
51158
- return countMdFilesRecursive(join38(dir, entry.name), depth + 1);
51215
+ const parseResult = CkConfigSchema.safeParse(normalizeCkConfigInput(config));
51216
+ if (!parseResult.success) {
51217
+ res.status(400).json({
51218
+ error: "Config validation failed",
51219
+ details: parseResult.error.issues
51220
+ });
51221
+ return;
51159
51222
  }
51160
- return entry.name.endsWith(".md") ? 1 : 0;
51161
- }));
51162
- return counts.reduce((sum, c) => sum + c, 0);
51163
- } catch {
51164
- return 0;
51165
- }
51166
- }
51167
- async function countSkills2() {
51168
- const skillsDir = join38(claudeDir2, "skills");
51169
- if (!existsSync20(skillsDir))
51170
- return 0;
51171
- try {
51172
- const entries = await readdir11(skillsDir);
51173
- let count = 0;
51174
- for (const entry of entries) {
51175
- if (existsSync20(join38(skillsDir, entry, "SKILL.md")))
51176
- count++;
51177
- }
51178
- return count;
51179
- } catch {
51180
- return 0;
51181
- }
51182
- }
51183
- async function countAllMcpServers() {
51184
- const settings = await readSettings();
51185
- const baseCount = settings ? countMcpServers(settings) : 0;
51186
- const seen = new Set;
51187
- if (settings?.mcpServers && typeof settings.mcpServers === "object") {
51188
- for (const name of Object.keys(settings.mcpServers)) {
51189
- seen.add(name);
51190
- }
51191
- }
51192
- let claudeJsonCount = 0;
51193
- try {
51194
- const claudeJsonPath = join38(homedir21(), ".claude.json");
51195
- if (existsSync20(claudeJsonPath)) {
51196
- const content = await readFile17(claudeJsonPath, "utf-8");
51197
- const data = JSON.parse(content);
51198
- if (data.mcpServers && typeof data.mcpServers === "object") {
51199
- for (const name of Object.keys(data.mcpServers)) {
51200
- if (!seen.has(name)) {
51201
- seen.add(name);
51202
- claudeJsonCount++;
51203
- }
51223
+ let projectDir = null;
51224
+ if (scope === "project") {
51225
+ if (!projectId) {
51226
+ res.status(400).json({ error: "projectId required for project scope" });
51227
+ return;
51228
+ }
51229
+ projectDir = await resolveProjectDir(projectId);
51230
+ if (!projectDir) {
51231
+ res.status(404).json({ error: "Project not found" });
51232
+ return;
51204
51233
  }
51205
51234
  }
51206
- }
51207
- } catch {}
51208
- let mcpJsonCount = 0;
51209
- const mcpJsonPath = join38(claudeDir2, ".mcp.json");
51210
- try {
51211
- if (existsSync20(mcpJsonPath)) {
51212
- const content = await readFile17(mcpJsonPath, "utf-8");
51213
- const data = JSON.parse(content);
51214
- if (data.mcpServers && typeof data.mcpServers === "object") {
51215
- for (const name of Object.keys(data.mcpServers)) {
51216
- if (!seen.has(name)) {
51217
- seen.add(name);
51218
- mcpJsonCount++;
51235
+ const savedPath = await CkConfigManager.saveFull(parseResult.data, scope, projectDir);
51236
+ let savedConfig = await CkConfigManager.loadScope(scope, projectDir);
51237
+ if (!savedConfig) {
51238
+ try {
51239
+ const rawContent = await readFile17(savedPath, "utf-8");
51240
+ const rawConfig = JSON.parse(rawContent);
51241
+ if (rawConfig && typeof rawConfig === "object" && !Array.isArray(rawConfig)) {
51242
+ savedConfig = normalizeCkConfigInput(rawConfig);
51219
51243
  }
51244
+ } catch {
51245
+ savedConfig = null;
51220
51246
  }
51221
51247
  }
51222
- }
51223
- } catch {}
51224
- return baseCount + claudeJsonCount + mcpJsonCount;
51225
- }
51226
- function classifyModel(model) {
51227
- const lower = model.toLowerCase();
51228
- if (lower === "unset" || !lower)
51229
- return "unset";
51230
- if (lower.includes("opus"))
51231
- return "opus";
51232
- if (lower.includes("haiku"))
51233
- return "haiku";
51234
- if (lower.includes("sonnet"))
51235
- return "sonnet";
51236
- return "unset";
51237
- }
51238
- function registerDashboardRoutes(app) {
51239
- app.get("/api/dashboard/stats", async (_req, res) => {
51240
- try {
51241
- const [agents, commandCount, skillCount, mcpCount] = await Promise.all([
51242
- readAgents(),
51243
- countMdFilesRecursive(join38(claudeDir2, "commands")),
51244
- countSkills2(),
51245
- countAllMcpServers()
51246
- ]);
51247
- const modelDistribution = { opus: 0, sonnet: 0, haiku: 0, unset: 0 };
51248
- for (const agent of agents) {
51249
- const tier = classifyModel(agent.model);
51250
- modelDistribution[tier]++;
51251
- }
51252
51248
  res.json({
51253
- agents: agents.length,
51254
- commands: commandCount,
51255
- skills: skillCount,
51256
- mcpServers: mcpCount,
51257
- modelDistribution
51249
+ success: true,
51250
+ path: savedPath,
51251
+ scope,
51252
+ config: savedConfig || {}
51258
51253
  });
51259
51254
  } catch (error) {
51260
- res.status(500).json({ error: "Failed to load dashboard stats" });
51255
+ logger.error(`Failed to save gck-config: ${error}`);
51256
+ res.status(500).json({ error: "Failed to save configuration" });
51261
51257
  }
51262
51258
  });
51263
- app.get("/api/agents/list", async (_req, res) => {
51264
- try {
51265
- const agents = await readAgents();
51266
- res.json({ agents: agents.slice(0, 6) });
51267
- } catch (error) {
51268
- res.status(500).json({ error: "Failed to load agents" });
51269
- }
51259
+ app.get("/api/gck-config/schema", (_req, res) => {
51260
+ res.json(gck_config_schema_default);
51270
51261
  });
51271
- app.get("/api/suggestions", async (_req, res) => {
51262
+ app.patch("/api/gck-config/field", async (req, res) => {
51272
51263
  try {
51273
- const agents = await readAgents();
51274
- const suggestions = [];
51275
- const unsetCount = agents.filter((a) => classifyModel(a.model) === "unset").length;
51276
- if (unsetCount > 0) {
51277
- suggestions.push({
51278
- type: "warning",
51279
- message: `${unsetCount} agent${unsetCount > 1 ? "s" : ""} have no model set`,
51280
- target: "agents"
51281
- });
51264
+ const { scope, projectId, fieldPath, value } = req.body;
51265
+ if (!scope || !["global", "project"].includes(scope)) {
51266
+ res.status(400).json({ error: "Invalid scope" });
51267
+ return;
51282
51268
  }
51283
- const skillCount = await countSkills2();
51284
- if (skillCount === 0) {
51285
- suggestions.push({
51286
- type: "info",
51287
- message: "No skills installed — browse the Skills Marketplace",
51288
- target: "skills"
51289
- });
51269
+ if (!fieldPath || typeof fieldPath !== "string") {
51270
+ res.status(400).json({ error: "fieldPath required" });
51271
+ return;
51290
51272
  }
51291
- const settings = await readSettings();
51292
- if (!settings) {
51293
- suggestions.push({
51294
- type: "warning",
51295
- message: "No ~/.claude/settings.json found — run gck init to create one"
51296
- });
51273
+ let projectDir = null;
51274
+ if (scope === "project") {
51275
+ if (!projectId) {
51276
+ res.status(400).json({ error: "projectId required for project scope" });
51277
+ return;
51278
+ }
51279
+ projectDir = await resolveProjectDir(projectId);
51280
+ if (!projectDir) {
51281
+ res.status(404).json({ error: "Project not found" });
51282
+ return;
51283
+ }
51297
51284
  }
51298
- if (agents.length === 0) {
51299
- suggestions.push({
51300
- type: "info",
51301
- message: "No agents configured in ~/.claude/agents/",
51302
- target: "agents"
51303
- });
51285
+ await CkConfigManager.updateField(fieldPath, value, scope, projectDir);
51286
+ res.json({ success: true, fieldPath, value, scope });
51287
+ } catch (error) {
51288
+ if (error instanceof ZodError) {
51289
+ res.status(400).json({ error: "Validation failed", issues: error.issues });
51290
+ return;
51304
51291
  }
51305
- if (suggestions.length === 0) {
51306
- suggestions.push({
51307
- type: "success",
51308
- message: "Everything looks good!"
51309
- });
51292
+ logger.error(`Failed to update gck-config field: ${error}`);
51293
+ res.status(500).json({ error: "Failed to update field" });
51294
+ }
51295
+ });
51296
+ app.get("/api/metadata/global", async (_req, res) => {
51297
+ try {
51298
+ const metadataPath = join38(PathResolver.getGlobalKitDir(), "metadata.json");
51299
+ let metadata = {};
51300
+ if (existsSync20(metadataPath)) {
51301
+ const content = await readFile17(metadataPath, "utf-8");
51302
+ try {
51303
+ metadata = JSON.parse(content);
51304
+ } catch (err) {
51305
+ logger.warning(`Invalid JSON in metadata.json: ${err}`);
51306
+ }
51310
51307
  }
51311
- res.json({ suggestions });
51308
+ res.json(metadata);
51312
51309
  } catch (error) {
51313
- res.status(500).json({ error: "Failed to load suggestions" });
51310
+ logger.error(`Failed to load global metadata: ${error}`);
51311
+ res.status(500).json({ error: "Failed to load metadata" });
51314
51312
  }
51315
51313
  });
51316
51314
  }
51317
- var claudeDir2;
51318
- var init_dashboard_routes = __esm(() => {
51319
- init_frontmatter_parser();
51320
- init_claude_data();
51321
- claudeDir2 = join38(homedir21(), ".claude");
51315
+ var init_gck_config_routes = __esm(() => {
51316
+ init_config();
51317
+ init_gck_config_schema();
51318
+ init_logger();
51319
+ init_path_resolver();
51320
+ init_types3();
51321
+ init_zod();
51322
51322
  });
51323
51323
 
51324
51324
  // src/domains/web-server/routes/health-routes.ts
@@ -56660,7 +56660,7 @@ var init_skill_schema = __esm(() => {
56660
56660
  type: "string",
56661
56661
  minLength: 1,
56662
56662
  maxLength: 100,
56663
- description: "Skill identifier, typically prefixed with 'ck:'"
56663
+ description: "Skill identifier, typically prefixed with 'gck:'"
56664
56664
  },
56665
56665
  description: {
56666
56666
  type: "string",
@@ -56882,7 +56882,7 @@ function toStringArray(v) {
56882
56882
  function extractCrossRefs(body) {
56883
56883
  const stripped = body.replace(/```[\s\S]*?```/g, "").replace(/`[^`]*`/g, "");
56884
56884
  const refs = new Set;
56885
- const pattern = /\/ck:([a-z0-9-]+)/g;
56885
+ const pattern = /\/g?ck:([a-z0-9-]+)/g;
56886
56886
  for (const match of stripped.matchAll(pattern)) {
56887
56887
  refs.add(match[1]);
56888
56888
  }
@@ -59541,7 +59541,7 @@ function generatePlanMd(options2) {
59541
59541
  } = options2;
59542
59542
  const created = new Date().toISOString();
59543
59543
  const escYaml = (s) => s.replace(/\\/g, "\\\\").replace(/"/g, "\\\"").replace(/\n/g, "\\n");
59544
- const createdBy = source === "skill" ? "ck:plan" : source === "dashboard" ? "dashboard" : "ck-cli";
59544
+ const createdBy = source === "skill" ? "gck:plan" : source === "dashboard" ? "dashboard" : "ck-cli";
59545
59545
  const branch = getCurrentBranch();
59546
59546
  const frontmatterLines = [
59547
59547
  `title: "${escYaml(title)}"`,
@@ -60257,7 +60257,7 @@ function updateRegistryEntry(entry, cwd = process.cwd()) {
60257
60257
  function registerNewPlan(options2) {
60258
60258
  const now = new Date().toISOString();
60259
60259
  const source = options2.source ?? "cli";
60260
- const createdBy = source === "skill" ? "ck:plan" : source === "dashboard" ? "dashboard" : "ck-cli";
60260
+ const createdBy = source === "skill" ? "gck:plan" : source === "dashboard" ? "dashboard" : "ck-cli";
60261
60261
  updateRegistryEntry({
60262
60262
  dir: options2.dir,
60263
60263
  title: options2.title,
@@ -63790,7 +63790,7 @@ var package_default;
63790
63790
  var init_package = __esm(() => {
63791
63791
  package_default = {
63792
63792
  name: "@thuanha-groove/gck",
63793
- version: "0.0.1",
63793
+ version: "0.0.2",
63794
63794
  description: "CLI tool for bootstrapping and updating ClaudeKit projects",
63795
63795
  type: "module",
63796
63796
  repository: {
@@ -68259,7 +68259,7 @@ async function promptMigrateUpdate(deps) {
68259
68259
  }
68260
68260
  var import_fs_extra8, execAsync2, SAFE_PROVIDER_NAME, HOOK_DEPENDENCY_EXTENSIONS, MANAGED_HOOKS_MANIFEST = "managed-hooks.json";
68261
68261
  var init_post_update_handler = __esm(() => {
68262
- init_ck_config_manager();
68262
+ init_gck_config_manager();
68263
68263
  init_hook_health_checker();
68264
68264
  init_metadata_migration();
68265
68265
  init_version_utils();
@@ -68822,7 +68822,7 @@ function registerSystemRoutes(app) {
68822
68822
  try {
68823
68823
  const [packageJson, installLocation, gitVersion, ghVersion, bunVersion] = await Promise.all([
68824
68824
  getPackageJson(),
68825
- runCommand("which", ["ck"], "not found"),
68825
+ runCommand("which", ["gck"], "not found"),
68826
68826
  runCommand("git", ["--version"], "unknown"),
68827
68827
  runCommand("gh", ["--version"], "unknown").then((out) => out.split(`
68828
68828
  `)[0] ?? "unknown"),
@@ -69098,9 +69098,9 @@ function registerRoutes(app) {
69098
69098
  var init_routes = __esm(() => {
69099
69099
  init_action_routes();
69100
69100
  init_agents_routes();
69101
- init_ck_config_routes();
69102
69101
  init_command_routes();
69103
69102
  init_dashboard_routes();
69103
+ init_gck_config_routes();
69104
69104
  init_health_routes();
69105
69105
  init_hook_log_routes();
69106
69106
  init_mcp_routes();
@@ -77010,11 +77010,11 @@ var require_extract_zip = __commonJS(function(exports2, module2) {
77010
77010
  class OwnershipDisplay {
77011
77011
  static formatOwnership(ownership) {
77012
77012
  switch (ownership) {
77013
- case "ck":
77013
+ case "gck":
77014
77014
  return import_picocolors26.default.blue("CK-owned");
77015
77015
  case "user":
77016
77016
  return import_picocolors26.default.green("User-created");
77017
- case "ck-modified":
77017
+ case "gck-modified":
77018
77018
  return import_picocolors26.default.yellow("CK-modified");
77019
77019
  default:
77020
77020
  return import_picocolors26.default.gray("Unknown");
@@ -77043,13 +77043,13 @@ class OwnershipDisplay {
77043
77043
  };
77044
77044
  for (const result of results) {
77045
77045
  switch (result.ownership) {
77046
- case "ck":
77046
+ case "gck":
77047
77047
  summary.ckOwned++;
77048
77048
  break;
77049
77049
  case "user":
77050
77050
  summary.userCreated++;
77051
77051
  break;
77052
- case "ck-modified":
77052
+ case "gck-modified":
77053
77053
  summary.ckModified++;
77054
77054
  break;
77055
77055
  }
@@ -77925,7 +77925,7 @@ ${context.gitEventDetails}
77925
77925
  - Dimensions: ${dimensions}
77926
77926
  - Style: Professional, modern design
77927
77927
  - Include relevant visual elements for a tech/dev update
77928
- - Use the /ck:ai-multimodal or /ck:banner-design skill
77928
+ - Use the /gck:ai-multimodal or /gck:banner-design skill
77929
77929
  - Save the image and provide the file path
77930
77930
 
77931
77931
  IMPORTANT: Generate the image and output the path as JSON: {"imagePath": "/path/to/image.png"}`;
@@ -81205,7 +81205,7 @@ var init_init_command_help = __esm(() => {
81205
81205
  },
81206
81206
  {
81207
81207
  flags: "--prefix",
81208
- description: "Add /ck: prefix to all slash commands"
81208
+ description: "Add /gck: prefix to all slash commands"
81209
81209
  },
81210
81210
  {
81211
81211
  flags: "--skip-setup",
@@ -81467,7 +81467,7 @@ var init_new_command_help = __esm(() => {
81467
81467
  },
81468
81468
  {
81469
81469
  flags: "--prefix",
81470
- description: "Add /ck: prefix to all slash commands"
81470
+ description: "Add /gck: prefix to all slash commands"
81471
81471
  }
81472
81472
  ]
81473
81473
  },
@@ -82390,12 +82390,12 @@ var BANNER_LINES;
82390
82390
  var init_help_banner = __esm(() => {
82391
82391
  init_help_colors();
82392
82392
  BANNER_LINES = [
82393
- " ██████╗██╗ ██╗",
82394
- "██╔════╝██║ ██╔╝",
82395
- "██║ █████╔╝ ",
82396
- "██║ ██╔═██╗ ",
82397
- "╚██████╗██║ ██╗",
82398
- " ╚═════╝╚═╝ ╚═╝"
82393
+ " ██████╗ ██████╗██╗ ██╗",
82394
+ "██╔════╝ ██╔════╝██║ ██╔╝",
82395
+ "██║ ███╗██║ █████╔╝ ",
82396
+ "██║ ██║██║ ██╔═██╗ ",
82397
+ "╚██████╔╝╚██████╗██║ ██╗",
82398
+ " ╚═════╝ ╚═════╝╚═╝ ╚═╝"
82399
82399
  ];
82400
82400
  });
82401
82401
 
@@ -88239,7 +88239,7 @@ async function findSkillDirs(dir) {
88239
88239
  function normalizeSkillId(rawName, fallbackId) {
88240
88240
  if (!rawName)
88241
88241
  return fallbackId;
88242
- return rawName.startsWith("ck:") ? rawName.slice(3) : rawName;
88242
+ return rawName.replace(/^g?ck:/, "");
88243
88243
  }
88244
88244
 
88245
88245
  // src/domains/health-checks/checkers/skill-budget-settings.ts
@@ -91116,7 +91116,7 @@ class OwnershipChecker {
91116
91116
  if (actualChecksum === tracked.checksum) {
91117
91117
  return {
91118
91118
  path: filePath,
91119
- ownership: "ck",
91119
+ ownership: "gck",
91120
91120
  expectedChecksum: tracked.checksum,
91121
91121
  actualChecksum,
91122
91122
  exists: true
@@ -91124,7 +91124,7 @@ class OwnershipChecker {
91124
91124
  }
91125
91125
  return {
91126
91126
  path: filePath,
91127
- ownership: "ck-modified",
91127
+ ownership: "gck-modified",
91128
91128
  expectedChecksum: tracked.checksum,
91129
91129
  actualChecksum,
91130
91130
  exists: true
@@ -92024,7 +92024,7 @@ class SyncEngine {
92024
92024
  plan.autoUpdate.push(file);
92025
92025
  continue;
92026
92026
  }
92027
- if (file.ownership === "ck") {
92027
+ if (file.ownership === "gck") {
92028
92028
  plan.autoUpdate.push(file);
92029
92029
  continue;
92030
92030
  }
@@ -92159,7 +92159,7 @@ class SyncEngine {
92159
92159
  }
92160
92160
  // src/shared/deletion-pattern-expander.ts
92161
92161
  var LEGACY_COMMAND_PREFIX_BY_KIT = {
92162
- engineer: "ck",
92162
+ engineer: "gck",
92163
92163
  marketing: "mkt"
92164
92164
  };
92165
92165
  function getLegacyCommandPrefix(kitType) {
@@ -105083,7 +105083,7 @@ function buildFileTrackingList(options2) {
105083
105083
  const relativePath = isGlobal ? installedPath : installedPath.replace(/^\.claude\//, "");
105084
105084
  const filePath = join114(claudeDir, relativePath);
105085
105085
  const manifestEntry = releaseManifest ? ReleaseManifestLoader.findFile(releaseManifest, installedPath) : null;
105086
- const ownership = manifestEntry ? "ck" : "user";
105086
+ const ownership = manifestEntry ? "gck" : "user";
105087
105087
  filesToTrack.push({
105088
105088
  filePath,
105089
105089
  relativePath,
@@ -105291,13 +105291,13 @@ User-created files (sample):`);
105291
105291
  trackedFiles.push({
105292
105292
  path: relativePath,
105293
105293
  checksum: manifestEntry.checksum,
105294
- ownership: "ck",
105294
+ ownership: "gck",
105295
105295
  installedVersion: kitVersion
105296
105296
  });
105297
105297
  }
105298
105298
  }
105299
105299
  const filesToChecksum = [
105300
- ...preview.ckModified.map((p) => ({ relativePath: p, ownership: "ck-modified" })),
105300
+ ...preview.ckModified.map((p) => ({ relativePath: p, ownership: "gck-modified" })),
105301
105301
  ...preview.userCreated.map((p) => ({ relativePath: p, ownership: "user" }))
105302
105302
  ];
105303
105303
  if (filesToChecksum.length > 0) {
@@ -105555,11 +105555,11 @@ function buildCommandPatterns() {
105555
105555
  for (const cmd of COMMAND_ROOTS) {
105556
105556
  patterns.push({
105557
105557
  regex: new RegExp(`(?:^|(?<=[\\s\`]))(/)(${cmd})(:)(?!/)`, "gm"),
105558
- replacement: "$1ck:$2$3"
105558
+ replacement: "$1gck:$2$3"
105559
105559
  });
105560
105560
  patterns.push({
105561
105561
  regex: new RegExp(`(?:^|(?<=[\\s\`]))(/)(${cmd})(?![?/=&:\\w])(?=[\\s\`\\]\\)]|$)`, "gm"),
105562
- replacement: "$1ck:$2"
105562
+ replacement: "$1gck:$2"
105563
105563
  });
105564
105564
  }
105565
105565
  return patterns;
@@ -105667,7 +105667,7 @@ async function applyPrefix(extractDir) {
105667
105667
  logger.verbose("No commands directory found, skipping prefix application");
105668
105668
  return;
105669
105669
  }
105670
- logger.info("Applying /ck: prefix to slash commands...");
105670
+ logger.info("Applying /gck: prefix to slash commands...");
105671
105671
  const backupDir = join118(extractDir, ".commands-backup");
105672
105672
  const tempDir = join118(extractDir, ".commands-prefix-temp");
105673
105673
  try {
@@ -105676,19 +105676,19 @@ async function applyPrefix(extractDir) {
105676
105676
  logger.verbose("Commands directory is empty, skipping prefix application");
105677
105677
  return;
105678
105678
  }
105679
- if (entries.length === 1 && entries[0] === "ck") {
105680
- const ckDir = join118(commandsDir, "ck");
105681
- const ckStat = await stat20(ckDir);
105679
+ if (entries.length === 1 && entries[0] === "gck") {
105680
+ const gckDir = join118(commandsDir, "gck");
105681
+ const ckStat = await stat20(gckDir);
105682
105682
  if (ckStat.isDirectory()) {
105683
- logger.verbose("Commands already have /ck: prefix, skipping");
105683
+ logger.verbose("Commands already have /gck: prefix, skipping");
105684
105684
  return;
105685
105685
  }
105686
105686
  }
105687
105687
  await import_fs_extra21.copy(commandsDir, backupDir);
105688
105688
  logger.verbose("Created backup of commands directory");
105689
105689
  await mkdir32(tempDir, { recursive: true });
105690
- const ckDir = join118(tempDir, "ck");
105691
- await mkdir32(ckDir, { recursive: true });
105690
+ const gckDir = join118(tempDir, "gck");
105691
+ await mkdir32(gckDir, { recursive: true });
105692
105692
  let processedCount = 0;
105693
105693
  for (const entry of entries) {
105694
105694
  const sourcePath = join118(commandsDir, entry);
@@ -105697,13 +105697,13 @@ async function applyPrefix(extractDir) {
105697
105697
  logger.warning(`Skipping symlink for security: ${entry}`);
105698
105698
  continue;
105699
105699
  }
105700
- const destPath = join118(ckDir, entry);
105700
+ const destPath = join118(gckDir, entry);
105701
105701
  await import_fs_extra21.copy(sourcePath, destPath, {
105702
105702
  overwrite: false,
105703
105703
  errorOnExist: true
105704
105704
  });
105705
105705
  processedCount++;
105706
- logger.verbose(`Moved ${entry} to ck/${entry}`);
105706
+ logger.verbose(`Moved ${entry} to gck/${entry}`);
105707
105707
  }
105708
105708
  if (processedCount === 0) {
105709
105709
  logger.warning("No files to move (all were symlinks or invalid)");
@@ -105714,7 +105714,7 @@ async function applyPrefix(extractDir) {
105714
105714
  await import_fs_extra21.remove(commandsDir);
105715
105715
  await import_fs_extra21.move(tempDir, commandsDir);
105716
105716
  await import_fs_extra21.remove(backupDir);
105717
- logger.success("Successfully reorganized commands to /ck: prefix");
105717
+ logger.success("Successfully reorganized commands to /gck: prefix");
105718
105718
  const claudeDir = join118(extractDir, ".claude");
105719
105719
  logger.info("Transforming command references in file contents...");
105720
105720
  const transformResult = await transformCommandReferences(claudeDir, {
@@ -105738,7 +105738,7 @@ async function applyPrefix(extractDir) {
105738
105738
  if (await import_fs_extra21.pathExists(tempDir)) {
105739
105739
  await import_fs_extra21.remove(tempDir).catch(() => {});
105740
105740
  }
105741
- logger.error("Failed to apply /ck: prefix to commands");
105741
+ logger.error("Failed to apply /gck: prefix to commands");
105742
105742
  throw error;
105743
105743
  } finally {
105744
105744
  if (await import_fs_extra21.pathExists(backupDir)) {
@@ -105782,10 +105782,10 @@ async function scanDirectoryFiles(dir) {
105782
105782
  async function processFileOwnership(file, relativePath, metadata, claudeDir, options2, accumulator) {
105783
105783
  const { dryRun = false, forceOverwrite = false } = options2;
105784
105784
  const ownershipResult = await OwnershipChecker.checkOwnership(file, metadata, claudeDir);
105785
- if (ownershipResult.ownership === "ck" && ownershipResult.exists) {
105785
+ if (ownershipResult.ownership === "gck" && ownershipResult.exists) {
105786
105786
  accumulator.results.push({
105787
105787
  path: relativePath,
105788
- ownership: "ck",
105788
+ ownership: "gck",
105789
105789
  action: "delete"
105790
105790
  });
105791
105791
  if (!dryRun) {
@@ -105795,11 +105795,11 @@ async function processFileOwnership(file, relativePath, metadata, claudeDir, opt
105795
105795
  accumulator.deletedCount++;
105796
105796
  return true;
105797
105797
  }
105798
- if (ownershipResult.ownership === "ck-modified") {
105798
+ if (ownershipResult.ownership === "gck-modified") {
105799
105799
  if (forceOverwrite) {
105800
105800
  accumulator.results.push({
105801
105801
  path: relativePath,
105802
- ownership: "ck-modified",
105802
+ ownership: "gck-modified",
105803
105803
  action: "delete",
105804
105804
  reason: "force overwrite"
105805
105805
  });
@@ -105812,7 +105812,7 @@ async function processFileOwnership(file, relativePath, metadata, claudeDir, opt
105812
105812
  }
105813
105813
  accumulator.results.push({
105814
105814
  path: relativePath,
105815
- ownership: "ck-modified",
105815
+ ownership: "gck-modified",
105816
105816
  action: "preserve",
105817
105817
  reason: "modified by user"
105818
105818
  });
@@ -105872,6 +105872,7 @@ function logCleanupSummary(deletedCount, preservedCount, dryRun, results) {
105872
105872
 
105873
105873
  // src/services/transformers/commands-prefix/prefix-cleaner.ts
105874
105874
  var KIT_PREFIX_MAP = {
105875
+ gck: "engineer",
105875
105876
  ck: "engineer",
105876
105877
  mkt: "marketing"
105877
105878
  };
@@ -107859,10 +107860,10 @@ async function analyzeFreshInstallation(claudeDir) {
107859
107860
  const userFiles = [];
107860
107861
  for (const file of allFiles) {
107861
107862
  switch (file.ownership) {
107862
- case "ck":
107863
+ case "gck":
107863
107864
  ckFiles.push(file);
107864
107865
  break;
107865
- case "ck-modified":
107866
+ case "gck-modified":
107866
107867
  ckModifiedFiles.push(file);
107867
107868
  break;
107868
107869
  case "user":
@@ -111443,7 +111444,7 @@ async function migrateCommand(options2) {
111443
111444
  console.log(line);
111444
111445
  }
111445
111446
  }
111446
- await Promise.resolve().then(() => init_ck_config_manager());
111447
+ await Promise.resolve().then(() => init_gck_config_manager());
111447
111448
  const ckConfigResult = await CkConfigManager.loadFull(process.cwd());
111448
111449
  setTaxonomyOverrides(ckConfigResult.config.modelTaxonomy);
111449
111450
  const reconcileSpinner = de();
@@ -114279,10 +114280,10 @@ function normalizeTrackedPath(relativePath) {
114279
114280
  return relativePath.replace(/\\/g, "/");
114280
114281
  }
114281
114282
  function classifyFileByOwnership(ownership, forceOverwrite, deleteReason) {
114282
- if (ownership === "ck") {
114283
+ if (ownership === "gck") {
114283
114284
  return { action: "delete", reason: deleteReason };
114284
114285
  }
114285
- if (ownership === "ck-modified") {
114286
+ if (ownership === "gck-modified") {
114286
114287
  if (forceOverwrite) {
114287
114288
  return { action: "delete", reason: "force overwrite" };
114288
114289
  }
@@ -115235,7 +115236,7 @@ Branch \`${branchName}\` may have partial work.`, options2.showBranding, false).
115235
115236
  }
115236
115237
  async function invokeImplementation(options2) {
115237
115238
  const { issueNumber, issueTitle, planPath, timeoutSec, cwd } = options2;
115238
- const prompt = `/ck:cook --auto ${planPath}
115239
+ const prompt = `/gck:cook --auto ${planPath}
115239
115240
 
115240
115241
  After completing the implementation:
115241
115242
  1. Stage all changes: git add -A
@@ -115567,7 +115568,7 @@ function parseStreamJsonOutput(stdout) {
115567
115568
  return parseClaudeOutput(stdout);
115568
115569
  }
115569
115570
  function buildBrainstormPrompt(issue, repoName, skillsAvailable) {
115570
- const skillPrefix = skillsAvailable ? "/ck:brainstorm " : "";
115571
+ const skillPrefix = skillsAvailable ? "/gck:brainstorm " : "";
115571
115572
  return `${skillPrefix}Analyze this GitHub issue for the project "${repoName}".
115572
115573
 
115573
115574
  ## Issue #${issue.number}: ${issue.title}
@@ -115898,7 +115899,7 @@ function buildPlanPrompt(issue, repoName, conversationHistory) {
115898
115899
  ---
115899
115900
 
115900
115901
  `);
115901
- return `/ck:plan --fast Create an implementation plan for GitHub issue #${issue.number} in "${repoName}".
115902
+ return `/gck:plan --fast Create an implementation plan for GitHub issue #${issue.number} in "${repoName}".
115902
115903
 
115903
115904
  CRITICAL LANGUAGE RULE: Detect what language the conversation history is written in, then respond ONLY in that same language. Do NOT use your system locale or any other language preference.
115904
115905
 
@@ -115910,7 +115911,7 @@ ${sanitizedHistory}
115910
115911
  </untrusted-content>
115911
115912
 
115912
115913
  IMPORTANT INSTRUCTIONS:
115913
- - Create the plan in the project's plans/ directory following standard /ck:plan structure
115914
+ - Create the plan in the project's plans/ directory following standard /gck:plan structure
115914
115915
  - The plan directory should include issue number in the name (e.g., plans/YYMMDD-issue-{number}-{slug}/)
115915
115916
  - Create plan.md overview + phase-XX-*.md files for each phase
115916
115917
  - After creating plan files, provide a summary suitable for posting as a GitHub comment
@@ -116217,7 +116218,7 @@ function cleanExpiredIssues(state, ttlDays) {
116217
116218
  }
116218
116219
 
116219
116220
  // src/commands/watch/phases/state-manager.ts
116220
- init_ck_config_manager();
116221
+ init_gck_config_manager();
116221
116222
  init_file_io();
116222
116223
  init_logger();
116223
116224
  import { existsSync as existsSync74 } from "node:fs";
@@ -116786,13 +116787,13 @@ function sleep(ms) {
116786
116787
  // src/cli/command-registry.ts
116787
116788
  init_logger();
116788
116789
  function registerCommands(cli) {
116789
- cli.command("new", "Bootstrap a new ClaudeKit project (with interactive version selection)").option("--dir <dir>", "Target directory (default: .)").option("--kit <kit>", "Kit to use: engineer, marketing, all, or comma-separated").option("-r, --release <version>", "Skip version selection, use specific version (e.g., latest, v1.0.0)").option("--force", "Overwrite existing files without confirmation").option("--exclude <pattern>", "Exclude files matching glob pattern (can be used multiple times)").option("--opencode", "Install OpenCode CLI package (non-interactive mode)").option("--gemini", "Install Google Gemini CLI package (non-interactive mode)").option("--install-skills", "Install skills dependencies (non-interactive mode)").option("--with-sudo", "Include system packages requiring sudo (Linux: ffmpeg, imagemagick)").option("--prefix", "Add /ck: prefix to all slash commands by moving them to commands/ck/ subdirectory").option("--beta", "Show beta versions in selection prompt").option("--refresh", "Bypass release cache to fetch latest versions from GitHub").option("--docs-dir <name>", "Custom docs folder name (default: docs)").option("--plans-dir <name>", "Custom plans folder name (default: plans)").option("-y, --yes", "Non-interactive mode with sensible defaults (skip all prompts)").option("--use-git", "Use git clone instead of GitHub API (uses SSH/HTTPS credentials)").option("--archive <path>", "Use local archive file instead of downloading (zip/tar.gz)").option("--kit-path <path>", "Use local kit directory instead of downloading").action(async (options2) => {
116790
+ cli.command("new", "Bootstrap a new ClaudeKit project (with interactive version selection)").option("--dir <dir>", "Target directory (default: .)").option("--kit <kit>", "Kit to use: engineer, marketing, all, or comma-separated").option("-r, --release <version>", "Skip version selection, use specific version (e.g., latest, v1.0.0)").option("--force", "Overwrite existing files without confirmation").option("--exclude <pattern>", "Exclude files matching glob pattern (can be used multiple times)").option("--opencode", "Install OpenCode CLI package (non-interactive mode)").option("--gemini", "Install Google Gemini CLI package (non-interactive mode)").option("--install-skills", "Install skills dependencies (non-interactive mode)").option("--with-sudo", "Include system packages requiring sudo (Linux: ffmpeg, imagemagick)").option("--prefix", "Add /gck: prefix to all slash commands by moving them to commands/gck/ subdirectory").option("--beta", "Show beta versions in selection prompt").option("--refresh", "Bypass release cache to fetch latest versions from GitHub").option("--docs-dir <name>", "Custom docs folder name (default: docs)").option("--plans-dir <name>", "Custom plans folder name (default: plans)").option("-y, --yes", "Non-interactive mode with sensible defaults (skip all prompts)").option("--use-git", "Use git clone instead of GitHub API (uses SSH/HTTPS credentials)").option("--archive <path>", "Use local archive file instead of downloading (zip/tar.gz)").option("--kit-path <path>", "Use local kit directory instead of downloading").action(async (options2) => {
116790
116791
  if (options2.exclude && !Array.isArray(options2.exclude)) {
116791
116792
  options2.exclude = [options2.exclude];
116792
116793
  }
116793
116794
  await newCommand(options2);
116794
116795
  });
116795
- cli.command("init", "Initialize or update ClaudeKit project (with interactive version selection)").option("--dir <dir>", "Target directory (default: .)").option("--kit <kit>", "Kit to use: engineer, marketing, all, or comma-separated").option("-r, --release <version>", "Skip version selection, use specific version (e.g., latest, v1.0.0)").option("--exclude <pattern>", "Exclude files matching glob pattern (can be used multiple times)").option("--only <pattern>", "Include only files matching glob pattern (can be used multiple times)").option("-g, --global", "Use platform-specific user configuration directory").option("--fresh", "Full reset: remove CK files, replace settings.json and CLAUDE.md, reinstall from scratch").option("--force", "Force reinstall even if already at latest version (use with --yes; re-onboards missing files without full reset)").option("--install-skills", "Install skills dependencies (non-interactive mode)").option("--with-sudo", "Include system packages requiring sudo (Linux: ffmpeg, imagemagick)").option("--prefix", "Add /ck: prefix to all slash commands by moving them to commands/ck/ subdirectory").option("--beta", "Show beta versions in selection prompt").option("--refresh", "Bypass release cache to fetch latest versions from GitHub").option("--dry-run", "Preview changes without applying them (requires --prefix)").option("--force-overwrite", "Override ownership protections and delete user-modified files (requires --prefix)").option("--force-overwrite-settings", "Fully replace settings.json instead of selective merge (destroys user customizations)").option("--restore-ck-hooks", "Restore CK-managed hook registrations during update self-heal").option("--skip-setup", "Skip interactive configuration wizard").option("--docs-dir <name>", "Custom docs folder name (default: docs)").option("--plans-dir <name>", "Custom plans folder name (default: plans)").option("-y, --yes", "Non-interactive mode with sensible defaults (skip all prompts)").option("--sync", "Sync config files from upstream with interactive hunk-by-hunk merge").option("--use-git", "Use git clone instead of GitHub API (uses SSH/HTTPS credentials)").option("--archive <path>", "Use local archive file instead of downloading (zip/tar.gz)").option("--kit-path <path>", "Use local kit directory instead of downloading").action(async (options2) => {
116796
+ cli.command("init", "Initialize or update ClaudeKit project (with interactive version selection)").option("--dir <dir>", "Target directory (default: .)").option("--kit <kit>", "Kit to use: engineer, marketing, all, or comma-separated").option("-r, --release <version>", "Skip version selection, use specific version (e.g., latest, v1.0.0)").option("--exclude <pattern>", "Exclude files matching glob pattern (can be used multiple times)").option("--only <pattern>", "Include only files matching glob pattern (can be used multiple times)").option("-g, --global", "Use platform-specific user configuration directory").option("--fresh", "Full reset: remove CK files, replace settings.json and CLAUDE.md, reinstall from scratch").option("--force", "Force reinstall even if already at latest version (use with --yes; re-onboards missing files without full reset)").option("--install-skills", "Install skills dependencies (non-interactive mode)").option("--with-sudo", "Include system packages requiring sudo (Linux: ffmpeg, imagemagick)").option("--prefix", "Add /gck: prefix to all slash commands by moving them to commands/gck/ subdirectory").option("--beta", "Show beta versions in selection prompt").option("--refresh", "Bypass release cache to fetch latest versions from GitHub").option("--dry-run", "Preview changes without applying them (requires --prefix)").option("--force-overwrite", "Override ownership protections and delete user-modified files (requires --prefix)").option("--force-overwrite-settings", "Fully replace settings.json instead of selective merge (destroys user customizations)").option("--restore-ck-hooks", "Restore CK-managed hook registrations during update self-heal").option("--skip-setup", "Skip interactive configuration wizard").option("--docs-dir <name>", "Custom docs folder name (default: docs)").option("--plans-dir <name>", "Custom plans folder name (default: plans)").option("-y, --yes", "Non-interactive mode with sensible defaults (skip all prompts)").option("--sync", "Sync config files from upstream with interactive hunk-by-hunk merge").option("--use-git", "Use git clone instead of GitHub API (uses SSH/HTTPS credentials)").option("--archive <path>", "Use local archive file instead of downloading (zip/tar.gz)").option("--kit-path <path>", "Use local kit directory instead of downloading").action(async (options2) => {
116796
116797
  if (options2.exclude && !Array.isArray(options2.exclude)) {
116797
116798
  options2.exclude = [options2.exclude];
116798
116799
  }