@brainervirus/workit-cli 0.7.1 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +28 -3
  2. package/dist/index.js +138 -97
  3. package/package.json +5 -5
package/README.md CHANGED
@@ -1,6 +1,10 @@
1
1
  # @brainervirus/workit-cli
2
2
 
3
- The workit CLI — an interactive wizard that configures workit for OpenCode and/or Cursor.
3
+ The workit CLI — an interactive Ink wizard that configures workit for OpenCode and/or Cursor, plus an offline installation doctor.
4
+
5
+ ## Requirements
6
+
7
+ - **Node.js ≥ 22** — the published CLI is a self-contained Node bundle (no Bun runtime). Node 21 and below fail (`ERR_MODULE_NOT_FOUND`/ESM syntax or the `>=22` engine gate).
4
8
 
5
9
  ## Install
6
10
 
@@ -13,10 +17,31 @@ npx @brainervirus/workit-cli init
13
17
  ## Usage
14
18
 
15
19
  ```bash
16
- workit init
20
+ workit init # interactive setup wizard
21
+ workit doctor # offline installation health report
22
+ workit doctor --json # machine-readable report
23
+ workit # help
24
+ ```
25
+
26
+ `workit init` guides you through: platform selection (OpenCode/Cursor), global config (locale, timezone, branch policy), YouTrack, VCS, workspaces (path globs → provider), and project hygiene files. The wizard is a TTY application — `workit init` requires an interactive terminal and prints guidance (exiting nonzero) when stdin is not a TTY.
27
+
28
+ `workit doctor` checks the offline installation health and exits nonzero when problems are found; `--json` prints the full report as JSON instead of the human-readable table.
29
+
30
+ ## Behavior
31
+
32
+ - **Safe apply semantics** — a malformed `config.json` is detected before the wizard renders and reported as a friendly blocked output instead of crashing; the same guard runs after the Apply preview.
33
+ - **Stable interaction** — unchanged wizard inputs are no-ops; they settle without React render warnings and never discard draft state.
34
+ - **Clean terminal** — only `warn`/`error` diagnostics print to stderr; routine structured `info` records stay in the JSONL journal. Nonzero failures and human-readable errors remain visible.
35
+ - **Node support** — the packed CLI runs on Node 22+; installation on Node 22.19 emits no engine warning from workit's dependency tree.
36
+
37
+ ## Package scripts
38
+
39
+ ```bash
40
+ bun run build # bundle dist/index.js (self-contained, Node shebang) + assets
41
+ bun run typecheck # tsc --noEmit
17
42
  ```
18
43
 
19
- Guides you through: platform selection (OpenCode/Cursor), global config (locale, timezone, branch policy), YouTrack, VCS, workspaces (path globs → provider), and project hygiene files.
44
+ The build produces a nonsplitting `dist/index.js` with a portable `#!/usr/bin/env node` shebang and copies the deterministic `assets/` (templates) from core.
20
45
 
21
46
  ## Docs
22
47
 
package/dist/index.js CHANGED
@@ -42763,9 +42763,9 @@ function writeFileExclusive(file, content, mode) {
42763
42763
 
42764
42764
  // packages/workit-core/src/core/setup.ts
42765
42765
  import {
42766
- chmodSync,
42766
+ copyFileSync as copyFileSync2,
42767
42767
  cpSync as cpSync2,
42768
- existsSync as existsSync9,
42768
+ existsSync as existsSync8,
42769
42769
  mkdirSync as mkdirSync3,
42770
42770
  mkdtempSync,
42771
42771
  readFileSync as readFileSync8,
@@ -42872,7 +42872,6 @@ var readSetupState = (dir = resolveConfigDir()) => ({
42872
42872
  });
42873
42873
 
42874
42874
  // packages/workit-core/src/core/registration.ts
42875
- import { existsSync as existsSync6 } from "node:fs";
42876
42875
  import path6 from "node:path";
42877
42876
  var isRecord = (v) => v !== null && typeof v === "object" && !Array.isArray(v);
42878
42877
  var named = (s, name) => s === name || s.startsWith(`${name}@`);
@@ -42880,7 +42879,7 @@ function isWorkitPlugin(value) {
42880
42879
  const s = String(value).replaceAll("\\", "/");
42881
42880
  const url = s.startsWith("file://") || s.startsWith("git+file://");
42882
42881
  const pkgPath = s.includes("/packages/workit-opencode/") || s.includes("/packages/workit-cursor/") || s.includes("/node_modules/@brainervirus/workit-opencode/") || s.includes("/node_modules/@brainervirus/workit-cursor/");
42883
- return named(s, "workflow-toolkit") || named(s, "workflow-toolkit-opencode") || named(s, "local/workflow-toolkit") || named(s, "@brainervirus/workit-opencode") || named(s, "@brainervirus/workit-cursor") || url && pkgPath || s.startsWith("git+file://") && s.includes("workflow-toolkit");
42882
+ return named(s, "workflow-toolkit") || named(s, "workflow-toolkit-opencode") || named(s, "local/workflow-toolkit") || named(s, "workit") || named(s, "local/workit") || named(s, "@brainervirus/workit-opencode") || named(s, "@brainervirus/workit-cursor") || url && pkgPath || s.startsWith("git+file://") && s.includes("workflow-toolkit");
42884
42883
  }
42885
42884
  function mergeOpenCodePlugins(plugin, pin) {
42886
42885
  const existing = Array.isArray(plugin) ? plugin : typeof plugin === "string" ? [plugin] : [];
@@ -42911,7 +42910,8 @@ function mergeOpenCodeConfig(config, pin) {
42911
42910
  }
42912
42911
  function mergeCursorEnabledPlugins(enabled) {
42913
42912
  const prev = isRecord(enabled) ? { ...enabled } : {};
42914
- const next = { ...prev, "workflow-toolkit": true };
42913
+ const next = { ...prev, workit: true };
42914
+ delete next["workflow-toolkit"];
42915
42915
  delete next["local/workflow-toolkit"];
42916
42916
  const changed = JSON.stringify(next) !== JSON.stringify(prev) ? ["enabled_plugins"] : [];
42917
42917
  return { config: next, changed };
@@ -42922,10 +42922,12 @@ function mergeCursorPluginDirs(pluginDirs, pluginDir) {
42922
42922
  return path6.dirname(j) === j ? j : j.replace(/[\\/]+$/, "");
42923
42923
  };
42924
42924
  const normalized = strip(pluginDir);
42925
+ const legacy = strip(path6.join(path6.dirname(pluginDir), "workflow-toolkit"));
42925
42926
  const prev = Array.isArray(pluginDirs) ? pluginDirs.map(String) : [];
42926
- const exists = prev.some((d) => strip(d) === normalized);
42927
- const next = exists ? prev : [...prev, normalized];
42928
- const changed = next.length !== prev.length ? ["plugin_dirs"] : [];
42927
+ const kept = prev.filter((d) => strip(d) !== legacy);
42928
+ const exists = kept.some((d) => strip(d) === normalized);
42929
+ const next = exists ? kept : [...kept, normalized];
42930
+ const changed = JSON.stringify(next) !== JSON.stringify(prev) ? ["plugin_dirs"] : [];
42929
42931
  return { config: next, changed };
42930
42932
  }
42931
42933
  function mergeCursorSettings(settings, pluginDir) {
@@ -42952,35 +42954,34 @@ function mergeCursorMcp(mcp, serverName, server) {
42952
42954
  base2.mcpServers = servers;
42953
42955
  return { config: base2, changed };
42954
42956
  }
42955
- function cursorMcpServerEntry(packageDir) {
42956
- if (existsSync6(path6.join(packageDir, "dist", "mcp-server.js"))) {
42957
- return {
42958
- command: "node",
42959
- args: [path6.join(packageDir, "dist", "mcp-server.js"), "${workspaceFolder}"]
42960
- };
42961
- }
42957
+ function cursorMcpServerEntry(_packageDir) {
42962
42958
  return {
42963
- command: "bash",
42964
- args: [path6.join(packageDir, "mcp", "run-server.sh"), "${workspaceFolder}"]
42959
+ command: "npx",
42960
+ args: [
42961
+ "-y",
42962
+ "--package=@brainervirus/workit-cursor@latest",
42963
+ "workit-cursor-mcp",
42964
+ "${workspaceFolder}"
42965
+ ]
42965
42966
  };
42966
42967
  }
42967
42968
 
42968
42969
  // packages/workit-core/src/core/doctor.ts
42969
42970
  import { spawnSync as spawnSync2 } from "node:child_process";
42970
- import { existsSync as existsSync8, mkdirSync as mkdirSync2, readFileSync as readFileSync7, statSync as statSync2, unlinkSync, writeFileSync as writeFileSync4 } from "node:fs";
42971
+ import { existsSync as existsSync7, mkdirSync as mkdirSync2, readFileSync as readFileSync7, statSync as statSync2, unlinkSync, writeFileSync as writeFileSync4 } from "node:fs";
42971
42972
  import os4 from "node:os";
42972
42973
  import path8 from "node:path";
42973
42974
 
42974
42975
  // packages/workit-core/src/core/support-matrix.ts
42975
42976
  var SUPPORT_MATRIX = {
42976
42977
  bun: "1.3.14",
42977
- node: { minimum: "20", current: "22" },
42978
+ node: { minimum: "22", current: "22" },
42978
42979
  opencode: { minimum: "1.15.0", current: "1.17.7" },
42979
42980
  os: ["ubuntu-latest", "macos-latest", "windows-latest"]
42980
42981
  };
42981
42982
 
42982
42983
  // packages/workit-core/src/core/skill-manifests.ts
42983
- import { existsSync as existsSync7, readFileSync as readFileSync6, readdirSync as readdirSync2, statSync } from "node:fs";
42984
+ import { existsSync as existsSync6, readFileSync as readFileSync6, readdirSync as readdirSync2, statSync } from "node:fs";
42984
42985
  import path7 from "node:path";
42985
42986
  var CANONICAL_SKILLS = {
42986
42987
  superpowers: [
@@ -43014,7 +43015,7 @@ var CANONICAL_SKILLS = {
43014
43015
  "wk-verify"
43015
43016
  ]
43016
43017
  };
43017
- var skillManifestNames = (root) => existsSync7(root) ? readdirSync2(root).filter((name) => existsSync7(path7.join(root, name, "SKILL.md"))).sort() : [];
43018
+ var skillManifestNames = (root) => existsSync6(root) ? readdirSync2(root).filter((name) => existsSync6(path7.join(root, name, "SKILL.md"))).sort() : [];
43018
43019
  var validateSkillManifests = (root, expected, label) => {
43019
43020
  const actual = skillManifestNames(root);
43020
43021
  const missing = expected.filter((name) => !actual.includes(name));
@@ -43050,7 +43051,7 @@ var TOKEN_PLACEHOLDER = "YOUR_TOKEN_HERE";
43050
43051
  var findDevFromCwd = (cwd2) => {
43051
43052
  let dir = path8.resolve(cwd2);
43052
43053
  while (true) {
43053
- if (existsSync8(path8.join(dir, "packages", "workit-core", "package.json")))
43054
+ if (existsSync7(path8.join(dir, "packages", "workit-core", "package.json")))
43054
43055
  return dir;
43055
43056
  const parent = path8.dirname(dir);
43056
43057
  if (parent === dir)
@@ -43075,7 +43076,7 @@ var resolve = (options) => {
43075
43076
  opencodeConfig: options.opencodeConfig ?? path8.join(home, ".config", "opencode", "opencode.json"),
43076
43077
  cursorSettings: options.cursorSettings ?? path8.join(home, ".cursor", "settings.json"),
43077
43078
  cursorMcp: options.cursorMcp ?? path8.join(home, ".cursor", "mcp.json"),
43078
- cursorPluginDir: options.cursorPluginDir ?? path8.join(home, ".cursor", "plugins", "local", "workflow-toolkit"),
43079
+ cursorPluginDir: options.cursorPluginDir ?? path8.join(home, ".cursor", "plugins", "local", "workit"),
43079
43080
  env: env3,
43080
43081
  installer: options.installer ?? false
43081
43082
  };
@@ -43136,14 +43137,14 @@ var semverAtLeast = (version, min2) => {
43136
43137
  return true;
43137
43138
  };
43138
43139
  var resolveBun = (env3) => {
43139
- if (env3.BUN && existsSync8(env3.BUN))
43140
+ if (env3.BUN && existsSync7(env3.BUN))
43140
43141
  return env3.BUN;
43141
43142
  for (const candidate of [
43142
43143
  path8.join(os4.homedir(), ".bun/bin/bun"),
43143
43144
  "/usr/local/bin/bun",
43144
43145
  "/usr/bin/bun"
43145
43146
  ]) {
43146
- if (existsSync8(candidate))
43147
+ if (existsSync7(candidate))
43147
43148
  return candidate;
43148
43149
  }
43149
43150
  return commandOnPath("bun", env3) ? "bun" : null;
@@ -43253,7 +43254,6 @@ var assetPathsFor = (host, dev) => {
43253
43254
  return [
43254
43255
  path8.join(pkg, "assets", "templates", "spec-template.md"),
43255
43256
  path8.join(pkg, "mcp.json"),
43256
- path8.join(pkg, "marketplace.json"),
43257
43257
  path8.join(pkg, ".cursor-plugin")
43258
43258
  ];
43259
43259
  case "cli":
@@ -43263,7 +43263,7 @@ var assetPathsFor = (host, dev) => {
43263
43263
  var hostsFor = (host) => host === "cli" ? ["opencode", "cursor", "cli"] : [host];
43264
43264
  var checkAssets = (res) => {
43265
43265
  const dev = res.dev;
43266
- const missing = dev ? hostsFor(res.host).flatMap((h) => assetPathsFor(h, dev).filter((p) => !existsSync8(p)).map((p) => `${h}: ${p}`)) : [];
43266
+ const missing = dev ? hostsFor(res.host).flatMap((h) => assetPathsFor(h, dev).filter((p) => !existsSync7(p)).map((p) => `${h}: ${p}`)) : [];
43267
43267
  if (res.host === "cursor" || res.host === "cli") {
43268
43268
  const cursorError = validateCursorSkills(res.cursorPluginDir);
43269
43269
  if (cursorError)
@@ -43297,11 +43297,8 @@ var launcherSlotsFor = (host, dev) => {
43297
43297
  return [[path8.join(pkg, "src", "plugin.ts"), path8.join(pkg, "dist", "plugin.js")]];
43298
43298
  case "cursor":
43299
43299
  return [
43300
- [path8.join(pkg, "dist", "mcp-server.js"), path8.join(pkg, "mcp", "run-server.sh")],
43301
- [
43302
- path8.join(pkg, "dist", "cursor-session-start.js"),
43303
- path8.join(pkg, "hooks", "session-start")
43304
- ]
43300
+ [path8.join(pkg, "dist", "mcp-server.js")],
43301
+ [path8.join(pkg, "dist", "cursor-session-start.js")]
43305
43302
  ];
43306
43303
  case "cli":
43307
43304
  return [[path8.join(pkg, "src", "index.tsx"), path8.join(pkg, "dist", "index.js")]];
@@ -43320,7 +43317,7 @@ var validNodeEntry = (entry, runtime, env3) => {
43320
43317
  }
43321
43318
  };
43322
43319
  var registeredCursorLauncher = (res) => {
43323
- if (!existsSync8(res.cursorMcp))
43320
+ if (!existsSync7(res.cursorMcp))
43324
43321
  return "invalid";
43325
43322
  const config = readJson(res.cursorMcp);
43326
43323
  if (!config)
@@ -43334,9 +43331,19 @@ var registeredCursorLauncher = (res) => {
43334
43331
  return "invalid";
43335
43332
  }
43336
43333
  const executable = path8.basename(command).toLowerCase();
43334
+ if (executable === "npx" || executable === "npx.exe" || executable === "npx.cmd") {
43335
+ if (args[0] !== "-y")
43336
+ return "invalid";
43337
+ if (args[1] !== "--package=@brainervirus/workit-cursor@latest")
43338
+ return "invalid";
43339
+ if (args[2] !== "workit-cursor-mcp")
43340
+ return "invalid";
43341
+ return { kind: "npx" };
43342
+ }
43337
43343
  if (executable !== "node" && executable !== "node.exe")
43338
43344
  return "invalid";
43339
43345
  return {
43346
+ kind: "node",
43340
43347
  runtime: command,
43341
43348
  entry: path8.isAbsolute(args[0]) ? args[0] : path8.resolve(path8.dirname(res.cursorMcp), args[0])
43342
43349
  };
@@ -43345,17 +43352,17 @@ var checkLauncher = (res) => {
43345
43352
  const dev = res.dev;
43346
43353
  const hosts = hostsFor(res.host);
43347
43354
  const registered = hosts.includes("cursor") ? registeredCursorLauncher(res) : null;
43348
- const runtime = registered && registered !== "invalid" ? registered.runtime : "node";
43355
+ const runtime = registered && registered !== "invalid" && registered.kind === "node" ? registered.runtime : "node";
43349
43356
  const missing = hosts.includes("cursor") ? ["dist/mcp-server.js", "dist/cursor-session-start.js"].map((rel) => path8.join(res.cursorPluginDir, rel)).filter((p) => !validNodeEntry(p, runtime, res.env)).map((p) => `cursor: ${p}`) : [];
43350
43357
  if (hosts.includes("cursor")) {
43351
43358
  if (registered === "invalid") {
43352
43359
  missing.push(`cursor: canonical workit MCP launcher in ${res.cursorMcp}`);
43353
- } else if (registered && !validNodeEntry(registered.entry, registered.runtime, res.env)) {
43360
+ } else if (registered && registered.kind === "node" && !validNodeEntry(registered.entry, registered.runtime, res.env)) {
43354
43361
  missing.push(`cursor: registered ${registered.entry}`);
43355
43362
  }
43356
43363
  }
43357
43364
  if (dev) {
43358
- missing.push(...hosts.filter((h) => h !== "cursor").flatMap((h) => launcherSlotsFor(h, dev).filter((slot) => !slot.some((p) => existsSync8(p))).map((slot) => `${h}: ${slot.join(" or ")}`)));
43365
+ missing.push(...hosts.filter((h) => h !== "cursor").flatMap((h) => launcherSlotsFor(h, dev).filter((slot) => !slot.some((p) => existsSync7(p))).map((slot) => `${h}: ${slot.join(" or ")}`)));
43359
43366
  }
43360
43367
  if (missing.length > 0) {
43361
43368
  return {
@@ -43401,7 +43408,7 @@ var staleEntry = (entry) => {
43401
43408
  return "stale";
43402
43409
  if (entry.startsWith("file:")) {
43403
43410
  const target = entry.replace(/^file:\/\//, "").replace(/^file:/, "");
43404
- return existsSync8(target) ? "ok" : "missing-file";
43411
+ return existsSync7(target) ? "ok" : "missing-file";
43405
43412
  }
43406
43413
  return "ok";
43407
43414
  };
@@ -43413,7 +43420,7 @@ var checkStalePin = (res) => {
43413
43420
  detail: "opencode pin not inspected on the cursor host"
43414
43421
  };
43415
43422
  }
43416
- if (!existsSync8(res.opencodeConfig)) {
43423
+ if (!existsSync7(res.opencodeConfig)) {
43417
43424
  return { id: "stale_pin", status: "pass", detail: "no opencode config — not registered" };
43418
43425
  }
43419
43426
  const cfg = readJson(res.opencodeConfig);
@@ -43449,13 +43456,13 @@ var checkDuplicateRegistration = (res) => {
43449
43456
  const problems = [];
43450
43457
  const opencodeHost = res.host !== "cursor";
43451
43458
  const cursorHost = res.host !== "opencode";
43452
- if (opencodeHost && existsSync8(res.opencodeConfig)) {
43459
+ if (opencodeHost && existsSync7(res.opencodeConfig)) {
43453
43460
  const cfg = readJson(res.opencodeConfig);
43454
43461
  const entries = pluginEntries(cfg);
43455
43462
  if (entries.length > 1)
43456
43463
  problems.push(`opencode registers ${entries.length} workit plugin entries`);
43457
43464
  }
43458
- if (cursorHost && existsSync8(res.cursorSettings)) {
43465
+ if (cursorHost && existsSync7(res.cursorSettings)) {
43459
43466
  const settings = readJson(res.cursorSettings);
43460
43467
  const enabled = settings?.enabled_plugins;
43461
43468
  if (enabled && typeof enabled === "object") {
@@ -43463,11 +43470,14 @@ var checkDuplicateRegistration = (res) => {
43463
43470
  if (keys2.length > 1)
43464
43471
  problems.push(`cursor enables ${keys2.length} workit plugin identities (${keys2.join(", ")})`);
43465
43472
  }
43466
- const dirs = Array.isArray(settings?.plugin_dirs) ? settings.plugin_dirs.map(String).filter((d) => isWorkitPlugin(d) || d.includes("workflow-toolkit")) : [];
43473
+ const dirs = Array.isArray(settings?.plugin_dirs) ? settings.plugin_dirs.map(String).filter((d) => {
43474
+ const n = d.replaceAll("\\", "/").replace(/\/+$/, "");
43475
+ return isWorkitPlugin(d) || n.endsWith("local/workit") || n.endsWith("local/workflow-toolkit");
43476
+ }) : [];
43467
43477
  if (dirs.length > 1)
43468
43478
  problems.push(`cursor plugin_dirs has ${dirs.length} workit entries`);
43469
43479
  }
43470
- if (cursorHost && existsSync8(res.cursorMcp)) {
43480
+ if (cursorHost && existsSync7(res.cursorMcp)) {
43471
43481
  const mcp = readJson(res.cursorMcp);
43472
43482
  const servers = mcp?.mcpServers ?? {};
43473
43483
  if (servers && typeof servers === "object") {
@@ -43490,16 +43500,16 @@ var checkMalformedConfig = (res) => {
43490
43500
  const files = [];
43491
43501
  for (const name of ["config.json", "youtrack.json", "vcs.json", "workspaces.json"]) {
43492
43502
  const p = path8.join(res.configDir, name);
43493
- if (existsSync8(p))
43503
+ if (existsSync7(p))
43494
43504
  files.push(p);
43495
43505
  }
43496
43506
  const opencodeHost = res.host !== "cursor";
43497
43507
  const cursorHost = res.host !== "opencode";
43498
- if (opencodeHost && existsSync8(res.opencodeConfig))
43508
+ if (opencodeHost && existsSync7(res.opencodeConfig))
43499
43509
  files.push(res.opencodeConfig);
43500
- if (cursorHost && existsSync8(res.cursorSettings))
43510
+ if (cursorHost && existsSync7(res.cursorSettings))
43501
43511
  files.push(res.cursorSettings);
43502
- if (cursorHost && existsSync8(res.cursorMcp))
43512
+ if (cursorHost && existsSync7(res.cursorMcp))
43503
43513
  files.push(res.cursorMcp);
43504
43514
  const bad = files.filter((p) => !parsesAsConfigObject(p));
43505
43515
  if (bad.length === 0)
@@ -43513,7 +43523,7 @@ var checkMalformedConfig = (res) => {
43513
43523
  };
43514
43524
  var checkWorkspaceMismatch = (res) => {
43515
43525
  const file = path8.join(res.configDir, "workspaces.json");
43516
- if (!existsSync8(file))
43526
+ if (!existsSync7(file))
43517
43527
  return { id: "workspace_mismatch", status: "pass", detail: "no workspaces configured" };
43518
43528
  const ws = readJson(file);
43519
43529
  if (!Array.isArray(ws?.workspaces)) {
@@ -43545,7 +43555,7 @@ var checkCredentialMetadata = (res) => {
43545
43555
  const youtrackJson = readJson(path8.join(res.configDir, "youtrack.json"));
43546
43556
  if (youtrackJson) {
43547
43557
  tokenPaths.push(typeof youtrackJson.tokenFile === "string" ? youtrackJson.tokenFile : path8.join(res.configDir, "youtrack.token"));
43548
- } else if (existsSync8(path8.join(res.configDir, "youtrack.token"))) {
43558
+ } else if (existsSync7(path8.join(res.configDir, "youtrack.token"))) {
43549
43559
  tokenPaths.push(path8.join(res.configDir, "youtrack.token"));
43550
43560
  }
43551
43561
  const vcsJson = readJson(path8.join(res.configDir, "vcs.json"));
@@ -43554,7 +43564,7 @@ var checkCredentialMetadata = (res) => {
43554
43564
  if (provider && typeof provider === "object") {
43555
43565
  const tf = provider.tokenFile;
43556
43566
  tokenPaths.push(typeof tf === "string" ? tf : path8.join(res.configDir, `${key}.token`));
43557
- } else if (!vcsJson && existsSync8(path8.join(res.configDir, `${key}.token`))) {
43567
+ } else if (!vcsJson && existsSync7(path8.join(res.configDir, `${key}.token`))) {
43558
43568
  tokenPaths.push(path8.join(res.configDir, `${key}.token`));
43559
43569
  }
43560
43570
  }
@@ -43564,7 +43574,7 @@ var checkCredentialMetadata = (res) => {
43564
43574
  const problems = [];
43565
43575
  for (const raw of tokenPaths) {
43566
43576
  const p = path8.isAbsolute(raw) ? raw : path8.resolve(res.configDir, raw);
43567
- if (!existsSync8(p)) {
43577
+ if (!existsSync7(p)) {
43568
43578
  problems.push(`${p} is missing`);
43569
43579
  continue;
43570
43580
  }
@@ -43830,7 +43840,7 @@ function buildSetupPreview(values2, opts = {}) {
43830
43840
  path: ytPath,
43831
43841
  value: draft
43832
43842
  });
43833
- if (existsSync9(tokenPath))
43843
+ if (existsSync8(tokenPath))
43834
43844
  preserved.push(tokenPath);
43835
43845
  else
43836
43846
  mutations.push({
@@ -43869,7 +43879,7 @@ function buildSetupPreview(values2, opts = {}) {
43869
43879
  value: draft
43870
43880
  });
43871
43881
  const activeToken = values2.vcsProvider === "gitlab" ? gitlabToken : githubToken;
43872
- if (existsSync9(activeToken))
43882
+ if (existsSync8(activeToken))
43873
43883
  preserved.push(activeToken);
43874
43884
  else
43875
43885
  mutations.push({
@@ -43896,7 +43906,7 @@ function buildSetupPreview(values2, opts = {}) {
43896
43906
  if (values2.applyProject) {
43897
43907
  const root = path9.resolve(opts.cwd ?? process.cwd());
43898
43908
  const gitignorePath = path9.join(root, ".gitignore");
43899
- const existing = existsSync9(gitignorePath) ? readFileSync8(gitignorePath, "utf8") : "";
43909
+ const existing = existsSync8(gitignorePath) ? readFileSync8(gitignorePath, "utf8") : "";
43900
43910
  const existingLines = new Set(existing.split(`
43901
43911
  `).map((l) => l.trim()).filter(Boolean));
43902
43912
  const entries = GITIGNORE_ENTRIES.filter((e) => e.trim() !== "" && !existingLines.has(e.trim()));
@@ -43927,7 +43937,7 @@ var resolveSetupPaths = (options, configDir2) => {
43927
43937
  opencodeConfig: options.opencodeConfig ?? path9.join(home, ".config", "opencode", "opencode.json"),
43928
43938
  cursorSettings: options.cursorSettings ?? path9.join(home, ".cursor", "settings.json"),
43929
43939
  cursorMcp: options.cursorMcp ?? path9.join(home, ".cursor", "mcp.json"),
43930
- cursorPluginDir: options.cursorPluginDir ?? path9.join(home, ".cursor", "plugins", "local", "workflow-toolkit")
43940
+ cursorPluginDir: options.cursorPluginDir ?? path9.join(home, ".cursor", "plugins", "local", "workit")
43931
43941
  };
43932
43942
  };
43933
43943
  function resolveApply(preview, options) {
@@ -43943,7 +43953,7 @@ var readExisting = (p) => {
43943
43953
  try {
43944
43954
  raw = readFileSync8(p, "utf8");
43945
43955
  } catch {
43946
- if (existsSync9(p))
43956
+ if (existsSync8(p))
43947
43957
  return { kind: "malformed", error: `${p} is not readable` };
43948
43958
  return { kind: "missing" };
43949
43959
  }
@@ -44117,7 +44127,7 @@ function adapterRoot(platform2, res) {
44117
44127
  var opencodePin = (root) => {
44118
44128
  for (const rel of ["src/plugin.ts", "dist/plugin.js"]) {
44119
44129
  const entry = path9.join(root, rel);
44120
- if (existsSync9(entry))
44130
+ if (existsSync8(entry))
44121
44131
  return `file://${entry}`;
44122
44132
  }
44123
44133
  return null;
@@ -44165,15 +44175,11 @@ function applyOpenCode(root, res) {
44165
44175
  detail: `pinned ${pin}`
44166
44176
  };
44167
44177
  }
44168
- var cursorMcpManifest = (dir) => JSON.stringify({ mcpServers: { workit: cursorMcpServerEntry(dir) } }, null, 2) + `
44169
- `;
44170
44178
  var samePluginContent = (src, dest, relative = "") => {
44171
44179
  try {
44172
44180
  for (const entry of readdirSync3(src, { withFileTypes: true })) {
44173
44181
  if (entry.name === "node_modules")
44174
44182
  continue;
44175
- if (relative === "" && entry.name === "mcp.json")
44176
- continue;
44177
44183
  const source = path9.join(src, entry.name);
44178
44184
  const installed = path9.join(dest, entry.name);
44179
44185
  if (entry.isDirectory()) {
@@ -44184,15 +44190,13 @@ var samePluginContent = (src, dest, relative = "") => {
44184
44190
  }
44185
44191
  }
44186
44192
  for (const entry of readdirSync3(dest, { withFileTypes: true })) {
44187
- if (existsSync9(path9.join(src, entry.name)))
44193
+ if (existsSync8(path9.join(src, entry.name)))
44188
44194
  continue;
44189
44195
  const rel = path9.join(relative, entry.name);
44190
44196
  if (rel === ".workflow-toolkit-root")
44191
44197
  continue;
44192
44198
  if (relative === "rules" && entry.isFile() && entry.name.endsWith(".mdc"))
44193
44199
  continue;
44194
- if (relative === "" && entry.name === "mcp.json")
44195
- continue;
44196
44200
  return false;
44197
44201
  }
44198
44202
  return true;
@@ -44208,7 +44212,7 @@ var preservedCursorRules = (src, dest) => {
44208
44212
  for (const entry of readdirSync3(installedRules, { withFileTypes: true })) {
44209
44213
  if (!entry.isFile() || !entry.name.endsWith(".mdc"))
44210
44214
  continue;
44211
- if (existsSync9(path9.join(sourceRules, entry.name)))
44215
+ if (existsSync8(path9.join(sourceRules, entry.name)))
44212
44216
  continue;
44213
44217
  preserved.set(entry.name, readFileSync8(path9.join(installedRules, entry.name)));
44214
44218
  }
@@ -44217,10 +44221,10 @@ var preservedCursorRules = (src, dest) => {
44217
44221
  };
44218
44222
  function copyPluginDir(src, dest) {
44219
44223
  const marker = path9.join(dest, ".workflow-toolkit-root");
44220
- const synced = readFileSafe(marker)?.trim() === src && samePluginContent(src, dest) && readFileSafe(path9.join(dest, "mcp.json")) === cursorMcpManifest(dest);
44224
+ const synced = readFileSafe(marker)?.trim() === src && samePluginContent(src, dest);
44221
44225
  if (synced)
44222
44226
  return "Skipped";
44223
- const hadDir = existsSync9(dest);
44227
+ const hadDir = existsSync8(dest);
44224
44228
  const rules = preservedCursorRules(src, dest);
44225
44229
  const parent = path9.dirname(dest);
44226
44230
  mkdirSync3(parent, { recursive: true });
@@ -44238,11 +44242,6 @@ function copyPluginDir(src, dest) {
44238
44242
  }
44239
44243
  writeFileSync5(path9.join(stage, ".workflow-toolkit-root"), src + `
44240
44244
  `, "utf8");
44241
- for (const rel of ["hooks/session-start", "mcp/run-server.sh"]) {
44242
- try {
44243
- chmodSync(path9.join(stage, rel), 493);
44244
- } catch {}
44245
- }
44246
44245
  if (!samePluginContent(src, stage))
44247
44246
  throw new Error("staged adapter content is incomplete");
44248
44247
  if (hadDir)
@@ -44250,17 +44249,35 @@ function copyPluginDir(src, dest) {
44250
44249
  try {
44251
44250
  renameSync(stage, dest);
44252
44251
  } catch (error) {
44253
- if (hadDir && !existsSync9(dest))
44252
+ if (hadDir && !existsSync8(dest))
44254
44253
  renameSync(backup, dest);
44255
44254
  throw error;
44256
44255
  }
44257
- writeFileSync5(path9.join(dest, "mcp.json"), cursorMcpManifest(dest), "utf8");
44258
44256
  rmSync(backup, { recursive: true, force: true });
44259
44257
  } finally {
44260
44258
  rmSync(swap, { recursive: true, force: true });
44261
44259
  }
44262
44260
  return hadDir ? "Configured" : "Installed";
44263
44261
  }
44262
+ var removeLegacyCursorDir = (res) => {
44263
+ const legacy = path9.join(res.home, ".cursor", "plugins", "local", "workflow-toolkit");
44264
+ if (legacy === res.cursorPluginDir || !existsSync8(legacy))
44265
+ return;
44266
+ const legacyRules = path9.join(legacy, "rules");
44267
+ if (existsSync8(legacyRules)) {
44268
+ try {
44269
+ mkdirSync3(path9.join(res.cursorPluginDir, "rules"), { recursive: true });
44270
+ for (const entry of readdirSync3(legacyRules, { withFileTypes: true })) {
44271
+ if (!entry.isFile() || !entry.name.endsWith(".mdc"))
44272
+ continue;
44273
+ const target = path9.join(res.cursorPluginDir, "rules", entry.name);
44274
+ if (!existsSync8(target))
44275
+ copyFileSync2(path9.join(legacyRules, entry.name), target);
44276
+ }
44277
+ } catch {}
44278
+ }
44279
+ rmSync(legacy, { recursive: true, force: true });
44280
+ };
44264
44281
  function applyCursorSettings(root, res) {
44265
44282
  mkdirSync3(path9.dirname(res.cursorSettings), { recursive: true });
44266
44283
  const settingsExisting = readExisting(res.cursorSettings);
@@ -44318,9 +44335,9 @@ function applyCursorMcp(root, res) {
44318
44335
  };
44319
44336
  }
44320
44337
  function verifyPlatform(platform2, res) {
44321
- if (platform2 === "opencode" && !existsSync9(res.opencodeConfig))
44338
+ if (platform2 === "opencode" && !existsSync8(res.opencodeConfig))
44322
44339
  return null;
44323
- if (platform2 === "cursor" && !existsSync9(res.cursorSettings) && !existsSync9(res.cursorMcp)) {
44340
+ if (platform2 === "cursor" && !existsSync8(res.cursorSettings) && !existsSync8(res.cursorMcp)) {
44324
44341
  return null;
44325
44342
  }
44326
44343
  return runDoctor({
@@ -44364,6 +44381,7 @@ function applySetupPreview(preview, options = {}) {
44364
44381
  const res = resolveApply(preview, options);
44365
44382
  const entries = [];
44366
44383
  const rootFor = new Map;
44384
+ let cursorCopyOk = false;
44367
44385
  for (const mutation of preview.mutations) {
44368
44386
  if (mutation.type !== "register-platform" && mutation.type !== "install-adapter") {
44369
44387
  entries.push(applyMutation(mutation));
@@ -44396,7 +44414,9 @@ function applySetupPreview(preview, options = {}) {
44396
44414
  }
44397
44415
  if (mutation.type === "install-adapter") {
44398
44416
  try {
44399
- entries.push({ platform: platform2, file: mutation.path, status: copyPluginDir(root, mutation.path) });
44417
+ const status = copyPluginDir(root, mutation.path);
44418
+ cursorCopyOk = true;
44419
+ entries.push({ platform: platform2, file: mutation.path, status });
44400
44420
  } catch (error) {
44401
44421
  entries.push({
44402
44422
  platform: platform2,
@@ -44407,10 +44427,15 @@ function applySetupPreview(preview, options = {}) {
44407
44427
  }
44408
44428
  } else if (platform2 === "opencode") {
44409
44429
  entries.push(applyOpenCode(root, res));
44430
+ } else if (!cursorCopyOk) {
44431
+ continue;
44410
44432
  } else {
44411
44433
  entries.push(mutation.path === res.cursorSettings ? applyCursorSettings(root, res) : applyCursorMcp(root, res));
44412
44434
  }
44413
44435
  }
44436
+ if (preview.platforms.includes("cursor") && cursorCopyOk && !entries.some((e) => e.platform === "cursor" && e.status === "Failed")) {
44437
+ removeLegacyCursorDir(res);
44438
+ }
44414
44439
  for (const preserved of preview.preserved) {
44415
44440
  entries.push({
44416
44441
  platform: "core",
@@ -44558,8 +44583,9 @@ function applyWizardBranchPolicy(branchPolicy, workspace_root, env3 = process.en
44558
44583
  }
44559
44584
 
44560
44585
  // packages/workit-cli/src/wizard-state.ts
44561
- import { existsSync as existsSync10 } from "node:fs";
44586
+ import { existsSync as existsSync9 } from "node:fs";
44562
44587
  import path10 from "node:path";
44588
+ import { isDeepStrictEqual as isDeepStrictEqual3 } from "node:util";
44563
44589
  var NEXT = {
44564
44590
  platforms: "locale",
44565
44591
  locale: "timezone",
@@ -44603,7 +44629,7 @@ var PREV = {
44603
44629
  exit: null
44604
44630
  };
44605
44631
  var skipsCustomBranch = (screen, preset) => (screen === "branchAllowed" || screen === "branchProtected") && preset !== "custom";
44606
- var skipsBranchPolicy = (screen) => (screen === "branchPolicy" || screen === "branchPolicyDevelop") && !existsSync10(path10.join(process.env.WORKFLOW_WORKSPACE_ROOT ?? process.cwd(), ".git"));
44632
+ var skipsBranchPolicy = (screen) => (screen === "branchPolicy" || screen === "branchPolicyDevelop") && !existsSync9(path10.join(process.env.WORKFLOW_WORKSPACE_ROOT ?? process.cwd(), ".git"));
44607
44633
  function nextScreen(screen, preset) {
44608
44634
  let next = NEXT[screen];
44609
44635
  while (next && (skipsCustomBranch(next, preset) || skipsBranchPolicy(next)))
@@ -44660,6 +44686,9 @@ var decodeBranchPreset = (value, fallback) => value === "gitflow" || value === "
44660
44686
  var decodeVcsProvider = (value, fallback) => value === "gitlab" || value === "github" || value === "skip" ? value : fallback;
44661
44687
  function setTextValue(draft, field, value) {
44662
44688
  const message = field === "locale" ? validateLocale(value) : field === "timezone" ? validateTimezone(value) : field === "branchAllowed" ? parseList(value).length > 0 ? null : "at least one allowed branch pattern is required" : field === "branchProtected" ? parseList(value).length > 0 ? null : "at least one protected branch name is required" : value.trim() === "" ? null : validateBaseUrl(value);
44689
+ if (draft.values[field] === value && (draft.errors[field] ?? undefined) === (message ?? undefined)) {
44690
+ return draft;
44691
+ }
44663
44692
  const errors = { ...draft.errors };
44664
44693
  if (message)
44665
44694
  errors[field] = message;
@@ -44694,24 +44723,24 @@ function reducer3(draft, action) {
44694
44723
  case "set":
44695
44724
  switch (action.field) {
44696
44725
  case "platforms":
44726
+ if (isDeepStrictEqual3(action.value, draft.values.platforms))
44727
+ return draft;
44697
44728
  return { ...draft, values: { ...draft.values, platforms: action.value } };
44698
- case "branchPreset":
44699
- return {
44700
- ...draft,
44701
- values: {
44702
- ...draft.values,
44703
- branchPreset: decodeBranchPreset(action.value, draft.values.branchPreset)
44704
- }
44705
- };
44706
- case "vcsProvider":
44707
- return {
44708
- ...draft,
44709
- values: {
44710
- ...draft.values,
44711
- vcsProvider: decodeVcsProvider(action.value, draft.values.vcsProvider)
44712
- }
44713
- };
44729
+ case "branchPreset": {
44730
+ const next = decodeBranchPreset(action.value, draft.values.branchPreset);
44731
+ if (next === draft.values.branchPreset)
44732
+ return draft;
44733
+ return { ...draft, values: { ...draft.values, branchPreset: next } };
44734
+ }
44735
+ case "vcsProvider": {
44736
+ const next = decodeVcsProvider(action.value, draft.values.vcsProvider);
44737
+ if (next === draft.values.vcsProvider)
44738
+ return draft;
44739
+ return { ...draft, values: { ...draft.values, vcsProvider: next } };
44740
+ }
44714
44741
  case "applyProject":
44742
+ if (action.value === draft.values.applyProject)
44743
+ return draft;
44715
44744
  return { ...draft, values: { ...draft.values, applyProject: action.value } };
44716
44745
  case "branchPolicyDetected":
44717
44746
  return { ...draft, values: { ...draft.values, branchPolicyDetected: action.value } };
@@ -44732,6 +44761,8 @@ function reducer3(draft, action) {
44732
44761
  const base2 = draft.values.branchPolicy ?? draft.values.branchPolicyDetected;
44733
44762
  if (!base2)
44734
44763
  return draft;
44764
+ if (action.value === base2.integration)
44765
+ return draft;
44735
44766
  return {
44736
44767
  ...draft,
44737
44768
  values: {
@@ -44748,11 +44779,14 @@ function reducer3(draft, action) {
44748
44779
  const base2 = draft.values.branchPolicy ?? draft.values.branchPolicyDetected;
44749
44780
  if (!base2)
44750
44781
  return draft;
44782
+ const next = action.value || undefined;
44783
+ if ((base2.developBranch ?? undefined) === next)
44784
+ return draft;
44751
44785
  return {
44752
44786
  ...draft,
44753
44787
  values: {
44754
44788
  ...draft.values,
44755
- branchPolicy: { ...base2, developBranch: action.value || undefined }
44789
+ branchPolicy: { ...base2, developBranch: next }
44756
44790
  }
44757
44791
  };
44758
44792
  }
@@ -44874,6 +44908,9 @@ function defaultWorkspaceProvider(vcs) {
44874
44908
  function workspaceDraftText(draft, field, value) {
44875
44909
  const errorKey = field === "name" ? "workspaceName" : "workspaceGlob";
44876
44910
  const message = field === "name" ? value.trim() ? null : "workspace name is required" : value.trim() ? null : "workspace pattern is required";
44911
+ if ((draft.workspaceDraft?.[field] ?? "") === value && (draft.errors[errorKey] ?? undefined) === (message ?? undefined)) {
44912
+ return draft;
44913
+ }
44877
44914
  const errors = { ...draft.errors };
44878
44915
  if (message)
44879
44916
  errors[errorKey] = message;
@@ -46124,8 +46161,12 @@ var createLogger = (options = {}) => {
46124
46161
  // packages/workit-cli/src/index.tsx
46125
46162
  var jsx_dev_runtime2 = __toESM(require_jsx_dev_runtime(), 1);
46126
46163
  var logger = createLogger({
46127
- stderr: (event) => process.stderr.write(`${JSON.stringify(event)}
46128
- `)
46164
+ stderr: (event) => {
46165
+ if (event.level === "debug" || event.level === "info")
46166
+ return;
46167
+ process.stderr.write(`${JSON.stringify(event)}
46168
+ `);
46169
+ }
46129
46170
  });
46130
46171
  var HELP = `workit — workflow rails for agentic coding
46131
46172
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brainervirus/workit-cli",
3
- "version": "0.7.1",
3
+ "version": "0.8.0",
4
4
  "private": false,
5
5
  "description": "Workit interactive setup wizard (Ink TUI)",
6
6
  "keywords": [
@@ -34,14 +34,14 @@
34
34
  "typecheck": "tsc --noEmit"
35
35
  },
36
36
  "dependencies": {
37
- "@brainervirus/workit-core": "^0.7.1",
38
- "@brainervirus/workit-cursor": "^0.7.1",
39
- "@brainervirus/workit-opencode": "^0.7.1",
37
+ "@brainervirus/workit-core": "^0.8.0",
38
+ "@brainervirus/workit-cursor": "^0.8.0",
39
+ "@brainervirus/workit-opencode": "^0.8.0",
40
40
  "@inkjs/ui": "2.0.0",
41
41
  "ink": "7.1.1",
42
42
  "react": "19.2.8"
43
43
  },
44
44
  "engines": {
45
- "node": ">=20"
45
+ "node": ">=22"
46
46
  }
47
47
  }