codebyplan 1.10.2 → 1.11.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 +719 -240
- package/package.json +1 -1
- package/templates/hooks/README.md +58 -16
- package/templates/hooks/cbp-statusline.mjs +385 -0
- package/templates/hooks/cbp-statusline.py +331 -0
- package/templates/hooks/cbp-statusline.sh +138 -82
- package/templates/hooks/cbp-subagent-statusline.mjs +200 -0
- package/templates/hooks/cbp-subagent-statusline.py +183 -0
- package/templates/hooks/cbp-subagent-statusline.sh +87 -39
- package/templates/skills/cbp-session-start/SKILL.md +38 -10
- package/templates/skills/cbp-session-start/qa-regression.md +51 -0
- package/templates/skills/cbp-todo/SKILL.md +77 -24
- package/templates/skills/cbp-todo/qa-regression.md +45 -0
package/dist/cli.js
CHANGED
|
@@ -14,7 +14,7 @@ var VERSION, PACKAGE_NAME;
|
|
|
14
14
|
var init_version = __esm({
|
|
15
15
|
"src/lib/version.ts"() {
|
|
16
16
|
"use strict";
|
|
17
|
-
VERSION = "1.
|
|
17
|
+
VERSION = "1.11.0";
|
|
18
18
|
PACKAGE_NAME = "codebyplan";
|
|
19
19
|
}
|
|
20
20
|
});
|
|
@@ -22,13 +22,16 @@ var init_version = __esm({
|
|
|
22
22
|
// src/lib/local-config.ts
|
|
23
23
|
import { execSync } from "node:child_process";
|
|
24
24
|
import { createHash } from "node:crypto";
|
|
25
|
-
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
25
|
+
import { mkdir, readFile, stat, writeFile } from "node:fs/promises";
|
|
26
26
|
import { hostname } from "node:os";
|
|
27
27
|
import { dirname, join } from "node:path";
|
|
28
28
|
function localConfigPath(projectPath) {
|
|
29
29
|
return join(projectPath, ".codebyplan", "device.local.json");
|
|
30
30
|
}
|
|
31
|
-
|
|
31
|
+
function legacyLocalConfigPath(projectPath) {
|
|
32
|
+
return join(projectPath, ".codebyplan.local.json");
|
|
33
|
+
}
|
|
34
|
+
async function readLocalConfig(projectPath, onMigrationNotice) {
|
|
32
35
|
try {
|
|
33
36
|
const raw = await readFile(localConfigPath(projectPath), "utf-8");
|
|
34
37
|
const parsed = JSON.parse(raw);
|
|
@@ -38,22 +41,81 @@ async function readLocalConfig(projectPath) {
|
|
|
38
41
|
console.error("Failed to read local config: invalid shape");
|
|
39
42
|
return null;
|
|
40
43
|
} catch (err) {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
)
|
|
44
|
+
const code = err.code;
|
|
45
|
+
if (code === "ENOENT") {
|
|
46
|
+
} else if (code === "ENOTDIR") {
|
|
47
|
+
try {
|
|
48
|
+
const dirPath = dirname(localConfigPath(projectPath));
|
|
49
|
+
const st = await stat(dirPath);
|
|
50
|
+
if (!st.isDirectory()) {
|
|
51
|
+
throw Object.assign(
|
|
52
|
+
new Error(`${dirPath} is a file, expected directory`),
|
|
53
|
+
{ code: "LEGACY_FILE_BLOCKS_DIR" }
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
} catch (statErr) {
|
|
57
|
+
if (statErr.code === "LEGACY_FILE_BLOCKS_DIR")
|
|
58
|
+
throw statErr;
|
|
59
|
+
}
|
|
60
|
+
throw err;
|
|
61
|
+
} else if (typeof code === "string") {
|
|
62
|
+
throw err;
|
|
63
|
+
} else {
|
|
64
|
+
console.error(
|
|
65
|
+
`Failed to read local config: ${err instanceof Error ? err.message : String(err)}`
|
|
66
|
+
);
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
try {
|
|
71
|
+
const raw = await readFile(legacyLocalConfigPath(projectPath), "utf-8");
|
|
72
|
+
const parsed = JSON.parse(raw);
|
|
73
|
+
if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed) && typeof parsed.device_id === "string") {
|
|
74
|
+
onMigrationNotice?.(
|
|
75
|
+
legacyLocalConfigPath(projectPath),
|
|
76
|
+
localConfigPath(projectPath)
|
|
77
|
+
);
|
|
78
|
+
return parsed;
|
|
79
|
+
}
|
|
44
80
|
return null;
|
|
81
|
+
} catch (err) {
|
|
82
|
+
const code = err.code;
|
|
83
|
+
if (code === "ENOENT") {
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
throw err;
|
|
45
87
|
}
|
|
46
88
|
}
|
|
47
89
|
async function writeLocalConfig(projectPath, config) {
|
|
48
90
|
const content = { device_id: config.device_id };
|
|
49
91
|
const path6 = localConfigPath(projectPath);
|
|
92
|
+
const dirPath = dirname(path6);
|
|
93
|
+
let phase = "stat config directory";
|
|
50
94
|
try {
|
|
51
|
-
|
|
95
|
+
try {
|
|
96
|
+
const st = await stat(dirPath);
|
|
97
|
+
if (!st.isDirectory()) {
|
|
98
|
+
const err = Object.assign(
|
|
99
|
+
new Error(`${dirPath} is a file, expected directory`),
|
|
100
|
+
{ code: "LEGACY_FILE_BLOCKS_DIR" }
|
|
101
|
+
);
|
|
102
|
+
throw err;
|
|
103
|
+
}
|
|
104
|
+
} catch (statErr) {
|
|
105
|
+
const code = statErr.code;
|
|
106
|
+
if (code === "LEGACY_FILE_BLOCKS_DIR") throw statErr;
|
|
107
|
+
if (code !== "ENOENT") throw statErr;
|
|
108
|
+
}
|
|
109
|
+
phase = "create config directory";
|
|
110
|
+
await mkdir(dirPath, { recursive: true });
|
|
111
|
+
phase = "write local config";
|
|
52
112
|
await writeFile(path6, JSON.stringify(content, null, 2) + "\n", "utf-8");
|
|
53
113
|
} catch (err) {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
114
|
+
const code = err.code;
|
|
115
|
+
if (code === "LEGACY_FILE_BLOCKS_DIR") {
|
|
116
|
+
throw err;
|
|
117
|
+
}
|
|
118
|
+
console.error(`Failed to ${phase}: ${err.message}`);
|
|
57
119
|
throw err;
|
|
58
120
|
}
|
|
59
121
|
}
|
|
@@ -73,8 +135,8 @@ async function resolveMachineSeed() {
|
|
|
73
135
|
}
|
|
74
136
|
return hostname();
|
|
75
137
|
}
|
|
76
|
-
async function getOrCreateDeviceId(projectPath) {
|
|
77
|
-
const existing = await readLocalConfig(projectPath);
|
|
138
|
+
async function getOrCreateDeviceId(projectPath, onMigrationNotice) {
|
|
139
|
+
const existing = await readLocalConfig(projectPath, onMigrationNotice);
|
|
78
140
|
if (existing?.device_id) {
|
|
79
141
|
return existing.device_id;
|
|
80
142
|
}
|
|
@@ -89,6 +151,122 @@ var init_local_config = __esm({
|
|
|
89
151
|
}
|
|
90
152
|
});
|
|
91
153
|
|
|
154
|
+
// src/lib/statusline-config.ts
|
|
155
|
+
import { spawnSync } from "node:child_process";
|
|
156
|
+
import { mkdir as mkdir2, readFile as readFile2, writeFile as writeFile2 } from "node:fs/promises";
|
|
157
|
+
import { join as join2 } from "node:path";
|
|
158
|
+
function statuslineConfigPath(projectPath) {
|
|
159
|
+
return join2(projectPath, ".codebyplan", "statusline.json");
|
|
160
|
+
}
|
|
161
|
+
function statuslineLocalConfigPath(projectPath) {
|
|
162
|
+
return join2(projectPath, ".codebyplan", "statusline.local.json");
|
|
163
|
+
}
|
|
164
|
+
function isValidRenderer(value) {
|
|
165
|
+
return typeof value === "string" && VALID_RENDERERS.has(value);
|
|
166
|
+
}
|
|
167
|
+
function mergeOverDefaults(raw) {
|
|
168
|
+
const result = {
|
|
169
|
+
lines: { ...STATUSLINE_DEFAULTS.lines },
|
|
170
|
+
no_color: STATUSLINE_DEFAULTS.no_color
|
|
171
|
+
};
|
|
172
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
|
|
173
|
+
return result;
|
|
174
|
+
}
|
|
175
|
+
const obj = raw;
|
|
176
|
+
if (typeof obj.no_color === "boolean") {
|
|
177
|
+
result.no_color = obj.no_color;
|
|
178
|
+
}
|
|
179
|
+
if (typeof obj.lines === "object" && obj.lines !== null && !Array.isArray(obj.lines)) {
|
|
180
|
+
const lines = obj.lines;
|
|
181
|
+
for (const key of Object.keys(STATUSLINE_DEFAULTS.lines)) {
|
|
182
|
+
if (typeof lines[key] === "boolean") {
|
|
183
|
+
result.lines[key] = lines[key];
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
return result;
|
|
188
|
+
}
|
|
189
|
+
async function readStatuslineConfig(projectPath) {
|
|
190
|
+
try {
|
|
191
|
+
const raw = await readFile2(statuslineConfigPath(projectPath), "utf-8");
|
|
192
|
+
const parsed = JSON.parse(raw);
|
|
193
|
+
return mergeOverDefaults(parsed);
|
|
194
|
+
} catch {
|
|
195
|
+
return mergeOverDefaults(void 0);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
async function readStatuslineLocalConfig(projectPath) {
|
|
199
|
+
try {
|
|
200
|
+
const raw = await readFile2(statuslineLocalConfigPath(projectPath), "utf-8");
|
|
201
|
+
const parsed = JSON.parse(raw);
|
|
202
|
+
if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
|
|
203
|
+
const obj = parsed;
|
|
204
|
+
if (isValidRenderer(obj.renderer)) {
|
|
205
|
+
return { renderer: obj.renderer };
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
return null;
|
|
209
|
+
} catch {
|
|
210
|
+
return null;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
async function writeStatuslineLocalConfig(projectPath, localConfig) {
|
|
214
|
+
if (!isValidRenderer(localConfig.renderer)) {
|
|
215
|
+
throw new TypeError(
|
|
216
|
+
`Unknown renderer: "${String(localConfig.renderer)}". Must be one of: bash, node, python.`
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
const filePath = statuslineLocalConfigPath(projectPath);
|
|
220
|
+
await mkdir2(join2(projectPath, ".codebyplan"), { recursive: true });
|
|
221
|
+
await writeFile2(
|
|
222
|
+
filePath,
|
|
223
|
+
JSON.stringify(localConfig, null, 2) + "\n",
|
|
224
|
+
"utf-8"
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
async function resolveRenderer(projectPath) {
|
|
228
|
+
const local = await readStatuslineLocalConfig(projectPath);
|
|
229
|
+
return local?.renderer ?? DEFAULT_RENDERER;
|
|
230
|
+
}
|
|
231
|
+
function detectAvailableRenderers() {
|
|
232
|
+
function probe(cmd, args) {
|
|
233
|
+
try {
|
|
234
|
+
const result = spawnSync(cmd, args, {
|
|
235
|
+
timeout: 1e3,
|
|
236
|
+
stdio: "ignore",
|
|
237
|
+
windowsHide: true
|
|
238
|
+
});
|
|
239
|
+
return result.status === 0;
|
|
240
|
+
} catch {
|
|
241
|
+
return false;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
return {
|
|
245
|
+
bash: true,
|
|
246
|
+
node: probe("node", ["--version"]),
|
|
247
|
+
python: probe("python3", ["--version"])
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
var STATUSLINE_DEFAULTS, DEFAULT_RENDERER, VALID_RENDERERS;
|
|
251
|
+
var init_statusline_config = __esm({
|
|
252
|
+
"src/lib/statusline-config.ts"() {
|
|
253
|
+
"use strict";
|
|
254
|
+
STATUSLINE_DEFAULTS = {
|
|
255
|
+
lines: {
|
|
256
|
+
identity: true,
|
|
257
|
+
context: true,
|
|
258
|
+
cost: true,
|
|
259
|
+
rate_limits: true,
|
|
260
|
+
repo_pr: true,
|
|
261
|
+
worktree: true
|
|
262
|
+
},
|
|
263
|
+
no_color: false
|
|
264
|
+
};
|
|
265
|
+
DEFAULT_RENDERER = "bash";
|
|
266
|
+
VALID_RENDERERS = /* @__PURE__ */ new Set(["bash", "node", "python"]);
|
|
267
|
+
}
|
|
268
|
+
});
|
|
269
|
+
|
|
92
270
|
// src/oauth/jwt-decode.ts
|
|
93
271
|
function base64UrlDecode(input) {
|
|
94
272
|
const padded = input + "=".repeat((4 - input.length % 4) % 4);
|
|
@@ -119,9 +297,9 @@ var init_jwt_decode = __esm({
|
|
|
119
297
|
});
|
|
120
298
|
|
|
121
299
|
// src/oauth/keychain.ts
|
|
122
|
-
import { chmod, mkdir as
|
|
300
|
+
import { chmod, mkdir as mkdir3, readFile as readFile3, unlink, writeFile as writeFile3 } from "node:fs/promises";
|
|
123
301
|
import { homedir, platform } from "node:os";
|
|
124
|
-
import { dirname as dirname2, join as
|
|
302
|
+
import { dirname as dirname2, join as join3 } from "node:path";
|
|
125
303
|
async function loadKeyring() {
|
|
126
304
|
if (keyringOverride !== void 0) return keyringOverride;
|
|
127
305
|
try {
|
|
@@ -133,18 +311,18 @@ async function loadKeyring() {
|
|
|
133
311
|
}
|
|
134
312
|
function fallbackDir() {
|
|
135
313
|
if (platform() === "win32") {
|
|
136
|
-
const appData = process.env.APPDATA ??
|
|
137
|
-
return
|
|
314
|
+
const appData = process.env.APPDATA ?? join3(homedir(), "AppData", "Roaming");
|
|
315
|
+
return join3(appData, "codebyplan");
|
|
138
316
|
}
|
|
139
|
-
const xdg = process.env.XDG_CONFIG_HOME ??
|
|
140
|
-
return
|
|
317
|
+
const xdg = process.env.XDG_CONFIG_HOME ?? join3(homedir(), ".config");
|
|
318
|
+
return join3(xdg, "codebyplan");
|
|
141
319
|
}
|
|
142
320
|
function fallbackFile(filename) {
|
|
143
|
-
return
|
|
321
|
+
return join3(fallbackDir(), filename);
|
|
144
322
|
}
|
|
145
323
|
async function readFallback(filename) {
|
|
146
324
|
try {
|
|
147
|
-
const raw = await
|
|
325
|
+
const raw = await readFile3(fallbackFile(filename), "utf-8");
|
|
148
326
|
return JSON.parse(raw);
|
|
149
327
|
} catch {
|
|
150
328
|
return null;
|
|
@@ -152,8 +330,8 @@ async function readFallback(filename) {
|
|
|
152
330
|
}
|
|
153
331
|
async function writeFallback(filename, data) {
|
|
154
332
|
const path6 = fallbackFile(filename);
|
|
155
|
-
await
|
|
156
|
-
await
|
|
333
|
+
await mkdir3(dirname2(path6), { recursive: true });
|
|
334
|
+
await writeFile3(path6, JSON.stringify(data, null, 2) + "\n", "utf-8");
|
|
157
335
|
if (platform() !== "win32") {
|
|
158
336
|
try {
|
|
159
337
|
await chmod(path6, 384);
|
|
@@ -504,8 +682,8 @@ var init_api = __esm({
|
|
|
504
682
|
});
|
|
505
683
|
|
|
506
684
|
// src/lib/resolve-worktree.ts
|
|
507
|
-
import { readFile as
|
|
508
|
-
import { join as
|
|
685
|
+
import { readFile as readFile4, writeFile as writeFile4 } from "node:fs/promises";
|
|
686
|
+
import { join as join4 } from "node:path";
|
|
509
687
|
async function resolveAndCacheWorktreeId(repoId, projectPath, options) {
|
|
510
688
|
let worktreeId;
|
|
511
689
|
try {
|
|
@@ -527,10 +705,10 @@ async function resolveAndCacheWorktreeId(repoId, projectPath, options) {
|
|
|
527
705
|
if (options?.skipWrite) {
|
|
528
706
|
return worktreeId;
|
|
529
707
|
}
|
|
530
|
-
const codebyplanPath =
|
|
708
|
+
const codebyplanPath = join4(projectPath, ".codebyplan.json");
|
|
531
709
|
let currentConfig = {};
|
|
532
710
|
try {
|
|
533
|
-
const raw = await
|
|
711
|
+
const raw = await readFile4(codebyplanPath, "utf-8");
|
|
534
712
|
const parsed = JSON.parse(raw);
|
|
535
713
|
if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
|
|
536
714
|
currentConfig = parsed;
|
|
@@ -545,7 +723,7 @@ async function resolveAndCacheWorktreeId(repoId, projectPath, options) {
|
|
|
545
723
|
worktree_id: worktreeId
|
|
546
724
|
};
|
|
547
725
|
try {
|
|
548
|
-
await
|
|
726
|
+
await writeFile4(
|
|
549
727
|
codebyplanPath,
|
|
550
728
|
JSON.stringify(merged, null, 2) + "\n",
|
|
551
729
|
"utf-8"
|
|
@@ -561,7 +739,8 @@ async function resolveAndCacheWorktreeId(repoId, projectPath, options) {
|
|
|
561
739
|
async function resolveWorktreeByBranch({
|
|
562
740
|
repoId,
|
|
563
741
|
deviceId,
|
|
564
|
-
branch
|
|
742
|
+
branch,
|
|
743
|
+
onError
|
|
565
744
|
}) {
|
|
566
745
|
if (!deviceId || !branch) {
|
|
567
746
|
return null;
|
|
@@ -588,6 +767,10 @@ async function resolveWorktreeByBranch({
|
|
|
588
767
|
console.warn(
|
|
589
768
|
`Worktree self-heal skipped (non-fatal): branch worktree resolve failed: ${err instanceof Error ? err.message : String(err)}. Continuing.`
|
|
590
769
|
);
|
|
770
|
+
onError?.(
|
|
771
|
+
"api_failed",
|
|
772
|
+
err instanceof Error ? err : new Error(String(err))
|
|
773
|
+
);
|
|
591
774
|
return null;
|
|
592
775
|
}
|
|
593
776
|
}
|
|
@@ -595,7 +778,8 @@ async function resolveWorktreeId({
|
|
|
595
778
|
repoId,
|
|
596
779
|
repoPath,
|
|
597
780
|
branch,
|
|
598
|
-
deviceId
|
|
781
|
+
deviceId,
|
|
782
|
+
onError
|
|
599
783
|
}) {
|
|
600
784
|
try {
|
|
601
785
|
const res = await apiPost(
|
|
@@ -607,6 +791,10 @@ async function resolveWorktreeId({
|
|
|
607
791
|
console.warn(
|
|
608
792
|
`Worktree self-heal skipped (non-fatal): ${err instanceof Error ? err.message : String(err)}. Continuing \u2014 run \`codebyplan config\` again later to retry.`
|
|
609
793
|
);
|
|
794
|
+
onError?.(
|
|
795
|
+
"api_failed",
|
|
796
|
+
err instanceof Error ? err : new Error(String(err))
|
|
797
|
+
);
|
|
610
798
|
return null;
|
|
611
799
|
}
|
|
612
800
|
}
|
|
@@ -869,13 +1057,13 @@ var setup_exports = {};
|
|
|
869
1057
|
__export(setup_exports, {
|
|
870
1058
|
runSetup: () => runSetup
|
|
871
1059
|
});
|
|
872
|
-
import { mkdir as
|
|
1060
|
+
import { mkdir as mkdir4, readFile as readFile5, writeFile as writeFile5 } from "node:fs/promises";
|
|
873
1061
|
import { homedir as homedir2 } from "node:os";
|
|
874
|
-
import { join as
|
|
1062
|
+
import { join as join5 } from "node:path";
|
|
875
1063
|
import { stdin, stdout as stdout2 } from "node:process";
|
|
876
1064
|
import { createInterface } from "node:readline/promises";
|
|
877
1065
|
function getConfigPath(scope) {
|
|
878
|
-
return scope === "user" ?
|
|
1066
|
+
return scope === "user" ? join5(homedir2(), ".claude.json") : join5(process.cwd(), ".mcp.json");
|
|
879
1067
|
}
|
|
880
1068
|
function legacyMcpUrl() {
|
|
881
1069
|
const baseUrl2 = process.env.CODEBYPLAN_API_URL ?? "https://www.codebyplan.com";
|
|
@@ -883,7 +1071,7 @@ function legacyMcpUrl() {
|
|
|
883
1071
|
}
|
|
884
1072
|
async function readConfig(path6) {
|
|
885
1073
|
try {
|
|
886
|
-
const raw = await
|
|
1074
|
+
const raw = await readFile5(path6, "utf-8");
|
|
887
1075
|
const parsed = JSON.parse(raw);
|
|
888
1076
|
if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
|
|
889
1077
|
return parsed;
|
|
@@ -906,7 +1094,7 @@ async function writeMcpConfig(scope, auth) {
|
|
|
906
1094
|
config.mcpServers = {};
|
|
907
1095
|
}
|
|
908
1096
|
config.mcpServers.codebyplan = buildMcpEntry(auth);
|
|
909
|
-
await
|
|
1097
|
+
await writeFile5(configPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
910
1098
|
return configPath;
|
|
911
1099
|
}
|
|
912
1100
|
async function fetchRepos(auth) {
|
|
@@ -961,8 +1149,8 @@ async function chooseAuthMode(rl) {
|
|
|
961
1149
|
return { kind: "legacy", apiKey };
|
|
962
1150
|
}
|
|
963
1151
|
async function writeCodebyplanDirectory(projectPath, selectedRepo, deviceId) {
|
|
964
|
-
const codebyplanDir =
|
|
965
|
-
await
|
|
1152
|
+
const codebyplanDir = join5(projectPath, ".codebyplan");
|
|
1153
|
+
await mkdir4(codebyplanDir, { recursive: true });
|
|
966
1154
|
const repoJson = {
|
|
967
1155
|
repo_id: selectedRepo.id
|
|
968
1156
|
};
|
|
@@ -973,13 +1161,13 @@ async function writeCodebyplanDirectory(projectPath, selectedRepo, deviceId) {
|
|
|
973
1161
|
if (typeof repoAny.project_id === "string") {
|
|
974
1162
|
repoJson.project_id = repoAny.project_id;
|
|
975
1163
|
}
|
|
976
|
-
await
|
|
977
|
-
|
|
1164
|
+
await writeFile5(
|
|
1165
|
+
join5(codebyplanDir, "repo.json"),
|
|
978
1166
|
JSON.stringify(repoJson, null, 2) + "\n",
|
|
979
1167
|
"utf-8"
|
|
980
1168
|
);
|
|
981
|
-
await
|
|
982
|
-
|
|
1169
|
+
await writeFile5(
|
|
1170
|
+
join5(codebyplanDir, "server.json"),
|
|
983
1171
|
JSON.stringify(
|
|
984
1172
|
{
|
|
985
1173
|
server_port: null,
|
|
@@ -992,40 +1180,68 @@ async function writeCodebyplanDirectory(projectPath, selectedRepo, deviceId) {
|
|
|
992
1180
|
) + "\n",
|
|
993
1181
|
"utf-8"
|
|
994
1182
|
);
|
|
995
|
-
await
|
|
996
|
-
|
|
1183
|
+
await writeFile5(
|
|
1184
|
+
join5(codebyplanDir, "git.json"),
|
|
997
1185
|
JSON.stringify({ git_branch: null, branch_config: null }, null, 2) + "\n",
|
|
998
1186
|
"utf-8"
|
|
999
1187
|
);
|
|
1000
|
-
await
|
|
1001
|
-
|
|
1188
|
+
await writeFile5(
|
|
1189
|
+
join5(codebyplanDir, "shipment.json"),
|
|
1002
1190
|
JSON.stringify({ shipment: null }, null, 2) + "\n",
|
|
1003
1191
|
"utf-8"
|
|
1004
1192
|
);
|
|
1005
|
-
await
|
|
1006
|
-
|
|
1193
|
+
await writeFile5(
|
|
1194
|
+
join5(codebyplanDir, "vendor.json"),
|
|
1007
1195
|
JSON.stringify({}, null, 2) + "\n",
|
|
1008
1196
|
"utf-8"
|
|
1009
1197
|
);
|
|
1198
|
+
const statuslinePath = join5(codebyplanDir, "statusline.json");
|
|
1199
|
+
let statuslineExists = false;
|
|
1200
|
+
try {
|
|
1201
|
+
await readFile5(statuslinePath, "utf-8");
|
|
1202
|
+
statuslineExists = true;
|
|
1203
|
+
} catch {
|
|
1204
|
+
}
|
|
1205
|
+
if (!statuslineExists) {
|
|
1206
|
+
await writeFile5(
|
|
1207
|
+
statuslinePath,
|
|
1208
|
+
JSON.stringify(STATUSLINE_DEFAULTS, null, 2) + "\n",
|
|
1209
|
+
"utf-8"
|
|
1210
|
+
);
|
|
1211
|
+
}
|
|
1010
1212
|
await writeLocalConfig(projectPath, { device_id: deviceId });
|
|
1011
1213
|
console.log(` Created ${codebyplanDir}/`);
|
|
1012
1214
|
console.log(
|
|
1013
|
-
` repo.json, server.json, git.json, shipment.json, vendor.json`
|
|
1215
|
+
` repo.json, server.json, git.json, shipment.json, vendor.json, statusline.json`
|
|
1014
1216
|
);
|
|
1015
1217
|
console.log(` device.local.json (gitignored)`);
|
|
1016
|
-
const gitignorePath =
|
|
1218
|
+
const gitignorePath = join5(projectPath, ".gitignore");
|
|
1017
1219
|
const gitignoreEntry = ".codebyplan/device.local.json";
|
|
1220
|
+
const statuslineLocalEntry = ".codebyplan/statusline.local.json";
|
|
1018
1221
|
try {
|
|
1019
|
-
const existing = await
|
|
1222
|
+
const existing = await readFile5(gitignorePath, "utf-8");
|
|
1020
1223
|
const lines = existing.split("\n");
|
|
1224
|
+
let content = existing;
|
|
1021
1225
|
if (!lines.some((l) => l.trimEnd() === gitignoreEntry)) {
|
|
1022
|
-
|
|
1023
|
-
await writeFile4(gitignorePath, appended, "utf-8");
|
|
1226
|
+
content = (content.endsWith("\n") ? content : content + "\n") + gitignoreEntry + "\n";
|
|
1024
1227
|
console.log(` Added '${gitignoreEntry}' to .gitignore`);
|
|
1025
1228
|
}
|
|
1229
|
+
if (!lines.some((l) => l.trimEnd() === statuslineLocalEntry)) {
|
|
1230
|
+
content = (content.endsWith("\n") ? content : content + "\n") + statuslineLocalEntry + "\n";
|
|
1231
|
+
console.log(` Added '${statuslineLocalEntry}' to .gitignore`);
|
|
1232
|
+
}
|
|
1233
|
+
if (content !== existing) {
|
|
1234
|
+
await writeFile5(gitignorePath, content, "utf-8");
|
|
1235
|
+
}
|
|
1026
1236
|
} catch {
|
|
1027
|
-
await
|
|
1028
|
-
|
|
1237
|
+
await writeFile5(
|
|
1238
|
+
gitignorePath,
|
|
1239
|
+
gitignoreEntry + "\n" + statuslineLocalEntry + "\n",
|
|
1240
|
+
"utf-8"
|
|
1241
|
+
);
|
|
1242
|
+
console.log(
|
|
1243
|
+
` Created .gitignore with '${gitignoreEntry}' and '${statuslineLocalEntry}'`
|
|
1244
|
+
);
|
|
1029
1245
|
}
|
|
1030
1246
|
}
|
|
1031
1247
|
async function runSetup() {
|
|
@@ -1118,6 +1334,31 @@ async function runSetup() {
|
|
|
1118
1334
|
const _worktreeId = tupleId ?? pathBasedId;
|
|
1119
1335
|
void _worktreeId;
|
|
1120
1336
|
await writeCodebyplanDirectory(projectPath, selectedRepo, deviceId);
|
|
1337
|
+
const existingRenderer = await readStatuslineLocalConfig(projectPath);
|
|
1338
|
+
const isInteractive = process.stdin.isTTY === true;
|
|
1339
|
+
if (!existingRenderer && isInteractive) {
|
|
1340
|
+
const availability = detectAvailableRenderers();
|
|
1341
|
+
const available = Object.entries(availability).filter(([, v]) => v).map(([k]) => k).join(", ");
|
|
1342
|
+
console.log(
|
|
1343
|
+
`
|
|
1344
|
+
Which statusline renderer would you like to use? (available: ${available})`
|
|
1345
|
+
);
|
|
1346
|
+
console.log(" bash \u2014 shell script, zero dependencies (default)");
|
|
1347
|
+
console.log(" node \u2014 Node.js renderer");
|
|
1348
|
+
console.log(" python \u2014 Python 3 renderer");
|
|
1349
|
+
const rendererInput = (await rl.question(
|
|
1350
|
+
" Select renderer (bash/node/python, default: bash): "
|
|
1351
|
+
)).trim().toLowerCase();
|
|
1352
|
+
const chosen = rendererInput === "node" ? "node" : rendererInput === "python" ? "python" : "bash";
|
|
1353
|
+
await writeStatuslineLocalConfig(projectPath, { renderer: chosen });
|
|
1354
|
+
if (!availability[chosen]) {
|
|
1355
|
+
console.log(
|
|
1356
|
+
` Warning: '${chosen}' was not detected on this machine. You can change it later with \`codebyplan statusline\`.`
|
|
1357
|
+
);
|
|
1358
|
+
} else {
|
|
1359
|
+
console.log(` Renderer set to '${chosen}'.`);
|
|
1360
|
+
}
|
|
1361
|
+
}
|
|
1121
1362
|
console.log(
|
|
1122
1363
|
"\n Run `npx codebyplan config` to sync repo config from the DB."
|
|
1123
1364
|
);
|
|
@@ -1132,6 +1373,7 @@ var init_setup = __esm({
|
|
|
1132
1373
|
"src/cli/setup.ts"() {
|
|
1133
1374
|
"use strict";
|
|
1134
1375
|
init_local_config();
|
|
1376
|
+
init_statusline_config();
|
|
1135
1377
|
init_resolve_worktree();
|
|
1136
1378
|
init_token_refresh();
|
|
1137
1379
|
init_urls();
|
|
@@ -1139,6 +1381,190 @@ var init_setup = __esm({
|
|
|
1139
1381
|
}
|
|
1140
1382
|
});
|
|
1141
1383
|
|
|
1384
|
+
// src/lib/flags.ts
|
|
1385
|
+
import { readFile as readFile6 } from "node:fs/promises";
|
|
1386
|
+
import { join as join6, resolve } from "node:path";
|
|
1387
|
+
async function findCodebyplanConfig(startDir, maxDepth = 20) {
|
|
1388
|
+
let cursor = resolve(startDir);
|
|
1389
|
+
for (let depth = 0; depth < maxDepth; depth++) {
|
|
1390
|
+
const sentinelPath2 = join6(cursor, ".codebyplan", "repo.json");
|
|
1391
|
+
try {
|
|
1392
|
+
const raw = await readFile6(sentinelPath2, "utf-8");
|
|
1393
|
+
const parsed = JSON.parse(raw);
|
|
1394
|
+
return { path: sentinelPath2, contents: parsed };
|
|
1395
|
+
} catch {
|
|
1396
|
+
}
|
|
1397
|
+
const legacyPath = join6(cursor, ".codebyplan.json");
|
|
1398
|
+
try {
|
|
1399
|
+
const raw = await readFile6(legacyPath, "utf-8");
|
|
1400
|
+
const parsed = JSON.parse(raw);
|
|
1401
|
+
return { path: legacyPath, contents: parsed };
|
|
1402
|
+
} catch {
|
|
1403
|
+
}
|
|
1404
|
+
const parent = resolve(cursor, "..");
|
|
1405
|
+
if (parent === cursor) return null;
|
|
1406
|
+
cursor = parent;
|
|
1407
|
+
}
|
|
1408
|
+
return null;
|
|
1409
|
+
}
|
|
1410
|
+
function parseFlags(startIndex) {
|
|
1411
|
+
const flags = {};
|
|
1412
|
+
const args = process.argv.slice(startIndex);
|
|
1413
|
+
for (let i = 0; i < args.length; i++) {
|
|
1414
|
+
const arg = args[i];
|
|
1415
|
+
if (arg.startsWith("--") && i + 1 < args.length) {
|
|
1416
|
+
const key = arg.slice(2);
|
|
1417
|
+
flags[key] = args[++i];
|
|
1418
|
+
}
|
|
1419
|
+
}
|
|
1420
|
+
return flags;
|
|
1421
|
+
}
|
|
1422
|
+
function hasFlag(name, startIndex) {
|
|
1423
|
+
return process.argv.slice(startIndex).includes(`--${name}`);
|
|
1424
|
+
}
|
|
1425
|
+
async function resolveConfig(flags) {
|
|
1426
|
+
const projectPath = flags["path"] ?? process.cwd();
|
|
1427
|
+
let repoId = flags["repo-id"] ?? process.env.CODEBYPLAN_REPO_ID;
|
|
1428
|
+
let worktreeId = flags["worktree-id"] ?? process.env.CODEBYPLAN_WORKTREE_ID;
|
|
1429
|
+
if (!repoId) {
|
|
1430
|
+
const found = await findCodebyplanConfig(projectPath);
|
|
1431
|
+
if (found) {
|
|
1432
|
+
repoId = found.contents.repo_id;
|
|
1433
|
+
if (!worktreeId) worktreeId = found.contents.worktree_id;
|
|
1434
|
+
}
|
|
1435
|
+
}
|
|
1436
|
+
if (!repoId) {
|
|
1437
|
+
throw new Error(
|
|
1438
|
+
`Could not determine repo_id.
|
|
1439
|
+
|
|
1440
|
+
Provide it via one of:
|
|
1441
|
+
--repo-id <uuid> CLI flag
|
|
1442
|
+
CODEBYPLAN_REPO_ID=<uuid> environment variable
|
|
1443
|
+
.codebyplan/repo.json { "repo_id": "<uuid>" } in project root
|
|
1444
|
+
Run 'codebyplan setup' to initialize this project`
|
|
1445
|
+
);
|
|
1446
|
+
}
|
|
1447
|
+
return { repoId, worktreeId, projectPath };
|
|
1448
|
+
}
|
|
1449
|
+
var init_flags = __esm({
|
|
1450
|
+
"src/lib/flags.ts"() {
|
|
1451
|
+
"use strict";
|
|
1452
|
+
}
|
|
1453
|
+
});
|
|
1454
|
+
|
|
1455
|
+
// src/cli/statusline.ts
|
|
1456
|
+
var statusline_exports = {};
|
|
1457
|
+
__export(statusline_exports, {
|
|
1458
|
+
runStatusline: () => runStatusline
|
|
1459
|
+
});
|
|
1460
|
+
import { dirname as dirname3 } from "node:path";
|
|
1461
|
+
async function resolveProjectPath() {
|
|
1462
|
+
const found = await findCodebyplanConfig(process.cwd());
|
|
1463
|
+
if (found) {
|
|
1464
|
+
return dirname3(dirname3(found.path));
|
|
1465
|
+
}
|
|
1466
|
+
return process.cwd();
|
|
1467
|
+
}
|
|
1468
|
+
function parseRendererArg(arg) {
|
|
1469
|
+
const normalised = arg.startsWith("--") ? arg.slice(2) : arg;
|
|
1470
|
+
if (VALID_RENDERERS2.has(normalised)) {
|
|
1471
|
+
return normalised;
|
|
1472
|
+
}
|
|
1473
|
+
return null;
|
|
1474
|
+
}
|
|
1475
|
+
async function runStatusline(args) {
|
|
1476
|
+
const positional = args.filter((a) => !a.startsWith("-"));
|
|
1477
|
+
const flagArgs = args.filter((a) => a.startsWith("--"));
|
|
1478
|
+
const rendererTokens = [...positional, ...flagArgs].filter(
|
|
1479
|
+
(a) => parseRendererArg(a) !== null
|
|
1480
|
+
);
|
|
1481
|
+
if (rendererTokens.length > 1) {
|
|
1482
|
+
process.stderr.write(
|
|
1483
|
+
"codebyplan statusline: bash, node, and python are mutually exclusive; pass only one.\n"
|
|
1484
|
+
);
|
|
1485
|
+
process.exitCode = 1;
|
|
1486
|
+
return;
|
|
1487
|
+
}
|
|
1488
|
+
let rendererArg;
|
|
1489
|
+
for (const a of [...positional, ...flagArgs]) {
|
|
1490
|
+
const parsed = parseRendererArg(a);
|
|
1491
|
+
if (parsed !== null) {
|
|
1492
|
+
rendererArg = a;
|
|
1493
|
+
break;
|
|
1494
|
+
}
|
|
1495
|
+
}
|
|
1496
|
+
const unknownPositional = args.filter(
|
|
1497
|
+
(a) => !a.startsWith("-") && !VALID_RENDERERS2.has(a)
|
|
1498
|
+
);
|
|
1499
|
+
if (unknownPositional.length > 0) {
|
|
1500
|
+
process.stderr.write(
|
|
1501
|
+
`codebyplan statusline: unknown argument '${unknownPositional[0]}'.
|
|
1502
|
+
|
|
1503
|
+
Usage:
|
|
1504
|
+
codebyplan statusline Show current renderer and line toggles
|
|
1505
|
+
codebyplan statusline bash|node|python Set the renderer
|
|
1506
|
+
codebyplan statusline --bash|--node|--python Set the renderer (flag form)
|
|
1507
|
+
`
|
|
1508
|
+
);
|
|
1509
|
+
process.exitCode = 1;
|
|
1510
|
+
return;
|
|
1511
|
+
}
|
|
1512
|
+
const unknownFlags = flagArgs.filter((a) => parseRendererArg(a) === null);
|
|
1513
|
+
if (unknownFlags.length > 0) {
|
|
1514
|
+
process.stderr.write(
|
|
1515
|
+
`codebyplan statusline: unknown flag '${unknownFlags[0]}'.
|
|
1516
|
+
|
|
1517
|
+
Usage:
|
|
1518
|
+
codebyplan statusline Show current renderer and line toggles
|
|
1519
|
+
codebyplan statusline bash|node|python Set the renderer
|
|
1520
|
+
codebyplan statusline --bash|--node|--python Set the renderer (flag form)
|
|
1521
|
+
`
|
|
1522
|
+
);
|
|
1523
|
+
process.exitCode = 1;
|
|
1524
|
+
return;
|
|
1525
|
+
}
|
|
1526
|
+
const projectPath = await resolveProjectPath();
|
|
1527
|
+
if (rendererArg !== void 0) {
|
|
1528
|
+
const chosen = parseRendererArg(rendererArg);
|
|
1529
|
+
await writeStatuslineLocalConfig(projectPath, { renderer: chosen });
|
|
1530
|
+
const availability2 = detectAvailableRenderers();
|
|
1531
|
+
if (!availability2[chosen]) {
|
|
1532
|
+
process.stderr.write(
|
|
1533
|
+
`Warning: '${chosen}' was not detected on this machine. The renderer is saved but may not run.
|
|
1534
|
+
`
|
|
1535
|
+
);
|
|
1536
|
+
}
|
|
1537
|
+
console.log(`Statusline renderer set to '${chosen}'.`);
|
|
1538
|
+
return;
|
|
1539
|
+
}
|
|
1540
|
+
const availability = detectAvailableRenderers();
|
|
1541
|
+
const [renderer, config] = await Promise.all([
|
|
1542
|
+
resolveRenderer(projectPath),
|
|
1543
|
+
readStatuslineConfig(projectPath)
|
|
1544
|
+
]);
|
|
1545
|
+
const availStr = Object.entries(availability).map(([k, v]) => `${k}: ${v ? "yes" : "no"}`).join(", ");
|
|
1546
|
+
console.log(`
|
|
1547
|
+
Statusline configuration`);
|
|
1548
|
+
console.log(` Renderer: ${renderer}`);
|
|
1549
|
+
console.log(` Available: ${availStr}`);
|
|
1550
|
+
console.log(` no_color: ${config.no_color}`);
|
|
1551
|
+
console.log(`
|
|
1552
|
+
Line toggles:`);
|
|
1553
|
+
for (const [key, val] of Object.entries(config.lines)) {
|
|
1554
|
+
console.log(` ${key.padEnd(14)} ${val ? "on" : "off"}`);
|
|
1555
|
+
}
|
|
1556
|
+
console.log();
|
|
1557
|
+
}
|
|
1558
|
+
var VALID_RENDERERS2;
|
|
1559
|
+
var init_statusline = __esm({
|
|
1560
|
+
"src/cli/statusline.ts"() {
|
|
1561
|
+
"use strict";
|
|
1562
|
+
init_flags();
|
|
1563
|
+
init_statusline_config();
|
|
1564
|
+
VALID_RENDERERS2 = /* @__PURE__ */ new Set(["bash", "node", "python"]);
|
|
1565
|
+
}
|
|
1566
|
+
});
|
|
1567
|
+
|
|
1142
1568
|
// src/cli/logout.ts
|
|
1143
1569
|
var logout_exports = {};
|
|
1144
1570
|
__export(logout_exports, {
|
|
@@ -1197,15 +1623,15 @@ var upgrade_auth_exports = {};
|
|
|
1197
1623
|
__export(upgrade_auth_exports, {
|
|
1198
1624
|
runUpgradeAuth: () => runUpgradeAuth
|
|
1199
1625
|
});
|
|
1200
|
-
import { readFile as
|
|
1626
|
+
import { readFile as readFile7, writeFile as writeFile6 } from "node:fs/promises";
|
|
1201
1627
|
import { homedir as homedir3 } from "node:os";
|
|
1202
|
-
import { join as
|
|
1628
|
+
import { join as join7 } from "node:path";
|
|
1203
1629
|
function configPaths() {
|
|
1204
|
-
return [
|
|
1630
|
+
return [join7(homedir3(), ".claude.json"), join7(process.cwd(), ".mcp.json")];
|
|
1205
1631
|
}
|
|
1206
1632
|
async function readConfig2(path6) {
|
|
1207
1633
|
try {
|
|
1208
|
-
const raw = await
|
|
1634
|
+
const raw = await readFile7(path6, "utf-8");
|
|
1209
1635
|
const parsed = JSON.parse(raw);
|
|
1210
1636
|
if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
|
|
1211
1637
|
return parsed;
|
|
@@ -1226,7 +1652,7 @@ async function rewriteConfig(path6, config, newUrl) {
|
|
|
1226
1652
|
if (!entry) return false;
|
|
1227
1653
|
if (!entryHasLegacyApiKey(entry) && entry.url === newUrl) return false;
|
|
1228
1654
|
servers.codebyplan = { url: newUrl };
|
|
1229
|
-
await
|
|
1655
|
+
await writeFile6(path6, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
1230
1656
|
return true;
|
|
1231
1657
|
}
|
|
1232
1658
|
async function runUpgradeAuth() {
|
|
@@ -1263,77 +1689,6 @@ var init_upgrade_auth = __esm({
|
|
|
1263
1689
|
}
|
|
1264
1690
|
});
|
|
1265
1691
|
|
|
1266
|
-
// src/lib/flags.ts
|
|
1267
|
-
import { readFile as readFile6 } from "node:fs/promises";
|
|
1268
|
-
import { join as join6, resolve } from "node:path";
|
|
1269
|
-
async function findCodebyplanConfig(startDir, maxDepth = 20) {
|
|
1270
|
-
let cursor = resolve(startDir);
|
|
1271
|
-
for (let depth = 0; depth < maxDepth; depth++) {
|
|
1272
|
-
const sentinelPath2 = join6(cursor, ".codebyplan", "repo.json");
|
|
1273
|
-
try {
|
|
1274
|
-
const raw = await readFile6(sentinelPath2, "utf-8");
|
|
1275
|
-
const parsed = JSON.parse(raw);
|
|
1276
|
-
return { path: sentinelPath2, contents: parsed };
|
|
1277
|
-
} catch {
|
|
1278
|
-
}
|
|
1279
|
-
const legacyPath = join6(cursor, ".codebyplan.json");
|
|
1280
|
-
try {
|
|
1281
|
-
const raw = await readFile6(legacyPath, "utf-8");
|
|
1282
|
-
const parsed = JSON.parse(raw);
|
|
1283
|
-
return { path: legacyPath, contents: parsed };
|
|
1284
|
-
} catch {
|
|
1285
|
-
}
|
|
1286
|
-
const parent = resolve(cursor, "..");
|
|
1287
|
-
if (parent === cursor) return null;
|
|
1288
|
-
cursor = parent;
|
|
1289
|
-
}
|
|
1290
|
-
return null;
|
|
1291
|
-
}
|
|
1292
|
-
function parseFlags(startIndex) {
|
|
1293
|
-
const flags = {};
|
|
1294
|
-
const args = process.argv.slice(startIndex);
|
|
1295
|
-
for (let i = 0; i < args.length; i++) {
|
|
1296
|
-
const arg = args[i];
|
|
1297
|
-
if (arg.startsWith("--") && i + 1 < args.length) {
|
|
1298
|
-
const key = arg.slice(2);
|
|
1299
|
-
flags[key] = args[++i];
|
|
1300
|
-
}
|
|
1301
|
-
}
|
|
1302
|
-
return flags;
|
|
1303
|
-
}
|
|
1304
|
-
function hasFlag(name, startIndex) {
|
|
1305
|
-
return process.argv.slice(startIndex).includes(`--${name}`);
|
|
1306
|
-
}
|
|
1307
|
-
async function resolveConfig(flags) {
|
|
1308
|
-
const projectPath = flags["path"] ?? process.cwd();
|
|
1309
|
-
let repoId = flags["repo-id"] ?? process.env.CODEBYPLAN_REPO_ID;
|
|
1310
|
-
let worktreeId = flags["worktree-id"] ?? process.env.CODEBYPLAN_WORKTREE_ID;
|
|
1311
|
-
if (!repoId) {
|
|
1312
|
-
const found = await findCodebyplanConfig(projectPath);
|
|
1313
|
-
if (found) {
|
|
1314
|
-
repoId = found.contents.repo_id;
|
|
1315
|
-
if (!worktreeId) worktreeId = found.contents.worktree_id;
|
|
1316
|
-
}
|
|
1317
|
-
}
|
|
1318
|
-
if (!repoId) {
|
|
1319
|
-
throw new Error(
|
|
1320
|
-
`Could not determine repo_id.
|
|
1321
|
-
|
|
1322
|
-
Provide it via one of:
|
|
1323
|
-
--repo-id <uuid> CLI flag
|
|
1324
|
-
CODEBYPLAN_REPO_ID=<uuid> environment variable
|
|
1325
|
-
.codebyplan/repo.json { "repo_id": "<uuid>" } in project root
|
|
1326
|
-
Run 'codebyplan setup' to initialize this project`
|
|
1327
|
-
);
|
|
1328
|
-
}
|
|
1329
|
-
return { repoId, worktreeId, projectPath };
|
|
1330
|
-
}
|
|
1331
|
-
var init_flags = __esm({
|
|
1332
|
-
"src/lib/flags.ts"() {
|
|
1333
|
-
"use strict";
|
|
1334
|
-
}
|
|
1335
|
-
});
|
|
1336
|
-
|
|
1337
1692
|
// src/cli/confirm.ts
|
|
1338
1693
|
var confirm_exports = {};
|
|
1339
1694
|
__export(confirm_exports, {
|
|
@@ -1378,8 +1733,8 @@ var init_confirm = __esm({
|
|
|
1378
1733
|
});
|
|
1379
1734
|
|
|
1380
1735
|
// src/lib/tech-detect.ts
|
|
1381
|
-
import { readFile as
|
|
1382
|
-
import { join as
|
|
1736
|
+
import { readFile as readFile8, access, readdir } from "node:fs/promises";
|
|
1737
|
+
import { join as join8, relative } from "node:path";
|
|
1383
1738
|
async function fileExists(filePath) {
|
|
1384
1739
|
try {
|
|
1385
1740
|
await access(filePath);
|
|
@@ -1392,8 +1747,8 @@ async function discoverMonorepoApps(projectPath) {
|
|
|
1392
1747
|
const apps = [];
|
|
1393
1748
|
const patterns = [];
|
|
1394
1749
|
try {
|
|
1395
|
-
const raw = await
|
|
1396
|
-
|
|
1750
|
+
const raw = await readFile8(
|
|
1751
|
+
join8(projectPath, "pnpm-workspace.yaml"),
|
|
1397
1752
|
"utf-8"
|
|
1398
1753
|
);
|
|
1399
1754
|
const matches = raw.match(/^\s*-\s*['"]?([^'"#\n]+)['"]?/gm);
|
|
@@ -1407,7 +1762,7 @@ async function discoverMonorepoApps(projectPath) {
|
|
|
1407
1762
|
}
|
|
1408
1763
|
if (patterns.length === 0) {
|
|
1409
1764
|
try {
|
|
1410
|
-
const raw = await
|
|
1765
|
+
const raw = await readFile8(join8(projectPath, "package.json"), "utf-8");
|
|
1411
1766
|
const pkg = JSON.parse(raw);
|
|
1412
1767
|
const ws = Array.isArray(pkg.workspaces) ? pkg.workspaces : pkg.workspaces?.packages;
|
|
1413
1768
|
if (ws) patterns.push(...ws);
|
|
@@ -1417,14 +1772,14 @@ async function discoverMonorepoApps(projectPath) {
|
|
|
1417
1772
|
for (const pattern of patterns) {
|
|
1418
1773
|
if (pattern.endsWith("/*")) {
|
|
1419
1774
|
const dir = pattern.slice(0, -2);
|
|
1420
|
-
const absDir =
|
|
1775
|
+
const absDir = join8(projectPath, dir);
|
|
1421
1776
|
try {
|
|
1422
1777
|
const entries = await readdir(absDir, { withFileTypes: true });
|
|
1423
1778
|
for (const entry of entries) {
|
|
1424
1779
|
if (entry.isDirectory()) {
|
|
1425
|
-
const relPath =
|
|
1426
|
-
const absPath =
|
|
1427
|
-
if (await fileExists(
|
|
1780
|
+
const relPath = join8(dir, entry.name);
|
|
1781
|
+
const absPath = join8(absDir, entry.name);
|
|
1782
|
+
if (await fileExists(join8(absPath, "package.json"))) {
|
|
1428
1783
|
apps.push({ name: entry.name, path: relPath, absPath });
|
|
1429
1784
|
}
|
|
1430
1785
|
}
|
|
@@ -1443,7 +1798,7 @@ async function hasJsxFile(dir, depth = 0) {
|
|
|
1443
1798
|
const name = entry.name;
|
|
1444
1799
|
if (entry.isDirectory()) {
|
|
1445
1800
|
if (SKIP_DIRS.has(name) || JSX_SKIP_DIRS.has(name)) continue;
|
|
1446
|
-
if (await hasJsxFile(
|
|
1801
|
+
if (await hasJsxFile(join8(dir, name), depth + 1)) return true;
|
|
1447
1802
|
} else if (entry.isFile()) {
|
|
1448
1803
|
if (JSX_TEST_PATTERN.test(name)) continue;
|
|
1449
1804
|
if (name.endsWith(".tsx") || name.endsWith(".jsx")) return true;
|
|
@@ -1462,7 +1817,7 @@ async function hasJsxFile(dir, depth = 0) {
|
|
|
1462
1817
|
async function detectCapabilities(dirPath, pkgJson) {
|
|
1463
1818
|
const caps = /* @__PURE__ */ new Set();
|
|
1464
1819
|
for (const sub of JSX_SCAN_DIRS) {
|
|
1465
|
-
if (await hasJsxFile(
|
|
1820
|
+
if (await hasJsxFile(join8(dirPath, sub))) {
|
|
1466
1821
|
caps.add("jsx");
|
|
1467
1822
|
break;
|
|
1468
1823
|
}
|
|
@@ -1484,7 +1839,7 @@ async function detectCapabilities(dirPath, pkgJson) {
|
|
|
1484
1839
|
}
|
|
1485
1840
|
}
|
|
1486
1841
|
}
|
|
1487
|
-
if (!caps.has("node-server") && await fileExists(
|
|
1842
|
+
if (!caps.has("node-server") && await fileExists(join8(dirPath, "src", "main.ts"))) {
|
|
1488
1843
|
caps.add("node-server");
|
|
1489
1844
|
}
|
|
1490
1845
|
if (pkgJson && pkgJson.bin) {
|
|
@@ -1500,7 +1855,7 @@ async function detectFromDirectory(dirPath) {
|
|
|
1500
1855
|
const seen = /* @__PURE__ */ new Map();
|
|
1501
1856
|
let pkgJson = null;
|
|
1502
1857
|
try {
|
|
1503
|
-
const raw = await
|
|
1858
|
+
const raw = await readFile8(join8(dirPath, "package.json"), "utf-8");
|
|
1504
1859
|
pkgJson = JSON.parse(raw);
|
|
1505
1860
|
const allDeps = {
|
|
1506
1861
|
...pkgJson.dependencies ?? {},
|
|
@@ -1532,7 +1887,7 @@ async function detectFromDirectory(dirPath) {
|
|
|
1532
1887
|
}
|
|
1533
1888
|
for (const { file, rule } of CONFIG_FILE_MAP) {
|
|
1534
1889
|
const key = rule.name.toLowerCase();
|
|
1535
|
-
if (!seen.has(key) && await fileExists(
|
|
1890
|
+
if (!seen.has(key) && await fileExists(join8(dirPath, file))) {
|
|
1536
1891
|
seen.set(key, { name: rule.name, category: rule.category });
|
|
1537
1892
|
}
|
|
1538
1893
|
}
|
|
@@ -1710,7 +2065,7 @@ function categorizeDependency(depName) {
|
|
|
1710
2065
|
async function findPackageJsonFiles(dir, projectPath, depth = 0) {
|
|
1711
2066
|
if (depth > 4) return [];
|
|
1712
2067
|
const results = [];
|
|
1713
|
-
const pkgPath =
|
|
2068
|
+
const pkgPath = join8(dir, "package.json");
|
|
1714
2069
|
if (await fileExists(pkgPath)) {
|
|
1715
2070
|
results.push(pkgPath);
|
|
1716
2071
|
}
|
|
@@ -1719,7 +2074,7 @@ async function findPackageJsonFiles(dir, projectPath, depth = 0) {
|
|
|
1719
2074
|
for (const entry of entries) {
|
|
1720
2075
|
if (!entry.isDirectory() || SKIP_DIRS.has(entry.name)) continue;
|
|
1721
2076
|
const subResults = await findPackageJsonFiles(
|
|
1722
|
-
|
|
2077
|
+
join8(dir, entry.name),
|
|
1723
2078
|
projectPath,
|
|
1724
2079
|
depth + 1
|
|
1725
2080
|
);
|
|
@@ -1734,7 +2089,7 @@ async function scanAllDependencies(projectPath) {
|
|
|
1734
2089
|
const dependencies = [];
|
|
1735
2090
|
for (const pkgPath of packageJsonPaths) {
|
|
1736
2091
|
try {
|
|
1737
|
-
const raw = await
|
|
2092
|
+
const raw = await readFile8(pkgPath, "utf-8");
|
|
1738
2093
|
const pkg = JSON.parse(raw);
|
|
1739
2094
|
const sourcePath = relative(projectPath, pkgPath);
|
|
1740
2095
|
const depSections = [
|
|
@@ -2334,8 +2689,8 @@ __export(eslint_exports, {
|
|
|
2334
2689
|
eslintInit: () => eslintInit,
|
|
2335
2690
|
runEslint: () => runEslint
|
|
2336
2691
|
});
|
|
2337
|
-
import { readFile as
|
|
2338
|
-
import { join as
|
|
2692
|
+
import { readFile as readFile9, writeFile as writeFile7, access as access2, readdir as readdir2 } from "node:fs/promises";
|
|
2693
|
+
import { join as join9, relative as relative2 } from "node:path";
|
|
2339
2694
|
async function fileExists2(filePath) {
|
|
2340
2695
|
try {
|
|
2341
2696
|
await access2(filePath);
|
|
@@ -2346,7 +2701,7 @@ async function fileExists2(filePath) {
|
|
|
2346
2701
|
}
|
|
2347
2702
|
async function autoDetectIgnorePatterns(absPath) {
|
|
2348
2703
|
const patterns = [];
|
|
2349
|
-
if (await fileExists2(
|
|
2704
|
+
if (await fileExists2(join9(absPath, "esbuild.js"))) {
|
|
2350
2705
|
patterns.push("esbuild.js");
|
|
2351
2706
|
}
|
|
2352
2707
|
let entries = [];
|
|
@@ -2366,19 +2721,19 @@ async function autoDetectIgnorePatterns(absPath) {
|
|
|
2366
2721
|
}
|
|
2367
2722
|
for (const ext of ["ts", "mts", "js", "mjs"]) {
|
|
2368
2723
|
const candidate = `vitest.config.${ext}`;
|
|
2369
|
-
if (await fileExists2(
|
|
2724
|
+
if (await fileExists2(join9(absPath, candidate))) {
|
|
2370
2725
|
patterns.push(candidate);
|
|
2371
2726
|
break;
|
|
2372
2727
|
}
|
|
2373
2728
|
}
|
|
2374
2729
|
for (const ext of ["ts", "mts", "js", "mjs"]) {
|
|
2375
2730
|
const candidate = `vite.config.${ext}`;
|
|
2376
|
-
if (await fileExists2(
|
|
2731
|
+
if (await fileExists2(join9(absPath, candidate))) {
|
|
2377
2732
|
patterns.push(candidate);
|
|
2378
2733
|
break;
|
|
2379
2734
|
}
|
|
2380
2735
|
}
|
|
2381
|
-
if (await fileExists2(
|
|
2736
|
+
if (await fileExists2(join9(absPath, "tauri.conf.json"))) {
|
|
2382
2737
|
patterns.push("src-tauri/**");
|
|
2383
2738
|
patterns.push("**/*.d.ts");
|
|
2384
2739
|
}
|
|
@@ -2386,14 +2741,14 @@ async function autoDetectIgnorePatterns(absPath) {
|
|
|
2386
2741
|
}
|
|
2387
2742
|
function detectPackageManager(projectPath) {
|
|
2388
2743
|
return (async () => {
|
|
2389
|
-
if (await fileExists2(
|
|
2390
|
-
if (await fileExists2(
|
|
2744
|
+
if (await fileExists2(join9(projectPath, "pnpm-lock.yaml"))) return "pnpm";
|
|
2745
|
+
if (await fileExists2(join9(projectPath, "yarn.lock"))) return "yarn";
|
|
2391
2746
|
return "npm";
|
|
2392
2747
|
})();
|
|
2393
2748
|
}
|
|
2394
2749
|
async function getInstalledDeps(pkgJsonPath) {
|
|
2395
2750
|
try {
|
|
2396
|
-
const raw = await
|
|
2751
|
+
const raw = await readFile9(pkgJsonPath, "utf-8");
|
|
2397
2752
|
const pkg = JSON.parse(raw);
|
|
2398
2753
|
const all = /* @__PURE__ */ new Set();
|
|
2399
2754
|
for (const name of Object.keys(pkg.dependencies ?? {})) all.add(name);
|
|
@@ -2506,7 +2861,7 @@ async function eslintInit(repoId, projectPath) {
|
|
|
2506
2861
|
ignorePatterns: detectedIgnores
|
|
2507
2862
|
});
|
|
2508
2863
|
const hash = hashConfig(content);
|
|
2509
|
-
const configPath =
|
|
2864
|
+
const configPath = join9(target.absPath, "eslint.config.mjs");
|
|
2510
2865
|
configsToWrite.push({
|
|
2511
2866
|
target,
|
|
2512
2867
|
presets,
|
|
@@ -2528,11 +2883,11 @@ async function eslintInit(repoId, projectPath) {
|
|
|
2528
2883
|
return;
|
|
2529
2884
|
}
|
|
2530
2885
|
const pm = await detectPackageManager(projectPath);
|
|
2531
|
-
const rootPkgJsonPath =
|
|
2886
|
+
const rootPkgJsonPath = join9(projectPath, "package.json");
|
|
2532
2887
|
const installed = await getInstalledDeps(rootPkgJsonPath);
|
|
2533
2888
|
if (isMonorepo) {
|
|
2534
2889
|
for (const { target } of configsToWrite) {
|
|
2535
|
-
const appPkgJson =
|
|
2890
|
+
const appPkgJson = join9(target.absPath, "package.json");
|
|
2536
2891
|
const appDeps = await getInstalledDeps(appPkgJson);
|
|
2537
2892
|
for (const dep of appDeps) {
|
|
2538
2893
|
installed.add(dep);
|
|
@@ -2584,7 +2939,7 @@ async function eslintInit(repoId, projectPath) {
|
|
|
2584
2939
|
} of configsToWrite) {
|
|
2585
2940
|
if (await fileExists2(configPath)) {
|
|
2586
2941
|
try {
|
|
2587
|
-
const existing = await
|
|
2942
|
+
const existing = await readFile9(configPath, "utf-8");
|
|
2588
2943
|
const existingHash = hashConfig(existing);
|
|
2589
2944
|
if (existingHash === hash) {
|
|
2590
2945
|
console.log(
|
|
@@ -2604,7 +2959,7 @@ async function eslintInit(repoId, projectPath) {
|
|
|
2604
2959
|
}
|
|
2605
2960
|
}
|
|
2606
2961
|
try {
|
|
2607
|
-
await
|
|
2962
|
+
await writeFile7(configPath, content, "utf-8");
|
|
2608
2963
|
} catch (err) {
|
|
2609
2964
|
console.error(
|
|
2610
2965
|
` ${target.name}: Failed to write config: ${err instanceof Error ? err.message : String(err)}`
|
|
@@ -2912,7 +3267,7 @@ __export(round_exports, {
|
|
|
2912
3267
|
runRoundSyncApprovals: () => runRoundSyncApprovals
|
|
2913
3268
|
});
|
|
2914
3269
|
import { access as access3 } from "node:fs/promises";
|
|
2915
|
-
import { join as
|
|
3270
|
+
import { join as join10 } from "node:path";
|
|
2916
3271
|
import { execSync as execSync2 } from "node:child_process";
|
|
2917
3272
|
async function runRoundCommand(args) {
|
|
2918
3273
|
const subcommand = args[0];
|
|
@@ -3006,7 +3361,7 @@ async function runRoundSyncApprovals(args) {
|
|
|
3006
3361
|
"sync-approvals: git status failed; proceeding with empty diff\n"
|
|
3007
3362
|
);
|
|
3008
3363
|
}
|
|
3009
|
-
const hookPath =
|
|
3364
|
+
const hookPath = join10(
|
|
3010
3365
|
repoRoot,
|
|
3011
3366
|
".claude",
|
|
3012
3367
|
"hooks",
|
|
@@ -3128,8 +3483,8 @@ var init_round = __esm({
|
|
|
3128
3483
|
});
|
|
3129
3484
|
|
|
3130
3485
|
// src/lib/migrate-branch-model.ts
|
|
3131
|
-
import { readFile as
|
|
3132
|
-
import { join as
|
|
3486
|
+
import { readFile as readFile10, writeFile as writeFile8 } from "node:fs/promises";
|
|
3487
|
+
import { join as join11 } from "node:path";
|
|
3133
3488
|
import { execSync as execSync3 } from "node:child_process";
|
|
3134
3489
|
function assertValidBranchName(branch) {
|
|
3135
3490
|
if (!/^[a-zA-Z0-9/_.-]+$/.test(branch)) {
|
|
@@ -3139,7 +3494,7 @@ function assertValidBranchName(branch) {
|
|
|
3139
3494
|
}
|
|
3140
3495
|
}
|
|
3141
3496
|
async function readJsonFile(filePath) {
|
|
3142
|
-
const raw = await
|
|
3497
|
+
const raw = await readFile10(filePath, "utf-8");
|
|
3143
3498
|
const parsed = JSON.parse(raw);
|
|
3144
3499
|
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
3145
3500
|
throw new Error(`${filePath} does not contain a JSON object`);
|
|
@@ -3208,12 +3563,12 @@ async function runBranchMigration(opts) {
|
|
|
3208
3563
|
if (found) {
|
|
3209
3564
|
if (found.path.endsWith("/repo.json")) {
|
|
3210
3565
|
const dir = found.path.slice(0, found.path.lastIndexOf("/"));
|
|
3211
|
-
configPath =
|
|
3566
|
+
configPath = join11(dir, "git.json");
|
|
3212
3567
|
} else {
|
|
3213
3568
|
configPath = found.path;
|
|
3214
3569
|
}
|
|
3215
3570
|
} else {
|
|
3216
|
-
configPath =
|
|
3571
|
+
configPath = join11(cwd, ".codebyplan", "git.json");
|
|
3217
3572
|
}
|
|
3218
3573
|
let fileRaw;
|
|
3219
3574
|
let fileParsed;
|
|
@@ -3287,7 +3642,7 @@ async function runBranchMigration(opts) {
|
|
|
3287
3642
|
const updatedParsed = { ...fileParsed, branch_config: after };
|
|
3288
3643
|
const newJson = JSON.stringify(updatedParsed, null, 2) + "\n";
|
|
3289
3644
|
if (newJson !== fileRaw) {
|
|
3290
|
-
await
|
|
3645
|
+
await writeFile8(configPath, newJson, "utf-8");
|
|
3291
3646
|
}
|
|
3292
3647
|
}
|
|
3293
3648
|
return {
|
|
@@ -3393,9 +3748,9 @@ var init_branch = __esm({
|
|
|
3393
3748
|
});
|
|
3394
3749
|
|
|
3395
3750
|
// src/lib/ship.ts
|
|
3396
|
-
import { readFile as
|
|
3397
|
-
import { join as
|
|
3398
|
-
import { execSync as execSync4, spawnSync } from "node:child_process";
|
|
3751
|
+
import { readFile as readFile11 } from "node:fs/promises";
|
|
3752
|
+
import { join as join12 } from "node:path";
|
|
3753
|
+
import { execSync as execSync4, spawnSync as spawnSync2 } from "node:child_process";
|
|
3399
3754
|
function assertValidBranchName2(branch, label) {
|
|
3400
3755
|
if (!/^[a-zA-Z0-9/_.-]+$/.test(branch)) {
|
|
3401
3756
|
throw new Error(
|
|
@@ -3409,15 +3764,15 @@ async function readBaseBranch(cwd) {
|
|
|
3409
3764
|
if (found) {
|
|
3410
3765
|
if (found.path.endsWith("/repo.json")) {
|
|
3411
3766
|
const dir = found.path.slice(0, found.path.lastIndexOf("/"));
|
|
3412
|
-
gitJsonPath =
|
|
3767
|
+
gitJsonPath = join12(dir, "git.json");
|
|
3413
3768
|
} else {
|
|
3414
3769
|
gitJsonPath = found.path;
|
|
3415
3770
|
}
|
|
3416
3771
|
} else {
|
|
3417
|
-
gitJsonPath =
|
|
3772
|
+
gitJsonPath = join12(cwd, ".codebyplan", "git.json");
|
|
3418
3773
|
}
|
|
3419
3774
|
try {
|
|
3420
|
-
const raw = await
|
|
3775
|
+
const raw = await readFile11(gitJsonPath, "utf-8");
|
|
3421
3776
|
const parsed = JSON.parse(raw);
|
|
3422
3777
|
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
3423
3778
|
return "main";
|
|
@@ -3450,7 +3805,7 @@ async function pollChecks(feat, timeoutSeconds, cwd, _sleepMs = (ms) => new Prom
|
|
|
3450
3805
|
let totalGhErrors = 0;
|
|
3451
3806
|
let iteration = 0;
|
|
3452
3807
|
while (Date.now() < deadline) {
|
|
3453
|
-
const ghResult =
|
|
3808
|
+
const ghResult = spawnSync2(
|
|
3454
3809
|
"gh",
|
|
3455
3810
|
["pr", "checks", feat, "--json", "name,state,conclusion"],
|
|
3456
3811
|
{
|
|
@@ -3616,7 +3971,7 @@ ${statusOut.trim()}`
|
|
|
3616
3971
|
} else {
|
|
3617
3972
|
createArgs.push("--body", defaultPrBody(feat, base));
|
|
3618
3973
|
}
|
|
3619
|
-
const createResult =
|
|
3974
|
+
const createResult = spawnSync2("gh", createArgs, {
|
|
3620
3975
|
cwd,
|
|
3621
3976
|
encoding: "utf-8",
|
|
3622
3977
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -3646,7 +4001,7 @@ ${statusOut.trim()}`
|
|
|
3646
4001
|
});
|
|
3647
4002
|
let mergeCommit = null;
|
|
3648
4003
|
try {
|
|
3649
|
-
const prViewResult =
|
|
4004
|
+
const prViewResult = spawnSync2(
|
|
3650
4005
|
"gh",
|
|
3651
4006
|
["pr", "view", feat, "--json", "state,mergeCommit"],
|
|
3652
4007
|
{
|
|
@@ -3806,85 +4161,167 @@ var init_ship2 = __esm({
|
|
|
3806
4161
|
// src/cli/resolve-worktree.ts
|
|
3807
4162
|
var resolve_worktree_exports = {};
|
|
3808
4163
|
__export(resolve_worktree_exports, {
|
|
4164
|
+
ProcessExitSignal: () => ProcessExitSignal,
|
|
3809
4165
|
runResolveWorktree: () => runResolveWorktree
|
|
3810
4166
|
});
|
|
3811
4167
|
import { execSync as execSync5 } from "node:child_process";
|
|
4168
|
+
function distress(kind, message, jsonMode) {
|
|
4169
|
+
if (jsonMode) return;
|
|
4170
|
+
process.stderr.write(`resolve-worktree: ${kind}: ${message}
|
|
4171
|
+
`);
|
|
4172
|
+
}
|
|
3812
4173
|
async function runResolveWorktree() {
|
|
4174
|
+
const jsonMode = hasFlag("json", 3);
|
|
4175
|
+
let errorContext = null;
|
|
4176
|
+
const migrationNoticeCallback = (legacyPath, primaryPath) => {
|
|
4177
|
+
if (!jsonMode) {
|
|
4178
|
+
process.stderr.write(
|
|
4179
|
+
`resolve-worktree: local_config_migration: ${legacyPath} is the legacy flat config; move device_id to ${primaryPath}
|
|
4180
|
+
`
|
|
4181
|
+
);
|
|
4182
|
+
}
|
|
4183
|
+
};
|
|
3813
4184
|
try {
|
|
3814
4185
|
const projectPath = process.cwd();
|
|
3815
4186
|
const found = await findCodebyplanConfig(projectPath);
|
|
3816
4187
|
if (!found?.contents.repo_id) {
|
|
3817
|
-
|
|
4188
|
+
emitAndExit(null, null, jsonMode);
|
|
3818
4189
|
}
|
|
3819
4190
|
const repoId = found.contents.repo_id;
|
|
3820
|
-
|
|
4191
|
+
try {
|
|
4192
|
+
await readLocalConfig(projectPath);
|
|
4193
|
+
} catch (readErr) {
|
|
4194
|
+
const readErrCode = readErr.code;
|
|
4195
|
+
errorContext = {
|
|
4196
|
+
kind: readErrCode === "LEGACY_FILE_BLOCKS_DIR" ? "legacy_file_blocks_dir" : "local_config_read_failed",
|
|
4197
|
+
message: readErr instanceof Error ? readErr.message : String(readErr)
|
|
4198
|
+
};
|
|
4199
|
+
}
|
|
4200
|
+
let deviceId;
|
|
4201
|
+
try {
|
|
4202
|
+
deviceId = await getOrCreateDeviceId(
|
|
4203
|
+
projectPath,
|
|
4204
|
+
migrationNoticeCallback
|
|
4205
|
+
);
|
|
4206
|
+
} catch (deviceErr) {
|
|
4207
|
+
const code = deviceErr.code;
|
|
4208
|
+
if (code === "LEGACY_FILE_BLOCKS_DIR") {
|
|
4209
|
+
errorContext = {
|
|
4210
|
+
kind: "legacy_file_blocks_dir",
|
|
4211
|
+
message: deviceErr instanceof Error ? deviceErr.message : String(deviceErr)
|
|
4212
|
+
};
|
|
4213
|
+
} else if (errorContext === null || errorContext.kind !== "local_config_read_failed" && errorContext.kind !== "legacy_file_blocks_dir") {
|
|
4214
|
+
errorContext = {
|
|
4215
|
+
kind: "local_config_write_failed",
|
|
4216
|
+
message: deviceErr instanceof Error ? deviceErr.message : String(deviceErr)
|
|
4217
|
+
};
|
|
4218
|
+
}
|
|
4219
|
+
emitAndExit(null, errorContext, jsonMode);
|
|
4220
|
+
}
|
|
3821
4221
|
let branch = "";
|
|
3822
4222
|
try {
|
|
3823
4223
|
branch = execSync5("git symbolic-ref --short HEAD", {
|
|
3824
4224
|
cwd: projectPath,
|
|
3825
4225
|
encoding: "utf-8"
|
|
3826
4226
|
}).trim();
|
|
3827
|
-
} catch {
|
|
4227
|
+
} catch (gitErr) {
|
|
4228
|
+
if (errorContext === null) {
|
|
4229
|
+
errorContext = {
|
|
4230
|
+
kind: "git_failed",
|
|
4231
|
+
message: gitErr instanceof Error ? gitErr.message : String(gitErr)
|
|
4232
|
+
};
|
|
4233
|
+
}
|
|
3828
4234
|
}
|
|
4235
|
+
const onResolverError = (kind, err) => {
|
|
4236
|
+
if (errorContext === null) {
|
|
4237
|
+
errorContext = { kind, message: err.message };
|
|
4238
|
+
}
|
|
4239
|
+
};
|
|
3829
4240
|
const worktreeId = await resolveWorktreeId({
|
|
3830
4241
|
repoId,
|
|
3831
4242
|
repoPath: projectPath,
|
|
3832
4243
|
branch,
|
|
3833
|
-
deviceId
|
|
4244
|
+
deviceId,
|
|
4245
|
+
onError: onResolverError
|
|
3834
4246
|
});
|
|
3835
4247
|
if (worktreeId) {
|
|
3836
|
-
|
|
3837
|
-
process.exit(0);
|
|
4248
|
+
emitAndExit(worktreeId, errorContext, jsonMode);
|
|
3838
4249
|
}
|
|
3839
4250
|
const useFallback = hasFlag("fallback-from-branch", 3);
|
|
3840
4251
|
if (useFallback) {
|
|
3841
4252
|
const fallbackId = await resolveWorktreeByBranch({
|
|
3842
4253
|
repoId,
|
|
3843
4254
|
deviceId,
|
|
3844
|
-
branch
|
|
4255
|
+
branch,
|
|
4256
|
+
onError: onResolverError
|
|
3845
4257
|
});
|
|
3846
4258
|
if (fallbackId) {
|
|
3847
|
-
|
|
4259
|
+
emitAndExit(fallbackId, errorContext, jsonMode);
|
|
3848
4260
|
}
|
|
3849
4261
|
}
|
|
3850
|
-
|
|
4262
|
+
emitAndExit(null, errorContext, jsonMode);
|
|
3851
4263
|
} catch (err) {
|
|
3852
|
-
if (
|
|
3853
|
-
|
|
3854
|
-
|
|
3855
|
-
|
|
4264
|
+
if (err instanceof ProcessExitSignal) throw err;
|
|
4265
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
4266
|
+
errorContext = { kind: "unhandled", message: msg };
|
|
4267
|
+
emitAndExit(null, errorContext, jsonMode);
|
|
4268
|
+
}
|
|
4269
|
+
}
|
|
4270
|
+
function emitAndExit(worktreeId, errorContext, jsonMode) {
|
|
4271
|
+
if (jsonMode) {
|
|
4272
|
+
const errorKind = errorContext?.kind ?? (worktreeId === null ? "tuple_miss" : null);
|
|
4273
|
+
process.stdout.write(
|
|
4274
|
+
JSON.stringify({ worktree_id: worktreeId, error_kind: errorKind }) + "\n"
|
|
4275
|
+
);
|
|
4276
|
+
} else {
|
|
4277
|
+
if (worktreeId !== null) {
|
|
4278
|
+
process.stdout.write(worktreeId);
|
|
4279
|
+
}
|
|
4280
|
+
if (errorContext !== null) {
|
|
4281
|
+
if (errorContext.kind !== "unhandled" || process.env.CODEBYPLAN_DEBUG === "1") {
|
|
4282
|
+
distress(errorContext.kind, errorContext.message, jsonMode);
|
|
4283
|
+
}
|
|
3856
4284
|
}
|
|
3857
|
-
process.exit(0);
|
|
3858
4285
|
}
|
|
4286
|
+
process.exit(0);
|
|
3859
4287
|
}
|
|
4288
|
+
var ProcessExitSignal;
|
|
3860
4289
|
var init_resolve_worktree2 = __esm({
|
|
3861
4290
|
"src/cli/resolve-worktree.ts"() {
|
|
3862
4291
|
"use strict";
|
|
3863
4292
|
init_flags();
|
|
3864
4293
|
init_local_config();
|
|
3865
4294
|
init_resolve_worktree();
|
|
4295
|
+
ProcessExitSignal = class extends Error {
|
|
4296
|
+
code;
|
|
4297
|
+
constructor(code) {
|
|
4298
|
+
super(`process.exit(${code})`);
|
|
4299
|
+
this.name = "ProcessExitSignal";
|
|
4300
|
+
this.code = code;
|
|
4301
|
+
}
|
|
4302
|
+
};
|
|
3866
4303
|
}
|
|
3867
4304
|
});
|
|
3868
4305
|
|
|
3869
4306
|
// src/lib/migrate-local-config.ts
|
|
3870
|
-
import { mkdir as
|
|
3871
|
-
import { join as
|
|
4307
|
+
import { mkdir as mkdir5, readFile as readFile12, unlink as unlink2, writeFile as writeFile9 } from "node:fs/promises";
|
|
4308
|
+
import { join as join13 } from "node:path";
|
|
3872
4309
|
function legacySharedPath(projectPath) {
|
|
3873
|
-
return
|
|
4310
|
+
return join13(projectPath, ".codebyplan.json");
|
|
3874
4311
|
}
|
|
3875
4312
|
function legacyLocalPath(projectPath) {
|
|
3876
|
-
return
|
|
4313
|
+
return join13(projectPath, ".codebyplan.local.json");
|
|
3877
4314
|
}
|
|
3878
4315
|
function newDirPath(projectPath) {
|
|
3879
|
-
return
|
|
4316
|
+
return join13(projectPath, ".codebyplan");
|
|
3880
4317
|
}
|
|
3881
4318
|
function sentinelPath(projectPath) {
|
|
3882
|
-
return
|
|
4319
|
+
return join13(projectPath, ".codebyplan", "repo.json");
|
|
3883
4320
|
}
|
|
3884
4321
|
async function statSafe(p) {
|
|
3885
|
-
const { stat } = await import("node:fs/promises");
|
|
4322
|
+
const { stat: stat2 } = await import("node:fs/promises");
|
|
3886
4323
|
try {
|
|
3887
|
-
return await
|
|
4324
|
+
return await stat2(p);
|
|
3888
4325
|
} catch {
|
|
3889
4326
|
return null;
|
|
3890
4327
|
}
|
|
@@ -3918,7 +4355,7 @@ async function runLocalMigration(projectPath) {
|
|
|
3918
4355
|
}
|
|
3919
4356
|
let legacyRaw;
|
|
3920
4357
|
try {
|
|
3921
|
-
legacyRaw = await
|
|
4358
|
+
legacyRaw = await readFile12(legacySharedPath(projectPath), "utf-8");
|
|
3922
4359
|
} catch {
|
|
3923
4360
|
return {
|
|
3924
4361
|
migrated: true,
|
|
@@ -3945,7 +4382,7 @@ async function runLocalMigration(projectPath) {
|
|
|
3945
4382
|
let deviceId;
|
|
3946
4383
|
let deviceWrittenByHelper = false;
|
|
3947
4384
|
try {
|
|
3948
|
-
const localRaw = await
|
|
4385
|
+
const localRaw = await readFile12(legacyLocalPath(projectPath), "utf-8");
|
|
3949
4386
|
const localParsed = JSON.parse(localRaw);
|
|
3950
4387
|
if (typeof localParsed.device_id === "string") {
|
|
3951
4388
|
deviceId = localParsed.device_id;
|
|
@@ -3953,7 +4390,7 @@ async function runLocalMigration(projectPath) {
|
|
|
3953
4390
|
} catch {
|
|
3954
4391
|
}
|
|
3955
4392
|
try {
|
|
3956
|
-
await
|
|
4393
|
+
await mkdir5(newDirPath(projectPath), { recursive: true });
|
|
3957
4394
|
} catch (err) {
|
|
3958
4395
|
const code = err.code;
|
|
3959
4396
|
if (code === "ENOTDIR" || code === "EEXIST") {
|
|
@@ -3972,8 +4409,8 @@ async function runLocalMigration(projectPath) {
|
|
|
3972
4409
|
if ("repo_id" in cfg) repoJson.repo_id = cfg.repo_id;
|
|
3973
4410
|
if ("organization_id" in cfg) repoJson.organization_id = cfg.organization_id;
|
|
3974
4411
|
if ("project_id" in cfg) repoJson.project_id = cfg.project_id;
|
|
3975
|
-
await
|
|
3976
|
-
|
|
4412
|
+
await writeFile9(
|
|
4413
|
+
join13(projectPath, ".codebyplan", "repo.json"),
|
|
3977
4414
|
JSON.stringify(repoJson, null, 2) + "\n",
|
|
3978
4415
|
"utf-8"
|
|
3979
4416
|
);
|
|
@@ -3985,8 +4422,8 @@ async function runLocalMigration(projectPath) {
|
|
|
3985
4422
|
serverJson.auto_push_enabled = cfg.auto_push_enabled;
|
|
3986
4423
|
if ("port_allocations" in cfg)
|
|
3987
4424
|
serverJson.port_allocations = cfg.port_allocations;
|
|
3988
|
-
await
|
|
3989
|
-
|
|
4425
|
+
await writeFile9(
|
|
4426
|
+
join13(projectPath, ".codebyplan", "server.json"),
|
|
3990
4427
|
JSON.stringify(serverJson, null, 2) + "\n",
|
|
3991
4428
|
"utf-8"
|
|
3992
4429
|
);
|
|
@@ -3994,30 +4431,30 @@ async function runLocalMigration(projectPath) {
|
|
|
3994
4431
|
const gitJson = {};
|
|
3995
4432
|
if ("git_branch" in cfg) gitJson.git_branch = cfg.git_branch;
|
|
3996
4433
|
if ("branch_config" in cfg) gitJson.branch_config = cfg.branch_config;
|
|
3997
|
-
await
|
|
3998
|
-
|
|
4434
|
+
await writeFile9(
|
|
4435
|
+
join13(projectPath, ".codebyplan", "git.json"),
|
|
3999
4436
|
JSON.stringify(gitJson, null, 2) + "\n",
|
|
4000
4437
|
"utf-8"
|
|
4001
4438
|
);
|
|
4002
4439
|
filesChanged.push(".codebyplan/git.json");
|
|
4003
4440
|
const shipmentJson = {};
|
|
4004
4441
|
if ("shipment" in cfg) shipmentJson.shipment = cfg.shipment;
|
|
4005
|
-
await
|
|
4006
|
-
|
|
4442
|
+
await writeFile9(
|
|
4443
|
+
join13(projectPath, ".codebyplan", "shipment.json"),
|
|
4007
4444
|
JSON.stringify(shipmentJson, null, 2) + "\n",
|
|
4008
4445
|
"utf-8"
|
|
4009
4446
|
);
|
|
4010
4447
|
filesChanged.push(".codebyplan/shipment.json");
|
|
4011
4448
|
const vendorJson = {};
|
|
4012
|
-
await
|
|
4013
|
-
|
|
4449
|
+
await writeFile9(
|
|
4450
|
+
join13(projectPath, ".codebyplan", "vendor.json"),
|
|
4014
4451
|
JSON.stringify(vendorJson, null, 2) + "\n",
|
|
4015
4452
|
"utf-8"
|
|
4016
4453
|
);
|
|
4017
4454
|
filesChanged.push(".codebyplan/vendor.json");
|
|
4018
4455
|
if (!deviceWrittenByHelper) {
|
|
4019
|
-
await
|
|
4020
|
-
|
|
4456
|
+
await writeFile9(
|
|
4457
|
+
join13(projectPath, ".codebyplan", "device.local.json"),
|
|
4021
4458
|
JSON.stringify({ device_id: deviceId }, null, 2) + "\n",
|
|
4022
4459
|
"utf-8"
|
|
4023
4460
|
);
|
|
@@ -4029,9 +4466,9 @@ async function runLocalMigration(projectPath) {
|
|
|
4029
4466
|
"Migration write incomplete: .codebyplan/repo.json was not persisted. Re-run migration to retry from a clean state."
|
|
4030
4467
|
);
|
|
4031
4468
|
}
|
|
4032
|
-
const gitignorePath =
|
|
4469
|
+
const gitignorePath = join13(projectPath, ".gitignore");
|
|
4033
4470
|
try {
|
|
4034
|
-
const gitignoreContent = await
|
|
4471
|
+
const gitignoreContent = await readFile12(gitignorePath, "utf-8");
|
|
4035
4472
|
const legacyLine = ".codebyplan.local.json";
|
|
4036
4473
|
const newLine = ".codebyplan/device.local.json";
|
|
4037
4474
|
const hasLegacy = gitignoreContent.split("\n").some((l) => l.trimEnd() === legacyLine);
|
|
@@ -4050,7 +4487,7 @@ async function runLocalMigration(projectPath) {
|
|
|
4050
4487
|
updated = gitignoreContent;
|
|
4051
4488
|
}
|
|
4052
4489
|
if (updated !== gitignoreContent) {
|
|
4053
|
-
await
|
|
4490
|
+
await writeFile9(gitignorePath, updated, "utf-8");
|
|
4054
4491
|
filesChanged.push(".gitignore");
|
|
4055
4492
|
}
|
|
4056
4493
|
} catch {
|
|
@@ -4089,8 +4526,8 @@ __export(config_exports, {
|
|
|
4089
4526
|
readVendorConfig: () => readVendorConfig,
|
|
4090
4527
|
runConfig: () => runConfig
|
|
4091
4528
|
});
|
|
4092
|
-
import { mkdir as
|
|
4093
|
-
import { join as
|
|
4529
|
+
import { mkdir as mkdir6, readFile as readFile13, writeFile as writeFile10 } from "node:fs/promises";
|
|
4530
|
+
import { join as join14 } from "node:path";
|
|
4094
4531
|
async function runConfig() {
|
|
4095
4532
|
const flags = parseFlags(3);
|
|
4096
4533
|
const dryRun = hasFlag("dry-run", 3);
|
|
@@ -4123,7 +4560,7 @@ async function runConfig() {
|
|
|
4123
4560
|
console.log("\n Config complete.\n");
|
|
4124
4561
|
}
|
|
4125
4562
|
async function syncConfigToFile(repoId, projectPath, dryRun) {
|
|
4126
|
-
const codebyplanDir =
|
|
4563
|
+
const codebyplanDir = join14(projectPath, ".codebyplan");
|
|
4127
4564
|
let resolvedWorktreeId;
|
|
4128
4565
|
try {
|
|
4129
4566
|
const deviceId = await getOrCreateDeviceId(projectPath);
|
|
@@ -4252,7 +4689,7 @@ async function syncConfigToFile(repoId, projectPath, dryRun) {
|
|
|
4252
4689
|
console.log(" Config would be updated (dry-run).");
|
|
4253
4690
|
return;
|
|
4254
4691
|
}
|
|
4255
|
-
await
|
|
4692
|
+
await mkdir6(codebyplanDir, { recursive: true });
|
|
4256
4693
|
const files = [
|
|
4257
4694
|
{ name: "repo.json", payload: repoPayload },
|
|
4258
4695
|
{ name: "server.json", payload: serverPayload },
|
|
@@ -4262,15 +4699,15 @@ async function syncConfigToFile(repoId, projectPath, dryRun) {
|
|
|
4262
4699
|
];
|
|
4263
4700
|
let anyUpdated = false;
|
|
4264
4701
|
for (const { name, payload } of files) {
|
|
4265
|
-
const filePath =
|
|
4702
|
+
const filePath = join14(codebyplanDir, name);
|
|
4266
4703
|
const newJson = JSON.stringify(payload, null, 2) + "\n";
|
|
4267
4704
|
let currentJson = "";
|
|
4268
4705
|
try {
|
|
4269
|
-
currentJson = await
|
|
4706
|
+
currentJson = await readFile13(filePath, "utf-8");
|
|
4270
4707
|
} catch {
|
|
4271
4708
|
}
|
|
4272
4709
|
if (currentJson === newJson) continue;
|
|
4273
|
-
await
|
|
4710
|
+
await writeFile10(filePath, newJson, "utf-8");
|
|
4274
4711
|
console.log(` Updated .codebyplan/${name}`);
|
|
4275
4712
|
anyUpdated = true;
|
|
4276
4713
|
}
|
|
@@ -4280,8 +4717,8 @@ async function syncConfigToFile(repoId, projectPath, dryRun) {
|
|
|
4280
4717
|
}
|
|
4281
4718
|
async function readRepoConfig(projectPath) {
|
|
4282
4719
|
try {
|
|
4283
|
-
const raw = await
|
|
4284
|
-
|
|
4720
|
+
const raw = await readFile13(
|
|
4721
|
+
join14(projectPath, ".codebyplan", "repo.json"),
|
|
4285
4722
|
"utf-8"
|
|
4286
4723
|
);
|
|
4287
4724
|
return JSON.parse(raw);
|
|
@@ -4291,8 +4728,8 @@ async function readRepoConfig(projectPath) {
|
|
|
4291
4728
|
}
|
|
4292
4729
|
async function readServerConfig(projectPath) {
|
|
4293
4730
|
try {
|
|
4294
|
-
const raw = await
|
|
4295
|
-
|
|
4731
|
+
const raw = await readFile13(
|
|
4732
|
+
join14(projectPath, ".codebyplan", "server.json"),
|
|
4296
4733
|
"utf-8"
|
|
4297
4734
|
);
|
|
4298
4735
|
return JSON.parse(raw);
|
|
@@ -4302,8 +4739,8 @@ async function readServerConfig(projectPath) {
|
|
|
4302
4739
|
}
|
|
4303
4740
|
async function readGitConfig(projectPath) {
|
|
4304
4741
|
try {
|
|
4305
|
-
const raw = await
|
|
4306
|
-
|
|
4742
|
+
const raw = await readFile13(
|
|
4743
|
+
join14(projectPath, ".codebyplan", "git.json"),
|
|
4307
4744
|
"utf-8"
|
|
4308
4745
|
);
|
|
4309
4746
|
return JSON.parse(raw);
|
|
@@ -4313,8 +4750,8 @@ async function readGitConfig(projectPath) {
|
|
|
4313
4750
|
}
|
|
4314
4751
|
async function readShipmentConfig(projectPath) {
|
|
4315
4752
|
try {
|
|
4316
|
-
const raw = await
|
|
4317
|
-
|
|
4753
|
+
const raw = await readFile13(
|
|
4754
|
+
join14(projectPath, ".codebyplan", "shipment.json"),
|
|
4318
4755
|
"utf-8"
|
|
4319
4756
|
);
|
|
4320
4757
|
return JSON.parse(raw);
|
|
@@ -4324,8 +4761,8 @@ async function readShipmentConfig(projectPath) {
|
|
|
4324
4761
|
}
|
|
4325
4762
|
async function readVendorConfig(projectPath) {
|
|
4326
4763
|
try {
|
|
4327
|
-
const raw = await
|
|
4328
|
-
|
|
4764
|
+
const raw = await readFile13(
|
|
4765
|
+
join14(projectPath, ".codebyplan", "vendor.json"),
|
|
4329
4766
|
"utf-8"
|
|
4330
4767
|
);
|
|
4331
4768
|
return JSON.parse(raw);
|
|
@@ -4381,14 +4818,14 @@ var init_server_detect = __esm({
|
|
|
4381
4818
|
});
|
|
4382
4819
|
|
|
4383
4820
|
// src/lib/port-verify.ts
|
|
4384
|
-
import { readFile as
|
|
4821
|
+
import { readFile as readFile14 } from "node:fs/promises";
|
|
4385
4822
|
async function verifyPorts(projectPath, portAllocations) {
|
|
4386
4823
|
const mismatches = [];
|
|
4387
4824
|
const allocatedPorts = new Set(portAllocations.map((a) => a.port));
|
|
4388
4825
|
const packageJsonPaths = await findPackageJsonFiles(projectPath, projectPath);
|
|
4389
4826
|
for (const pkgPath of packageJsonPaths) {
|
|
4390
4827
|
try {
|
|
4391
|
-
const raw = await
|
|
4828
|
+
const raw = await readFile14(pkgPath, "utf-8");
|
|
4392
4829
|
const pkg = JSON.parse(raw);
|
|
4393
4830
|
const scriptPort = detectPortFromScripts(pkg);
|
|
4394
4831
|
if (scriptPort !== null && !allocatedPorts.has(scriptPort)) {
|
|
@@ -4451,7 +4888,7 @@ async function findUnallocatedApps(projectPath, portAllocations) {
|
|
|
4451
4888
|
}
|
|
4452
4889
|
let pkg;
|
|
4453
4890
|
try {
|
|
4454
|
-
const raw = await
|
|
4891
|
+
const raw = await readFile14(`${app.absPath}/package.json`, "utf-8");
|
|
4455
4892
|
pkg = JSON.parse(raw);
|
|
4456
4893
|
} catch {
|
|
4457
4894
|
continue;
|
|
@@ -5253,6 +5690,11 @@ async function runInstall(opts, deps = {}) {
|
|
|
5253
5690
|
await Promise.resolve();
|
|
5254
5691
|
const scope = opts.scope ?? "project";
|
|
5255
5692
|
if (scope === "user") {
|
|
5693
|
+
if (opts.renderer) {
|
|
5694
|
+
console.warn(
|
|
5695
|
+
"codebyplan claude install: --bash/--node/--python is ignored for --scope user (no project root for statusline.local.json)."
|
|
5696
|
+
);
|
|
5697
|
+
}
|
|
5256
5698
|
runInstallUser(opts, deps);
|
|
5257
5699
|
return;
|
|
5258
5700
|
}
|
|
@@ -5329,6 +5771,9 @@ async function runInstall(opts, deps = {}) {
|
|
|
5329
5771
|
console.log(
|
|
5330
5772
|
`codebyplan claude install${opts.dryRun ? " (dry-run)" : ""}: ${manifestEntries.length} files, ${countHookEntries(templatesDir)} hook entries.`
|
|
5331
5773
|
);
|
|
5774
|
+
if (opts.renderer && !opts.dryRun) {
|
|
5775
|
+
await writeStatuslineLocalConfig(projectDir, { renderer: opts.renderer });
|
|
5776
|
+
}
|
|
5332
5777
|
} catch (err) {
|
|
5333
5778
|
console.error(
|
|
5334
5779
|
`codebyplan claude install failed: ${err instanceof Error ? err.message : String(err)}`
|
|
@@ -5414,6 +5859,7 @@ var init_install = __esm({
|
|
|
5414
5859
|
init_template_walker();
|
|
5415
5860
|
init_manifest();
|
|
5416
5861
|
init_settings_merge();
|
|
5862
|
+
init_statusline_config();
|
|
5417
5863
|
}
|
|
5418
5864
|
});
|
|
5419
5865
|
|
|
@@ -5541,6 +5987,11 @@ async function runUpdate(opts, deps = {}) {
|
|
|
5541
5987
|
await Promise.resolve();
|
|
5542
5988
|
const scope = opts.scope ?? "project";
|
|
5543
5989
|
if (scope === "user") {
|
|
5990
|
+
if (opts.renderer) {
|
|
5991
|
+
console.warn(
|
|
5992
|
+
"codebyplan claude update: --bash/--node/--python is ignored for --scope user (no project root for statusline.local.json)."
|
|
5993
|
+
);
|
|
5994
|
+
}
|
|
5544
5995
|
runUpdateUser(opts, deps);
|
|
5545
5996
|
return;
|
|
5546
5997
|
}
|
|
@@ -5685,6 +6136,9 @@ async function runUpdate(opts, deps = {}) {
|
|
|
5685
6136
|
console.log(
|
|
5686
6137
|
`codebyplan claude update${opts.dryRun ? " (dry-run)" : ""}: ${finalManifestEntries.length} files tracked.`
|
|
5687
6138
|
);
|
|
6139
|
+
if (opts.renderer && !opts.dryRun) {
|
|
6140
|
+
await writeStatuslineLocalConfig(projectDir, { renderer: opts.renderer });
|
|
6141
|
+
}
|
|
5688
6142
|
} catch (err) {
|
|
5689
6143
|
console.error(
|
|
5690
6144
|
`codebyplan claude update failed: ${err instanceof Error ? err.message : String(err)}`
|
|
@@ -5831,6 +6285,7 @@ var init_update = __esm({
|
|
|
5831
6285
|
init_manifest();
|
|
5832
6286
|
init_settings_merge();
|
|
5833
6287
|
init_prompt();
|
|
6288
|
+
init_statusline_config();
|
|
5834
6289
|
}
|
|
5835
6290
|
});
|
|
5836
6291
|
|
|
@@ -6010,8 +6465,8 @@ function pruneEmptyManagedDirs(projectDir) {
|
|
|
6010
6465
|
}
|
|
6011
6466
|
function pruneLeafFirst(dir) {
|
|
6012
6467
|
if (!fs5.existsSync(dir)) return;
|
|
6013
|
-
const
|
|
6014
|
-
if (!
|
|
6468
|
+
const stat2 = fs5.statSync(dir);
|
|
6469
|
+
if (!stat2.isDirectory()) return;
|
|
6015
6470
|
for (const entry of fs5.readdirSync(dir, { withFileTypes: true })) {
|
|
6016
6471
|
if (entry.isDirectory()) {
|
|
6017
6472
|
pruneLeafFirst(path5.join(dir, entry.name));
|
|
@@ -6068,6 +6523,12 @@ void (async () => {
|
|
|
6068
6523
|
await runSetup2();
|
|
6069
6524
|
process.exit(0);
|
|
6070
6525
|
}
|
|
6526
|
+
if (arg === "statusline") {
|
|
6527
|
+
const { runStatusline: runStatusline2 } = await Promise.resolve().then(() => (init_statusline(), statusline_exports));
|
|
6528
|
+
const rest = process.argv.slice(3);
|
|
6529
|
+
await runStatusline2(rest);
|
|
6530
|
+
process.exit(process.exitCode ?? 0);
|
|
6531
|
+
}
|
|
6071
6532
|
if (arg === "login") {
|
|
6072
6533
|
const { runLogin: runLogin2 } = await Promise.resolve().then(() => (init_login(), login_exports));
|
|
6073
6534
|
try {
|
|
@@ -6203,6 +6664,9 @@ void (async () => {
|
|
|
6203
6664
|
user \u2014 writes only owned base settings into ~/.claude/settings.json;
|
|
6204
6665
|
never copies templates or hooks
|
|
6205
6666
|
--project-dir <path> Override the project root (default: cwd). Cannot be combined with --scope user.
|
|
6667
|
+
--bash Set statusline renderer to bash after install/update
|
|
6668
|
+
--node Set statusline renderer to node after install/update
|
|
6669
|
+
--python Set statusline renderer to python after install/update
|
|
6206
6670
|
`);
|
|
6207
6671
|
process.exit(0);
|
|
6208
6672
|
}
|
|
@@ -6225,6 +6689,7 @@ void (async () => {
|
|
|
6225
6689
|
codebyplan ship Ship current feat branch to production via PR
|
|
6226
6690
|
codebyplan branch migrate Rewrite branch_config from 3-branch to 2-tier model
|
|
6227
6691
|
codebyplan claude Claude asset management (install/update/uninstall)
|
|
6692
|
+
codebyplan statusline Show or set the statusline renderer (bash/node/python)
|
|
6228
6693
|
codebyplan resolve-worktree Resolve active worktree UUID from device+path+branch tuple
|
|
6229
6694
|
codebyplan help Show this help message
|
|
6230
6695
|
codebyplan --version Print version
|
|
@@ -6259,6 +6724,9 @@ void (async () => {
|
|
|
6259
6724
|
--verbose Log every file operation
|
|
6260
6725
|
--scope <user|project> Target scope (default: project)
|
|
6261
6726
|
--project-dir <path> Override the project root (default: cwd)
|
|
6727
|
+
--bash Set statusline renderer to bash after install/update
|
|
6728
|
+
--node Set statusline renderer to node after install/update
|
|
6729
|
+
--python Set statusline renderer to python after install/update
|
|
6262
6730
|
|
|
6263
6731
|
MCP Server:
|
|
6264
6732
|
Claude Code connects to CodeByPlan via remote MCP:
|
|
@@ -6309,8 +6777,19 @@ function parseClaudeFlags(rest) {
|
|
|
6309
6777
|
);
|
|
6310
6778
|
return null;
|
|
6311
6779
|
}
|
|
6780
|
+
const hasBash = rest.includes("--bash");
|
|
6781
|
+
const hasNode = rest.includes("--node");
|
|
6782
|
+
const hasPython = rest.includes("--python");
|
|
6783
|
+
const rendererCount = [hasBash, hasNode, hasPython].filter(Boolean).length;
|
|
6784
|
+
if (rendererCount > 1) {
|
|
6785
|
+
process.stderr.write(
|
|
6786
|
+
"error: --bash, --node, and --python are mutually exclusive; pass only one.\n"
|
|
6787
|
+
);
|
|
6788
|
+
return null;
|
|
6789
|
+
}
|
|
6790
|
+
const renderer = hasBash ? "bash" : hasNode ? "node" : hasPython ? "python" : void 0;
|
|
6312
6791
|
return {
|
|
6313
|
-
opts: { yes, dryRun, verbose, scope },
|
|
6792
|
+
opts: { yes, dryRun, verbose, scope, renderer },
|
|
6314
6793
|
projectDir
|
|
6315
6794
|
};
|
|
6316
6795
|
}
|