codeam-cli 2.2.1 → 2.3.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/CHANGELOG.md +12 -0
- package/dist/index.js +394 -42
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,18 @@ All notable changes to `codeam-cli` are documented here.
|
|
|
4
4
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
6
|
|
|
7
|
+
## [2.2.2] — 2026-05-02
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- **clients:** Recursive suffix search for read_file (v2.2.2)
|
|
12
|
+
|
|
13
|
+
## [2.2.1] — 2026-05-02
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
|
|
17
|
+
- **cli:** Subdir fallback for read_file when CLI cwd is a monorepo parent (v2.2.1)
|
|
18
|
+
|
|
7
19
|
## [2.1.0] — 2026-04-25
|
|
8
20
|
|
|
9
21
|
### Added
|
package/dist/index.js
CHANGED
|
@@ -87,11 +87,11 @@ var require_src = __commonJS({
|
|
|
87
87
|
});
|
|
88
88
|
|
|
89
89
|
// src/commands/start.ts
|
|
90
|
-
var
|
|
90
|
+
var fs7 = __toESM(require("fs"));
|
|
91
91
|
var os5 = __toESM(require("os"));
|
|
92
|
-
var
|
|
92
|
+
var path7 = __toESM(require("path"));
|
|
93
93
|
var import_crypto = require("crypto");
|
|
94
|
-
var
|
|
94
|
+
var import_child_process4 = require("child_process");
|
|
95
95
|
var import_picocolors2 = __toESM(require("picocolors"));
|
|
96
96
|
|
|
97
97
|
// src/config.ts
|
|
@@ -179,7 +179,7 @@ var import_qrcode_terminal = __toESM(require("qrcode-terminal"));
|
|
|
179
179
|
// package.json
|
|
180
180
|
var package_default = {
|
|
181
181
|
name: "codeam-cli",
|
|
182
|
-
version: "2.
|
|
182
|
+
version: "2.3.0",
|
|
183
183
|
description: "Remote control Claude Code (and other AI coding agents) from your mobile phone. Pair your device, send prompts, stream responses in real-time, and approve commands \u2014 from anywhere.",
|
|
184
184
|
main: "dist/index.js",
|
|
185
185
|
bin: {
|
|
@@ -2163,7 +2163,15 @@ var startCommandSchema = import_zod2.z.object({
|
|
|
2163
2163
|
// `path` is bounded to 4096 chars (a comfortable POSIX path max) so a
|
|
2164
2164
|
// malformed payload can't blow up the disk-side validator.
|
|
2165
2165
|
path: import_zod2.z.string().min(1).max(4096).optional(),
|
|
2166
|
-
content: import_zod2.z.string().optional()
|
|
2166
|
+
content: import_zod2.z.string().optional(),
|
|
2167
|
+
// Mini-IDE / project ops. `paths` (plural, strings) is used for
|
|
2168
|
+
// git_commit's optional file selection — distinct from `files`
|
|
2169
|
+
// (FileEntry[]) used by `start_task` for attachments.
|
|
2170
|
+
query: import_zod2.z.string().max(256).optional(),
|
|
2171
|
+
message: import_zod2.z.string().max(8e3).optional(),
|
|
2172
|
+
paths: import_zod2.z.array(import_zod2.z.string().max(4096)).optional(),
|
|
2173
|
+
side: import_zod2.z.enum(["ours", "theirs"]).optional(),
|
|
2174
|
+
limit: import_zod2.z.number().int().min(1).max(500).optional()
|
|
2167
2175
|
});
|
|
2168
2176
|
function parsePayload(schema, raw) {
|
|
2169
2177
|
const result = schema.safeParse(raw);
|
|
@@ -2174,6 +2182,8 @@ function parsePayload(schema, raw) {
|
|
|
2174
2182
|
var fs5 = __toESM(require("fs/promises"));
|
|
2175
2183
|
var path5 = __toESM(require("path"));
|
|
2176
2184
|
var MAX_FILE_BYTES = 5 * 1024 * 1024;
|
|
2185
|
+
var MAX_WALK_DEPTH = 6;
|
|
2186
|
+
var MAX_VISITED_DIRS = 5e3;
|
|
2177
2187
|
var SUBDIR_IGNORE = /* @__PURE__ */ new Set([
|
|
2178
2188
|
"node_modules",
|
|
2179
2189
|
".git",
|
|
@@ -2188,43 +2198,80 @@ var SUBDIR_IGNORE = /* @__PURE__ */ new Set([
|
|
|
2188
2198
|
".parcel-cache",
|
|
2189
2199
|
".idea",
|
|
2190
2200
|
".vscode",
|
|
2201
|
+
".vscode-test",
|
|
2191
2202
|
"ios",
|
|
2192
|
-
"android"
|
|
2203
|
+
"android",
|
|
2193
2204
|
// expo-managed native dirs are huge and rarely interesting
|
|
2205
|
+
".gradle",
|
|
2206
|
+
".cxx",
|
|
2207
|
+
".intellijPlatform",
|
|
2208
|
+
".kotlin",
|
|
2209
|
+
"tmp",
|
|
2210
|
+
"target",
|
|
2211
|
+
"venv",
|
|
2212
|
+
".venv",
|
|
2213
|
+
".mypy_cache",
|
|
2214
|
+
".pytest_cache",
|
|
2215
|
+
"__pycache__"
|
|
2194
2216
|
]);
|
|
2195
|
-
|
|
2217
|
+
function isUnder(parent, candidate) {
|
|
2218
|
+
const rel = path5.relative(parent, candidate);
|
|
2219
|
+
return rel === "" || !rel.startsWith("..") && !path5.isAbsolute(rel);
|
|
2220
|
+
}
|
|
2221
|
+
async function isExistingFile(absPath) {
|
|
2196
2222
|
try {
|
|
2197
|
-
const
|
|
2198
|
-
return
|
|
2223
|
+
const stat3 = await fs5.stat(absPath);
|
|
2224
|
+
return stat3.isFile();
|
|
2199
2225
|
} catch {
|
|
2200
|
-
return
|
|
2226
|
+
return false;
|
|
2201
2227
|
}
|
|
2202
2228
|
}
|
|
2203
|
-
function
|
|
2204
|
-
|
|
2205
|
-
|
|
2229
|
+
async function walkForSuffix(dir, needleVariants, depth, ctx) {
|
|
2230
|
+
if (depth > MAX_WALK_DEPTH) return;
|
|
2231
|
+
if (ctx.visited > MAX_VISITED_DIRS) return;
|
|
2232
|
+
if (ctx.matches.length >= ctx.cap) return;
|
|
2233
|
+
ctx.visited++;
|
|
2234
|
+
let entries = [];
|
|
2235
|
+
try {
|
|
2236
|
+
entries = await fs5.readdir(dir, { withFileTypes: true });
|
|
2237
|
+
} catch {
|
|
2238
|
+
return;
|
|
2239
|
+
}
|
|
2240
|
+
for (const e of entries) {
|
|
2241
|
+
if (!e.isFile()) continue;
|
|
2242
|
+
const full = path5.join(dir, e.name);
|
|
2243
|
+
if (needleVariants.some((needle) => full.endsWith(needle))) {
|
|
2244
|
+
ctx.matches.push(full);
|
|
2245
|
+
if (ctx.matches.length >= ctx.cap) return;
|
|
2246
|
+
}
|
|
2247
|
+
}
|
|
2248
|
+
for (const e of entries) {
|
|
2249
|
+
if (!e.isDirectory()) continue;
|
|
2250
|
+
if (SUBDIR_IGNORE.has(e.name)) continue;
|
|
2251
|
+
if (e.name.startsWith(".") && SUBDIR_IGNORE.has(e.name)) continue;
|
|
2252
|
+
await walkForSuffix(path5.join(dir, e.name), needleVariants, depth + 1, ctx);
|
|
2253
|
+
if (ctx.matches.length >= ctx.cap) return;
|
|
2254
|
+
}
|
|
2206
2255
|
}
|
|
2207
2256
|
async function findFile(rawPath) {
|
|
2208
2257
|
const cwd = process.cwd();
|
|
2209
|
-
const candidates = [];
|
|
2210
2258
|
if (path5.isAbsolute(rawPath)) {
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
return null;
|
|
2259
|
+
const abs = path5.normalize(rawPath);
|
|
2260
|
+
if (isUnder(cwd, abs) && await isExistingFile(abs)) return abs;
|
|
2261
|
+
}
|
|
2262
|
+
const direct = path5.resolve(cwd, rawPath);
|
|
2263
|
+
if (isUnder(cwd, direct) && await isExistingFile(direct)) return direct;
|
|
2264
|
+
const normalized = path5.normalize(rawPath).replace(/^[./\\]+/, "");
|
|
2265
|
+
const needles = [
|
|
2266
|
+
`${path5.sep}${normalized}`,
|
|
2267
|
+
`/${normalized}`
|
|
2268
|
+
].filter((v, i, a) => a.indexOf(v) === i);
|
|
2269
|
+
const ctx = { visited: 0, matches: [], cap: 16 };
|
|
2270
|
+
await walkForSuffix(cwd, needles, 0, ctx);
|
|
2271
|
+
const candidates = ctx.matches.filter((c2) => isUnder(cwd, c2));
|
|
2272
|
+
if (candidates.length === 0) return null;
|
|
2273
|
+
candidates.sort((a, b) => a.length - b.length);
|
|
2274
|
+
return candidates[0];
|
|
2228
2275
|
}
|
|
2229
2276
|
async function findWriteTarget(rawPath) {
|
|
2230
2277
|
const found = await findFile(rawPath);
|
|
@@ -2247,9 +2294,9 @@ async function readProjectFile(rawPath) {
|
|
|
2247
2294
|
if (!abs) {
|
|
2248
2295
|
return { error: `File not found in the project tree: ${rawPath}` };
|
|
2249
2296
|
}
|
|
2250
|
-
const
|
|
2251
|
-
if (
|
|
2252
|
-
return { error: `File too large (${(
|
|
2297
|
+
const stat3 = await fs5.stat(abs);
|
|
2298
|
+
if (stat3.size > MAX_FILE_BYTES) {
|
|
2299
|
+
return { error: `File too large (${(stat3.size / 1024 / 1024).toFixed(1)} MB > ${MAX_FILE_BYTES / 1024 / 1024} MB).` };
|
|
2253
2300
|
}
|
|
2254
2301
|
const buf = await fs5.readFile(abs);
|
|
2255
2302
|
if (looksBinary(buf)) {
|
|
@@ -2279,12 +2326,261 @@ async function writeProjectFile(rawPath, content) {
|
|
|
2279
2326
|
}
|
|
2280
2327
|
}
|
|
2281
2328
|
|
|
2329
|
+
// src/services/project-ops.service.ts
|
|
2330
|
+
var import_child_process3 = require("child_process");
|
|
2331
|
+
var import_util = require("util");
|
|
2332
|
+
var fs6 = __toESM(require("fs/promises"));
|
|
2333
|
+
var path6 = __toESM(require("path"));
|
|
2334
|
+
var execFileP = (0, import_util.promisify)(import_child_process3.execFile);
|
|
2335
|
+
var PROJECT_IGNORE = /* @__PURE__ */ new Set([
|
|
2336
|
+
"node_modules",
|
|
2337
|
+
".git",
|
|
2338
|
+
".next",
|
|
2339
|
+
".expo",
|
|
2340
|
+
"dist",
|
|
2341
|
+
"build",
|
|
2342
|
+
"out",
|
|
2343
|
+
".cache",
|
|
2344
|
+
"coverage",
|
|
2345
|
+
".turbo",
|
|
2346
|
+
".parcel-cache",
|
|
2347
|
+
".idea",
|
|
2348
|
+
".vscode",
|
|
2349
|
+
".vscode-test",
|
|
2350
|
+
"ios",
|
|
2351
|
+
"android",
|
|
2352
|
+
".gradle",
|
|
2353
|
+
".cxx",
|
|
2354
|
+
".intellijPlatform",
|
|
2355
|
+
".kotlin",
|
|
2356
|
+
"tmp",
|
|
2357
|
+
"target",
|
|
2358
|
+
"venv",
|
|
2359
|
+
".venv",
|
|
2360
|
+
".mypy_cache",
|
|
2361
|
+
".pytest_cache",
|
|
2362
|
+
"__pycache__",
|
|
2363
|
+
".DS_Store"
|
|
2364
|
+
]);
|
|
2365
|
+
var MAX_TREE_FILES = 5e3;
|
|
2366
|
+
var MAX_DIFF_BYTES = 512 * 1024;
|
|
2367
|
+
var MAX_GIT_OUTPUT = 256 * 1024;
|
|
2368
|
+
async function listProjectFiles(opts = {}) {
|
|
2369
|
+
const root = opts.cwd ?? process.cwd();
|
|
2370
|
+
const cap = opts.cap ?? MAX_TREE_FILES;
|
|
2371
|
+
const q2 = (opts.query ?? "").trim().toLowerCase();
|
|
2372
|
+
const out = [];
|
|
2373
|
+
let truncated = false;
|
|
2374
|
+
async function walk(dir, depth) {
|
|
2375
|
+
if (out.length >= cap) {
|
|
2376
|
+
truncated = true;
|
|
2377
|
+
return;
|
|
2378
|
+
}
|
|
2379
|
+
let entries = [];
|
|
2380
|
+
try {
|
|
2381
|
+
entries = await fs6.readdir(dir, { withFileTypes: true });
|
|
2382
|
+
} catch {
|
|
2383
|
+
return;
|
|
2384
|
+
}
|
|
2385
|
+
for (const e of entries) {
|
|
2386
|
+
if (out.length >= cap) {
|
|
2387
|
+
truncated = true;
|
|
2388
|
+
return;
|
|
2389
|
+
}
|
|
2390
|
+
if (PROJECT_IGNORE.has(e.name)) continue;
|
|
2391
|
+
const full = path6.join(dir, e.name);
|
|
2392
|
+
if (e.isDirectory()) {
|
|
2393
|
+
if (depth >= 12) continue;
|
|
2394
|
+
await walk(full, depth + 1);
|
|
2395
|
+
} else if (e.isFile()) {
|
|
2396
|
+
const rel = path6.relative(root, full);
|
|
2397
|
+
if (q2 && !rel.toLowerCase().includes(q2) && !e.name.toLowerCase().includes(q2)) {
|
|
2398
|
+
continue;
|
|
2399
|
+
}
|
|
2400
|
+
let size = 0;
|
|
2401
|
+
try {
|
|
2402
|
+
const st3 = await fs6.stat(full);
|
|
2403
|
+
size = st3.size;
|
|
2404
|
+
} catch {
|
|
2405
|
+
}
|
|
2406
|
+
out.push({ path: rel, name: e.name, size });
|
|
2407
|
+
}
|
|
2408
|
+
}
|
|
2409
|
+
}
|
|
2410
|
+
await walk(root, 0);
|
|
2411
|
+
out.sort((a, b) => a.path.localeCompare(b.path));
|
|
2412
|
+
return { files: out, truncated, root };
|
|
2413
|
+
}
|
|
2414
|
+
async function git(args2, cwd) {
|
|
2415
|
+
try {
|
|
2416
|
+
const { stdout, stderr } = await execFileP("git", args2, {
|
|
2417
|
+
cwd: cwd ?? process.cwd(),
|
|
2418
|
+
maxBuffer: MAX_GIT_OUTPUT,
|
|
2419
|
+
timeout: 3e4
|
|
2420
|
+
});
|
|
2421
|
+
return { stdout, stderr, code: 0 };
|
|
2422
|
+
} catch (err) {
|
|
2423
|
+
const e = err;
|
|
2424
|
+
return {
|
|
2425
|
+
stdout: e.stdout ?? "",
|
|
2426
|
+
stderr: e.stderr ?? e.message ?? "git failed",
|
|
2427
|
+
code: typeof e.code === "number" ? e.code : 1
|
|
2428
|
+
};
|
|
2429
|
+
}
|
|
2430
|
+
}
|
|
2431
|
+
async function gitStatus(cwd) {
|
|
2432
|
+
const root = cwd ?? process.cwd();
|
|
2433
|
+
const r = await git(["status", "--porcelain=v2", "--branch"], root);
|
|
2434
|
+
if (r.code !== 0) {
|
|
2435
|
+
return {
|
|
2436
|
+
branch: null,
|
|
2437
|
+
upstream: null,
|
|
2438
|
+
ahead: 0,
|
|
2439
|
+
behind: 0,
|
|
2440
|
+
entries: [],
|
|
2441
|
+
hasMergeInProgress: false,
|
|
2442
|
+
error: r.stderr.trim()
|
|
2443
|
+
};
|
|
2444
|
+
}
|
|
2445
|
+
const lines = r.stdout.split("\n").filter(Boolean);
|
|
2446
|
+
let branch = null;
|
|
2447
|
+
let upstream = null;
|
|
2448
|
+
let ahead = 0;
|
|
2449
|
+
let behind = 0;
|
|
2450
|
+
const entries = [];
|
|
2451
|
+
for (const line of lines) {
|
|
2452
|
+
if (line.startsWith("# branch.head ")) branch = line.slice("# branch.head ".length).trim();
|
|
2453
|
+
else if (line.startsWith("# branch.upstream ")) upstream = line.slice("# branch.upstream ".length).trim();
|
|
2454
|
+
else if (line.startsWith("# branch.ab ")) {
|
|
2455
|
+
const m = line.match(/\+(\d+)\s+-(\d+)/);
|
|
2456
|
+
if (m) {
|
|
2457
|
+
ahead = parseInt(m[1], 10);
|
|
2458
|
+
behind = parseInt(m[2], 10);
|
|
2459
|
+
}
|
|
2460
|
+
} else if (line.startsWith("1 ")) {
|
|
2461
|
+
const parts = line.split(" ");
|
|
2462
|
+
const xy = parts[1];
|
|
2463
|
+
const p2 = parts.slice(8).join(" ");
|
|
2464
|
+
entries.push({
|
|
2465
|
+
code: xy,
|
|
2466
|
+
path: p2,
|
|
2467
|
+
staged: xy[0] !== ".",
|
|
2468
|
+
conflict: false
|
|
2469
|
+
});
|
|
2470
|
+
} else if (line.startsWith("2 ")) {
|
|
2471
|
+
const parts = line.split(" ");
|
|
2472
|
+
const xy = parts[1];
|
|
2473
|
+
const tail = parts.slice(9).join(" ");
|
|
2474
|
+
const [newPath, oldPath] = tail.split(" ");
|
|
2475
|
+
entries.push({
|
|
2476
|
+
code: xy,
|
|
2477
|
+
path: newPath ?? "",
|
|
2478
|
+
oldPath: oldPath ?? void 0,
|
|
2479
|
+
staged: xy[0] !== ".",
|
|
2480
|
+
conflict: false
|
|
2481
|
+
});
|
|
2482
|
+
} else if (line.startsWith("? ")) {
|
|
2483
|
+
entries.push({
|
|
2484
|
+
code: "??",
|
|
2485
|
+
path: line.slice(2),
|
|
2486
|
+
staged: false,
|
|
2487
|
+
conflict: false
|
|
2488
|
+
});
|
|
2489
|
+
} else if (line.startsWith("u ")) {
|
|
2490
|
+
const parts = line.split(" ");
|
|
2491
|
+
const xy = parts[1];
|
|
2492
|
+
const p2 = parts.slice(10).join(" ");
|
|
2493
|
+
entries.push({
|
|
2494
|
+
code: xy,
|
|
2495
|
+
path: p2,
|
|
2496
|
+
staged: false,
|
|
2497
|
+
conflict: true
|
|
2498
|
+
});
|
|
2499
|
+
}
|
|
2500
|
+
}
|
|
2501
|
+
let hasMergeInProgress = false;
|
|
2502
|
+
try {
|
|
2503
|
+
const gitDir = (await git(["rev-parse", "--git-dir"], root)).stdout.trim();
|
|
2504
|
+
const mergeHead = path6.isAbsolute(gitDir) ? path6.join(gitDir, "MERGE_HEAD") : path6.join(root, gitDir, "MERGE_HEAD");
|
|
2505
|
+
await fs6.access(mergeHead);
|
|
2506
|
+
hasMergeInProgress = true;
|
|
2507
|
+
} catch {
|
|
2508
|
+
}
|
|
2509
|
+
return { branch, upstream, ahead, behind, entries, hasMergeInProgress };
|
|
2510
|
+
}
|
|
2511
|
+
async function gitDiff(file, cwd) {
|
|
2512
|
+
const args2 = ["diff", "--no-color", "--patch"];
|
|
2513
|
+
if (file) args2.push("--", file);
|
|
2514
|
+
const r = await git(args2, cwd);
|
|
2515
|
+
if (r.code !== 0 && !r.stdout) {
|
|
2516
|
+
return { diff: "", truncated: false, error: r.stderr.trim() };
|
|
2517
|
+
}
|
|
2518
|
+
const truncated = r.stdout.length >= MAX_DIFF_BYTES;
|
|
2519
|
+
return { diff: r.stdout.slice(0, MAX_DIFF_BYTES), truncated };
|
|
2520
|
+
}
|
|
2521
|
+
async function gitDiffStaged(file, cwd) {
|
|
2522
|
+
const args2 = ["diff", "--cached", "--no-color", "--patch"];
|
|
2523
|
+
if (file) args2.push("--", file);
|
|
2524
|
+
const r = await git(args2, cwd);
|
|
2525
|
+
if (r.code !== 0 && !r.stdout) {
|
|
2526
|
+
return { diff: "", truncated: false, error: r.stderr.trim() };
|
|
2527
|
+
}
|
|
2528
|
+
const truncated = r.stdout.length >= MAX_DIFF_BYTES;
|
|
2529
|
+
return { diff: r.stdout.slice(0, MAX_DIFF_BYTES), truncated };
|
|
2530
|
+
}
|
|
2531
|
+
async function gitLog(limit = 30, cwd) {
|
|
2532
|
+
const sep2 = "";
|
|
2533
|
+
const fmt = ["%H", "%h", "%an", "%aI", "%s"].join(sep2);
|
|
2534
|
+
const r = await git(["log", `-n${Math.min(limit, 200)}`, `--pretty=format:${fmt}`], cwd);
|
|
2535
|
+
if (r.code !== 0) return { commits: [], error: r.stderr.trim() };
|
|
2536
|
+
const commits = r.stdout.split("\n").filter(Boolean).map((line) => {
|
|
2537
|
+
const [hash, shortHash, author, date, subject] = line.split(sep2);
|
|
2538
|
+
return { hash, shortHash, author, date, subject };
|
|
2539
|
+
});
|
|
2540
|
+
return { commits };
|
|
2541
|
+
}
|
|
2542
|
+
async function gitCommit(message, files, cwd) {
|
|
2543
|
+
if (!message || message.trim().length === 0) {
|
|
2544
|
+
return { error: "Commit message is required." };
|
|
2545
|
+
}
|
|
2546
|
+
if (files && files.length > 0) {
|
|
2547
|
+
const r2 = await git(["add", "--", ...files], cwd);
|
|
2548
|
+
if (r2.code !== 0) return { error: `git add failed: ${r2.stderr.trim()}` };
|
|
2549
|
+
} else {
|
|
2550
|
+
const r2 = await git(["add", "-A"], cwd);
|
|
2551
|
+
if (r2.code !== 0) return { error: `git add failed: ${r2.stderr.trim()}` };
|
|
2552
|
+
}
|
|
2553
|
+
const r = await git(["commit", "-m", message], cwd);
|
|
2554
|
+
if (r.code !== 0) {
|
|
2555
|
+
return { error: r.stderr.trim() || "git commit failed" };
|
|
2556
|
+
}
|
|
2557
|
+
const head = await git(["rev-parse", "HEAD"], cwd);
|
|
2558
|
+
return { ok: true, commit: head.stdout.trim() };
|
|
2559
|
+
}
|
|
2560
|
+
async function gitPush(cwd) {
|
|
2561
|
+
const r = await git(["push"], cwd);
|
|
2562
|
+
if (r.code !== 0) return { error: r.stderr.trim() || "git push failed" };
|
|
2563
|
+
return { ok: true, output: (r.stdout + r.stderr).trim() };
|
|
2564
|
+
}
|
|
2565
|
+
async function gitPull(cwd) {
|
|
2566
|
+
const r = await git(["pull", "--ff-only"], cwd);
|
|
2567
|
+
if (r.code !== 0) return { error: r.stderr.trim() || "git pull failed" };
|
|
2568
|
+
return { ok: true, output: (r.stdout + r.stderr).trim() };
|
|
2569
|
+
}
|
|
2570
|
+
async function gitResolve(file, side, cwd) {
|
|
2571
|
+
const r = await git(["checkout", `--${side}`, "--", file], cwd);
|
|
2572
|
+
if (r.code !== 0) return { error: r.stderr.trim() || `git checkout --${side} failed` };
|
|
2573
|
+
const add = await git(["add", "--", file], cwd);
|
|
2574
|
+
if (add.code !== 0) return { error: add.stderr.trim() || "git add (resolve) failed" };
|
|
2575
|
+
return { ok: true };
|
|
2576
|
+
}
|
|
2577
|
+
|
|
2282
2578
|
// src/commands/start.ts
|
|
2283
2579
|
function saveFilesTemp(files) {
|
|
2284
2580
|
return files.filter(({ base64 }) => base64 && base64.length > 0).map(({ filename, base64 }) => {
|
|
2285
2581
|
const safeName = filename.replace(/[^a-zA-Z0-9._-]/g, "_").slice(0, 80);
|
|
2286
|
-
const tmpPath =
|
|
2287
|
-
|
|
2582
|
+
const tmpPath = path7.join(os5.tmpdir(), `codeam-${(0, import_crypto.randomUUID)()}-${safeName}`);
|
|
2583
|
+
fs7.writeFileSync(tmpPath, Buffer.from(base64, "base64"));
|
|
2288
2584
|
return tmpPath;
|
|
2289
2585
|
});
|
|
2290
2586
|
}
|
|
@@ -2357,14 +2653,14 @@ try:
|
|
|
2357
2653
|
sys.exit((st>>8)&0xFF)
|
|
2358
2654
|
except Exception:sys.exit(0)
|
|
2359
2655
|
`;
|
|
2360
|
-
const helperPath =
|
|
2361
|
-
|
|
2656
|
+
const helperPath = path7.join(os5.tmpdir(), "codeam-quota-helper.py");
|
|
2657
|
+
fs7.writeFileSync(helperPath, helperScript, { mode: 420 });
|
|
2362
2658
|
const python = findInPath("python3") ?? findInPath("python");
|
|
2363
2659
|
if (!python) {
|
|
2364
2660
|
quotaFetchInProgress = false;
|
|
2365
2661
|
return;
|
|
2366
2662
|
}
|
|
2367
|
-
const proc = (0,
|
|
2663
|
+
const proc = (0, import_child_process4.spawn)(python, [helperPath, claudeCmd, "--tools", ""], {
|
|
2368
2664
|
stdio: ["pipe", "pipe", "ignore"],
|
|
2369
2665
|
cwd: process.cwd(),
|
|
2370
2666
|
env: { ...process.env, TERM: "dumb", COLUMNS: "120", LINES: "30" }
|
|
@@ -2390,7 +2686,7 @@ except Exception:sys.exit(0)
|
|
|
2390
2686
|
} catch {
|
|
2391
2687
|
}
|
|
2392
2688
|
try {
|
|
2393
|
-
|
|
2689
|
+
fs7.unlinkSync(helperPath);
|
|
2394
2690
|
} catch {
|
|
2395
2691
|
}
|
|
2396
2692
|
quotaFetchInProgress = false;
|
|
@@ -2440,7 +2736,7 @@ except Exception:sys.exit(0)
|
|
|
2440
2736
|
setTimeout(() => {
|
|
2441
2737
|
for (const p2 of paths) {
|
|
2442
2738
|
try {
|
|
2443
|
-
|
|
2739
|
+
fs7.unlinkSync(p2);
|
|
2444
2740
|
} catch {
|
|
2445
2741
|
}
|
|
2446
2742
|
}
|
|
@@ -2532,6 +2828,62 @@ except Exception:sys.exit(0)
|
|
|
2532
2828
|
await relay.sendResult(cmd.id, "completed", result);
|
|
2533
2829
|
break;
|
|
2534
2830
|
}
|
|
2831
|
+
case "list_files": {
|
|
2832
|
+
const result = await listProjectFiles({ query: parsed.query });
|
|
2833
|
+
await relay.sendResult(cmd.id, "completed", result);
|
|
2834
|
+
break;
|
|
2835
|
+
}
|
|
2836
|
+
case "git_status": {
|
|
2837
|
+
const result = await gitStatus();
|
|
2838
|
+
await relay.sendResult(cmd.id, "completed", result);
|
|
2839
|
+
break;
|
|
2840
|
+
}
|
|
2841
|
+
case "git_diff": {
|
|
2842
|
+
const { path: filePath } = parsed;
|
|
2843
|
+
const result = await gitDiff(filePath ?? null);
|
|
2844
|
+
await relay.sendResult(cmd.id, "completed", result);
|
|
2845
|
+
break;
|
|
2846
|
+
}
|
|
2847
|
+
case "git_diff_staged": {
|
|
2848
|
+
const { path: filePath } = parsed;
|
|
2849
|
+
const result = await gitDiffStaged(filePath ?? null);
|
|
2850
|
+
await relay.sendResult(cmd.id, "completed", result);
|
|
2851
|
+
break;
|
|
2852
|
+
}
|
|
2853
|
+
case "git_log": {
|
|
2854
|
+
const result = await gitLog(parsed.limit ?? 30);
|
|
2855
|
+
await relay.sendResult(cmd.id, "completed", result);
|
|
2856
|
+
break;
|
|
2857
|
+
}
|
|
2858
|
+
case "git_commit": {
|
|
2859
|
+
if (!parsed.message) {
|
|
2860
|
+
await relay.sendResult(cmd.id, "failed", { error: "Missing message" });
|
|
2861
|
+
break;
|
|
2862
|
+
}
|
|
2863
|
+
const result = await gitCommit(parsed.message, parsed.paths);
|
|
2864
|
+
await relay.sendResult(cmd.id, "completed", result);
|
|
2865
|
+
break;
|
|
2866
|
+
}
|
|
2867
|
+
case "git_push": {
|
|
2868
|
+
const result = await gitPush();
|
|
2869
|
+
await relay.sendResult(cmd.id, "completed", result);
|
|
2870
|
+
break;
|
|
2871
|
+
}
|
|
2872
|
+
case "git_pull": {
|
|
2873
|
+
const result = await gitPull();
|
|
2874
|
+
await relay.sendResult(cmd.id, "completed", result);
|
|
2875
|
+
break;
|
|
2876
|
+
}
|
|
2877
|
+
case "git_resolve": {
|
|
2878
|
+
const { path: filePath, side } = parsed;
|
|
2879
|
+
if (!filePath || !side) {
|
|
2880
|
+
await relay.sendResult(cmd.id, "failed", { error: "Missing path or side" });
|
|
2881
|
+
break;
|
|
2882
|
+
}
|
|
2883
|
+
const result = await gitResolve(filePath, side);
|
|
2884
|
+
await relay.sendResult(cmd.id, "completed", result);
|
|
2885
|
+
break;
|
|
2886
|
+
}
|
|
2535
2887
|
}
|
|
2536
2888
|
});
|
|
2537
2889
|
ws.addHandler({
|
|
@@ -2559,7 +2911,7 @@ except Exception:sys.exit(0)
|
|
|
2559
2911
|
setTimeout(() => {
|
|
2560
2912
|
for (const p2 of paths) {
|
|
2561
2913
|
try {
|
|
2562
|
-
|
|
2914
|
+
fs7.unlinkSync(p2);
|
|
2563
2915
|
} catch {
|
|
2564
2916
|
}
|
|
2565
2917
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codeam-cli",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.3.0",
|
|
4
4
|
"description": "Remote control Claude Code (and other AI coding agents) from your mobile phone. Pair your device, send prompts, stream responses in real-time, and approve commands — from anywhere.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|