@difflab/difflab-cli 0.4.0 → 0.5.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 +3 -1
  2. package/dist/index.js +161 -54
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -57,7 +57,9 @@ difflab-cli templates scaffold spec-driven-plan docs/plans first-plan.md --cwd /
57
57
 
58
58
  The CLI defaults `--cwd` to its current directory. The MCP server exposes `template_list({})` and `scaffold({"name":"spec-driven-plan","cwd":"/absolute/project/path","path":"docs/plans","filename":"first-plan.md"})`. Both interfaces take a hyphenated template name, a relative destination **directory** (`path`, or `.` for `cwd`), and a separate **filename**. MCP always requires an absolute existing `cwd`. Missing destination directories are created. Available names: `spec-driven-plan`, `software-architecture-design`, `architecture-decision-record`, `product-requirements-document`, `code-review`, `planning-intent`, `ui-component-architecture`, and `pull-request-description` (adapted from Diffpi's PR template).
59
59
 
60
- On first use, missing bundled templates and filled examples are copied to `~/.difflab/templates` and `~/.difflab/templates/examples`. Edit the installed templates to customize future documents. Later runs add missing files but never replace existing ones, even after an upgrade. Examples are for reference, not selectable templates. Copying is literal: prompts and placeholders are not substituted. An unknown name, missing or invalid source, unsafe path, symlink, or existing destination causes an error; destination files are never overwritten. The target directory must remain inside `cwd` and the filename must be a single file name without separators. To restore a bundled default, move your installed copy aside before listing again.
60
+ On first use, missing bundled templates and filled examples are copied to `~/.difflab/templates` and `~/.difflab/templates/examples`. Edit the installed templates to customize future documents. Later runs add missing files but never replace existing ones, even after an upgrade. Examples are for reference, not selectable templates. Copying is literal: prompts and placeholders are not substituted. An unknown name, missing or invalid source, unsafe path, symlink, or existing destination causes an error; destination files are never overwritten. The target directory must remain inside `cwd` and the filename must be a single file name without separators. The one symlink exception is an initialized repository's `.difflab` link to its own user project store: `templates scaffold spec-driven-plan .difflab/plans/YYMMDD-name PLAN.md --cwd /absolute/repo` works in Git worktrees as well. To restore a bundled default, move your installed copy aside before listing again.
61
+
62
+ The portable `skills/difflab-plan` and `skills/difflab-review` directories contain natural-language agent workflows for these templates. Install each skill in your client's Agent Skills directory. The MCP-only `log_append({"path":"/absolute/physical/project-store/plans/YYMMDD-name/logs.txt","message":"Phase started"})` tool creates an append-only progress log on first use; pass the physical path, not the `.difflab` symlink. There is no log CLI command.
61
63
 
62
64
  ## Develop
63
65
 
package/dist/index.js CHANGED
@@ -12796,17 +12796,17 @@ var init_registry = __esm(() => {
12796
12796
  // ../../libs/difflab-ts/src/gitx.ts
12797
12797
  import { execFileSync } from "child_process";
12798
12798
  import { appendFile, readFile } from "fs/promises";
12799
- import { isAbsolute, join, resolve as resolve2 } from "path";
12799
+ import { isAbsolute, resolve as resolve2 } from "path";
12800
12800
  function gitRoot(cwd) {
12801
12801
  return runGit(cwd, ["rev-parse", "--show-toplevel"]);
12802
12802
  }
12803
12803
  function discoverGitRepository(cwd) {
12804
12804
  const root = gitRoot(cwd);
12805
- const gitDir = runGit(root, ["rev-parse", "--git-dir"]);
12805
+ const exclude = runGit(root, ["rev-parse", "--git-path", "info/exclude"]);
12806
12806
  return {
12807
12807
  root,
12808
12808
  originUrl: runGit(root, ["remote", "get-url", "origin"]),
12809
- excludePath: join(isAbsolute(gitDir) ? gitDir : resolve2(root, gitDir), "info", "exclude")
12809
+ excludePath: isAbsolute(exclude) ? exclude : resolve2(root, exclude)
12810
12810
  };
12811
12811
  }
12812
12812
  async function addPathToGitExcludes(path, entry) {
@@ -26485,9 +26485,9 @@ var init_dist3 = __esm(() => {
26485
26485
 
26486
26486
  // src/projects/config.ts
26487
26487
  import { lstat as lstat2, readFile as readFile2, writeFile } from "fs/promises";
26488
- import { join as join2 } from "path";
26488
+ import { join } from "path";
26489
26489
  function repoConfigPath(root = process.cwd()) {
26490
- return join2(root, "difflab.yaml");
26490
+ return join(root, "difflab.yaml");
26491
26491
  }
26492
26492
  async function readRepoConfig(root = process.cwd()) {
26493
26493
  const path = repoConfigPath(root);
@@ -26548,7 +26548,7 @@ var init_config = __esm(() => {
26548
26548
  import { realpathSync } from "fs";
26549
26549
  import { lstat as lstat3, mkdir } from "fs/promises";
26550
26550
  import { homedir } from "os";
26551
- import { join as join3, resolve as resolve3 } from "path";
26551
+ import { join as join2, resolve as resolve3 } from "path";
26552
26552
  function userPaths(home = process.env.HOME || homedir()) {
26553
26553
  let tilde;
26554
26554
  try {
@@ -26558,8 +26558,8 @@ function userPaths(home = process.env.HOME || homedir()) {
26558
26558
  throw error;
26559
26559
  tilde = resolve3(home);
26560
26560
  }
26561
- const root = join3(tilde, ".difflab");
26562
- return { root, projects: join3(root, "projects"), database: join3(root, "difflab.sqlite") };
26561
+ const root = join2(tilde, ".difflab");
26562
+ return { root, projects: join2(root, "projects"), database: join2(root, "difflab.sqlite") };
26563
26563
  }
26564
26564
  function createUserStore(home) {
26565
26565
  return {
@@ -26645,7 +26645,7 @@ var init_osx = () => {};
26645
26645
 
26646
26646
  // src/extensions/pathx.ts
26647
26647
  import { lstat as lstat4, realpath, stat } from "fs/promises";
26648
- import { isAbsolute as isAbsolute2, join as join4, relative, resolve as resolve4, sep } from "path";
26648
+ import { isAbsolute as isAbsolute2, join as join3, relative, resolve as resolve4, sep } from "path";
26649
26649
  async function resolvePath(cwd, path) {
26650
26650
  if (!isAbsolute2(cwd))
26651
26651
  throw new Error("cwd must be an absolute path to an existing directory");
@@ -26661,7 +26661,7 @@ async function resolvePath(cwd, path) {
26661
26661
  }
26662
26662
  let current = root;
26663
26663
  for (const segment of inside.split(sep)) {
26664
- current = join4(current, segment);
26664
+ current = join3(current, segment);
26665
26665
  try {
26666
26666
  if ((await lstat4(current)).isSymbolicLink()) {
26667
26667
  throw new Error(`Symlink paths are not allowed for files: ${current}`);
@@ -26732,7 +26732,7 @@ var init_fsx = __esm(() => {
26732
26732
 
26733
26733
  // src/projects/repo-store.ts
26734
26734
  import { mkdir as mkdir3, readlink, symlink } from "fs/promises";
26735
- import { dirname as dirname2, join as join5, resolve as resolve5 } from "path";
26735
+ import { dirname as dirname2, join as join4, resolve as resolve5 } from "path";
26736
26736
  async function preflightRepoStore(root, target) {
26737
26737
  for (const path of [root, dirname2(target)]) {
26738
26738
  const entry = await lstatOrNull(path);
@@ -26742,7 +26742,7 @@ async function preflightRepoStore(root, target) {
26742
26742
  const targetStat = await lstatOrNull(target);
26743
26743
  if (targetStat && !targetStat.isDirectory())
26744
26744
  throw new RepoStoreError(`Refusing existing target: ${target}`);
26745
- const link = join5(root, ".difflab");
26745
+ const link = join4(root, ".difflab");
26746
26746
  const entry = await lstatOrNull(link);
26747
26747
  if (entry) {
26748
26748
  if (!entry.isSymbolicLink())
@@ -26752,7 +26752,7 @@ async function preflightRepoStore(root, target) {
26752
26752
  }
26753
26753
  }
26754
26754
  async function checkRepoStore(root, target, excludePath) {
26755
- const link = join5(root, ".difflab");
26755
+ const link = join4(root, ".difflab");
26756
26756
  const entry = await lstatOrNull(link);
26757
26757
  if (!entry)
26758
26758
  throw new RepoStoreError(`Missing .difflab symlink at ${link}`);
@@ -26776,7 +26776,7 @@ async function ensureRepoStore(root, target, excludePath) {
26776
26776
  throw error;
26777
26777
  }
26778
26778
  }
26779
- const link = join5(root, ".difflab");
26779
+ const link = join4(root, ".difflab");
26780
26780
  if (!await lstatOrNull(link)) {
26781
26781
  try {
26782
26782
  await symlink(target, link);
@@ -26794,7 +26794,7 @@ var init_repo_store = __esm(() => {
26794
26794
  });
26795
26795
 
26796
26796
  // src/context.ts
26797
- import { join as join6 } from "path";
26797
+ import { join as join5 } from "path";
26798
26798
  async function readProjectContext(cwd = process.cwd(), home) {
26799
26799
  const git = discoverGitRepository(cwd);
26800
26800
  const config = await readRepoConfig(git.root);
@@ -26815,7 +26815,7 @@ async function getProjectContext(git, projectId, home) {
26815
26815
  const repository = project.repositories.find((item) => item.url === origin);
26816
26816
  if (!repository)
26817
26817
  throw new RepoStoreError("Repository is not registered in the selected project database");
26818
- const target = join6(userPaths(home).projects, project.id, repository.slug);
26818
+ const target = join5(userPaths(home).projects, project.id, repository.slug);
26819
26819
  await checkRepoStore(git.root, target);
26820
26820
  return {
26821
26821
  project: { id: project.id, name: project.name },
@@ -48181,14 +48181,100 @@ var init_tools = __esm(() => {
48181
48181
  ];
48182
48182
  });
48183
48183
 
48184
- // src/projects/tools.ts
48184
+ // src/extensions/zodx.ts
48185
48185
  import { isAbsolute as isAbsolute4 } from "path";
48186
+ var absolutePath, relativePath, fileName;
48187
+ var init_zodx = __esm(() => {
48188
+ init_zod();
48189
+ absolutePath = string2().refine(isAbsolute4, "path must be absolute");
48190
+ relativePath = string2().min(1).refine((value) => value.trim().length > 0 && !isAbsolute4(value), "path must be relative");
48191
+ fileName = string2().refine((value) => value.trim().length > 0 && value !== "." && value !== ".." && !/[/\\\0]/.test(value), "filename must be a single non-empty file name");
48192
+ });
48193
+
48194
+ // src/logs/store.ts
48195
+ import { constants } from "fs";
48196
+ import { lstat as lstat6, open as open2, realpath as realpath2 } from "fs/promises";
48197
+ import { basename, dirname as dirname5, isAbsolute as isAbsolute5, resolve as resolve8 } from "path";
48198
+ async function appendProgressLog(path, message, now = new Date) {
48199
+ if (!isAbsolute5(path) || resolve8(path) !== path || basename(path) !== "logs.txt") {
48200
+ throw new ProgressLogError("path must be an absolute, normalized path ending in logs.txt");
48201
+ }
48202
+ if (!message.trim() || /[\r\n\v\f\0\u0085\u2028\u2029]/.test(message) || Buffer.byteLength(message, "utf8") > 2048) {
48203
+ throw new ProgressLogError("message must be one non-empty line of at most 2048 bytes");
48204
+ }
48205
+ const parent = dirname5(path);
48206
+ if (await realpath2(parent) !== parent) {
48207
+ throw new ProgressLogError("path must not contain symlinked directories");
48208
+ }
48209
+ const pad = (value) => String(value).padStart(2, "0");
48210
+ const stamp = `${pad(now.getFullYear() % 100)}${pad(now.getMonth() + 1)}${pad(now.getDate())} ${pad(now.getHours())}:${pad(now.getMinutes())}:${pad(now.getSeconds())}`;
48211
+ const entry = `[${stamp}]: ${message}`;
48212
+ try {
48213
+ const existing = await lstat6(path);
48214
+ if (existing.isSymbolicLink())
48215
+ throw new ProgressLogError("logs.txt must not be a symlink");
48216
+ if (!existing.isFile())
48217
+ throw new ProgressLogError("logs.txt must be a regular file");
48218
+ } catch (error) {
48219
+ if (!hasErrorCode(error, "ENOENT"))
48220
+ throw error;
48221
+ }
48222
+ let handle;
48223
+ try {
48224
+ handle = await open2(path, constants.O_WRONLY | constants.O_APPEND | constants.O_CREAT | constants.O_NOFOLLOW, 384);
48225
+ } catch (error) {
48226
+ if (hasErrorCode(error, "ELOOP"))
48227
+ throw new ProgressLogError("logs.txt must not be a symlink");
48228
+ throw error;
48229
+ }
48230
+ try {
48231
+ if (!(await handle.stat()).isFile())
48232
+ throw new ProgressLogError("logs.txt must be a regular file");
48233
+ const line = Buffer.from(`${entry}
48234
+ `, "utf8");
48235
+ const { bytesWritten } = await handle.write(line);
48236
+ if (bytesWritten !== line.length)
48237
+ throw new ProgressLogError("Could not append the full log line");
48238
+ } finally {
48239
+ await handle.close();
48240
+ }
48241
+ return { path, entry };
48242
+ }
48243
+ var ProgressLogError;
48244
+ var init_store2 = __esm(() => {
48245
+ init_osx();
48246
+ ProgressLogError = class ProgressLogError extends Error {
48247
+ };
48248
+ });
48249
+
48250
+ // src/logs/tools.ts
48251
+ var logOutput, logTools;
48252
+ var init_tools2 = __esm(() => {
48253
+ init_zod();
48254
+ init_zodx();
48255
+ init_store2();
48256
+ logOutput = object({ result: object({ path: string2(), entry: string2() }) });
48257
+ logTools = [
48258
+ defineTool("log_append", {
48259
+ description: "Append one locally timestamped progress line to logs.txt at an absolute physical path. The parent directory must already exist; create the file if absent, never overwrite prior entries.",
48260
+ inputSchema: strictObject({
48261
+ path: absolutePath.describe("Absolute physical path to an existing plan directory/logs.txt"),
48262
+ message: string2().min(1).describe("Single-line progress message")
48263
+ }),
48264
+ outputSchema: logOutput,
48265
+ annotations: { openWorldHint: false }
48266
+ }, async ({ path, message }) => appendProgressLog(path, message), { toStructuredContent: (result) => ({ result }) })
48267
+ ];
48268
+ });
48269
+
48270
+ // src/projects/tools.ts
48271
+ import { isAbsolute as isAbsolute6 } from "path";
48186
48272
  function createProjectTools(home) {
48187
48273
  return [
48188
48274
  defineTool("project_context", {
48189
48275
  description: "Read the configured Difflab project and GitHub repository for an existing absolute cwd. Never sets up or modifies a repository.",
48190
48276
  inputSchema: strictObject({
48191
- cwd: string2().refine(isAbsolute4, "cwd must be an absolute existing directory")
48277
+ cwd: string2().refine(isAbsolute6, "cwd must be an absolute existing directory")
48192
48278
  }),
48193
48279
  outputSchema: projectContextOutput,
48194
48280
  annotations: { readOnlyHint: true, openWorldHint: false }
@@ -48196,7 +48282,7 @@ function createProjectTools(home) {
48196
48282
  ];
48197
48283
  }
48198
48284
  var setupHint = "Use the difflab-init skill to set up this repository with the Difflab CLI.", projectContextOutput, projectTools;
48199
- var init_tools2 = __esm(() => {
48285
+ var init_tools3 = __esm(() => {
48200
48286
  init_zod();
48201
48287
  init_context();
48202
48288
  projectContextOutput = strictObject({
@@ -48213,16 +48299,6 @@ var init_tools2 = __esm(() => {
48213
48299
  projectTools = createProjectTools();
48214
48300
  });
48215
48301
 
48216
- // src/extensions/zodx.ts
48217
- import { isAbsolute as isAbsolute5 } from "path";
48218
- var absolutePath, relativePath, fileName;
48219
- var init_zodx = __esm(() => {
48220
- init_zod();
48221
- absolutePath = string2().refine(isAbsolute5, "path must be absolute");
48222
- relativePath = string2().min(1).refine((value) => value.trim().length > 0 && !isAbsolute5(value), "path must be relative");
48223
- fileName = string2().refine((value) => value.trim().length > 0 && value !== "." && value !== ".." && !/[/\\\0]/.test(value), "filename must be a single non-empty file name");
48224
- });
48225
-
48226
48302
  // templates/spec-driven-plan.md
48227
48303
  var spec_driven_plan_default = `# [Outcome to deliver]
48228
48304
 
@@ -48511,6 +48587,21 @@ var code_review_default = `# Review: [change or pull request]
48511
48587
 
48512
48588
  [Evaluate authentication and autorization, permissions, secrets, dependencies, input validation, proper error handling, and trust boundaries. State SKIPPED if skipped with reason, for example for chores, simple scripting, pure cosmetic UI work, documentation, etc.]
48513
48589
 
48590
+ ## Change Requests
48591
+
48592
+ Track actionable inline requests with one entry per request. End each generated request and response with its own model attribution quote; preserve human text unchanged.
48593
+
48594
+ ### [file:line \u2014 request]
48595
+
48596
+ - **Status:** [open/in progress/resolved]
48597
+ - **Request:** [verbatim request or concise generated finding]
48598
+
48599
+ > Generated via [actual model name, only for a generated request]
48600
+
48601
+ - **Response:** [resolution, evidence, or reason this remains open]
48602
+
48603
+ > Generated via [actual model name, only for a generated response]
48604
+
48514
48605
  ## Findings
48515
48606
 
48516
48607
  | Severity | Location | Evidence and impact | Required change or question |
@@ -49193,9 +49284,9 @@ var exports_service = {};
49193
49284
  __export(exports_service, {
49194
49285
  TemplateService: () => TemplateService
49195
49286
  });
49196
- import { constants } from "fs";
49197
- import { lstat as lstat6, open as open2, rm as rm3 } from "fs/promises";
49198
- import { isAbsolute as isAbsolute6, join as join7 } from "path";
49287
+ import { constants as constants2 } from "fs";
49288
+ import { lstat as lstat7, open as open3, rm as rm3 } from "fs/promises";
49289
+ import { isAbsolute as isAbsolute7, join as join6 } from "path";
49199
49290
 
49200
49291
  class TemplateService {
49201
49292
  home;
@@ -49212,16 +49303,16 @@ class TemplateService {
49212
49303
  if (!this.source.catalog.some((item) => item.name === name)) {
49213
49304
  throw new UnknownTemplateError(`Unknown template: ${name}`);
49214
49305
  }
49215
- if (!path.trim() || isAbsolute6(path)) {
49306
+ if (!path.trim() || isAbsolute7(path)) {
49216
49307
  throw new TemplateError("path must be a non-empty relative directory inside cwd");
49217
49308
  }
49218
49309
  if (!filename.trim() || filename === "." || filename === ".." || /[/\\\0]/.test(filename)) {
49219
49310
  throw new TemplateError("filename must be a single non-empty file name");
49220
49311
  }
49221
49312
  await this.installMissing();
49222
- const installed = await resolvePath(this.home, join7(".difflab", "templates", `${name}.md`));
49313
+ const installed = await resolvePath(this.home, join6(".difflab", "templates", `${name}.md`));
49223
49314
  const contents = await readMarkdown(installed);
49224
- const destination = await ensurePathDirsExist(join7(path, filename), cwd);
49315
+ const destination = path.startsWith(".difflab/") ? await this.projectDestination(cwd, path.slice(".difflab/".length), filename) : await ensurePathDirsExist(join6(path, filename), cwd);
49225
49316
  try {
49226
49317
  await createExclusive(destination, contents);
49227
49318
  } catch (error) {
@@ -49234,12 +49325,23 @@ class TemplateService {
49234
49325
  }
49235
49326
  return destination;
49236
49327
  }
49328
+ async projectDestination(cwd, path, filename) {
49329
+ if (!isAbsolute7(cwd))
49330
+ throw new TemplateError("cwd must be an absolute path");
49331
+ const git = discoverGitRepository(cwd);
49332
+ const config = await readRepoConfig(git.root);
49333
+ if (!config)
49334
+ throw new TemplateError(`Initialize this repository with difflab init: ${git.root}`);
49335
+ const target = join6(userPaths(this.home).projects, config.project.id, repositorySlug(canonicalGitUrl(git.originUrl)));
49336
+ await checkRepoStore(git.root, target);
49337
+ return ensurePathDirsExist(join6(path, filename), target);
49338
+ }
49237
49339
  async installMissing() {
49238
49340
  for (const asset of this.source.assets) {
49239
- const relative = join7(".difflab", "templates", asset);
49341
+ const relative = join6(".difflab", "templates", asset);
49240
49342
  const destination = await ensurePathDirsExist(relative, this.home);
49241
49343
  try {
49242
- if ((await lstat6(destination)).isSymbolicLink()) {
49344
+ if ((await lstat7(destination)).isSymbolicLink()) {
49243
49345
  throw new TemplateSourceError(`Installed template asset is a symlink: ${destination}`);
49244
49346
  }
49245
49347
  await readMarkdown(destination);
@@ -49273,7 +49375,7 @@ async function readSource(source, asset) {
49273
49375
  async function readMarkdown(path) {
49274
49376
  let handle;
49275
49377
  try {
49276
- handle = await open2(path, constants.O_RDONLY | constants.O_NOFOLLOW);
49378
+ handle = await open3(path, constants2.O_RDONLY | constants2.O_NOFOLLOW);
49277
49379
  if (!(await handle.stat()).isFile())
49278
49380
  throw new TemplateSourceError(`Template is not a file: ${path}`);
49279
49381
  const content = await handle.readFile("utf8");
@@ -49289,7 +49391,7 @@ async function readMarkdown(path) {
49289
49391
  }
49290
49392
  }
49291
49393
  async function createExclusive(path, content) {
49292
- const handle = await open2(path, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | constants.O_NOFOLLOW, 384);
49394
+ const handle = await open3(path, constants2.O_WRONLY | constants2.O_CREAT | constants2.O_EXCL | constants2.O_NOFOLLOW, 384);
49293
49395
  try {
49294
49396
  await handle.writeFile(content, "utf8");
49295
49397
  } catch (error) {
@@ -49305,6 +49407,10 @@ var init_service = __esm(() => {
49305
49407
  init_fsx();
49306
49408
  init_osx();
49307
49409
  init_pathx();
49410
+ init_gitx2();
49411
+ init_config();
49412
+ init_repo_store();
49413
+ init_user_store();
49308
49414
  init_source();
49309
49415
  TemplateError = class TemplateError extends Error {
49310
49416
  };
@@ -49318,7 +49424,7 @@ var init_service = __esm(() => {
49318
49424
 
49319
49425
  // src/templates/tools.ts
49320
49426
  var templateInfo, templateListOutput, scaffoldOutput, templateTools;
49321
- var init_tools3 = __esm(() => {
49427
+ var init_tools4 = __esm(() => {
49322
49428
  init_zod();
49323
49429
  init_zodx();
49324
49430
  init_service();
@@ -49357,7 +49463,7 @@ var init_tools3 = __esm(() => {
49357
49463
  // src/mcp/server.ts
49358
49464
  async function serveMcp(version = "0.1.0") {
49359
49465
  const server = new McpServer({ name: "difflab", version });
49360
- for (const tool of [...todoTools, ...projectTools, ...templateTools]) {
49466
+ for (const tool of [...todoTools, ...projectTools, ...templateTools, ...logTools]) {
49361
49467
  server.registerTool(tool.name, tool.config, tool.callback);
49362
49468
  }
49363
49469
  await server.connect(new StdioServerTransport);
@@ -49368,6 +49474,7 @@ var init_server3 = __esm(() => {
49368
49474
  init_tools();
49369
49475
  init_tools2();
49370
49476
  init_tools3();
49477
+ init_tools4();
49371
49478
  });
49372
49479
 
49373
49480
  // src/extensions/processx.ts
@@ -49442,7 +49549,7 @@ var init_json_config = __esm(() => {
49442
49549
  });
49443
49550
 
49444
49551
  // src/agents/claude-code.ts
49445
- import { join as join8 } from "path";
49552
+ import { join as join7 } from "path";
49446
49553
  var claudeCodeAdapter;
49447
49554
  var init_claude_code = __esm(() => {
49448
49555
  init_adapter();
@@ -49452,7 +49559,7 @@ var init_claude_code = __esm(() => {
49452
49559
  id: "claude-code",
49453
49560
  async setupMcpConfig(context = {}) {
49454
49561
  const home = setupHome(context);
49455
- const file = join8(home, ".claude.json");
49562
+ const file = join7(home, ".claude.json");
49456
49563
  const status = serverStatus(await readJsonConfig(file), file);
49457
49564
  if (status === "matching")
49458
49565
  return { file, status: "existing" };
@@ -49468,7 +49575,7 @@ var init_claude_code = __esm(() => {
49468
49575
 
49469
49576
  // src/agents/codex.ts
49470
49577
  import { readFile as readFile5 } from "fs/promises";
49471
- import { join as join9 } from "path";
49578
+ import { join as join8 } from "path";
49472
49579
  function codexServerStatus(content) {
49473
49580
  const lines = content.split(/\r?\n/);
49474
49581
  const start = lines.findIndex((line) => /^\s*\[mcp_servers\.(?:difflab|"difflab"|'difflab')\]\s*(?:#.*)?$/.test(line));
@@ -49492,7 +49599,7 @@ var init_codex = __esm(() => {
49492
49599
  id: "codex",
49493
49600
  async setupMcpConfig(context = {}) {
49494
49601
  const home = setupHome(context);
49495
- const file = join9(home, ".codex", "config.toml");
49602
+ const file = join8(home, ".codex", "config.toml");
49496
49603
  let content = "";
49497
49604
  try {
49498
49605
  content = await readFile5(file, "utf8");
@@ -49514,7 +49621,7 @@ var init_codex = __esm(() => {
49514
49621
  });
49515
49622
 
49516
49623
  // src/agents/index.ts
49517
- import { join as join10 } from "path";
49624
+ import { join as join9 } from "path";
49518
49625
  function agentAdapter(name) {
49519
49626
  return Object.hasOwn(agentAdapters, name) ? agentAdapters[name] : undefined;
49520
49627
  }
@@ -49525,20 +49632,20 @@ var init_agents = __esm(() => {
49525
49632
  init_codex();
49526
49633
  init_json_config();
49527
49634
  agentAdapters = {
49528
- pi: jsonAgentAdapter("pi", (context) => join10(setupHome(context), ".config", "mcp", "mcp.json")),
49529
- cursor: jsonAgentAdapter("cursor", (context) => join10(setupHome(context), ".cursor", "mcp.json")),
49635
+ pi: jsonAgentAdapter("pi", (context) => join9(setupHome(context), ".config", "mcp", "mcp.json")),
49636
+ cursor: jsonAgentAdapter("cursor", (context) => join9(setupHome(context), ".cursor", "mcp.json")),
49530
49637
  codex: codexAdapter,
49531
49638
  "claude-code": claudeCodeAdapter,
49532
49639
  "claude-desktop": jsonAgentAdapter("claude-desktop", (context) => {
49533
49640
  const home = setupHome(context);
49534
49641
  const platform = context.platform ?? process.platform;
49535
49642
  if (platform === "darwin") {
49536
- return join10(home, "Library", "Application Support", "Claude", "claude_desktop_config.json");
49643
+ return join9(home, "Library", "Application Support", "Claude", "claude_desktop_config.json");
49537
49644
  }
49538
49645
  if (platform === "win32") {
49539
- return join10(process.env.APPDATA ?? join10(home, "AppData", "Roaming"), "Claude", "claude_desktop_config.json");
49646
+ return join9(process.env.APPDATA ?? join9(home, "AppData", "Roaming"), "Claude", "claude_desktop_config.json");
49540
49647
  }
49541
- return join10(home, ".config", "Claude", "claude_desktop_config.json");
49648
+ return join9(home, ".config", "Claude", "claude_desktop_config.json");
49542
49649
  })
49543
49650
  };
49544
49651
  agentNames = Object.keys(agentAdapters);
@@ -51626,7 +51733,7 @@ init_config();
51626
51733
  init_context();
51627
51734
  init_repo_store();
51628
51735
  init_user_store();
51629
- import { join as join11 } from "path";
51736
+ import { join as join10 } from "path";
51630
51737
  function createProgram(write = console.log, version = "0.1.0", options = {}) {
51631
51738
  const program = new Command2().name("difflab").description("Difflab CLI and local MCP server").version(version);
51632
51739
  program.action(() => program.outputHelp());
@@ -51660,7 +51767,7 @@ function createProgram(write = console.log, version = "0.1.0", options = {}) {
51660
51767
  if (config && config.project.id !== key)
51661
51768
  throw new RepoStoreError(`Repository already belongs to project ${config.project.id}; requested ${key}`);
51662
51769
  const origin = canonicalGitUrl(git.originUrl);
51663
- const target = join11(userPaths(options.home).projects, key, repositorySlug(origin));
51770
+ const target = join10(userPaths(options.home).projects, key, repositorySlug(origin));
51664
51771
  await preflightRepoStore(git.root, target);
51665
51772
  const project = await withUserStore(async (db) => {
51666
51773
  const project = await db.projects.getProjectById(key);
@@ -51709,7 +51816,7 @@ function createProgram(write = console.log, version = "0.1.0", options = {}) {
51709
51816
 
51710
51817
  // src/index.ts
51711
51818
  init_errors3();
51712
- var version2 = "0.4.0";
51819
+ var version2 = "0.5.0";
51713
51820
  try {
51714
51821
  await createProgram(console.log, version2).parseAsync(process.argv);
51715
51822
  } catch (error) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@difflab/difflab-cli",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "private": false,
5
5
  "description": "A minimal Bun CLI starter built with Commander",
6
6
  "license": "Apache-2.0",