@dadado/agent-kit-cli 4.3.0 → 4.4.1

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.
Files changed (2) hide show
  1. package/dist/index.js +204 -28
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -2167,6 +2167,27 @@ async function runScanner(rootDir) {
2167
2167
 
2168
2168
  // src/utils/prompts.ts
2169
2169
  import { cancel, confirm, isCancel, multiselect, select, text } from "@clack/prompts";
2170
+ var WORKSPACE_SKIN_MODE_DEFAULTS = {
2171
+ default: "autopilot",
2172
+ modes: {
2173
+ "continue-plan": "autopilot",
2174
+ "run-plan": "night-shift",
2175
+ "cli-run-plan": "ghost-runner"
2176
+ }
2177
+ };
2178
+ function workspaceSkinConfigFromChoice(choice) {
2179
+ if (choice.kind === "skip") return null;
2180
+ if (choice.kind === "mode-defaults")
2181
+ return { ...WORKSPACE_SKIN_MODE_DEFAULTS, modes: { ...WORKSPACE_SKIN_MODE_DEFAULTS.modes } };
2182
+ return {
2183
+ default: choice.id,
2184
+ modes: {
2185
+ "continue-plan": choice.id,
2186
+ "run-plan": choice.id,
2187
+ "cli-run-plan": choice.id
2188
+ }
2189
+ };
2190
+ }
2170
2191
  function ensureNotCancelled(value) {
2171
2192
  if (isCancel(value)) {
2172
2193
  cancel("Operation cancelled.");
@@ -2238,6 +2259,27 @@ async function askProjectManagement(existing = []) {
2238
2259
  })
2239
2260
  );
2240
2261
  }
2262
+ async function askWorkspaceSkin() {
2263
+ const value = ensureNotCancelled(
2264
+ await select({
2265
+ message: "Workspace skin for chat/CLI chrome? (optional)",
2266
+ initialValue: "mode-defaults",
2267
+ options: [
2268
+ {
2269
+ label: "Keep mode defaults (Autopilot/Night Shift/Ghost Runner per mode)",
2270
+ value: "mode-defaults"
2271
+ },
2272
+ { label: "Autopilot", value: "autopilot" },
2273
+ { label: "Night Shift", value: "night-shift" },
2274
+ { label: "Ghost Runner", value: "ghost-runner" },
2275
+ { label: "Skip for now", value: "skip" }
2276
+ ]
2277
+ })
2278
+ );
2279
+ if (value === "skip") return { kind: "skip" };
2280
+ if (value === "mode-defaults") return { kind: "mode-defaults" };
2281
+ return { kind: "skin", id: value };
2282
+ }
2241
2283
  async function runExistingProjectWizard(scan) {
2242
2284
  const ide = await askIdeAndPlan(scan.ide);
2243
2285
  const workflow = await askGitWorkflow(scan.git.workflow);
@@ -2248,6 +2290,7 @@ async function runExistingProjectWizard(scan) {
2248
2290
  initialValue: true
2249
2291
  })
2250
2292
  );
2293
+ const workspaceSkinChoice = await askWorkspaceSkin();
2251
2294
  return {
2252
2295
  rootDir: scan.rootDir,
2253
2296
  stack: scan.stack,
@@ -2265,7 +2308,8 @@ async function runExistingProjectWizard(scan) {
2265
2308
  "clean-code",
2266
2309
  "docs-repo",
2267
2310
  "ide-guide"
2268
- ]
2311
+ ],
2312
+ workspaceSkinChoice
2269
2313
  };
2270
2314
  }
2271
2315
  async function runGreenfieldWizard(scan) {
@@ -2324,6 +2368,7 @@ async function runGreenfieldWizard(scan) {
2324
2368
  initialValue: true
2325
2369
  })
2326
2370
  );
2371
+ const workspaceSkinChoice = await askWorkspaceSkin();
2327
2372
  return {
2328
2373
  rootDir: scan.rootDir,
2329
2374
  stack: {
@@ -2349,11 +2394,20 @@ async function runGreenfieldWizard(scan) {
2349
2394
  "clean-code",
2350
2395
  "docs-repo",
2351
2396
  "ide-guide"
2352
- ]
2397
+ ],
2398
+ workspaceSkinChoice
2353
2399
  };
2354
2400
  }
2355
2401
 
2356
2402
  // src/commands/init.ts
2403
+ async function mergeWorkspaceSkinConfig(rootDir, workspaceSkin) {
2404
+ const contextDir = path23.join(rootDir, ".cursor", "context");
2405
+ const configPath = path23.join(contextDir, "config.json");
2406
+ await ensureDir(contextDir);
2407
+ const existing = await readJson(configPath) ?? {};
2408
+ await writeJson(configPath, { ...existing, workspaceSkin });
2409
+ return configPath;
2410
+ }
2357
2411
  var initCommand = defineCommand5({
2358
2412
  meta: {
2359
2413
  name: "init",
@@ -2374,9 +2428,15 @@ var initCommand = defineCommand5({
2374
2428
  scan.isGreenfield ? "Empty repo detected (greenfield)." : "Existing project detected."
2375
2429
  );
2376
2430
  const profile = scan.isGreenfield ? await runGreenfieldWizard(scan) : await runExistingProjectWizard(scan);
2431
+ const { workspaceSkinChoice, ...profileToSave } = profile;
2377
2432
  const configPath = path23.join(scan.rootDir, ".cursor", "agent-kit.config.json");
2378
- await writeJson(configPath, profile);
2433
+ await writeJson(configPath, profileToSave);
2379
2434
  logger.success(`Profile saved in ${configPath}`);
2435
+ const skinConfig = workspaceSkinChoice !== void 0 ? workspaceSkinConfigFromChoice(workspaceSkinChoice) : null;
2436
+ if (skinConfig) {
2437
+ const contextConfigPath = await mergeWorkspaceSkinConfig(scan.rootDir, skinConfig);
2438
+ logger.success(`Workspace skin saved in ${contextConfigPath}`);
2439
+ }
2380
2440
  await generateFromProfile(profile);
2381
2441
  try {
2382
2442
  const registry = await resolveRegistryRoot({ cwd: scan.rootDir });
@@ -2521,7 +2581,7 @@ var installCommand = defineCommand6({
2521
2581
  });
2522
2582
 
2523
2583
  // src/commands/run-plan.ts
2524
- import path28 from "path";
2584
+ import path29 from "path";
2525
2585
  import { defineCommand as defineCommand7 } from "citty";
2526
2586
 
2527
2587
  // src/plan-loop/backends.ts
@@ -2607,7 +2667,7 @@ function listBackendIds() {
2607
2667
 
2608
2668
  // src/plan-loop/run-loop.ts
2609
2669
  import { mkdir as mkdir4, readFile as readFile10, rm, unlink } from "fs/promises";
2610
- import path27 from "path";
2670
+ import path28 from "path";
2611
2671
 
2612
2672
  // src/plan-loop/external-review.ts
2613
2673
  import { spawn as spawn3 } from "child_process";
@@ -2771,6 +2831,101 @@ function formatSentinelLine(sentinel) {
2771
2831
  return "";
2772
2832
  }
2773
2833
 
2834
+ // src/plan-loop/skin-banners.ts
2835
+ import path27 from "path";
2836
+ import {
2837
+ blue,
2838
+ cyan as cyan2,
2839
+ gray,
2840
+ green as green2,
2841
+ lightGray,
2842
+ lightGreen,
2843
+ magenta,
2844
+ red as red2,
2845
+ white,
2846
+ yellow as yellow2
2847
+ } from "kolorist";
2848
+ var CLI_RUN_PLAN_MODE = "cli-run-plan";
2849
+ var DEFAULT_CLI_SKIN_ID = "ghost-runner";
2850
+ var COLORS = {
2851
+ white,
2852
+ gray,
2853
+ green: green2,
2854
+ cyan: cyan2,
2855
+ magenta,
2856
+ yellow: yellow2,
2857
+ red: red2,
2858
+ blue,
2859
+ "light-green": lightGreen,
2860
+ lightgreen: lightGreen,
2861
+ "light-gray": lightGray,
2862
+ lightgray: lightGray
2863
+ };
2864
+ function resolveColor(name, fallback) {
2865
+ if (!name) return fallback;
2866
+ return COLORS[name.toLowerCase()] ?? fallback;
2867
+ }
2868
+ async function resolveCliSkinId(root) {
2869
+ try {
2870
+ const cfg = await readJson(
2871
+ path27.join(root, ".cursor", "context", "config.json")
2872
+ );
2873
+ const id = cfg?.workspaceSkin?.modes?.[CLI_RUN_PLAN_MODE];
2874
+ if (typeof id === "string" && id.trim()) return id.trim();
2875
+ } catch {
2876
+ }
2877
+ return DEFAULT_CLI_SKIN_ID;
2878
+ }
2879
+ async function loadSkinPack(root, skinId) {
2880
+ try {
2881
+ const skinPath = path27.join(root, "registry", "skins", "core", skinId, "skin.json");
2882
+ const pack = await readJson(skinPath);
2883
+ if (!pack || typeof pack.id !== "string") return null;
2884
+ return pack;
2885
+ } catch {
2886
+ return null;
2887
+ }
2888
+ }
2889
+ async function loadCliRunPlanSkin(root) {
2890
+ const id = await resolveCliSkinId(root);
2891
+ return loadSkinPack(root, id);
2892
+ }
2893
+ function createSkinBannerPrinter(skin) {
2894
+ if (!skin?.cliBanners) return null;
2895
+ const banners = skin.cliBanners;
2896
+ if (!banners.tickStart && !banners.tickEnd && !banners.phaseComplete) return null;
2897
+ const primary = resolveColor(skin.ansiPalette?.primary, white);
2898
+ const secondary = resolveColor(skin.ansiPalette?.secondary, gray);
2899
+ const accent = resolveColor(skin.ansiPalette?.accent, green2);
2900
+ return {
2901
+ tickStart(detail) {
2902
+ if (banners.tickStart) {
2903
+ console.log(`${accent(banners.tickStart)} ${primary(detail)}`);
2904
+ } else {
2905
+ console.log(primary(detail));
2906
+ }
2907
+ },
2908
+ tickEnd(detail) {
2909
+ if (!banners.tickEnd) return;
2910
+ const line = detail ? `${banners.tickEnd} ${detail}` : banners.tickEnd;
2911
+ console.log(secondary(line));
2912
+ },
2913
+ phaseComplete(detail) {
2914
+ if (!banners.phaseComplete) return;
2915
+ const line = detail ? `${banners.phaseComplete} ${detail}` : banners.phaseComplete;
2916
+ console.log(accent(line));
2917
+ },
2918
+ sleep(seconds) {
2919
+ console.log(secondary(`sleep ${seconds}s`));
2920
+ },
2921
+ stop(reason) {
2922
+ if (banners.tickEnd) {
2923
+ console.log(secondary(`${banners.tickEnd} ${reason}`));
2924
+ }
2925
+ }
2926
+ };
2927
+ }
2928
+
2774
2929
  // src/plan-loop/run-loop.ts
2775
2930
  var TICK_PROMPT = '/run-plan - single tick from the headless runner (agent-kit run-plan). Read .cursor/HANDOFF.md and the active plan in .cursor/plans/. Mark the next to-do as in_progress in the frontmatter, execute ONLY that to-do, mark completed and update HANDOFF. If there is a commitable diff: run /git-staging without asking for confirmation. NEVER /git-prod. Do NOT re-arm an internal Loop skill - the external runner starts the next agent. End the response with exactly one line: "LOOP_TICK_RESULT: continue" if implementable to-dos remain, or "LOOP_TICK_RESULT: stop - <reason>" (plan exhausted, external blocker, or human decision needed).';
2776
2931
  function stamp() {
@@ -2782,9 +2937,9 @@ function sleep(ms) {
2782
2937
  return new Promise((r) => setTimeout(r, ms));
2783
2938
  }
2784
2939
  async function runPlanLoop(opts) {
2785
- const plansDir = path27.join(opts.root, ".cursor", "plans");
2786
- const stopFile = path27.join(opts.root, ".cursor", "loop.stop");
2787
- const logDir = path27.join(opts.root, ".cursor", "loop-logs");
2940
+ const plansDir = path28.join(opts.root, ".cursor", "plans");
2941
+ const stopFile = path28.join(opts.root, ".cursor", "loop.stop");
2942
+ const logDir = path28.join(opts.root, ".cursor", "loop-logs");
2788
2943
  const planPath = await findActivePlanFile(plansDir);
2789
2944
  if (!planPath) {
2790
2945
  logger.error("No active plan in .cursor/plans/");
@@ -2803,9 +2958,14 @@ async function runPlanLoop(opts) {
2803
2958
  };
2804
2959
  process.on("SIGINT", onSigInt);
2805
2960
  try {
2806
- console.log(`Active plan: ${path27.basename(planPath)}`);
2961
+ const skin = await loadCliRunPlanSkin(opts.root);
2962
+ const banners = createSkinBannerPrinter(skin);
2963
+ console.log(`Active plan: ${path28.basename(planPath)}`);
2807
2964
  console.log(`Pending to-dos: ${await pending()} | max ticks: ${opts.maxTicks}`);
2808
2965
  console.log(`Backend: ${opts.backend.id}`);
2966
+ if (skin) {
2967
+ console.log(`CLI skin: ${skin.displayName ?? skin.id}`);
2968
+ }
2809
2969
  if (opts.dryRun) {
2810
2970
  console.log("--dry-run: no agent will be started. Tick prompt:");
2811
2971
  console.log(TICK_PROMPT);
@@ -2822,11 +2982,15 @@ async function runPlanLoop(opts) {
2822
2982
  while (true) {
2823
2983
  tick += 1;
2824
2984
  if (tick > opts.maxTicks) {
2825
- console.log(`Budget of ${opts.maxTicks} ticks reached - stopping. Run again to continue.`);
2985
+ const msg = `Budget of ${opts.maxTicks} ticks reached - stopping. Run again to continue.`;
2986
+ if (banners) banners.stop(msg);
2987
+ else console.log(msg);
2826
2988
  break;
2827
2989
  }
2828
2990
  if (await fileExists(stopFile)) {
2829
- console.log(`Stop file found (${stopFile}) - stopping.`);
2991
+ const msg = `Stop file found (${stopFile}) - stopping.`;
2992
+ if (banners) banners.stop(msg);
2993
+ else console.log(msg);
2830
2994
  try {
2831
2995
  await rm(stopFile, { force: true });
2832
2996
  } catch {
@@ -2839,10 +3003,12 @@ async function runPlanLoop(opts) {
2839
3003
  planExhausted = true;
2840
3004
  break;
2841
3005
  }
2842
- const logPath = path27.join(logDir, `tick-${stamp()}.log`);
2843
- const relLog = path27.relative(opts.root, logPath);
3006
+ const logPath = path28.join(logDir, `tick-${stamp()}.log`);
3007
+ const relLog = path28.relative(opts.root, logPath);
2844
3008
  console.log("");
2845
- console.log(`=== tick ${tick}/${opts.maxTicks} - pending: ${before} - log: ${relLog} ===`);
3009
+ const tickLine = `=== tick ${tick}/${opts.maxTicks} - pending: ${before} - log: ${relLog} ===`;
3010
+ if (banners) banners.tickStart(tickLine);
3011
+ else console.log(tickLine);
2846
3012
  let agentExit = 0;
2847
3013
  try {
2848
3014
  const result = await opts.backend.run({
@@ -2859,21 +3025,28 @@ async function runPlanLoop(opts) {
2859
3025
  try {
2860
3026
  const logText = await readFile10(logPath, "utf8");
2861
3027
  if (logText.includes("Too many MCP tools")) {
2862
- console.log(
2863
- "Too many MCP tools for the headless model - disable servers (cursor-agent mcp disable <id>) and run again."
2864
- );
3028
+ const msg = "Too many MCP tools for the headless model - disable servers (cursor-agent mcp disable <id>) and run again.";
3029
+ if (banners) banners.stop(msg);
3030
+ else console.log(msg);
2865
3031
  break;
2866
3032
  }
2867
3033
  } catch {
2868
3034
  }
2869
3035
  if (agentExit !== 0) {
2870
- console.log(
2871
- `${opts.backend.id} exited with code ${agentExit} - stopping. See log: ${logPath}`
2872
- );
3036
+ const msg = `${opts.backend.id} exited with code ${agentExit} - stopping. See log: ${logPath}`;
3037
+ if (banners) {
3038
+ banners.tickEnd(`exit ${agentExit}`);
3039
+ banners.stop(msg);
3040
+ } else {
3041
+ console.log(msg);
3042
+ }
2873
3043
  break;
2874
3044
  }
2875
3045
  const sentinel = await parseSentinelFromLogFile(logPath);
2876
3046
  const after = await pending();
3047
+ if (banners) {
3048
+ banners.tickEnd(`pending: ${after}`);
3049
+ }
2877
3050
  if (sentinel.kind === "continue") {
2878
3051
  } else if (sentinel.kind === "stop") {
2879
3052
  console.log(`Agent requested stop: ${formatSentinelLine(sentinel)}`);
@@ -2887,9 +3060,9 @@ async function runPlanLoop(opts) {
2887
3060
  `warn: tick without sentinel, but progress (${before} -> ${after}) - continuing.`
2888
3061
  );
2889
3062
  } else {
2890
- console.log(
2891
- `Tick without sentinel and no progress (${before} -> ${after}) - stopping for safety.`
2892
- );
3063
+ const msg = `Tick without sentinel and no progress (${before} -> ${after}) - stopping for safety.`;
3064
+ if (banners) banners.stop(msg);
3065
+ else console.log(msg);
2893
3066
  break;
2894
3067
  }
2895
3068
  if (after === 0) {
@@ -2898,13 +3071,16 @@ async function runPlanLoop(opts) {
2898
3071
  break;
2899
3072
  }
2900
3073
  if (opts.sleepSeconds > 0) {
3074
+ if (banners) banners.sleep(opts.sleepSeconds);
2901
3075
  await sleep(opts.sleepSeconds * 1e3);
2902
3076
  }
2903
3077
  }
2904
3078
  const pendingNow = await pending();
2905
3079
  console.log("");
3080
+ const finishDetail = `after ${tick} tick(s); pending: ${pendingNow}`;
3081
+ if (banners) banners.phaseComplete(finishDetail);
2906
3082
  console.log(
2907
- `Loop finished after ${tick} tick(s). Pending now: ${pendingNow}. Logs in ${path27.relative(opts.root, logDir)}/`
3083
+ `Loop finished after ${tick} tick(s). Pending now: ${pendingNow}. Logs in ${path28.relative(opts.root, logDir)}/`
2908
3084
  );
2909
3085
  if (planExhausted || shouldArmExternalPlanReview({ pending: pendingNow, stopReason })) {
2910
3086
  await armExternalPlanReview(opts.root);
@@ -2975,7 +3151,7 @@ var runPlanCommand = defineCommand7({
2975
3151
  return;
2976
3152
  }
2977
3153
  const code = await runPlanLoop({
2978
- root: path28.resolve(args.cwd),
3154
+ root: path29.resolve(args.cwd),
2979
3155
  maxTicks,
2980
3156
  sleepSeconds,
2981
3157
  model: args.model ? String(args.model) : void 0,
@@ -3009,7 +3185,7 @@ var scanCommand = defineCommand8({
3009
3185
  });
3010
3186
 
3011
3187
  // src/commands/status.ts
3012
- import path29 from "path";
3188
+ import path30 from "path";
3013
3189
  import { defineCommand as defineCommand9 } from "citty";
3014
3190
  var statusCommand = defineCommand9({
3015
3191
  meta: {
@@ -3029,7 +3205,7 @@ var statusCommand = defineCommand9({
3029
3205
  },
3030
3206
  async run({ args }) {
3031
3207
  const manifest = await loadAgentKitManifest(args.cwd);
3032
- const profilePath = path29.join(args.cwd, ".cursor", "agent-kit.config.json");
3208
+ const profilePath = path30.join(args.cwd, ".cursor", "agent-kit.config.json");
3033
3209
  const profile = await readJson(profilePath);
3034
3210
  if (args.json) {
3035
3211
  console.log(
@@ -3119,7 +3295,7 @@ var updateCommand = defineCommand10({
3119
3295
  var main = defineCommand11({
3120
3296
  meta: {
3121
3297
  name: "agent-kit",
3122
- description: "Developer bootstrapper for AI-assisted IDEs"
3298
+ description: "HITL framework for AI-assisted IDEs"
3123
3299
  },
3124
3300
  subCommands: {
3125
3301
  init: initCommand,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@dadado/agent-kit-cli",
3
- "version": "4.3.0",
4
- "description": "Agent Kit CLI — bootstraps AI-assisted IDEs with project-aware rules, skills, and context management",
3
+ "version": "4.4.1",
4
+ "description": "Agent Kit CLI: HITL framework install and tooling for AI-assisted IDEs (rules, skills, plan/handoff, context).",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "agent-kit": "./dist/index.js"