@codebyplan/cli 2.3.0 → 3.0.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.
- package/dist/cli.js +534 -762
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -37,179 +37,11 @@ var VERSION, PACKAGE_NAME;
|
|
|
37
37
|
var init_version = __esm({
|
|
38
38
|
"src/lib/version.ts"() {
|
|
39
39
|
"use strict";
|
|
40
|
-
VERSION = "
|
|
40
|
+
VERSION = "3.0.0";
|
|
41
41
|
PACKAGE_NAME = "@codebyplan/cli";
|
|
42
42
|
}
|
|
43
43
|
});
|
|
44
44
|
|
|
45
|
-
// src/cli/setup.ts
|
|
46
|
-
var setup_exports = {};
|
|
47
|
-
__export(setup_exports, {
|
|
48
|
-
runSetup: () => runSetup
|
|
49
|
-
});
|
|
50
|
-
import { createInterface } from "node:readline/promises";
|
|
51
|
-
import { stdin, stdout } from "node:process";
|
|
52
|
-
import { readFile, writeFile } from "node:fs/promises";
|
|
53
|
-
import { homedir } from "node:os";
|
|
54
|
-
import { join } from "node:path";
|
|
55
|
-
function getConfigPath(scope) {
|
|
56
|
-
return scope === "user" ? join(homedir(), ".claude.json") : join(process.cwd(), ".mcp.json");
|
|
57
|
-
}
|
|
58
|
-
async function readConfig(path) {
|
|
59
|
-
try {
|
|
60
|
-
const raw = await readFile(path, "utf-8");
|
|
61
|
-
const parsed = JSON.parse(raw);
|
|
62
|
-
if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
|
|
63
|
-
return parsed;
|
|
64
|
-
}
|
|
65
|
-
return {};
|
|
66
|
-
} catch {
|
|
67
|
-
return {};
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
function buildMcpEntry(apiKey) {
|
|
71
|
-
return {
|
|
72
|
-
command: "npx",
|
|
73
|
-
args: ["-y", PACKAGE_NAME],
|
|
74
|
-
env: { CODEBYPLAN_API_KEY: apiKey }
|
|
75
|
-
};
|
|
76
|
-
}
|
|
77
|
-
async function writeMcpConfig(scope, apiKey) {
|
|
78
|
-
const configPath = getConfigPath(scope);
|
|
79
|
-
const config2 = await readConfig(configPath);
|
|
80
|
-
if (typeof config2.mcpServers !== "object" || config2.mcpServers === null || Array.isArray(config2.mcpServers)) {
|
|
81
|
-
config2.mcpServers = {};
|
|
82
|
-
}
|
|
83
|
-
config2.mcpServers.codebyplan = buildMcpEntry(apiKey);
|
|
84
|
-
await writeFile(configPath, JSON.stringify(config2, null, 2) + "\n", "utf-8");
|
|
85
|
-
return configPath;
|
|
86
|
-
}
|
|
87
|
-
async function verifyMcpConfig(scope, apiKey) {
|
|
88
|
-
try {
|
|
89
|
-
const config2 = await readConfig(getConfigPath(scope));
|
|
90
|
-
const servers = config2.mcpServers;
|
|
91
|
-
if (!servers) return false;
|
|
92
|
-
const entry = servers.codebyplan;
|
|
93
|
-
return entry?.env?.CODEBYPLAN_API_KEY === apiKey;
|
|
94
|
-
} catch {
|
|
95
|
-
return false;
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
async function runSetup() {
|
|
99
|
-
const rl = createInterface({ input: stdin, output: stdout });
|
|
100
|
-
console.log("\n CodeByPlan MCP Server Setup\n");
|
|
101
|
-
console.log(" This will configure Claude Code to use CodeByPlan.\n");
|
|
102
|
-
console.log(" 1. Sign up at https://codebyplan.com");
|
|
103
|
-
console.log(" 2. Create an API key at https://codebyplan.com/settings/api-keys/\n");
|
|
104
|
-
try {
|
|
105
|
-
const apiKey = (await rl.question(" Enter your API key: ")).trim();
|
|
106
|
-
if (!apiKey) {
|
|
107
|
-
console.log("\n No API key provided. Aborting setup.\n");
|
|
108
|
-
return;
|
|
109
|
-
}
|
|
110
|
-
console.log("\n Validating API key...");
|
|
111
|
-
const baseUrl = process.env.CODEBYPLAN_API_URL ?? "https://codebyplan.com";
|
|
112
|
-
const res = await fetch(`${baseUrl}/api/repos`, {
|
|
113
|
-
headers: { "x-api-key": apiKey },
|
|
114
|
-
signal: AbortSignal.timeout(1e4)
|
|
115
|
-
});
|
|
116
|
-
if (res.status === 401) {
|
|
117
|
-
console.log(" Invalid API key. Please check and try again.\n");
|
|
118
|
-
return;
|
|
119
|
-
}
|
|
120
|
-
if (!res.ok) {
|
|
121
|
-
console.log(` Warning: API returned status ${res.status}, but continuing.
|
|
122
|
-
`);
|
|
123
|
-
} else {
|
|
124
|
-
try {
|
|
125
|
-
const body = await res.json();
|
|
126
|
-
if (Array.isArray(body.data) && body.data.length === 0) {
|
|
127
|
-
console.log(" API key is valid but no repositories found.");
|
|
128
|
-
console.log(" Create one at https://codebyplan.com after setup.\n");
|
|
129
|
-
} else {
|
|
130
|
-
console.log(" API key is valid!\n");
|
|
131
|
-
}
|
|
132
|
-
} catch {
|
|
133
|
-
console.log(" API key is valid!\n");
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
console.log(" Where should the MCP server be configured?\n");
|
|
137
|
-
console.log(" 1. Global \u2014 available in all projects (~/.claude.json)");
|
|
138
|
-
console.log(" 2. Project \u2014 only this project (.mcp.json)\n");
|
|
139
|
-
const scopeInput = (await rl.question(" Select (1/2, default: 1): ")).trim();
|
|
140
|
-
const scope = scopeInput === "2" ? "project" : "user";
|
|
141
|
-
console.log("\n Configuring MCP server...");
|
|
142
|
-
const configPath = await writeMcpConfig(scope, apiKey);
|
|
143
|
-
const verified = await verifyMcpConfig(scope, apiKey);
|
|
144
|
-
if (verified) {
|
|
145
|
-
console.log(` Done! Config written to ${configPath}
|
|
146
|
-
`);
|
|
147
|
-
if (scope === "project") {
|
|
148
|
-
console.log(" Note: .mcp.json contains your API key \u2014 add it to .gitignore.\n");
|
|
149
|
-
}
|
|
150
|
-
console.log(" Start a new Claude Code session to begin using it.\n");
|
|
151
|
-
} else {
|
|
152
|
-
console.log(" Warning: Could not verify the saved configuration.\n");
|
|
153
|
-
console.log(" You can configure manually by adding to your Claude config:\n");
|
|
154
|
-
console.log(` claude mcp add codebyplan -e CODEBYPLAN_API_KEY=${apiKey} -- npx -y ${PACKAGE_NAME}
|
|
155
|
-
`);
|
|
156
|
-
}
|
|
157
|
-
} finally {
|
|
158
|
-
rl.close();
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
var init_setup = __esm({
|
|
162
|
-
"src/cli/setup.ts"() {
|
|
163
|
-
"use strict";
|
|
164
|
-
init_version();
|
|
165
|
-
}
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
// src/cli/config.ts
|
|
169
|
-
import { readFile as readFile2 } from "node:fs/promises";
|
|
170
|
-
import { join as join2 } from "node:path";
|
|
171
|
-
function parseFlags(startIndex) {
|
|
172
|
-
const flags = {};
|
|
173
|
-
const args = process.argv.slice(startIndex);
|
|
174
|
-
for (let i = 0; i < args.length; i++) {
|
|
175
|
-
const arg2 = args[i];
|
|
176
|
-
if (arg2.startsWith("--") && i + 1 < args.length) {
|
|
177
|
-
const key = arg2.slice(2);
|
|
178
|
-
flags[key] = args[++i];
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
return flags;
|
|
182
|
-
}
|
|
183
|
-
function hasFlag(name, startIndex) {
|
|
184
|
-
return process.argv.slice(startIndex).includes(`--${name}`);
|
|
185
|
-
}
|
|
186
|
-
async function resolveConfig(flags) {
|
|
187
|
-
const projectPath = flags["path"] ?? process.cwd();
|
|
188
|
-
let repoId = flags["repo-id"] ?? process.env.CODEBYPLAN_REPO_ID;
|
|
189
|
-
let worktreeId = flags["worktree-id"] ?? process.env.CODEBYPLAN_WORKTREE_ID;
|
|
190
|
-
if (!repoId || !worktreeId) {
|
|
191
|
-
try {
|
|
192
|
-
const configPath = join2(projectPath, ".codebyplan.json");
|
|
193
|
-
const raw = await readFile2(configPath, "utf-8");
|
|
194
|
-
const config2 = JSON.parse(raw);
|
|
195
|
-
if (!repoId) repoId = config2.repo_id;
|
|
196
|
-
if (!worktreeId) worktreeId = config2.worktree_id;
|
|
197
|
-
} catch {
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
if (!repoId) {
|
|
201
|
-
throw new Error(
|
|
202
|
-
'Could not determine repo_id.\n\nProvide it via one of:\n --repo-id <uuid> CLI flag\n CODEBYPLAN_REPO_ID=<uuid> environment variable\n .codebyplan.json { "repo_id": "<uuid>" } in project root'
|
|
203
|
-
);
|
|
204
|
-
}
|
|
205
|
-
return { repoId, worktreeId, projectPath };
|
|
206
|
-
}
|
|
207
|
-
var init_config = __esm({
|
|
208
|
-
"src/cli/config.ts"() {
|
|
209
|
-
"use strict";
|
|
210
|
-
}
|
|
211
|
-
});
|
|
212
|
-
|
|
213
45
|
// src/lib/api.ts
|
|
214
46
|
function validateApiKey() {
|
|
215
47
|
if (!API_KEY) {
|
|
@@ -432,8 +264,8 @@ var init_settings_merge = __esm({
|
|
|
432
264
|
});
|
|
433
265
|
|
|
434
266
|
// src/lib/hook-registry.ts
|
|
435
|
-
import { readdir, readFile
|
|
436
|
-
import { join
|
|
267
|
+
import { readdir, readFile } from "node:fs/promises";
|
|
268
|
+
import { join } from "node:path";
|
|
437
269
|
function parseHookMeta(content) {
|
|
438
270
|
const match = content.match(/^#\s*@hook:\s*(\S+)(?:\s+(.+))?$/m);
|
|
439
271
|
if (!match) return null;
|
|
@@ -452,7 +284,7 @@ async function discoverHooks(hooksDir) {
|
|
|
452
284
|
return discovered;
|
|
453
285
|
}
|
|
454
286
|
for (const filename of filenames) {
|
|
455
|
-
const content = await
|
|
287
|
+
const content = await readFile(join(hooksDir, filename), "utf-8");
|
|
456
288
|
const meta = parseHookMeta(content);
|
|
457
289
|
if (meta) {
|
|
458
290
|
discovered.set(filename.replace(/\.sh$/, ""), meta);
|
|
@@ -557,38 +389,42 @@ var init_variables = __esm({
|
|
|
557
389
|
});
|
|
558
390
|
|
|
559
391
|
// src/lib/sync-engine.ts
|
|
560
|
-
|
|
561
|
-
|
|
392
|
+
var sync_engine_exports = {};
|
|
393
|
+
__export(sync_engine_exports, {
|
|
394
|
+
executeSyncToLocal: () => executeSyncToLocal
|
|
395
|
+
});
|
|
396
|
+
import { readdir as readdir2, readFile as readFile2, writeFile, unlink, mkdir, rmdir, chmod, stat } from "node:fs/promises";
|
|
397
|
+
import { join as join2, dirname } from "node:path";
|
|
562
398
|
function getTypeDir(claudeDir, dir) {
|
|
563
|
-
if (dir === "commands") return
|
|
564
|
-
return
|
|
399
|
+
if (dir === "commands") return join2(claudeDir, dir, "cbp");
|
|
400
|
+
return join2(claudeDir, dir);
|
|
565
401
|
}
|
|
566
402
|
function getFilePath(claudeDir, typeName, file) {
|
|
567
403
|
const cfg = typeConfig[typeName];
|
|
568
404
|
const typeDir = getTypeDir(claudeDir, cfg.dir);
|
|
569
405
|
if (cfg.subfolder) {
|
|
570
|
-
return
|
|
406
|
+
return join2(typeDir, file.name, `${cfg.subfolder}${cfg.ext}`);
|
|
571
407
|
}
|
|
572
408
|
if (typeName === "command" && file.category) {
|
|
573
|
-
return
|
|
409
|
+
return join2(typeDir, file.category, `${file.name}${cfg.ext}`);
|
|
574
410
|
}
|
|
575
411
|
if (typeName === "template") {
|
|
576
|
-
return
|
|
412
|
+
return join2(typeDir, file.name);
|
|
577
413
|
}
|
|
578
|
-
return
|
|
414
|
+
return join2(typeDir, `${file.name}${cfg.ext}`);
|
|
579
415
|
}
|
|
580
416
|
async function readDirRecursive(dir, base = dir) {
|
|
581
417
|
const result = /* @__PURE__ */ new Map();
|
|
582
418
|
try {
|
|
583
419
|
const entries = await readdir2(dir, { withFileTypes: true });
|
|
584
420
|
for (const entry of entries) {
|
|
585
|
-
const fullPath =
|
|
421
|
+
const fullPath = join2(dir, entry.name);
|
|
586
422
|
if (entry.isDirectory()) {
|
|
587
423
|
const sub = await readDirRecursive(fullPath, base);
|
|
588
424
|
for (const [k, v] of sub) result.set(k, v);
|
|
589
425
|
} else {
|
|
590
426
|
const relPath = fullPath.slice(base.length + 1);
|
|
591
|
-
const fileContent = await
|
|
427
|
+
const fileContent = await readFile2(fullPath, "utf-8");
|
|
592
428
|
result.set(relPath, fileContent);
|
|
593
429
|
}
|
|
594
430
|
}
|
|
@@ -598,7 +434,7 @@ async function readDirRecursive(dir, base = dir) {
|
|
|
598
434
|
}
|
|
599
435
|
async function isGitWorktree(projectPath) {
|
|
600
436
|
try {
|
|
601
|
-
const gitPath =
|
|
437
|
+
const gitPath = join2(projectPath, ".git");
|
|
602
438
|
const info = await stat(gitPath);
|
|
603
439
|
return info.isFile();
|
|
604
440
|
} catch {
|
|
@@ -625,7 +461,7 @@ async function executeSyncToLocal(options) {
|
|
|
625
461
|
const syncData = syncRes.data;
|
|
626
462
|
const repoData = repoRes.data;
|
|
627
463
|
syncData.claude_md = [];
|
|
628
|
-
const claudeDir =
|
|
464
|
+
const claudeDir = join2(projectPath, ".claude");
|
|
629
465
|
const worktree = await isGitWorktree(projectPath);
|
|
630
466
|
const byType = {};
|
|
631
467
|
const totals = { created: 0, updated: 0, deleted: 0, unchanged: 0 };
|
|
@@ -650,19 +486,19 @@ async function executeSyncToLocal(options) {
|
|
|
650
486
|
remotePathMap.set(relPath, { content: substituted, name: remote.name });
|
|
651
487
|
}
|
|
652
488
|
for (const [relPath, { content, name }] of remotePathMap) {
|
|
653
|
-
const fullPath =
|
|
489
|
+
const fullPath = join2(targetDir, relPath);
|
|
654
490
|
const localContent = localFiles.get(relPath);
|
|
655
491
|
if (localContent === void 0) {
|
|
656
492
|
if (!dryRun) {
|
|
657
493
|
await mkdir(dirname(fullPath), { recursive: true });
|
|
658
|
-
await
|
|
494
|
+
await writeFile(fullPath, content, "utf-8");
|
|
659
495
|
if (typeName === "hook") await chmod(fullPath, 493);
|
|
660
496
|
}
|
|
661
497
|
result.created.push(name);
|
|
662
498
|
totals.created++;
|
|
663
499
|
} else if (localContent !== content) {
|
|
664
500
|
if (!dryRun) {
|
|
665
|
-
await
|
|
501
|
+
await writeFile(fullPath, content, "utf-8");
|
|
666
502
|
if (typeName === "hook") await chmod(fullPath, 493);
|
|
667
503
|
}
|
|
668
504
|
result.updated.push(name);
|
|
@@ -674,7 +510,7 @@ async function executeSyncToLocal(options) {
|
|
|
674
510
|
}
|
|
675
511
|
for (const [relPath] of localFiles) {
|
|
676
512
|
if (!remotePathMap.has(relPath)) {
|
|
677
|
-
const fullPath =
|
|
513
|
+
const fullPath = join2(targetDir, relPath);
|
|
678
514
|
if (!dryRun) {
|
|
679
515
|
await unlink(fullPath);
|
|
680
516
|
await removeEmptyParents(fullPath, targetDir);
|
|
@@ -689,7 +525,7 @@ async function executeSyncToLocal(options) {
|
|
|
689
525
|
{
|
|
690
526
|
const typeName = "docs_stack";
|
|
691
527
|
const syncKey = "docs_stack";
|
|
692
|
-
const targetDir =
|
|
528
|
+
const targetDir = join2(projectPath, "docs", "stack");
|
|
693
529
|
const remoteFiles = syncData[syncKey] ?? [];
|
|
694
530
|
const result = { created: [], updated: [], deleted: [], unchanged: [] };
|
|
695
531
|
if (remoteFiles.length > 0 && !dryRun) {
|
|
@@ -698,23 +534,23 @@ async function executeSyncToLocal(options) {
|
|
|
698
534
|
const localFiles = await readDirRecursive(targetDir);
|
|
699
535
|
const remotePathMap = /* @__PURE__ */ new Map();
|
|
700
536
|
for (const remote of remoteFiles) {
|
|
701
|
-
const relPath = remote.category ?
|
|
537
|
+
const relPath = remote.category ? join2(remote.category, remote.name) : remote.name;
|
|
702
538
|
const substituted = substituteVariables(remote.content, repoData);
|
|
703
539
|
remotePathMap.set(relPath, { content: substituted, name: `${remote.category ?? ""}/${remote.name}` });
|
|
704
540
|
}
|
|
705
541
|
for (const [relPath, { content, name }] of remotePathMap) {
|
|
706
|
-
const fullPath =
|
|
542
|
+
const fullPath = join2(targetDir, relPath);
|
|
707
543
|
const localContent = localFiles.get(relPath);
|
|
708
544
|
if (localContent === void 0) {
|
|
709
545
|
if (!dryRun) {
|
|
710
546
|
await mkdir(dirname(fullPath), { recursive: true });
|
|
711
|
-
await
|
|
547
|
+
await writeFile(fullPath, content, "utf-8");
|
|
712
548
|
}
|
|
713
549
|
result.created.push(name);
|
|
714
550
|
totals.created++;
|
|
715
551
|
} else if (localContent !== content) {
|
|
716
552
|
if (!dryRun) {
|
|
717
|
-
await
|
|
553
|
+
await writeFile(fullPath, content, "utf-8");
|
|
718
554
|
}
|
|
719
555
|
result.updated.push(name);
|
|
720
556
|
totals.updated++;
|
|
@@ -725,7 +561,7 @@ async function executeSyncToLocal(options) {
|
|
|
725
561
|
}
|
|
726
562
|
for (const [relPath] of localFiles) {
|
|
727
563
|
if (!remotePathMap.has(relPath)) {
|
|
728
|
-
const fullPath =
|
|
564
|
+
const fullPath = join2(targetDir, relPath);
|
|
729
565
|
if (!dryRun) {
|
|
730
566
|
await unlink(fullPath);
|
|
731
567
|
await removeEmptyParents(fullPath, targetDir);
|
|
@@ -743,8 +579,8 @@ async function executeSyncToLocal(options) {
|
|
|
743
579
|
globalSettings = { ...globalSettings, ...parsed };
|
|
744
580
|
}
|
|
745
581
|
const specialTypes = {
|
|
746
|
-
claude_md: () =>
|
|
747
|
-
settings: () =>
|
|
582
|
+
claude_md: () => join2(projectPath, "CLAUDE.md"),
|
|
583
|
+
settings: () => join2(projectPath, ".claude", "settings.json")
|
|
748
584
|
};
|
|
749
585
|
for (const [typeName, getPath] of Object.entries(specialTypes)) {
|
|
750
586
|
const remoteFiles = syncData[typeName] ?? [];
|
|
@@ -754,13 +590,13 @@ async function executeSyncToLocal(options) {
|
|
|
754
590
|
const remoteContent = substituteVariables(remote.content, repoData);
|
|
755
591
|
let localContent;
|
|
756
592
|
try {
|
|
757
|
-
localContent = await
|
|
593
|
+
localContent = await readFile2(targetPath, "utf-8");
|
|
758
594
|
} catch {
|
|
759
595
|
}
|
|
760
596
|
if (typeName === "settings") {
|
|
761
597
|
const repoSettings = JSON.parse(remoteContent);
|
|
762
598
|
const combinedTemplate = mergeGlobalAndRepoSettings(globalSettings, repoSettings);
|
|
763
|
-
const hooksDir =
|
|
599
|
+
const hooksDir = join2(projectPath, ".claude", "hooks");
|
|
764
600
|
const discovered = await discoverHooks(hooksDir);
|
|
765
601
|
if (localContent === void 0) {
|
|
766
602
|
let finalSettings = stripPermissionsAllow(combinedTemplate);
|
|
@@ -772,7 +608,7 @@ async function executeSyncToLocal(options) {
|
|
|
772
608
|
}
|
|
773
609
|
if (!dryRun) {
|
|
774
610
|
await mkdir(dirname(targetPath), { recursive: true });
|
|
775
|
-
await
|
|
611
|
+
await writeFile(targetPath, JSON.stringify(finalSettings, null, 2) + "\n", "utf-8");
|
|
776
612
|
}
|
|
777
613
|
result.created.push(remote.name);
|
|
778
614
|
totals.created++;
|
|
@@ -789,7 +625,7 @@ async function executeSyncToLocal(options) {
|
|
|
789
625
|
const mergedContent = JSON.stringify(merged, null, 2) + "\n";
|
|
790
626
|
if (localContent !== mergedContent) {
|
|
791
627
|
if (!dryRun) {
|
|
792
|
-
await
|
|
628
|
+
await writeFile(targetPath, mergedContent, "utf-8");
|
|
793
629
|
}
|
|
794
630
|
result.updated.push(remote.name);
|
|
795
631
|
totals.updated++;
|
|
@@ -802,13 +638,13 @@ async function executeSyncToLocal(options) {
|
|
|
802
638
|
if (localContent === void 0) {
|
|
803
639
|
if (!dryRun) {
|
|
804
640
|
await mkdir(dirname(targetPath), { recursive: true });
|
|
805
|
-
await
|
|
641
|
+
await writeFile(targetPath, remoteContent, "utf-8");
|
|
806
642
|
}
|
|
807
643
|
result.created.push(remote.name);
|
|
808
644
|
totals.created++;
|
|
809
645
|
} else if (localContent !== remoteContent) {
|
|
810
646
|
if (!dryRun) {
|
|
811
|
-
await
|
|
647
|
+
await writeFile(targetPath, remoteContent, "utf-8");
|
|
812
648
|
}
|
|
813
649
|
result.updated.push(remote.name);
|
|
814
650
|
totals.updated++;
|
|
@@ -852,327 +688,244 @@ var init_sync_engine = __esm({
|
|
|
852
688
|
}
|
|
853
689
|
});
|
|
854
690
|
|
|
855
|
-
// src/cli/
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
691
|
+
// src/cli/setup.ts
|
|
692
|
+
var setup_exports = {};
|
|
693
|
+
__export(setup_exports, {
|
|
694
|
+
runSetup: () => runSetup
|
|
695
|
+
});
|
|
696
|
+
import { createInterface } from "node:readline/promises";
|
|
697
|
+
import { stdin, stdout } from "node:process";
|
|
698
|
+
import { readFile as readFile3, writeFile as writeFile2 } from "node:fs/promises";
|
|
699
|
+
import { homedir } from "node:os";
|
|
700
|
+
import { join as join3 } from "node:path";
|
|
701
|
+
function getConfigPath(scope) {
|
|
702
|
+
return scope === "user" ? join3(homedir(), ".claude.json") : join3(process.cwd(), ".mcp.json");
|
|
703
|
+
}
|
|
704
|
+
async function readConfig(path) {
|
|
860
705
|
try {
|
|
861
|
-
const
|
|
862
|
-
const
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
706
|
+
const raw = await readFile3(path, "utf-8");
|
|
707
|
+
const parsed = JSON.parse(raw);
|
|
708
|
+
if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
|
|
709
|
+
return parsed;
|
|
710
|
+
}
|
|
711
|
+
return {};
|
|
712
|
+
} catch {
|
|
713
|
+
return {};
|
|
866
714
|
}
|
|
867
715
|
}
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
716
|
+
function buildMcpEntry(apiKey) {
|
|
717
|
+
return {
|
|
718
|
+
command: "npx",
|
|
719
|
+
args: ["-y", PACKAGE_NAME],
|
|
720
|
+
env: { CODEBYPLAN_API_KEY: apiKey }
|
|
721
|
+
};
|
|
722
|
+
}
|
|
723
|
+
async function writeMcpConfig(scope, apiKey) {
|
|
724
|
+
const configPath = getConfigPath(scope);
|
|
725
|
+
const config2 = await readConfig(configPath);
|
|
726
|
+
if (typeof config2.mcpServers !== "object" || config2.mcpServers === null || Array.isArray(config2.mcpServers)) {
|
|
727
|
+
config2.mcpServers = {};
|
|
871
728
|
}
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
async function fileExists(filePath) {
|
|
729
|
+
config2.mcpServers.codebyplan = buildMcpEntry(apiKey);
|
|
730
|
+
await writeFile2(configPath, JSON.stringify(config2, null, 2) + "\n", "utf-8");
|
|
731
|
+
return configPath;
|
|
732
|
+
}
|
|
733
|
+
async function verifyMcpConfig(scope, apiKey) {
|
|
878
734
|
try {
|
|
879
|
-
await
|
|
880
|
-
|
|
735
|
+
const config2 = await readConfig(getConfigPath(scope));
|
|
736
|
+
const servers = config2.mcpServers;
|
|
737
|
+
if (!servers) return false;
|
|
738
|
+
const entry = servers.codebyplan;
|
|
739
|
+
return entry?.env?.CODEBYPLAN_API_KEY === apiKey;
|
|
881
740
|
} catch {
|
|
882
741
|
return false;
|
|
883
742
|
}
|
|
884
743
|
}
|
|
885
|
-
async function
|
|
886
|
-
const
|
|
744
|
+
async function runSetup() {
|
|
745
|
+
const rl = createInterface({ input: stdin, output: stdout });
|
|
746
|
+
console.log("\n CodeByPlan Setup\n");
|
|
747
|
+
console.log(" This will configure Claude Code to use CodeByPlan.\n");
|
|
748
|
+
console.log(" 1. Sign up at https://codebyplan.com");
|
|
749
|
+
console.log(" 2. Create an API key at https://codebyplan.com/settings/api-keys/\n");
|
|
887
750
|
try {
|
|
888
|
-
const
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
751
|
+
const apiKey = (await rl.question(" Enter your API key: ")).trim();
|
|
752
|
+
if (!apiKey) {
|
|
753
|
+
console.log("\n No API key provided. Aborting setup.\n");
|
|
754
|
+
return;
|
|
755
|
+
}
|
|
756
|
+
console.log("\n Validating API key...");
|
|
757
|
+
const baseUrl = process.env.CODEBYPLAN_API_URL ?? "https://codebyplan.com";
|
|
758
|
+
const res = await fetch(`${baseUrl}/api/repos`, {
|
|
759
|
+
headers: { "x-api-key": apiKey },
|
|
760
|
+
signal: AbortSignal.timeout(1e4)
|
|
761
|
+
});
|
|
762
|
+
if (res.status === 401) {
|
|
763
|
+
console.log(" Invalid API key. Please check and try again.\n");
|
|
764
|
+
return;
|
|
765
|
+
}
|
|
766
|
+
let repos = [];
|
|
767
|
+
if (res.ok) {
|
|
768
|
+
try {
|
|
769
|
+
const body = await res.json();
|
|
770
|
+
repos = body.data ?? [];
|
|
771
|
+
if (repos.length === 0) {
|
|
772
|
+
console.log(" API key is valid but no repositories found.");
|
|
773
|
+
console.log(" Create one at https://codebyplan.com after setup.\n");
|
|
774
|
+
} else {
|
|
775
|
+
console.log(" API key is valid!\n");
|
|
900
776
|
}
|
|
777
|
+
} catch {
|
|
778
|
+
console.log(" API key is valid!\n");
|
|
901
779
|
}
|
|
780
|
+
} else {
|
|
781
|
+
console.log(` Warning: API returned status ${res.status}, but continuing.
|
|
782
|
+
`);
|
|
902
783
|
}
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
const
|
|
907
|
-
|
|
908
|
-
|
|
784
|
+
console.log(" Where should the MCP server be configured?\n");
|
|
785
|
+
console.log(" 1. Global \u2014 available in all projects (~/.claude.json)");
|
|
786
|
+
console.log(" 2. Project \u2014 only this project (.mcp.json)\n");
|
|
787
|
+
const scopeInput = (await rl.question(" Select (1/2, default: 1): ")).trim();
|
|
788
|
+
const scope = scopeInput === "2" ? "project" : "user";
|
|
789
|
+
console.log("\n Configuring MCP server...");
|
|
790
|
+
const configPath = await writeMcpConfig(scope, apiKey);
|
|
791
|
+
const verified = await verifyMcpConfig(scope, apiKey);
|
|
792
|
+
if (verified) {
|
|
793
|
+
console.log(` Done! Config written to ${configPath}
|
|
794
|
+
`);
|
|
795
|
+
if (scope === "project") {
|
|
796
|
+
console.log(" Note: .mcp.json contains your API key \u2014 add it to .gitignore.\n");
|
|
797
|
+
}
|
|
798
|
+
} else {
|
|
799
|
+
console.log(" Warning: Could not verify the saved configuration.\n");
|
|
800
|
+
console.log(` claude mcp add codebyplan -e CODEBYPLAN_API_KEY=${apiKey} -- npx -y ${PACKAGE_NAME}
|
|
801
|
+
`);
|
|
909
802
|
}
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
803
|
+
if (repos.length > 0) {
|
|
804
|
+
console.log(" Initialize this project?\n");
|
|
805
|
+
const initAnswer = (await rl.question(" Link to a repository? (Y/n): ")).trim().toLowerCase();
|
|
806
|
+
if (initAnswer === "" || initAnswer === "y" || initAnswer === "yes") {
|
|
807
|
+
process.env.CODEBYPLAN_API_KEY = apiKey;
|
|
808
|
+
console.log("\n Your repositories:\n");
|
|
809
|
+
for (let i = 0; i < repos.length; i++) {
|
|
810
|
+
console.log(` ${i + 1}. ${repos[i].name}`);
|
|
811
|
+
}
|
|
812
|
+
console.log();
|
|
813
|
+
const choice = (await rl.question(" Select a repository (number): ")).trim();
|
|
814
|
+
const index = parseInt(choice, 10) - 1;
|
|
815
|
+
if (isNaN(index) || index < 0 || index >= repos.length) {
|
|
816
|
+
console.log(" Invalid selection. Skipping project init.\n");
|
|
817
|
+
} else {
|
|
818
|
+
const selectedRepo = repos[index];
|
|
819
|
+
console.log(`
|
|
820
|
+
Selected: ${selectedRepo.name}
|
|
821
|
+
`);
|
|
822
|
+
let worktreeId;
|
|
823
|
+
const projectPath = process.cwd();
|
|
824
|
+
try {
|
|
825
|
+
const worktreesRes = await apiGet(`/worktrees?repo_id=${selectedRepo.id}`);
|
|
826
|
+
const match = worktreesRes.data.find((wt) => projectPath === wt.path || projectPath.startsWith(wt.path + "/"));
|
|
827
|
+
if (match) worktreeId = match.id;
|
|
828
|
+
} catch {
|
|
829
|
+
}
|
|
830
|
+
const codebyplanPath = join3(projectPath, ".codebyplan.json");
|
|
831
|
+
const codebyplanConfig = { repo_id: selectedRepo.id };
|
|
832
|
+
if (worktreeId) codebyplanConfig.worktree_id = worktreeId;
|
|
833
|
+
await writeFile2(codebyplanPath, JSON.stringify(codebyplanConfig, null, 2) + "\n", "utf-8");
|
|
834
|
+
console.log(` Created ${codebyplanPath}`);
|
|
835
|
+
console.log("\n Running initial sync...\n");
|
|
836
|
+
try {
|
|
837
|
+
const { executeSyncToLocal: executeSyncToLocal2 } = await Promise.resolve().then(() => (init_sync_engine(), sync_engine_exports));
|
|
838
|
+
const syncResult = await executeSyncToLocal2({
|
|
839
|
+
repoId: selectedRepo.id,
|
|
840
|
+
projectPath
|
|
841
|
+
});
|
|
842
|
+
const totalChanges = syncResult.totals.created + syncResult.totals.updated + syncResult.totals.deleted;
|
|
843
|
+
if (totalChanges > 0) {
|
|
844
|
+
console.log(` Synced: ${syncResult.totals.created} created, ${syncResult.totals.updated} updated, ${syncResult.totals.deleted} deleted
|
|
845
|
+
`);
|
|
846
|
+
} else {
|
|
847
|
+
console.log(" All files already up to date.\n");
|
|
848
|
+
}
|
|
849
|
+
} catch (err) {
|
|
850
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
851
|
+
console.log(` Sync failed: ${msg}`);
|
|
852
|
+
console.log(" Run 'codebyplan sync' later to sync files.\n");
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
}
|
|
928
856
|
}
|
|
857
|
+
console.log(" Setup complete! Start a new Claude Code session to begin.\n");
|
|
858
|
+
} finally {
|
|
859
|
+
rl.close();
|
|
929
860
|
}
|
|
930
|
-
const merged = Array.from(seen.values()).sort((a, b) => {
|
|
931
|
-
const catCmp = a.category.localeCompare(b.category);
|
|
932
|
-
if (catCmp !== 0) return catCmp;
|
|
933
|
-
return a.name.localeCompare(b.name);
|
|
934
|
-
});
|
|
935
|
-
return { merged, added };
|
|
936
|
-
}
|
|
937
|
-
function parseTechStack(raw) {
|
|
938
|
-
if (!Array.isArray(raw)) return [];
|
|
939
|
-
return raw.filter(
|
|
940
|
-
(item) => typeof item === "object" && item !== null && typeof item.name === "string" && typeof item.category === "string"
|
|
941
|
-
);
|
|
942
861
|
}
|
|
943
|
-
var
|
|
944
|
-
|
|
945
|
-
"src/lib/tech-detect.ts"() {
|
|
862
|
+
var init_setup = __esm({
|
|
863
|
+
"src/cli/setup.ts"() {
|
|
946
864
|
"use strict";
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
next: { name: "Next.js", category: "framework" },
|
|
950
|
-
nuxt: { name: "Nuxt", category: "framework" },
|
|
951
|
-
gatsby: { name: "Gatsby", category: "framework" },
|
|
952
|
-
express: { name: "Express", category: "framework" },
|
|
953
|
-
fastify: { name: "Fastify", category: "framework" },
|
|
954
|
-
hono: { name: "Hono", category: "framework" },
|
|
955
|
-
"@remix-run/node": { name: "Remix", category: "framework" },
|
|
956
|
-
svelte: { name: "Svelte", category: "framework" },
|
|
957
|
-
astro: { name: "Astro", category: "framework" },
|
|
958
|
-
"@angular/core": { name: "Angular", category: "framework" },
|
|
959
|
-
// Libraries (UI)
|
|
960
|
-
react: { name: "React", category: "framework" },
|
|
961
|
-
vue: { name: "Vue", category: "framework" },
|
|
962
|
-
"solid-js": { name: "Solid", category: "framework" },
|
|
963
|
-
preact: { name: "Preact", category: "framework" },
|
|
964
|
-
// Languages (detected via devDeps)
|
|
965
|
-
typescript: { name: "TypeScript", category: "language" },
|
|
966
|
-
// Styling
|
|
967
|
-
tailwindcss: { name: "Tailwind CSS", category: "styling" },
|
|
968
|
-
sass: { name: "SCSS", category: "styling" },
|
|
969
|
-
"styled-components": { name: "styled-components", category: "styling" },
|
|
970
|
-
"@emotion/react": { name: "Emotion", category: "styling" },
|
|
971
|
-
// Database
|
|
972
|
-
prisma: { name: "Prisma", category: "database" },
|
|
973
|
-
"@prisma/client": { name: "Prisma", category: "database" },
|
|
974
|
-
"drizzle-orm": { name: "Drizzle", category: "database" },
|
|
975
|
-
"@supabase/supabase-js": { name: "Supabase", category: "database" },
|
|
976
|
-
mongoose: { name: "MongoDB", category: "database" },
|
|
977
|
-
typeorm: { name: "TypeORM", category: "database" },
|
|
978
|
-
knex: { name: "Knex", category: "database" },
|
|
979
|
-
// Testing
|
|
980
|
-
jest: { name: "Jest", category: "testing" },
|
|
981
|
-
vitest: { name: "Vitest", category: "testing" },
|
|
982
|
-
mocha: { name: "Mocha", category: "testing" },
|
|
983
|
-
playwright: { name: "Playwright", category: "testing" },
|
|
984
|
-
"@playwright/test": { name: "Playwright", category: "testing" },
|
|
985
|
-
cypress: { name: "Cypress", category: "testing" },
|
|
986
|
-
// Build tools
|
|
987
|
-
turbo: { name: "Turborepo", category: "build" },
|
|
988
|
-
vite: { name: "Vite", category: "build" },
|
|
989
|
-
webpack: { name: "Webpack", category: "build" },
|
|
990
|
-
esbuild: { name: "esbuild", category: "build" },
|
|
991
|
-
rollup: { name: "Rollup", category: "build" },
|
|
992
|
-
// Tools
|
|
993
|
-
eslint: { name: "ESLint", category: "tool" },
|
|
994
|
-
prettier: { name: "Prettier", category: "tool" },
|
|
995
|
-
"@biomejs/biome": { name: "Biome", category: "tool" }
|
|
996
|
-
};
|
|
997
|
-
CONFIG_FILE_MAP = [
|
|
998
|
-
{ file: "tsconfig.json", rule: { name: "TypeScript", category: "language" } },
|
|
999
|
-
{ file: "next.config.js", rule: { name: "Next.js", category: "framework" } },
|
|
1000
|
-
{ file: "next.config.mjs", rule: { name: "Next.js", category: "framework" } },
|
|
1001
|
-
{ file: "next.config.ts", rule: { name: "Next.js", category: "framework" } },
|
|
1002
|
-
{ file: "tailwind.config.js", rule: { name: "Tailwind CSS", category: "styling" } },
|
|
1003
|
-
{ file: "tailwind.config.ts", rule: { name: "Tailwind CSS", category: "styling" } },
|
|
1004
|
-
{ file: "turbo.json", rule: { name: "Turborepo", category: "build" } },
|
|
1005
|
-
{ file: "docker-compose.yml", rule: { name: "Docker", category: "deployment" } },
|
|
1006
|
-
{ file: "docker-compose.yaml", rule: { name: "Docker", category: "deployment" } },
|
|
1007
|
-
{ file: "Dockerfile", rule: { name: "Docker", category: "deployment" } },
|
|
1008
|
-
{ file: "vercel.json", rule: { name: "Vercel", category: "deployment" } }
|
|
1009
|
-
];
|
|
865
|
+
init_version();
|
|
866
|
+
init_api();
|
|
1010
867
|
}
|
|
1011
868
|
});
|
|
1012
869
|
|
|
1013
|
-
// src/cli/
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
const previewResult = await executeSyncToLocal({
|
|
1025
|
-
repoId,
|
|
1026
|
-
projectPath,
|
|
1027
|
-
dryRun: needsConfirm ? true : dryRun
|
|
1028
|
-
});
|
|
1029
|
-
for (const [typeKey, result] of Object.entries(previewResult.byType)) {
|
|
1030
|
-
const displayType = displayTypeMap[typeKey] ?? typeKey;
|
|
1031
|
-
for (const name of result.created) {
|
|
1032
|
-
log(` + ${displayType}/${name}`);
|
|
1033
|
-
}
|
|
1034
|
-
for (const name of result.updated) {
|
|
1035
|
-
log(` ~ ${displayType}/${name}`);
|
|
1036
|
-
}
|
|
1037
|
-
for (const name of result.deleted) {
|
|
1038
|
-
log(` - ${displayType}/${name}`);
|
|
1039
|
-
}
|
|
1040
|
-
}
|
|
1041
|
-
const totalChanges = previewResult.totals.created + previewResult.totals.updated + previewResult.totals.deleted;
|
|
1042
|
-
if (needsConfirm && totalChanges > 0) {
|
|
1043
|
-
log("");
|
|
1044
|
-
log(` ${previewResult.totals.created} to create, ${previewResult.totals.updated} to update, ${previewResult.totals.deleted} to delete`);
|
|
1045
|
-
const confirmed = await confirmProceed();
|
|
1046
|
-
if (!confirmed) {
|
|
1047
|
-
log(" Cancelled.");
|
|
1048
|
-
return { created: 0, updated: 0, deleted: 0, unchanged: previewResult.totals.unchanged };
|
|
870
|
+
// src/cli/config.ts
|
|
871
|
+
import { readFile as readFile4 } from "node:fs/promises";
|
|
872
|
+
import { join as join4 } from "node:path";
|
|
873
|
+
function parseFlags(startIndex) {
|
|
874
|
+
const flags = {};
|
|
875
|
+
const args = process.argv.slice(startIndex);
|
|
876
|
+
for (let i = 0; i < args.length; i++) {
|
|
877
|
+
const arg2 = args[i];
|
|
878
|
+
if (arg2.startsWith("--") && i + 1 < args.length) {
|
|
879
|
+
const key = arg2.slice(2);
|
|
880
|
+
flags[key] = args[++i];
|
|
1049
881
|
}
|
|
1050
|
-
const realResult = await executeSyncToLocal({ repoId, projectPath, dryRun: false });
|
|
1051
|
-
return {
|
|
1052
|
-
created: realResult.totals.created,
|
|
1053
|
-
updated: realResult.totals.updated,
|
|
1054
|
-
deleted: realResult.totals.deleted,
|
|
1055
|
-
unchanged: realResult.totals.unchanged
|
|
1056
|
-
};
|
|
1057
882
|
}
|
|
1058
|
-
return
|
|
1059
|
-
created: previewResult.totals.created,
|
|
1060
|
-
updated: previewResult.totals.updated,
|
|
1061
|
-
deleted: previewResult.totals.deleted,
|
|
1062
|
-
unchanged: previewResult.totals.unchanged
|
|
1063
|
-
};
|
|
883
|
+
return flags;
|
|
1064
884
|
}
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
const dryRun = hasFlag("dry-run", 3);
|
|
1068
|
-
const force = hasFlag("force", 3);
|
|
1069
|
-
const statusOnly = hasFlag("status", 3);
|
|
1070
|
-
validateApiKey();
|
|
1071
|
-
const config2 = await resolveConfig(flags);
|
|
1072
|
-
const { repoId, projectPath } = config2;
|
|
1073
|
-
if (statusOnly) {
|
|
1074
|
-
console.log(`
|
|
1075
|
-
CodeByPlan Sync Status
|
|
1076
|
-
`);
|
|
1077
|
-
await printSyncStatus(repoId);
|
|
1078
|
-
return;
|
|
1079
|
-
}
|
|
1080
|
-
console.log(`
|
|
1081
|
-
CodeByPlan Pull`);
|
|
1082
|
-
console.log(` Repo: ${repoId}`);
|
|
1083
|
-
console.log(` Path: ${projectPath}`);
|
|
1084
|
-
if (dryRun) console.log(` Mode: dry-run (no changes will be made)`);
|
|
1085
|
-
if (force) console.log(` Mode: force (no confirmation prompt)`);
|
|
1086
|
-
console.log();
|
|
1087
|
-
const repoRes = await apiGet(`/repos/${repoId}`);
|
|
1088
|
-
const result = await executePull({ repoId, projectPath, dryRun, force });
|
|
1089
|
-
console.log();
|
|
1090
|
-
if (result.created + result.updated + result.deleted === 0) {
|
|
1091
|
-
console.log(" All files up to date.");
|
|
1092
|
-
} else {
|
|
1093
|
-
console.log(` ${result.created} created, ${result.updated} updated, ${result.deleted} deleted, ${result.unchanged} unchanged`);
|
|
1094
|
-
}
|
|
1095
|
-
if (dryRun) {
|
|
1096
|
-
console.log(" (dry-run \u2014 no changes were made)");
|
|
1097
|
-
}
|
|
1098
|
-
printTechStack(repoRes.data.tech_stack);
|
|
1099
|
-
await printSyncStatus(repoId);
|
|
885
|
+
function hasFlag(name, startIndex) {
|
|
886
|
+
return process.argv.slice(startIndex).includes(`--${name}`);
|
|
1100
887
|
}
|
|
1101
|
-
function
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
const
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
for (const [category, items] of grouped) {
|
|
1114
|
-
const label = category.charAt(0).toUpperCase() + category.slice(1);
|
|
1115
|
-
const names = items.map((e) => e.name).join(", ");
|
|
1116
|
-
console.log(` ${label}: ${names}`);
|
|
888
|
+
async function resolveConfig(flags) {
|
|
889
|
+
const projectPath = flags["path"] ?? process.cwd();
|
|
890
|
+
let repoId = flags["repo-id"] ?? process.env.CODEBYPLAN_REPO_ID;
|
|
891
|
+
let worktreeId = flags["worktree-id"] ?? process.env.CODEBYPLAN_WORKTREE_ID;
|
|
892
|
+
if (!repoId || !worktreeId) {
|
|
893
|
+
try {
|
|
894
|
+
const configPath = join4(projectPath, ".codebyplan.json");
|
|
895
|
+
const raw = await readFile4(configPath, "utf-8");
|
|
896
|
+
const config2 = JSON.parse(raw);
|
|
897
|
+
if (!repoId) repoId = config2.repo_id;
|
|
898
|
+
if (!worktreeId) worktreeId = config2.worktree_id;
|
|
899
|
+
} catch {
|
|
1117
900
|
}
|
|
1118
|
-
} catch {
|
|
1119
901
|
}
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
const { repos } = statusRes.data;
|
|
1125
|
-
const otherRepos = repos.filter((r) => r.id !== currentRepoId);
|
|
1126
|
-
const stale = otherRepos.filter((r) => r.needs_sync);
|
|
1127
|
-
if (stale.length > 0) {
|
|
1128
|
-
console.log();
|
|
1129
|
-
console.log(` Other repos needing sync (${stale.length}):`);
|
|
1130
|
-
for (const repo of stale) {
|
|
1131
|
-
const syncedAt = repo.claude_sync_at ? new Date(repo.claude_sync_at).toLocaleDateString() : "never";
|
|
1132
|
-
console.log(` - ${repo.name} (last synced: ${syncedAt})`);
|
|
1133
|
-
}
|
|
1134
|
-
} else if (otherRepos.length > 0) {
|
|
1135
|
-
console.log();
|
|
1136
|
-
console.log(" All repos are in sync.");
|
|
1137
|
-
}
|
|
1138
|
-
} catch {
|
|
902
|
+
if (!repoId) {
|
|
903
|
+
throw new Error(
|
|
904
|
+
'Could not determine repo_id.\n\nProvide it via one of:\n --repo-id <uuid> CLI flag\n CODEBYPLAN_REPO_ID=<uuid> environment variable\n .codebyplan.json { "repo_id": "<uuid>" } in project root'
|
|
905
|
+
);
|
|
1139
906
|
}
|
|
1140
|
-
|
|
907
|
+
return { repoId, worktreeId, projectPath };
|
|
1141
908
|
}
|
|
1142
|
-
var
|
|
1143
|
-
|
|
1144
|
-
"src/cli/pull.ts"() {
|
|
909
|
+
var init_config = __esm({
|
|
910
|
+
"src/cli/config.ts"() {
|
|
1145
911
|
"use strict";
|
|
1146
|
-
init_config();
|
|
1147
|
-
init_api();
|
|
1148
|
-
init_sync_engine();
|
|
1149
|
-
init_confirm();
|
|
1150
|
-
init_tech_detect();
|
|
1151
|
-
displayTypeMap = {
|
|
1152
|
-
commands: "command",
|
|
1153
|
-
agents: "agent",
|
|
1154
|
-
skills: "skill",
|
|
1155
|
-
rules: "rule",
|
|
1156
|
-
hooks: "hook",
|
|
1157
|
-
templates: "template"
|
|
1158
|
-
};
|
|
1159
912
|
}
|
|
1160
913
|
});
|
|
1161
914
|
|
|
1162
915
|
// src/cli/fileMapper.ts
|
|
1163
|
-
import { readdir as readdir3, readFile as
|
|
1164
|
-
import { join as
|
|
916
|
+
import { readdir as readdir3, readFile as readFile5 } from "node:fs/promises";
|
|
917
|
+
import { join as join5, extname } from "node:path";
|
|
1165
918
|
function compositeKey(type, name, category) {
|
|
1166
919
|
return category ? `${type}:${category}/${name}` : `${type}:${name}`;
|
|
1167
920
|
}
|
|
1168
921
|
async function scanLocalFiles(claudeDir, projectPath) {
|
|
1169
922
|
const result = /* @__PURE__ */ new Map();
|
|
1170
|
-
await scanCommands(
|
|
1171
|
-
await scanSubfolderType(
|
|
1172
|
-
await scanSubfolderType(
|
|
1173
|
-
await scanFlatType(
|
|
1174
|
-
await scanFlatType(
|
|
1175
|
-
await scanTemplates(
|
|
923
|
+
await scanCommands(join5(claudeDir, "commands", "cbp"), result);
|
|
924
|
+
await scanSubfolderType(join5(claudeDir, "agents"), "agent", "AGENT.md", result);
|
|
925
|
+
await scanSubfolderType(join5(claudeDir, "skills"), "skill", "SKILL.md", result);
|
|
926
|
+
await scanFlatType(join5(claudeDir, "rules"), "rule", ".md", result);
|
|
927
|
+
await scanFlatType(join5(claudeDir, "hooks"), "hook", ".sh", result);
|
|
928
|
+
await scanTemplates(join5(claudeDir, "templates"), result);
|
|
1176
929
|
await scanSettings(claudeDir, projectPath, result);
|
|
1177
930
|
return result;
|
|
1178
931
|
}
|
|
@@ -1188,10 +941,10 @@ async function scanCommandsRecursive(baseDir, currentDir, result) {
|
|
|
1188
941
|
}
|
|
1189
942
|
for (const entry of entries) {
|
|
1190
943
|
if (entry.isDirectory()) {
|
|
1191
|
-
await scanCommandsRecursive(baseDir,
|
|
944
|
+
await scanCommandsRecursive(baseDir, join5(currentDir, entry.name), result);
|
|
1192
945
|
} else if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
1193
946
|
const name = entry.name.slice(0, -3);
|
|
1194
|
-
const content = await
|
|
947
|
+
const content = await readFile5(join5(currentDir, entry.name), "utf-8");
|
|
1195
948
|
const relDir = currentDir.slice(baseDir.length + 1);
|
|
1196
949
|
const category = relDir || null;
|
|
1197
950
|
const key = compositeKey("command", name, category);
|
|
@@ -1208,9 +961,9 @@ async function scanSubfolderType(dir, type, fileName, result) {
|
|
|
1208
961
|
}
|
|
1209
962
|
for (const entry of entries) {
|
|
1210
963
|
if (entry.isDirectory()) {
|
|
1211
|
-
const filePath =
|
|
964
|
+
const filePath = join5(dir, entry.name, fileName);
|
|
1212
965
|
try {
|
|
1213
|
-
const content = await
|
|
966
|
+
const content = await readFile5(filePath, "utf-8");
|
|
1214
967
|
const key = compositeKey(type, entry.name, null);
|
|
1215
968
|
result.set(key, { type, name: entry.name, category: null, content });
|
|
1216
969
|
} catch {
|
|
@@ -1228,7 +981,7 @@ async function scanFlatType(dir, type, ext, result) {
|
|
|
1228
981
|
for (const entry of entries) {
|
|
1229
982
|
if (entry.isFile() && entry.name.endsWith(ext)) {
|
|
1230
983
|
const name = entry.name.slice(0, -ext.length);
|
|
1231
|
-
const content = await
|
|
984
|
+
const content = await readFile5(join5(dir, entry.name), "utf-8");
|
|
1232
985
|
const key = compositeKey(type, name, null);
|
|
1233
986
|
result.set(key, { type, name, category: null, content });
|
|
1234
987
|
}
|
|
@@ -1243,17 +996,17 @@ async function scanTemplates(dir, result) {
|
|
|
1243
996
|
}
|
|
1244
997
|
for (const entry of entries) {
|
|
1245
998
|
if (entry.isFile() && extname(entry.name)) {
|
|
1246
|
-
const content = await
|
|
999
|
+
const content = await readFile5(join5(dir, entry.name), "utf-8");
|
|
1247
1000
|
const key = compositeKey("template", entry.name, null);
|
|
1248
1001
|
result.set(key, { type: "template", name: entry.name, category: null, content });
|
|
1249
1002
|
}
|
|
1250
1003
|
}
|
|
1251
1004
|
}
|
|
1252
1005
|
async function scanSettings(claudeDir, projectPath, result) {
|
|
1253
|
-
const settingsPath =
|
|
1006
|
+
const settingsPath = join5(claudeDir, "settings.json");
|
|
1254
1007
|
let raw;
|
|
1255
1008
|
try {
|
|
1256
|
-
raw = await
|
|
1009
|
+
raw = await readFile5(settingsPath, "utf-8");
|
|
1257
1010
|
} catch {
|
|
1258
1011
|
return;
|
|
1259
1012
|
}
|
|
@@ -1265,7 +1018,7 @@ async function scanSettings(claudeDir, projectPath, result) {
|
|
|
1265
1018
|
}
|
|
1266
1019
|
parsed = stripPermissionsAllow(parsed);
|
|
1267
1020
|
if (parsed.hooks && typeof parsed.hooks === "object") {
|
|
1268
|
-
const hooksDir = projectPath ?
|
|
1021
|
+
const hooksDir = projectPath ? join5(projectPath, ".claude", "hooks") : join5(claudeDir, "hooks");
|
|
1269
1022
|
const discovered = await discoverHooks(hooksDir);
|
|
1270
1023
|
if (discovered.size > 0) {
|
|
1271
1024
|
parsed.hooks = stripDiscoveredHooks(
|
|
@@ -1290,12 +1043,12 @@ var init_fileMapper = __esm({
|
|
|
1290
1043
|
});
|
|
1291
1044
|
|
|
1292
1045
|
// src/cli/conflict.ts
|
|
1293
|
-
import { createInterface as
|
|
1294
|
-
import { stdin as
|
|
1046
|
+
import { createInterface as createInterface2 } from "node:readline/promises";
|
|
1047
|
+
import { stdin as stdin2, stdout as stdout2 } from "node:process";
|
|
1295
1048
|
async function resolveConflicts(conflicts) {
|
|
1296
1049
|
const resolutions = /* @__PURE__ */ new Map();
|
|
1297
1050
|
if (conflicts.length === 0) return resolutions;
|
|
1298
|
-
const rl =
|
|
1051
|
+
const rl = createInterface2({ input: stdin2, output: stdout2 });
|
|
1299
1052
|
try {
|
|
1300
1053
|
console.log(`
|
|
1301
1054
|
${conflicts.length} conflict(s) found:
|
|
@@ -1357,47 +1110,221 @@ var init_conflict = __esm({
|
|
|
1357
1110
|
}
|
|
1358
1111
|
});
|
|
1359
1112
|
|
|
1360
|
-
// src/cli/
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1113
|
+
// src/cli/confirm.ts
|
|
1114
|
+
import { createInterface as createInterface3 } from "node:readline/promises";
|
|
1115
|
+
import { stdin as stdin3, stdout as stdout3 } from "node:process";
|
|
1116
|
+
async function confirmProceed(message) {
|
|
1117
|
+
const rl = createInterface3({ input: stdin3, output: stdout3 });
|
|
1118
|
+
try {
|
|
1119
|
+
const answer = await rl.question(message ?? " Proceed? [Y/n] ");
|
|
1120
|
+
const a = answer.trim().toLowerCase();
|
|
1121
|
+
return a !== "n" && a !== "no";
|
|
1122
|
+
} finally {
|
|
1123
|
+
rl.close();
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
var init_confirm = __esm({
|
|
1127
|
+
"src/cli/confirm.ts"() {
|
|
1128
|
+
"use strict";
|
|
1129
|
+
}
|
|
1130
|
+
});
|
|
1131
|
+
|
|
1132
|
+
// src/lib/tech-detect.ts
|
|
1133
|
+
import { readFile as readFile6, access } from "node:fs/promises";
|
|
1134
|
+
import { join as join6 } from "node:path";
|
|
1135
|
+
async function fileExists(filePath) {
|
|
1136
|
+
try {
|
|
1137
|
+
await access(filePath);
|
|
1138
|
+
return true;
|
|
1139
|
+
} catch {
|
|
1140
|
+
return false;
|
|
1141
|
+
}
|
|
1142
|
+
}
|
|
1143
|
+
async function detectTechStack(projectPath) {
|
|
1144
|
+
const seen = /* @__PURE__ */ new Map();
|
|
1145
|
+
try {
|
|
1146
|
+
const raw = await readFile6(join6(projectPath, "package.json"), "utf-8");
|
|
1147
|
+
const pkg = JSON.parse(raw);
|
|
1148
|
+
const allDeps = {
|
|
1149
|
+
...pkg.dependencies,
|
|
1150
|
+
...pkg.devDependencies
|
|
1151
|
+
};
|
|
1152
|
+
for (const depName of Object.keys(allDeps)) {
|
|
1153
|
+
const rule = PACKAGE_MAP[depName];
|
|
1154
|
+
if (rule) {
|
|
1155
|
+
const key = rule.name.toLowerCase();
|
|
1156
|
+
if (!seen.has(key)) {
|
|
1157
|
+
seen.set(key, { name: rule.name, category: rule.category });
|
|
1158
|
+
}
|
|
1159
|
+
}
|
|
1160
|
+
}
|
|
1161
|
+
} catch {
|
|
1162
|
+
}
|
|
1163
|
+
for (const { file, rule } of CONFIG_FILE_MAP) {
|
|
1164
|
+
const key = rule.name.toLowerCase();
|
|
1165
|
+
if (!seen.has(key) && await fileExists(join6(projectPath, file))) {
|
|
1166
|
+
seen.set(key, { name: rule.name, category: rule.category });
|
|
1167
|
+
}
|
|
1168
|
+
}
|
|
1169
|
+
return Array.from(seen.values()).sort((a, b) => {
|
|
1170
|
+
const catCmp = a.category.localeCompare(b.category);
|
|
1171
|
+
if (catCmp !== 0) return catCmp;
|
|
1172
|
+
return a.name.localeCompare(b.name);
|
|
1173
|
+
});
|
|
1174
|
+
}
|
|
1175
|
+
function mergeTechStack(remote, detected) {
|
|
1176
|
+
const seen = /* @__PURE__ */ new Map();
|
|
1177
|
+
for (const entry of remote) {
|
|
1178
|
+
seen.set(entry.name.toLowerCase(), entry);
|
|
1179
|
+
}
|
|
1180
|
+
const added = [];
|
|
1181
|
+
for (const entry of detected) {
|
|
1182
|
+
const key = entry.name.toLowerCase();
|
|
1183
|
+
if (!seen.has(key)) {
|
|
1184
|
+
seen.set(key, entry);
|
|
1185
|
+
added.push(entry);
|
|
1186
|
+
}
|
|
1187
|
+
}
|
|
1188
|
+
const merged = Array.from(seen.values()).sort((a, b) => {
|
|
1189
|
+
const catCmp = a.category.localeCompare(b.category);
|
|
1190
|
+
if (catCmp !== 0) return catCmp;
|
|
1191
|
+
return a.name.localeCompare(b.name);
|
|
1192
|
+
});
|
|
1193
|
+
return { merged, added };
|
|
1194
|
+
}
|
|
1195
|
+
function parseTechStack(raw) {
|
|
1196
|
+
if (!Array.isArray(raw)) return [];
|
|
1197
|
+
return raw.filter(
|
|
1198
|
+
(item) => typeof item === "object" && item !== null && typeof item.name === "string" && typeof item.category === "string"
|
|
1199
|
+
);
|
|
1200
|
+
}
|
|
1201
|
+
var PACKAGE_MAP, CONFIG_FILE_MAP;
|
|
1202
|
+
var init_tech_detect = __esm({
|
|
1203
|
+
"src/lib/tech-detect.ts"() {
|
|
1204
|
+
"use strict";
|
|
1205
|
+
PACKAGE_MAP = {
|
|
1206
|
+
// Frameworks
|
|
1207
|
+
next: { name: "Next.js", category: "framework" },
|
|
1208
|
+
nuxt: { name: "Nuxt", category: "framework" },
|
|
1209
|
+
gatsby: { name: "Gatsby", category: "framework" },
|
|
1210
|
+
express: { name: "Express", category: "framework" },
|
|
1211
|
+
fastify: { name: "Fastify", category: "framework" },
|
|
1212
|
+
hono: { name: "Hono", category: "framework" },
|
|
1213
|
+
"@remix-run/node": { name: "Remix", category: "framework" },
|
|
1214
|
+
svelte: { name: "Svelte", category: "framework" },
|
|
1215
|
+
astro: { name: "Astro", category: "framework" },
|
|
1216
|
+
"@angular/core": { name: "Angular", category: "framework" },
|
|
1217
|
+
// Libraries (UI)
|
|
1218
|
+
react: { name: "React", category: "framework" },
|
|
1219
|
+
vue: { name: "Vue", category: "framework" },
|
|
1220
|
+
"solid-js": { name: "Solid", category: "framework" },
|
|
1221
|
+
preact: { name: "Preact", category: "framework" },
|
|
1222
|
+
// Languages (detected via devDeps)
|
|
1223
|
+
typescript: { name: "TypeScript", category: "language" },
|
|
1224
|
+
// Styling
|
|
1225
|
+
tailwindcss: { name: "Tailwind CSS", category: "styling" },
|
|
1226
|
+
sass: { name: "SCSS", category: "styling" },
|
|
1227
|
+
"styled-components": { name: "styled-components", category: "styling" },
|
|
1228
|
+
"@emotion/react": { name: "Emotion", category: "styling" },
|
|
1229
|
+
// Database
|
|
1230
|
+
prisma: { name: "Prisma", category: "database" },
|
|
1231
|
+
"@prisma/client": { name: "Prisma", category: "database" },
|
|
1232
|
+
"drizzle-orm": { name: "Drizzle", category: "database" },
|
|
1233
|
+
"@supabase/supabase-js": { name: "Supabase", category: "database" },
|
|
1234
|
+
mongoose: { name: "MongoDB", category: "database" },
|
|
1235
|
+
typeorm: { name: "TypeORM", category: "database" },
|
|
1236
|
+
knex: { name: "Knex", category: "database" },
|
|
1237
|
+
// Testing
|
|
1238
|
+
jest: { name: "Jest", category: "testing" },
|
|
1239
|
+
vitest: { name: "Vitest", category: "testing" },
|
|
1240
|
+
mocha: { name: "Mocha", category: "testing" },
|
|
1241
|
+
playwright: { name: "Playwright", category: "testing" },
|
|
1242
|
+
"@playwright/test": { name: "Playwright", category: "testing" },
|
|
1243
|
+
cypress: { name: "Cypress", category: "testing" },
|
|
1244
|
+
// Build tools
|
|
1245
|
+
turbo: { name: "Turborepo", category: "build" },
|
|
1246
|
+
vite: { name: "Vite", category: "build" },
|
|
1247
|
+
webpack: { name: "Webpack", category: "build" },
|
|
1248
|
+
esbuild: { name: "esbuild", category: "build" },
|
|
1249
|
+
rollup: { name: "Rollup", category: "build" },
|
|
1250
|
+
// Tools
|
|
1251
|
+
eslint: { name: "ESLint", category: "tool" },
|
|
1252
|
+
prettier: { name: "Prettier", category: "tool" },
|
|
1253
|
+
"@biomejs/biome": { name: "Biome", category: "tool" }
|
|
1254
|
+
};
|
|
1255
|
+
CONFIG_FILE_MAP = [
|
|
1256
|
+
{ file: "tsconfig.json", rule: { name: "TypeScript", category: "language" } },
|
|
1257
|
+
{ file: "next.config.js", rule: { name: "Next.js", category: "framework" } },
|
|
1258
|
+
{ file: "next.config.mjs", rule: { name: "Next.js", category: "framework" } },
|
|
1259
|
+
{ file: "next.config.ts", rule: { name: "Next.js", category: "framework" } },
|
|
1260
|
+
{ file: "tailwind.config.js", rule: { name: "Tailwind CSS", category: "styling" } },
|
|
1261
|
+
{ file: "tailwind.config.ts", rule: { name: "Tailwind CSS", category: "styling" } },
|
|
1262
|
+
{ file: "turbo.json", rule: { name: "Turborepo", category: "build" } },
|
|
1263
|
+
{ file: "docker-compose.yml", rule: { name: "Docker", category: "deployment" } },
|
|
1264
|
+
{ file: "docker-compose.yaml", rule: { name: "Docker", category: "deployment" } },
|
|
1265
|
+
{ file: "Dockerfile", rule: { name: "Docker", category: "deployment" } },
|
|
1266
|
+
{ file: "vercel.json", rule: { name: "Vercel", category: "deployment" } }
|
|
1267
|
+
];
|
|
1268
|
+
}
|
|
1269
|
+
});
|
|
1270
|
+
|
|
1271
|
+
// src/cli/sync.ts
|
|
1272
|
+
var sync_exports = {};
|
|
1273
|
+
__export(sync_exports, {
|
|
1274
|
+
runSync: () => runSync
|
|
1364
1275
|
});
|
|
1365
|
-
import {
|
|
1276
|
+
import { readFile as readFile7, writeFile as writeFile3 } from "node:fs/promises";
|
|
1366
1277
|
import { join as join7 } from "node:path";
|
|
1367
|
-
async function
|
|
1278
|
+
async function runSync() {
|
|
1368
1279
|
const flags = parseFlags(3);
|
|
1369
1280
|
const dryRun = hasFlag("dry-run", 3);
|
|
1370
1281
|
const force = hasFlag("force", 3);
|
|
1371
|
-
const isGlobal = hasFlag("global", 3);
|
|
1372
1282
|
validateApiKey();
|
|
1373
1283
|
const config2 = await resolveConfig(flags);
|
|
1374
1284
|
const { repoId, projectPath } = config2;
|
|
1375
1285
|
console.log(`
|
|
1376
|
-
CodeByPlan
|
|
1286
|
+
CodeByPlan Sync`);
|
|
1377
1287
|
console.log(` Repo: ${repoId}`);
|
|
1378
1288
|
console.log(` Path: ${projectPath}`);
|
|
1379
|
-
if (dryRun) console.log(` Mode: dry-run
|
|
1380
|
-
if (force) console.log(` Mode: force
|
|
1289
|
+
if (dryRun) console.log(` Mode: dry-run`);
|
|
1290
|
+
if (force) console.log(` Mode: force`);
|
|
1381
1291
|
console.log();
|
|
1292
|
+
console.log(" Phase 1: Pull (DB \u2192 local)...");
|
|
1293
|
+
const pullResult = await executeSyncToLocal({ repoId, projectPath, dryRun });
|
|
1294
|
+
const pullChanges = pullResult.totals.created + pullResult.totals.updated + pullResult.totals.deleted;
|
|
1295
|
+
if (pullChanges > 0) {
|
|
1296
|
+
for (const [typeKey, result] of Object.entries(pullResult.byType)) {
|
|
1297
|
+
for (const name of result.created) console.log(` + ${typeKey}/${name}`);
|
|
1298
|
+
for (const name of result.updated) console.log(` ~ ${typeKey}/${name}`);
|
|
1299
|
+
for (const name of result.deleted) console.log(` - ${typeKey}/${name}`);
|
|
1300
|
+
}
|
|
1301
|
+
console.log(` ${pullResult.totals.created} created, ${pullResult.totals.updated} updated, ${pullResult.totals.deleted} deleted`);
|
|
1302
|
+
} else {
|
|
1303
|
+
console.log(" All files up to date.");
|
|
1304
|
+
}
|
|
1305
|
+
console.log("\n Phase 2: Push (local \u2192 DB)...");
|
|
1306
|
+
await executePush(repoId, projectPath, dryRun, force);
|
|
1307
|
+
console.log("\n Phase 3: Config sync...");
|
|
1308
|
+
await syncConfig(repoId, projectPath, dryRun);
|
|
1309
|
+
console.log("\n Phase 4: Tech stack...");
|
|
1310
|
+
await syncTechStack(repoId, projectPath, dryRun);
|
|
1311
|
+
console.log("\n Sync complete.\n");
|
|
1312
|
+
}
|
|
1313
|
+
async function executePush(repoId, projectPath, dryRun, force) {
|
|
1382
1314
|
const claudeDir = join7(projectPath, ".claude");
|
|
1315
|
+
let localFiles;
|
|
1383
1316
|
try {
|
|
1384
|
-
await
|
|
1317
|
+
localFiles = await scanLocalFiles(claudeDir, projectPath);
|
|
1385
1318
|
} catch {
|
|
1386
|
-
console.log("
|
|
1319
|
+
console.log(" No .claude/ directory found. Skipping push.");
|
|
1387
1320
|
return;
|
|
1388
1321
|
}
|
|
1389
|
-
console.log(" Scanning local files...");
|
|
1390
|
-
const localFiles = await scanLocalFiles(claudeDir, projectPath);
|
|
1391
|
-
console.log(` Found ${localFiles.size} local files.`);
|
|
1392
|
-
console.log(" Fetching remote state...");
|
|
1393
1322
|
const [syncRes, repoRes] = await Promise.all([
|
|
1394
1323
|
apiGet("/sync/files", { repo_id: repoId }),
|
|
1395
1324
|
apiGet(`/repos/${repoId}`)
|
|
1396
1325
|
]);
|
|
1397
1326
|
const repoData = repoRes.data;
|
|
1398
1327
|
const remoteFiles = flattenSyncData(syncRes.data);
|
|
1399
|
-
console.log(` Found ${remoteFiles.size} remote files.
|
|
1400
|
-
`);
|
|
1401
1328
|
const toUpsert = [];
|
|
1402
1329
|
const toDelete = [];
|
|
1403
1330
|
const conflicts = [];
|
|
@@ -1415,7 +1342,7 @@ async function runPush() {
|
|
|
1415
1342
|
} else {
|
|
1416
1343
|
conflicts.push({
|
|
1417
1344
|
key,
|
|
1418
|
-
displayPath:
|
|
1345
|
+
displayPath: `${local.type}/${local.category ? local.category + "/" : ""}${local.name}`,
|
|
1419
1346
|
remoteModified: remote.updated_at
|
|
1420
1347
|
});
|
|
1421
1348
|
conflictLocalRecords.set(key, reversed);
|
|
@@ -1438,69 +1365,101 @@ async function runPush() {
|
|
|
1438
1365
|
}
|
|
1439
1366
|
}
|
|
1440
1367
|
if (toUpsert.length > 0) {
|
|
1441
|
-
console.log(` Files to push: ${toUpsert.length}`);
|
|
1442
1368
|
for (const f of toUpsert) {
|
|
1443
|
-
console.log(` + ${
|
|
1369
|
+
console.log(` + ${f.type}/${f.category ? f.category + "/" : ""}${f.name}`);
|
|
1444
1370
|
}
|
|
1445
1371
|
}
|
|
1446
1372
|
if (toDelete.length > 0) {
|
|
1447
|
-
console.log(` Files to delete from remote: ${toDelete.length}`);
|
|
1448
1373
|
for (const d of toDelete) {
|
|
1449
1374
|
console.log(` - ${d.type}/${d.category ? d.category + "/" : ""}${d.name}`);
|
|
1450
1375
|
}
|
|
1451
1376
|
}
|
|
1452
1377
|
if (toUpsert.length === 0 && toDelete.length === 0) {
|
|
1453
|
-
|
|
1454
|
-
const repoUpdate2 = { claude_sync_at: (/* @__PURE__ */ new Date()).toISOString() };
|
|
1455
|
-
await pushTechStack(projectPath, repoRes.data.tech_stack, repoUpdate2);
|
|
1456
|
-
await apiPut(`/repos/${repoId}`, repoUpdate2);
|
|
1457
|
-
}
|
|
1458
|
-
console.log(" Everything is in sync. Nothing to push.\n");
|
|
1378
|
+
console.log(" Nothing to push.");
|
|
1459
1379
|
return;
|
|
1460
1380
|
}
|
|
1461
1381
|
if (dryRun) {
|
|
1462
|
-
console.log("
|
|
1382
|
+
console.log(" (dry-run \u2014 no changes)");
|
|
1463
1383
|
return;
|
|
1464
1384
|
}
|
|
1465
1385
|
if (!force) {
|
|
1466
1386
|
const confirmed = await confirmProceed();
|
|
1467
1387
|
if (!confirmed) {
|
|
1468
|
-
console.log("
|
|
1388
|
+
console.log(" Cancelled.");
|
|
1469
1389
|
return;
|
|
1470
1390
|
}
|
|
1471
1391
|
}
|
|
1472
|
-
console.log("\n Pushing changes...");
|
|
1473
1392
|
const result = await apiPost("/sync/files", {
|
|
1474
1393
|
repo_id: repoId,
|
|
1475
1394
|
files: toUpsert.map((f) => ({
|
|
1476
1395
|
type: f.type,
|
|
1477
1396
|
name: f.name,
|
|
1478
1397
|
category: f.category,
|
|
1479
|
-
content: f.content
|
|
1480
|
-
...f.type === "settings" ? { scope: isGlobal ? "global" : "repo" } : {}
|
|
1398
|
+
content: f.content
|
|
1481
1399
|
})),
|
|
1482
1400
|
delete_keys: toDelete
|
|
1483
1401
|
});
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1402
|
+
await apiPut(`/repos/${repoId}`, { claude_sync_at: (/* @__PURE__ */ new Date()).toISOString() });
|
|
1403
|
+
console.log(` ${result.data.upserted} upserted, ${result.data.deleted} deleted`);
|
|
1404
|
+
}
|
|
1405
|
+
async function syncConfig(repoId, projectPath, dryRun) {
|
|
1406
|
+
const configPath = join7(projectPath, ".codebyplan.json");
|
|
1407
|
+
let currentConfig = {};
|
|
1408
|
+
try {
|
|
1409
|
+
const raw = await readFile7(configPath, "utf-8");
|
|
1410
|
+
currentConfig = JSON.parse(raw);
|
|
1411
|
+
} catch {
|
|
1412
|
+
currentConfig = { repo_id: repoId };
|
|
1413
|
+
}
|
|
1414
|
+
const repoRes = await apiGet(`/repos/${repoId}`);
|
|
1415
|
+
const repo = repoRes.data;
|
|
1416
|
+
let portAllocations = [];
|
|
1417
|
+
try {
|
|
1418
|
+
const portsRes = await apiGet(`/port-allocations`, { repo_id: repoId });
|
|
1419
|
+
portAllocations = portsRes.data ?? [];
|
|
1420
|
+
} catch {
|
|
1421
|
+
}
|
|
1422
|
+
const newConfig = {
|
|
1423
|
+
repo_id: repoId,
|
|
1424
|
+
...currentConfig.worktree_id ? { worktree_id: currentConfig.worktree_id } : {},
|
|
1425
|
+
server_port: repo.server_port,
|
|
1426
|
+
server_type: repo.server_type,
|
|
1427
|
+
git_branch: repo.git_branch ?? "development",
|
|
1428
|
+
auto_push_enabled: repo.auto_push_enabled,
|
|
1429
|
+
...portAllocations.length > 0 ? { port_allocations: portAllocations } : {}
|
|
1430
|
+
};
|
|
1431
|
+
const currentJson = JSON.stringify(currentConfig, null, 2);
|
|
1432
|
+
const newJson = JSON.stringify(newConfig, null, 2);
|
|
1433
|
+
if (currentJson === newJson) {
|
|
1434
|
+
console.log(" Config up to date.");
|
|
1435
|
+
return;
|
|
1436
|
+
}
|
|
1437
|
+
if (dryRun) {
|
|
1438
|
+
console.log(" Config would be updated (dry-run).");
|
|
1439
|
+
return;
|
|
1440
|
+
}
|
|
1441
|
+
await writeFile3(configPath, newJson + "\n", "utf-8");
|
|
1442
|
+
console.log(" Updated .codebyplan.json");
|
|
1489
1443
|
}
|
|
1490
|
-
async function
|
|
1444
|
+
async function syncTechStack(repoId, projectPath, dryRun) {
|
|
1491
1445
|
try {
|
|
1492
1446
|
const detected = await detectTechStack(projectPath);
|
|
1493
|
-
if (detected.length === 0)
|
|
1494
|
-
|
|
1447
|
+
if (detected.length === 0) {
|
|
1448
|
+
console.log(" No tech stack detected.");
|
|
1449
|
+
return;
|
|
1450
|
+
}
|
|
1451
|
+
const repoRes = await apiGet(`/repos/${repoId}`);
|
|
1452
|
+
const remote = parseTechStack(repoRes.data.tech_stack);
|
|
1495
1453
|
const { merged, added } = mergeTechStack(remote, detected);
|
|
1496
|
-
console.log(`
|
|
1454
|
+
console.log(` ${detected.length} detected${added.length > 0 ? ` (${added.length} new)` : ""}`);
|
|
1497
1455
|
for (const entry of added) {
|
|
1498
|
-
console.log(`
|
|
1456
|
+
console.log(` + ${entry.name} (${entry.category})`);
|
|
1499
1457
|
}
|
|
1500
|
-
if (added.length > 0) {
|
|
1501
|
-
|
|
1458
|
+
if (added.length > 0 && !dryRun) {
|
|
1459
|
+
await apiPut(`/repos/${repoId}`, { tech_stack: merged });
|
|
1502
1460
|
}
|
|
1503
1461
|
} catch {
|
|
1462
|
+
console.log(" Tech stack detection skipped.");
|
|
1504
1463
|
}
|
|
1505
1464
|
}
|
|
1506
1465
|
function flattenSyncData(data) {
|
|
@@ -1529,206 +1488,20 @@ function flattenSyncData(data) {
|
|
|
1529
1488
|
}
|
|
1530
1489
|
return result;
|
|
1531
1490
|
}
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
return `${f.type}/${f.name}`;
|
|
1535
|
-
}
|
|
1536
|
-
var init_push = __esm({
|
|
1537
|
-
"src/cli/push.ts"() {
|
|
1491
|
+
var init_sync = __esm({
|
|
1492
|
+
"src/cli/sync.ts"() {
|
|
1538
1493
|
"use strict";
|
|
1539
1494
|
init_config();
|
|
1540
1495
|
init_fileMapper();
|
|
1541
1496
|
init_conflict();
|
|
1542
1497
|
init_confirm();
|
|
1543
1498
|
init_api();
|
|
1499
|
+
init_sync_engine();
|
|
1544
1500
|
init_variables();
|
|
1545
1501
|
init_tech_detect();
|
|
1546
1502
|
}
|
|
1547
1503
|
});
|
|
1548
1504
|
|
|
1549
|
-
// src/cli/init.ts
|
|
1550
|
-
var init_exports = {};
|
|
1551
|
-
__export(init_exports, {
|
|
1552
|
-
runInit: () => runInit
|
|
1553
|
-
});
|
|
1554
|
-
import { createInterface as createInterface4 } from "node:readline/promises";
|
|
1555
|
-
import { stdin as stdin4, stdout as stdout4 } from "node:process";
|
|
1556
|
-
import { writeFile as writeFile3, mkdir as mkdir2, chmod as chmod2 } from "node:fs/promises";
|
|
1557
|
-
import { join as join8, dirname as dirname2 } from "node:path";
|
|
1558
|
-
async function runInit() {
|
|
1559
|
-
const flags = parseFlags(3);
|
|
1560
|
-
const projectPath = flags["path"] ?? process.cwd();
|
|
1561
|
-
console.log("\n CodeByPlan Init\n");
|
|
1562
|
-
validateApiKey();
|
|
1563
|
-
const rl = createInterface4({ input: stdin4, output: stdout4 });
|
|
1564
|
-
try {
|
|
1565
|
-
let repoId = flags["repo-id"] ?? process.env.CODEBYPLAN_REPO_ID;
|
|
1566
|
-
if (!repoId) {
|
|
1567
|
-
console.log(" Fetching your repositories...\n");
|
|
1568
|
-
const res = await apiGet("/repos");
|
|
1569
|
-
const repos = res.data;
|
|
1570
|
-
if (repos.length === 0) {
|
|
1571
|
-
console.log(" No repositories found. Create one at https://codebyplan.com first.\n");
|
|
1572
|
-
return;
|
|
1573
|
-
}
|
|
1574
|
-
for (let i = 0; i < repos.length; i++) {
|
|
1575
|
-
console.log(` ${i + 1}. ${repos[i].name}`);
|
|
1576
|
-
}
|
|
1577
|
-
console.log();
|
|
1578
|
-
const choice = (await rl.question(" Select a repository (number): ")).trim();
|
|
1579
|
-
const index = parseInt(choice, 10) - 1;
|
|
1580
|
-
if (isNaN(index) || index < 0 || index >= repos.length) {
|
|
1581
|
-
console.log(" Invalid selection. Aborting.\n");
|
|
1582
|
-
return;
|
|
1583
|
-
}
|
|
1584
|
-
repoId = repos[index].id;
|
|
1585
|
-
console.log(`
|
|
1586
|
-
Selected: ${repos[index].name}
|
|
1587
|
-
`);
|
|
1588
|
-
}
|
|
1589
|
-
let worktreeId;
|
|
1590
|
-
try {
|
|
1591
|
-
const worktreesRes = await apiGet(`/worktrees?repo_id=${repoId}`);
|
|
1592
|
-
const match = worktreesRes.data.find((wt) => projectPath === wt.path || projectPath.startsWith(wt.path + "/"));
|
|
1593
|
-
if (match) worktreeId = match.id;
|
|
1594
|
-
} catch {
|
|
1595
|
-
}
|
|
1596
|
-
const configPath = join8(projectPath, ".codebyplan.json");
|
|
1597
|
-
const configData = { repo_id: repoId };
|
|
1598
|
-
if (worktreeId) configData.worktree_id = worktreeId;
|
|
1599
|
-
const configContent = JSON.stringify(configData, null, 2) + "\n";
|
|
1600
|
-
await writeFile3(configPath, configContent, "utf-8");
|
|
1601
|
-
console.log(` Created ${configPath}`);
|
|
1602
|
-
const seedAnswer = (await rl.question("\n Seed with CodeByPlan defaults? (Y/n): ")).trim().toLowerCase();
|
|
1603
|
-
if (seedAnswer === "" || seedAnswer === "y" || seedAnswer === "yes") {
|
|
1604
|
-
let getFilePath3 = function(typeName, file) {
|
|
1605
|
-
const cfg = typeConfig2[typeName];
|
|
1606
|
-
const typeDir = typeName === "command" ? join8(claudeDir, cfg.dir, "cbp") : join8(claudeDir, cfg.dir);
|
|
1607
|
-
if (cfg.subfolder) {
|
|
1608
|
-
return join8(typeDir, file.name, `${cfg.subfolder}${cfg.ext}`);
|
|
1609
|
-
}
|
|
1610
|
-
if (typeName === "command" && file.category) {
|
|
1611
|
-
return join8(typeDir, file.category, `${file.name}${cfg.ext}`);
|
|
1612
|
-
}
|
|
1613
|
-
if (typeName === "template") {
|
|
1614
|
-
return join8(typeDir, file.name);
|
|
1615
|
-
}
|
|
1616
|
-
return join8(typeDir, `${file.name}${cfg.ext}`);
|
|
1617
|
-
};
|
|
1618
|
-
var getFilePath2 = getFilePath3;
|
|
1619
|
-
console.log("\n Fetching default files...");
|
|
1620
|
-
let defaultsData;
|
|
1621
|
-
try {
|
|
1622
|
-
const defaultsRes = await apiGet("/sync/defaults");
|
|
1623
|
-
defaultsData = defaultsRes.data;
|
|
1624
|
-
} catch (err) {
|
|
1625
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
1626
|
-
console.log(` Could not fetch defaults: ${msg}`);
|
|
1627
|
-
console.log(" You can run 'codebyplan pull' later to sync files.\n");
|
|
1628
|
-
printNextSteps(projectPath);
|
|
1629
|
-
return;
|
|
1630
|
-
}
|
|
1631
|
-
const claudeDir = join8(projectPath, ".claude");
|
|
1632
|
-
let written = 0;
|
|
1633
|
-
const typeConfig2 = {
|
|
1634
|
-
command: { dir: "commands", ext: ".md" },
|
|
1635
|
-
agent: { dir: "agents", ext: ".md", subfolder: "AGENT" },
|
|
1636
|
-
skill: { dir: "skills", ext: ".md", subfolder: "SKILL" },
|
|
1637
|
-
rule: { dir: "rules", ext: ".md" },
|
|
1638
|
-
hook: { dir: "hooks", ext: ".sh" },
|
|
1639
|
-
template: { dir: "templates", ext: "" }
|
|
1640
|
-
};
|
|
1641
|
-
const syncKeyToType2 = {
|
|
1642
|
-
commands: "command",
|
|
1643
|
-
agents: "agent",
|
|
1644
|
-
skills: "skill",
|
|
1645
|
-
rules: "rule",
|
|
1646
|
-
hooks: "hook",
|
|
1647
|
-
templates: "template"
|
|
1648
|
-
};
|
|
1649
|
-
for (const [syncKey, typeName] of Object.entries(syncKeyToType2)) {
|
|
1650
|
-
const files = defaultsData[syncKey] ?? [];
|
|
1651
|
-
for (const file of files) {
|
|
1652
|
-
const filePath = getFilePath3(typeName, file);
|
|
1653
|
-
await mkdir2(dirname2(filePath), { recursive: true });
|
|
1654
|
-
await writeFile3(filePath, file.content, "utf-8");
|
|
1655
|
-
if (typeName === "hook") await chmod2(filePath, 493);
|
|
1656
|
-
written++;
|
|
1657
|
-
}
|
|
1658
|
-
}
|
|
1659
|
-
const specialFiles = [
|
|
1660
|
-
...defaultsData.claude_md ?? []
|
|
1661
|
-
];
|
|
1662
|
-
for (const file of specialFiles) {
|
|
1663
|
-
const targetPath = join8(projectPath, "CLAUDE.md");
|
|
1664
|
-
await mkdir2(dirname2(targetPath), { recursive: true });
|
|
1665
|
-
await writeFile3(targetPath, file.content, "utf-8");
|
|
1666
|
-
written++;
|
|
1667
|
-
}
|
|
1668
|
-
const settingsFiles = defaultsData.settings ?? [];
|
|
1669
|
-
for (const file of settingsFiles) {
|
|
1670
|
-
const targetPath = join8(claudeDir, "settings.json");
|
|
1671
|
-
await mkdir2(dirname2(targetPath), { recursive: true });
|
|
1672
|
-
await writeFile3(targetPath, file.content, "utf-8");
|
|
1673
|
-
written++;
|
|
1674
|
-
}
|
|
1675
|
-
console.log(` Wrote ${written} files to .claude/
|
|
1676
|
-
`);
|
|
1677
|
-
console.log(" Syncing defaults to your repository...");
|
|
1678
|
-
try {
|
|
1679
|
-
const allFiles = [];
|
|
1680
|
-
for (const [syncKey, typeName] of Object.entries(syncKeyToType2)) {
|
|
1681
|
-
const files = defaultsData[syncKey] ?? [];
|
|
1682
|
-
for (const file of files) {
|
|
1683
|
-
allFiles.push({
|
|
1684
|
-
type: typeName,
|
|
1685
|
-
name: file.name,
|
|
1686
|
-
category: file.category ?? null,
|
|
1687
|
-
content: file.content
|
|
1688
|
-
});
|
|
1689
|
-
}
|
|
1690
|
-
}
|
|
1691
|
-
for (const file of specialFiles) {
|
|
1692
|
-
allFiles.push({ type: "claude_md", name: file.name, content: file.content });
|
|
1693
|
-
}
|
|
1694
|
-
for (const file of settingsFiles) {
|
|
1695
|
-
allFiles.push({ type: "settings", name: file.name, content: file.content, scope: file.scope ?? "repo" });
|
|
1696
|
-
}
|
|
1697
|
-
if (allFiles.length > 0) {
|
|
1698
|
-
await apiPost("/sync/files", {
|
|
1699
|
-
repo_id: repoId,
|
|
1700
|
-
files: allFiles
|
|
1701
|
-
});
|
|
1702
|
-
console.log(` Synced ${allFiles.length} files to database.
|
|
1703
|
-
`);
|
|
1704
|
-
}
|
|
1705
|
-
} catch (err) {
|
|
1706
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
1707
|
-
console.log(` Warning: Could not sync to database: ${msg}`);
|
|
1708
|
-
console.log(" Local files were written successfully. Run 'codebyplan push' to sync later.\n");
|
|
1709
|
-
}
|
|
1710
|
-
} else {
|
|
1711
|
-
console.log();
|
|
1712
|
-
}
|
|
1713
|
-
printNextSteps(projectPath);
|
|
1714
|
-
} finally {
|
|
1715
|
-
rl.close();
|
|
1716
|
-
}
|
|
1717
|
-
}
|
|
1718
|
-
function printNextSteps(_projectPath) {
|
|
1719
|
-
console.log(" Next steps:");
|
|
1720
|
-
console.log(" 1. Start a Claude Code session in this project");
|
|
1721
|
-
console.log(" 2. Use 'codebyplan pull' to sync latest files from database");
|
|
1722
|
-
console.log(" 3. Use 'codebyplan push' to push local changes back\n");
|
|
1723
|
-
}
|
|
1724
|
-
var init_init = __esm({
|
|
1725
|
-
"src/cli/init.ts"() {
|
|
1726
|
-
"use strict";
|
|
1727
|
-
init_config();
|
|
1728
|
-
init_api();
|
|
1729
|
-
}
|
|
1730
|
-
});
|
|
1731
|
-
|
|
1732
1505
|
// ../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/helpers/util.js
|
|
1733
1506
|
var util, objectUtil, ZodParsedType, getParsedType;
|
|
1734
1507
|
var init_util = __esm({
|
|
@@ -23543,11 +23316,11 @@ async function createPR(options) {
|
|
|
23543
23316
|
}
|
|
23544
23317
|
} catch {
|
|
23545
23318
|
}
|
|
23546
|
-
const { stdout:
|
|
23319
|
+
const { stdout: stdout4 } = await exec(
|
|
23547
23320
|
`gh pr create --head "${head}" --base "${base}" --title "${title.replace(/"/g, '\\"')}" --body "${body.replace(/"/g, '\\"')}"`,
|
|
23548
23321
|
{ cwd: repoPath }
|
|
23549
23322
|
);
|
|
23550
|
-
const prUrl =
|
|
23323
|
+
const prUrl = stdout4.trim();
|
|
23551
23324
|
const prNumber = parseInt(prUrl.split("/").pop() ?? "0", 10);
|
|
23552
23325
|
return { pr_url: prUrl, pr_number: prNumber || null };
|
|
23553
23326
|
} catch (err) {
|
|
@@ -23558,11 +23331,11 @@ async function createPR(options) {
|
|
|
23558
23331
|
async function mergePR(options) {
|
|
23559
23332
|
const { repoPath, prNumber, mergeMethod } = options;
|
|
23560
23333
|
try {
|
|
23561
|
-
const { stdout:
|
|
23334
|
+
const { stdout: stdout4 } = await exec(
|
|
23562
23335
|
`gh pr merge ${prNumber} --${mergeMethod} --delete-branch`,
|
|
23563
23336
|
{ cwd: repoPath }
|
|
23564
23337
|
);
|
|
23565
|
-
return { merged: true, message:
|
|
23338
|
+
return { merged: true, message: stdout4.trim() || `PR #${prNumber} merged via ${mergeMethod}` };
|
|
23566
23339
|
} catch (err) {
|
|
23567
23340
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
23568
23341
|
return { merged: false, message: "Merge failed", error: errorMessage };
|
|
@@ -23570,11 +23343,11 @@ async function mergePR(options) {
|
|
|
23570
23343
|
}
|
|
23571
23344
|
async function getPRStatus(repoPath, prNumber) {
|
|
23572
23345
|
try {
|
|
23573
|
-
const { stdout:
|
|
23346
|
+
const { stdout: stdout4 } = await exec(
|
|
23574
23347
|
`gh pr view ${prNumber} --json state,mergeable,title,url,number`,
|
|
23575
23348
|
{ cwd: repoPath }
|
|
23576
23349
|
);
|
|
23577
|
-
const pr = JSON.parse(
|
|
23350
|
+
const pr = JSON.parse(stdout4.trim());
|
|
23578
23351
|
return {
|
|
23579
23352
|
state: pr.state,
|
|
23580
23353
|
mergeable: pr.mergeable,
|
|
@@ -24419,7 +24192,7 @@ function registerFileGenTools(server) {
|
|
|
24419
24192
|
}
|
|
24420
24193
|
const addCmd = files && files.length > 0 ? `git add ${files.map((f) => `"${f}"`).join(" ")}` : "git add .";
|
|
24421
24194
|
await exec2(addCmd, { cwd: repo.path });
|
|
24422
|
-
const { stdout:
|
|
24195
|
+
const { stdout: stdout4, stderr } = await exec2(
|
|
24423
24196
|
`git commit -m "${message.replace(/"/g, '\\"')}"`,
|
|
24424
24197
|
{ cwd: repo.path }
|
|
24425
24198
|
);
|
|
@@ -24430,7 +24203,7 @@ function registerFileGenTools(server) {
|
|
|
24430
24203
|
text: JSON.stringify({
|
|
24431
24204
|
status: "committed",
|
|
24432
24205
|
branch: branch.trim(),
|
|
24433
|
-
output:
|
|
24206
|
+
output: stdout4.trim(),
|
|
24434
24207
|
warnings: stderr.trim() || void 0
|
|
24435
24208
|
}, null, 2)
|
|
24436
24209
|
}]
|
|
@@ -24555,19 +24328,30 @@ if (arg === "setup") {
|
|
|
24555
24328
|
await runSetup2();
|
|
24556
24329
|
process.exit(0);
|
|
24557
24330
|
}
|
|
24558
|
-
if (arg === "
|
|
24559
|
-
const {
|
|
24560
|
-
await
|
|
24561
|
-
process.exit(0);
|
|
24562
|
-
}
|
|
24563
|
-
if (arg === "push") {
|
|
24564
|
-
const { runPush: runPush2 } = await Promise.resolve().then(() => (init_push(), push_exports));
|
|
24565
|
-
await runPush2();
|
|
24331
|
+
if (arg === "sync") {
|
|
24332
|
+
const { runSync: runSync2 } = await Promise.resolve().then(() => (init_sync(), sync_exports));
|
|
24333
|
+
await runSync2();
|
|
24566
24334
|
process.exit(0);
|
|
24567
24335
|
}
|
|
24568
|
-
if (arg === "
|
|
24569
|
-
|
|
24570
|
-
|
|
24336
|
+
if (arg === "help" || arg === "--help" || arg === "-h") {
|
|
24337
|
+
console.log(`
|
|
24338
|
+
CodeByPlan CLI v${VERSION}
|
|
24339
|
+
|
|
24340
|
+
Usage:
|
|
24341
|
+
codebyplan Start MCP server (for Claude Code)
|
|
24342
|
+
codebyplan setup Interactive setup (API key + project init + first sync)
|
|
24343
|
+
codebyplan sync Bidirectional sync (pull + push + config)
|
|
24344
|
+
codebyplan help Show this help message
|
|
24345
|
+
codebyplan --version Print version
|
|
24346
|
+
|
|
24347
|
+
Sync options:
|
|
24348
|
+
--path <dir> Project root directory (default: cwd)
|
|
24349
|
+
--repo-id <uuid> Repository ID (or set via .codebyplan.json)
|
|
24350
|
+
--dry-run Preview changes without writing
|
|
24351
|
+
--force Skip confirmation and conflict prompts
|
|
24352
|
+
|
|
24353
|
+
Learn more: https://codebyplan.com
|
|
24354
|
+
`);
|
|
24571
24355
|
process.exit(0);
|
|
24572
24356
|
}
|
|
24573
24357
|
if (arg === void 0) {
|
|
@@ -24578,18 +24362,6 @@ if (arg === void 0) {
|
|
|
24578
24362
|
});
|
|
24579
24363
|
} else {
|
|
24580
24364
|
console.error(`Unknown command: ${arg}`);
|
|
24581
|
-
console.error("
|
|
24582
|
-
console.error(" codebyplan Start MCP server");
|
|
24583
|
-
console.error(" codebyplan setup Interactive setup");
|
|
24584
|
-
console.error(" codebyplan init Initialize project with defaults");
|
|
24585
|
-
console.error(" codebyplan pull Pull .claude/ files from database");
|
|
24586
|
-
console.error(" codebyplan push Push .claude/ files to database");
|
|
24587
|
-
console.error(" codebyplan --version Print version");
|
|
24588
|
-
console.error("\nOptions (pull/push):");
|
|
24589
|
-
console.error(" --path <dir> Project root directory (default: cwd)");
|
|
24590
|
-
console.error(" --repo-id <uuid> Repository ID (or set CODEBYPLAN_REPO_ID or .codebyplan.json)");
|
|
24591
|
-
console.error(" --dry-run Preview changes without writing");
|
|
24592
|
-
console.error(" --force Skip confirmation and conflict prompts");
|
|
24593
|
-
console.error(" --status Show cross-repo sync status (pull only)");
|
|
24365
|
+
console.error("Run 'codebyplan help' for usage.");
|
|
24594
24366
|
process.exit(1);
|
|
24595
24367
|
}
|