codeam-cli 2.23.31 → 2.23.33
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 +13 -0
- package/dist/index.js +176 -6
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,19 @@ 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.23.32] — 2026-05-31
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- **shared:** Add CommitEntry + BlameLine wire types for git enrichment
|
|
12
|
+
- **cli:** Capture git log + blame per changed file at turn end
|
|
13
|
+
|
|
14
|
+
## [2.23.31] — 2026-05-30
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
|
|
18
|
+
- **cli:** TurnFileAggregator captures pre-pair baseline silently
|
|
19
|
+
|
|
7
20
|
## [2.23.30] — 2026-05-30
|
|
8
21
|
|
|
9
22
|
### Chore
|
package/dist/index.js
CHANGED
|
@@ -441,7 +441,7 @@ var import_qrcode_terminal = __toESM(require("qrcode-terminal"));
|
|
|
441
441
|
// package.json
|
|
442
442
|
var package_default = {
|
|
443
443
|
name: "codeam-cli",
|
|
444
|
-
version: "2.23.
|
|
444
|
+
version: "2.23.33",
|
|
445
445
|
description: "Workflow-continuity bridge for AI coding agents. Wrap Claude Code or Codex in a PTY and supervise, approve, and redirect the session from any device \u2014 async. The terminal companion for CodeAgent Mobile.",
|
|
446
446
|
type: "commonjs",
|
|
447
447
|
main: "dist/index.js",
|
|
@@ -5774,7 +5774,7 @@ function readAnonId() {
|
|
|
5774
5774
|
}
|
|
5775
5775
|
function superProperties() {
|
|
5776
5776
|
return {
|
|
5777
|
-
cliVersion: true ? "2.23.
|
|
5777
|
+
cliVersion: true ? "2.23.33" : "0.0.0-dev",
|
|
5778
5778
|
nodeVersion: process.version,
|
|
5779
5779
|
platform: process.platform,
|
|
5780
5780
|
arch: process.arch,
|
|
@@ -12825,8 +12825,12 @@ function _post2(url, headers, payload) {
|
|
|
12825
12825
|
// src/services/file-watcher.service.ts
|
|
12826
12826
|
var API_BASE5 = resolveApiBaseUrl();
|
|
12827
12827
|
var DEBOUNCE_MS = 250;
|
|
12828
|
+
var COALESCE_WINDOW_MS = 250;
|
|
12829
|
+
var COALESCE_MAX_HOLD_MS = 2e3;
|
|
12828
12830
|
var MAX_RETRIES = 2;
|
|
12829
12831
|
var RETRY_BACKOFF_MS = 300;
|
|
12832
|
+
var HISTORY_MAX_COMMITS = 50;
|
|
12833
|
+
var BLAME_MAX_LINES = 500;
|
|
12830
12834
|
var WINDOWS_LEGACY_JUNCTIONS = [
|
|
12831
12835
|
/[\\/]Application Data([\\/]|$)/i,
|
|
12832
12836
|
/[\\/]Cookies([\\/]|$)/i,
|
|
@@ -12906,6 +12910,17 @@ var FileWatcherService = class {
|
|
|
12906
12910
|
*/
|
|
12907
12911
|
gitRootByDir = /* @__PURE__ */ new Map();
|
|
12908
12912
|
stopped = false;
|
|
12913
|
+
/**
|
|
12914
|
+
* Cross-file coalescing buffer. Keyed by absPath so multiple
|
|
12915
|
+
* scheduled emits for the same file collapse to the latest
|
|
12916
|
+
* `changeType`. The buffer drains via `coalesceTimer` after
|
|
12917
|
+
* `COALESCE_WINDOW_MS` of quiescence, or forcibly after
|
|
12918
|
+
* `COALESCE_MAX_HOLD_MS` so the UI never starves during a long
|
|
12919
|
+
* continuous edit.
|
|
12920
|
+
*/
|
|
12921
|
+
coalesceBuffer = /* @__PURE__ */ new Map();
|
|
12922
|
+
coalesceTimer = null;
|
|
12923
|
+
coalesceMaxHoldTimer = null;
|
|
12909
12924
|
/**
|
|
12910
12925
|
* Start watching `opts.workingDir`. Idempotent (second call is a
|
|
12911
12926
|
* no-op). Resolves once chokidar's initial scan completes; that
|
|
@@ -13007,6 +13022,15 @@ var FileWatcherService = class {
|
|
|
13007
13022
|
clearTimeout(entry.timer);
|
|
13008
13023
|
}
|
|
13009
13024
|
this.pending.clear();
|
|
13025
|
+
if (this.coalesceTimer) {
|
|
13026
|
+
clearTimeout(this.coalesceTimer);
|
|
13027
|
+
this.coalesceTimer = null;
|
|
13028
|
+
}
|
|
13029
|
+
if (this.coalesceMaxHoldTimer) {
|
|
13030
|
+
clearTimeout(this.coalesceMaxHoldTimer);
|
|
13031
|
+
this.coalesceMaxHoldTimer = null;
|
|
13032
|
+
}
|
|
13033
|
+
this.coalesceBuffer.clear();
|
|
13010
13034
|
if (this.watcher) {
|
|
13011
13035
|
try {
|
|
13012
13036
|
await this.watcher.close();
|
|
@@ -13034,7 +13058,7 @@ var FileWatcherService = class {
|
|
|
13034
13058
|
if (existing) clearTimeout(existing.timer);
|
|
13035
13059
|
const timer = setTimeout(() => {
|
|
13036
13060
|
this.pending.delete(absPath);
|
|
13037
|
-
|
|
13061
|
+
this.enqueueForCoalesce(absPath, changeType);
|
|
13038
13062
|
}, DEBOUNCE_MS);
|
|
13039
13063
|
this.pending.set(absPath, {
|
|
13040
13064
|
lastEventAt: Date.now(),
|
|
@@ -13042,6 +13066,49 @@ var FileWatcherService = class {
|
|
|
13042
13066
|
changeType
|
|
13043
13067
|
});
|
|
13044
13068
|
}
|
|
13069
|
+
/**
|
|
13070
|
+
* Drop the file into the cross-file coalescing buffer. The buffer
|
|
13071
|
+
* flushes after `COALESCE_WINDOW_MS` of quiescence (resets on each
|
|
13072
|
+
* new enqueue) or after `COALESCE_MAX_HOLD_MS` regardless. Same
|
|
13073
|
+
* file enqueued twice in a row keeps only the latest `changeType`
|
|
13074
|
+
* (typically the most recent FS event wins).
|
|
13075
|
+
*/
|
|
13076
|
+
enqueueForCoalesce(absPath, changeType) {
|
|
13077
|
+
if (this.stopped) return;
|
|
13078
|
+
this.coalesceBuffer.set(absPath, { absPath, changeType });
|
|
13079
|
+
if (this.coalesceTimer) clearTimeout(this.coalesceTimer);
|
|
13080
|
+
this.coalesceTimer = setTimeout(() => {
|
|
13081
|
+
void this.flushCoalesceBuffer();
|
|
13082
|
+
}, COALESCE_WINDOW_MS);
|
|
13083
|
+
if (!this.coalesceMaxHoldTimer) {
|
|
13084
|
+
this.coalesceMaxHoldTimer = setTimeout(() => {
|
|
13085
|
+
void this.flushCoalesceBuffer();
|
|
13086
|
+
}, COALESCE_MAX_HOLD_MS);
|
|
13087
|
+
}
|
|
13088
|
+
}
|
|
13089
|
+
/**
|
|
13090
|
+
* Drain the coalesce buffer. Snapshots the entries up-front so any
|
|
13091
|
+
* emissions that arrive mid-flush (chokidar fires again, agent
|
|
13092
|
+
* keeps writing) land in a fresh buffer rather than competing with
|
|
13093
|
+
* the in-flight one.
|
|
13094
|
+
*/
|
|
13095
|
+
async flushCoalesceBuffer() {
|
|
13096
|
+
if (this.coalesceTimer) {
|
|
13097
|
+
clearTimeout(this.coalesceTimer);
|
|
13098
|
+
this.coalesceTimer = null;
|
|
13099
|
+
}
|
|
13100
|
+
if (this.coalesceMaxHoldTimer) {
|
|
13101
|
+
clearTimeout(this.coalesceMaxHoldTimer);
|
|
13102
|
+
this.coalesceMaxHoldTimer = null;
|
|
13103
|
+
}
|
|
13104
|
+
if (this.coalesceBuffer.size === 0) return;
|
|
13105
|
+
const entries = Array.from(this.coalesceBuffer.values());
|
|
13106
|
+
this.coalesceBuffer.clear();
|
|
13107
|
+
for (const entry of entries) {
|
|
13108
|
+
if (this.stopped) return;
|
|
13109
|
+
await this.emitForFile(entry.absPath, entry.changeType);
|
|
13110
|
+
}
|
|
13111
|
+
}
|
|
13045
13112
|
/**
|
|
13046
13113
|
* Visible for tests — lets vitest pump a synthetic file event
|
|
13047
13114
|
* through the debounce + diff + emit pipeline without spinning up
|
|
@@ -13137,6 +13204,31 @@ var FileWatcherService = class {
|
|
|
13137
13204
|
linesRemoved: hunk.linesRemoved
|
|
13138
13205
|
});
|
|
13139
13206
|
}
|
|
13207
|
+
await this.emitGitEnrichment(gitRoot, relPathInRepo, repoPath, repoName, fileStatus);
|
|
13208
|
+
}
|
|
13209
|
+
async emitGitEnrichment(gitRoot, relPathInRepo, repoPath, repoName, fileStatus) {
|
|
13210
|
+
if (this.stopped) return;
|
|
13211
|
+
const commits = await captureHistory(gitRoot, relPathInRepo, HISTORY_MAX_COMMITS);
|
|
13212
|
+
if (this.stopped) return;
|
|
13213
|
+
await this.postReviewHistory({
|
|
13214
|
+
sessionId: this.opts.sessionId,
|
|
13215
|
+
pluginId: this.opts.pluginId,
|
|
13216
|
+
filePath: relPathInRepo,
|
|
13217
|
+
repoPath,
|
|
13218
|
+
repoName,
|
|
13219
|
+
commits
|
|
13220
|
+
});
|
|
13221
|
+
if (this.stopped || fileStatus === "deleted") return;
|
|
13222
|
+
const blameLines = await captureBlame(gitRoot, relPathInRepo, BLAME_MAX_LINES);
|
|
13223
|
+
if (this.stopped) return;
|
|
13224
|
+
await this.postReviewBlame({
|
|
13225
|
+
sessionId: this.opts.sessionId,
|
|
13226
|
+
pluginId: this.opts.pluginId,
|
|
13227
|
+
filePath: relPathInRepo,
|
|
13228
|
+
repoPath,
|
|
13229
|
+
repoName,
|
|
13230
|
+
lines: blameLines
|
|
13231
|
+
});
|
|
13140
13232
|
}
|
|
13141
13233
|
/**
|
|
13142
13234
|
* Compute the unified diff for a single path relative to the
|
|
@@ -13173,6 +13265,12 @@ var FileWatcherService = class {
|
|
|
13173
13265
|
async postReviewHunk(body) {
|
|
13174
13266
|
await this.postWithRetries(`${this.apiBase}/api/review/hunks`, body);
|
|
13175
13267
|
}
|
|
13268
|
+
async postReviewHistory(body) {
|
|
13269
|
+
await this.postWithRetries(`${this.apiBase}/api/review/history`, body);
|
|
13270
|
+
}
|
|
13271
|
+
async postReviewBlame(body) {
|
|
13272
|
+
await this.postWithRetries(`${this.apiBase}/api/review/blame`, body);
|
|
13273
|
+
}
|
|
13176
13274
|
async postWithRetries(url, body) {
|
|
13177
13275
|
const payload = JSON.stringify(body);
|
|
13178
13276
|
const headers = {
|
|
@@ -13223,6 +13321,78 @@ var FileWatcherService = class {
|
|
|
13223
13321
|
function sleep(ms) {
|
|
13224
13322
|
return new Promise((r) => setTimeout(r, ms));
|
|
13225
13323
|
}
|
|
13324
|
+
async function captureHistory(repoRoot, relPath, maxCommits) {
|
|
13325
|
+
const out2 = await runGit(repoRoot, [
|
|
13326
|
+
"log",
|
|
13327
|
+
`--max-count=${maxCommits}`,
|
|
13328
|
+
"--no-color",
|
|
13329
|
+
"--format=%H%x09%an%x09%ae%x09%aI%x09%s",
|
|
13330
|
+
"--",
|
|
13331
|
+
relPath
|
|
13332
|
+
]);
|
|
13333
|
+
if (!out2) return [];
|
|
13334
|
+
const commits = [];
|
|
13335
|
+
for (const line of out2.split("\n")) {
|
|
13336
|
+
if (!line) continue;
|
|
13337
|
+
const cols = line.split(" ");
|
|
13338
|
+
if (cols.length < 5) continue;
|
|
13339
|
+
const [sha, authorName, authorEmail, committedAt, ...subjectParts] = cols;
|
|
13340
|
+
commits.push({
|
|
13341
|
+
sha,
|
|
13342
|
+
authorName: authorName ?? "",
|
|
13343
|
+
authorEmail: authorEmail ?? "",
|
|
13344
|
+
committedAt: committedAt ?? "",
|
|
13345
|
+
subject: subjectParts.join(" ")
|
|
13346
|
+
});
|
|
13347
|
+
}
|
|
13348
|
+
return commits;
|
|
13349
|
+
}
|
|
13350
|
+
async function captureBlame(repoRoot, relPath, maxLines) {
|
|
13351
|
+
const out2 = await runGit(repoRoot, [
|
|
13352
|
+
"blame",
|
|
13353
|
+
"--line-porcelain",
|
|
13354
|
+
"--no-progress",
|
|
13355
|
+
"-L",
|
|
13356
|
+
`1,${maxLines}`,
|
|
13357
|
+
"--",
|
|
13358
|
+
relPath
|
|
13359
|
+
]);
|
|
13360
|
+
if (!out2) return [];
|
|
13361
|
+
const lines = [];
|
|
13362
|
+
const blocks = out2.split(/(?=^[0-9a-f]{40} )/m);
|
|
13363
|
+
for (const block of blocks) {
|
|
13364
|
+
if (!block) continue;
|
|
13365
|
+
const blockLines = block.split("\n");
|
|
13366
|
+
const headerMatch = blockLines[0].match(/^([0-9a-f]{40}) \d+ (\d+)/);
|
|
13367
|
+
if (!headerMatch) continue;
|
|
13368
|
+
const sha = headerMatch[1];
|
|
13369
|
+
const lineNumber = parseInt(headerMatch[2], 10);
|
|
13370
|
+
let authorName = "";
|
|
13371
|
+
let authorTime = null;
|
|
13372
|
+
let text = "";
|
|
13373
|
+
for (let i = 1; i < blockLines.length; i += 1) {
|
|
13374
|
+
const bl = blockLines[i];
|
|
13375
|
+
if (bl.startsWith("author ")) {
|
|
13376
|
+
authorName = bl.slice(7);
|
|
13377
|
+
} else if (bl.startsWith("author-time ")) {
|
|
13378
|
+
const parsed = parseInt(bl.slice(12), 10);
|
|
13379
|
+
if (!Number.isNaN(parsed)) authorTime = parsed;
|
|
13380
|
+
} else if (bl.startsWith(" ")) {
|
|
13381
|
+
text = bl.slice(1);
|
|
13382
|
+
break;
|
|
13383
|
+
}
|
|
13384
|
+
}
|
|
13385
|
+
const committedAt = authorTime !== null ? new Date(authorTime * 1e3).toISOString() : "";
|
|
13386
|
+
lines.push({
|
|
13387
|
+
lineNumber,
|
|
13388
|
+
sha,
|
|
13389
|
+
authorName,
|
|
13390
|
+
committedAt,
|
|
13391
|
+
text
|
|
13392
|
+
});
|
|
13393
|
+
}
|
|
13394
|
+
return lines;
|
|
13395
|
+
}
|
|
13226
13396
|
async function runGit(cwd, args2, opts = {}) {
|
|
13227
13397
|
return _runGit(cwd, args2, opts);
|
|
13228
13398
|
}
|
|
@@ -19001,7 +19171,7 @@ function checkChokidar() {
|
|
|
19001
19171
|
}
|
|
19002
19172
|
async function doctor(args2 = []) {
|
|
19003
19173
|
const json = args2.includes("--json");
|
|
19004
|
-
const cliVersion = true ? "2.23.
|
|
19174
|
+
const cliVersion = true ? "2.23.33" : "0.0.0-dev";
|
|
19005
19175
|
const apiBase = resolveApiBaseUrl();
|
|
19006
19176
|
const diagnosticId = (0, import_node_crypto6.randomUUID)();
|
|
19007
19177
|
log.info("doctor", `run id=${diagnosticId} cli=${cliVersion}`);
|
|
@@ -19200,7 +19370,7 @@ async function completion(args2) {
|
|
|
19200
19370
|
// src/commands/version.ts
|
|
19201
19371
|
var import_picocolors13 = __toESM(require("picocolors"));
|
|
19202
19372
|
function version2() {
|
|
19203
|
-
const v = true ? "2.23.
|
|
19373
|
+
const v = true ? "2.23.33" : "unknown";
|
|
19204
19374
|
console.log(`${import_picocolors13.default.bold("codeam-cli")} ${import_picocolors13.default.cyan(v)}`);
|
|
19205
19375
|
}
|
|
19206
19376
|
|
|
@@ -19428,7 +19598,7 @@ function checkForUpdates() {
|
|
|
19428
19598
|
if (process.env.CODEAM_DISABLE_UPDATE_CHECK === "1") return;
|
|
19429
19599
|
if (process.env.CI) return;
|
|
19430
19600
|
if (!process.stdout.isTTY) return;
|
|
19431
|
-
const current = true ? "2.23.
|
|
19601
|
+
const current = true ? "2.23.33" : null;
|
|
19432
19602
|
if (!current) return;
|
|
19433
19603
|
const cache = readCache();
|
|
19434
19604
|
const fresh = cache && Date.now() - cache.fetchedAt < TTL_MS;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codeam-cli",
|
|
3
|
-
"version": "2.23.
|
|
3
|
+
"version": "2.23.33",
|
|
4
4
|
"description": "Workflow-continuity bridge for AI coding agents. Wrap Claude Code or Codex in a PTY and supervise, approve, and redirect the session from any device — async. The terminal companion for CodeAgent Mobile.",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"main": "dist/index.js",
|