@norman-else/dsh-claude 0.1.42 → 0.1.43
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/lib/client.d.ts +18 -0
- package/lib/client.js +368 -99
- package/lib/client.js.map +1 -1
- package/lib/index.mjs +165 -35
- package/lib/index.mjs.map +1 -1
- package/package.json +1 -1
package/lib/index.mjs
CHANGED
|
@@ -2876,20 +2876,7 @@ var ClaudeSupervisor = class {
|
|
|
2876
2876
|
...result.usage.outputTokens === void 0 ? {} : { outputTokens: result.usage.outputTokens }
|
|
2877
2877
|
};
|
|
2878
2878
|
}
|
|
2879
|
-
async #completeProgressSegment(active, result
|
|
2880
|
-
if (recordUsage && (result.usage.inputTokens !== void 0 || result.usage.outputTokens !== void 0 || result.usage.cumulativeCostUsd !== void 0)) {
|
|
2881
|
-
await this.#appendSafely(active, {
|
|
2882
|
-
kind: "usage",
|
|
2883
|
-
phase: "completed",
|
|
2884
|
-
title: "Claude usage",
|
|
2885
|
-
summary: usageSummary(result.usage),
|
|
2886
|
-
usage: this.#timedUsage(active, result.usage)
|
|
2887
|
-
});
|
|
2888
|
-
active.output.push({
|
|
2889
|
-
type: "usage",
|
|
2890
|
-
usage: this.#reportedUsage(active, result)
|
|
2891
|
-
});
|
|
2892
|
-
}
|
|
2879
|
+
async #completeProgressSegment(active, result) {
|
|
2893
2880
|
if (!active.sawTextDelta && active.text.length === 0 && result.text !== void 0) {
|
|
2894
2881
|
active.text = result.text;
|
|
2895
2882
|
active.transcriptText = result.text;
|
|
@@ -2909,6 +2896,25 @@ var ClaudeSupervisor = class {
|
|
|
2909
2896
|
this.#closeTranscriptTextSegment(active);
|
|
2910
2897
|
active.thinking = "";
|
|
2911
2898
|
}
|
|
2899
|
+
/** The turn's accounting, recorded once the turn is actually over.
|
|
2900
|
+
*
|
|
2901
|
+
* A turn that hands off to background tasks passes through the same result
|
|
2902
|
+
* handling on its way to `waiting-tasks`, and recording there drew a closing
|
|
2903
|
+
* total under a turn that was still running. */
|
|
2904
|
+
async #recordTurnUsage(active, result) {
|
|
2905
|
+
if (result.usage.inputTokens === void 0 && result.usage.outputTokens === void 0 && result.usage.cumulativeCostUsd === void 0) return;
|
|
2906
|
+
await this.#appendSafely(active, {
|
|
2907
|
+
kind: "usage",
|
|
2908
|
+
phase: "completed",
|
|
2909
|
+
title: "Claude usage",
|
|
2910
|
+
summary: usageSummary(result.usage),
|
|
2911
|
+
usage: this.#timedUsage(active, result.usage)
|
|
2912
|
+
});
|
|
2913
|
+
active.output.push({
|
|
2914
|
+
type: "usage",
|
|
2915
|
+
usage: this.#reportedUsage(active, result)
|
|
2916
|
+
});
|
|
2917
|
+
}
|
|
2912
2918
|
async #completeTurn(entry, active, result) {
|
|
2913
2919
|
if (entry.active !== active) return;
|
|
2914
2920
|
if (active.aborted) {
|
|
@@ -2930,19 +2936,6 @@ var ClaudeSupervisor = class {
|
|
|
2930
2936
|
this.#scheduleLimitReconciliation();
|
|
2931
2937
|
return;
|
|
2932
2938
|
}
|
|
2933
|
-
if (result.usage.inputTokens !== void 0 || result.usage.outputTokens !== void 0 || result.usage.cumulativeCostUsd !== void 0) {
|
|
2934
|
-
await this.#appendSafely(active, {
|
|
2935
|
-
kind: "usage",
|
|
2936
|
-
phase: "completed",
|
|
2937
|
-
title: "Claude usage",
|
|
2938
|
-
summary: usageSummary(result.usage),
|
|
2939
|
-
usage: this.#timedUsage(active, result.usage)
|
|
2940
|
-
});
|
|
2941
|
-
active.output.push({
|
|
2942
|
-
type: "usage",
|
|
2943
|
-
usage: this.#reportedUsage(active, result)
|
|
2944
|
-
});
|
|
2945
|
-
}
|
|
2946
2939
|
const unmatchedDenials = (result.permissionDenials ?? []).filter((denial) => !active.deniedToolUseIds.has(denial.toolUseId));
|
|
2947
2940
|
if (unmatchedDenials.length > 0) await this.#appendSafely(active, {
|
|
2948
2941
|
kind: "permission",
|
|
@@ -2951,6 +2944,7 @@ var ClaudeSupervisor = class {
|
|
|
2951
2944
|
summary: unmatchedDenials.map((denial) => denial.toolName).join(", ")
|
|
2952
2945
|
});
|
|
2953
2946
|
if (!result.success) {
|
|
2947
|
+
await this.#recordTurnUsage(active, result);
|
|
2954
2948
|
if (!active.sawTextDelta && active.text.length === 0 && result.text !== void 0) {
|
|
2955
2949
|
active.text = result.text;
|
|
2956
2950
|
active.transcriptText = result.text;
|
|
@@ -2989,9 +2983,10 @@ var ClaudeSupervisor = class {
|
|
|
2989
2983
|
phase: "updated",
|
|
2990
2984
|
title: "Claude Code is waiting for background tasks"
|
|
2991
2985
|
});
|
|
2992
|
-
await this.#completeProgressSegment(active, result
|
|
2986
|
+
await this.#completeProgressSegment(active, result);
|
|
2993
2987
|
return;
|
|
2994
2988
|
}
|
|
2989
|
+
await this.#recordTurnUsage(active, result);
|
|
2995
2990
|
await this.#appendSafely(active, {
|
|
2996
2991
|
kind: "status",
|
|
2997
2992
|
phase: "completed",
|
|
@@ -4328,6 +4323,7 @@ const GIT_TIMEOUT_MS$3 = 5e3;
|
|
|
4328
4323
|
const GH_TIMEOUT_MS$1 = 8e3;
|
|
4329
4324
|
const CACHE_TTL_MS = 5e3;
|
|
4330
4325
|
const MAX_TEXT_CHARS = 1024;
|
|
4326
|
+
const MAX_CONFLICT_PATHS = 100;
|
|
4331
4327
|
function bounded(value) {
|
|
4332
4328
|
return value.trim().slice(0, MAX_TEXT_CHARS);
|
|
4333
4329
|
}
|
|
@@ -4365,6 +4361,7 @@ function parseGitStatus(output) {
|
|
|
4365
4361
|
let upstream = false;
|
|
4366
4362
|
let ahead;
|
|
4367
4363
|
let behind;
|
|
4364
|
+
const conflicts = [];
|
|
4368
4365
|
for (const line of output.split(/\r?\n/u)) {
|
|
4369
4366
|
if (line.startsWith("# branch.head ")) {
|
|
4370
4367
|
const head = bounded(line.slice(14));
|
|
@@ -4384,6 +4381,10 @@ function parseGitStatus(output) {
|
|
|
4384
4381
|
}
|
|
4385
4382
|
continue;
|
|
4386
4383
|
}
|
|
4384
|
+
if (line.startsWith("u ")) {
|
|
4385
|
+
const path = line.split(" ").slice(10).join(" ");
|
|
4386
|
+
if (path.length > 0 && conflicts.length < MAX_CONFLICT_PATHS) conflicts.push(bounded(path));
|
|
4387
|
+
}
|
|
4387
4388
|
if (line.length > 0 && !line.startsWith("# ")) dirty = true;
|
|
4388
4389
|
}
|
|
4389
4390
|
return {
|
|
@@ -4392,9 +4393,36 @@ function parseGitStatus(output) {
|
|
|
4392
4393
|
dirty,
|
|
4393
4394
|
upstream,
|
|
4394
4395
|
...ahead === void 0 ? {} : { ahead },
|
|
4395
|
-
...behind === void 0 ? {} : { behind }
|
|
4396
|
+
...behind === void 0 ? {} : { behind },
|
|
4397
|
+
...conflicts.length === 0 ? {} : { conflicts }
|
|
4396
4398
|
};
|
|
4397
4399
|
}
|
|
4400
|
+
/** Ordered so the rebase directories win: a conflicted rebase also writes
|
|
4401
|
+
* MERGE_HEAD-like state, and the two are resumed by different commands. */
|
|
4402
|
+
const OPERATION_MARKERS = [
|
|
4403
|
+
["rebase-merge", "rebase"],
|
|
4404
|
+
["rebase-apply", "rebase"],
|
|
4405
|
+
["MERGE_HEAD", "merge"],
|
|
4406
|
+
["CHERRY_PICK_HEAD", "cherry-pick"],
|
|
4407
|
+
["REVERT_HEAD", "revert"]
|
|
4408
|
+
];
|
|
4409
|
+
/** Reads the in-progress operation out of the worktree's own git dir -- linked
|
|
4410
|
+
* worktrees keep their own, so this must not be handed the common dir. */
|
|
4411
|
+
async function detectRepositoryOperation(gitDir) {
|
|
4412
|
+
for (const [marker, operation] of OPERATION_MARKERS) {
|
|
4413
|
+
const path = join(gitDir, marker);
|
|
4414
|
+
try {
|
|
4415
|
+
await stat(path);
|
|
4416
|
+
} catch {
|
|
4417
|
+
continue;
|
|
4418
|
+
}
|
|
4419
|
+
const branch = bounded(operation === "rebase" ? await readFile(join(path, "head-name"), "utf8").catch(() => "") : "").replace(/^refs\/heads\//u, "");
|
|
4420
|
+
return {
|
|
4421
|
+
operation,
|
|
4422
|
+
...branch.length === 0 || branch.includes(" ") ? {} : { branch }
|
|
4423
|
+
};
|
|
4424
|
+
}
|
|
4425
|
+
}
|
|
4398
4426
|
function parseDiffNumstat(value) {
|
|
4399
4427
|
let additions = 0;
|
|
4400
4428
|
let deletions = 0;
|
|
@@ -4601,13 +4629,15 @@ var RepositoryStatusService = class {
|
|
|
4601
4629
|
cwd: safeCwd
|
|
4602
4630
|
};
|
|
4603
4631
|
const status = parseGitStatus(statusResult.stdout);
|
|
4632
|
+
const operation = await detectRepositoryOperation(gitDir);
|
|
4633
|
+
const branch = operation?.branch ?? status.branch;
|
|
4604
4634
|
const remoteResult = await run(this.#runtime, git, [
|
|
4605
4635
|
"remote",
|
|
4606
4636
|
"get-url",
|
|
4607
4637
|
"origin"
|
|
4608
4638
|
], cwd, GIT_TIMEOUT_MS$3);
|
|
4609
4639
|
const remote = remoteResult.exitCode === 0 ? parseGitHubRemote(remoteResult.stdout) : void 0;
|
|
4610
|
-
const pullRequest =
|
|
4640
|
+
const pullRequest = branch === void 0 || remote === void 0 ? void 0 : await this.#pullRequest(cwd, remote, branch);
|
|
4611
4641
|
const diffBase = pullRequest?.baseBranch === void 0 ? "HEAD" : await this.#mergeBase(cwd, git, pullRequest.baseBranch) ?? "HEAD";
|
|
4612
4642
|
const diff = status.dirty || diffBase !== "HEAD" ? await this.#diff(cwd, git, diffBase, signal) : {
|
|
4613
4643
|
additions: 0,
|
|
@@ -4621,6 +4651,8 @@ var RepositoryStatusService = class {
|
|
|
4621
4651
|
cwd: safeCwd,
|
|
4622
4652
|
root,
|
|
4623
4653
|
...status,
|
|
4654
|
+
...branch === void 0 ? {} : { branch },
|
|
4655
|
+
...operation === void 0 ? {} : { operation: operation.operation },
|
|
4624
4656
|
worktree: normalizedPath(gitDir) !== normalizedPath(commonDir),
|
|
4625
4657
|
...remote === void 0 ? {} : { remote },
|
|
4626
4658
|
...pullRequest === void 0 ? {} : { pullRequest },
|
|
@@ -5496,6 +5528,10 @@ function parseRepositoryActionStatus(output) {
|
|
|
5496
5528
|
}
|
|
5497
5529
|
return [...files.values()].sort((left, right) => left.path.localeCompare(right.path));
|
|
5498
5530
|
}
|
|
5531
|
+
function conflictPaths(result) {
|
|
5532
|
+
if (result.exitCode !== 0 || result.lossy) return [];
|
|
5533
|
+
return result.stdout.split(/\r?\n/u).filter((line) => line.length > 0).slice(0, 100);
|
|
5534
|
+
}
|
|
5499
5535
|
function fallbackCommitMessage(files) {
|
|
5500
5536
|
if (files.length === 1) return `Update ${files[0]?.path ?? "repository files"}`;
|
|
5501
5537
|
return `Update ${files.length} repository files`;
|
|
@@ -5560,6 +5596,7 @@ var RepositoryActionService = class {
|
|
|
5560
5596
|
return operation;
|
|
5561
5597
|
}
|
|
5562
5598
|
async #execute(cwd, request) {
|
|
5599
|
+
if (request.action === "resolve-continue" || request.action === "resolve-abort") return this.#resolve(cwd, request.action, request.push === true);
|
|
5563
5600
|
const before = await this.#preview(cwd);
|
|
5564
5601
|
if (before.fingerprint !== request.fingerprint) throw new RepositoryActionError("repository-changed", "Repository changes have changed. Refresh the commit panel.");
|
|
5565
5602
|
if (request.action === "push") {
|
|
@@ -5622,13 +5659,12 @@ var RepositoryActionService = class {
|
|
|
5622
5659
|
`origin/${base}`
|
|
5623
5660
|
], before.root, REMOTE_TIMEOUT_MS);
|
|
5624
5661
|
if (merged.exitCode !== 0 || merged.lossy) {
|
|
5625
|
-
const
|
|
5662
|
+
const conflicts = conflictPaths(await this.#run(git, [
|
|
5626
5663
|
"diff",
|
|
5627
5664
|
"--name-only",
|
|
5628
5665
|
"--diff-filter=U",
|
|
5629
5666
|
"--"
|
|
5630
|
-
], before.root, GIT_TIMEOUT_MS$1);
|
|
5631
|
-
const conflicts = conflicted.exitCode === 0 && !conflicted.lossy ? conflicted.stdout.split(/\r?\n/u).filter((line) => line.length > 0).slice(0, 100) : [];
|
|
5667
|
+
], before.root, GIT_TIMEOUT_MS$1));
|
|
5632
5668
|
if (conflicts.length === 0) {
|
|
5633
5669
|
await this.#run(git, [method, "--abort"], before.root, GIT_TIMEOUT_MS$1).catch(() => void 0);
|
|
5634
5670
|
throw new RepositoryActionError("merge-failed", `Git could not ${method} the base branch.`);
|
|
@@ -5727,6 +5763,87 @@ var RepositoryActionService = class {
|
|
|
5727
5763
|
pullRequestUrl: url
|
|
5728
5764
|
};
|
|
5729
5765
|
}
|
|
5766
|
+
/** Finishes or discards the merge, rebase, cherry-pick or revert git is
|
|
5767
|
+
* waiting on. The operation is read from the git dir rather than taken from
|
|
5768
|
+
* the caller: `--continue` and `--abort` are only safe against the one that
|
|
5769
|
+
* is actually in progress. */
|
|
5770
|
+
async #resolve(cwd, action, push) {
|
|
5771
|
+
const git = await this.#git();
|
|
5772
|
+
const [rootValue, gitDirValue] = (await this.#mustRun(git, [
|
|
5773
|
+
"rev-parse",
|
|
5774
|
+
"--path-format=absolute",
|
|
5775
|
+
"--show-toplevel",
|
|
5776
|
+
"--absolute-git-dir"
|
|
5777
|
+
], cwd, GIT_TIMEOUT_MS$1, "not-repository", "The session directory is not a Git repository.")).stdout.split(/\r?\n/u);
|
|
5778
|
+
const root = (rootValue ?? "").trim();
|
|
5779
|
+
const gitDir = (gitDirValue ?? "").trim();
|
|
5780
|
+
if (root.length === 0 || gitDir.length === 0) throw new RepositoryActionError("repository-unavailable", "Repository state is unavailable.");
|
|
5781
|
+
const state = await detectRepositoryOperation(gitDir);
|
|
5782
|
+
if (state === void 0) throw new RepositoryActionError("no-operation", "No merge, rebase, cherry-pick or revert is in progress.");
|
|
5783
|
+
const operation = state.operation;
|
|
5784
|
+
if (action === "resolve-abort") {
|
|
5785
|
+
await this.#mustRun(git, [operation, "--abort"], root, GIT_TIMEOUT_MS$1, "abort-failed", `Git could not abort the ${operation}.`);
|
|
5786
|
+
this.#invalidate(root);
|
|
5787
|
+
return {
|
|
5788
|
+
commit: await this.#head(git, root),
|
|
5789
|
+
pushed: false
|
|
5790
|
+
};
|
|
5791
|
+
}
|
|
5792
|
+
if (conflictPaths(await this.#run(git, [
|
|
5793
|
+
"diff",
|
|
5794
|
+
"--name-only",
|
|
5795
|
+
"--diff-filter=U",
|
|
5796
|
+
"--"
|
|
5797
|
+
], root, GIT_TIMEOUT_MS$1)).length > 0) throw new RepositoryActionError("unresolved-conflicts", "Resolve and stage every conflicted file before continuing.");
|
|
5798
|
+
const continued = await this.#run(git, [
|
|
5799
|
+
"-c",
|
|
5800
|
+
"core.editor=true",
|
|
5801
|
+
operation,
|
|
5802
|
+
"--continue"
|
|
5803
|
+
], root, REMOTE_TIMEOUT_MS);
|
|
5804
|
+
this.#invalidate(root);
|
|
5805
|
+
if (continued.exitCode !== 0 || continued.lossy) {
|
|
5806
|
+
const next = conflictPaths(await this.#run(git, [
|
|
5807
|
+
"diff",
|
|
5808
|
+
"--name-only",
|
|
5809
|
+
"--diff-filter=U",
|
|
5810
|
+
"--"
|
|
5811
|
+
], root, GIT_TIMEOUT_MS$1));
|
|
5812
|
+
if (next.length > 0) return {
|
|
5813
|
+
commit: await this.#head(git, root),
|
|
5814
|
+
pushed: false,
|
|
5815
|
+
conflicts: next
|
|
5816
|
+
};
|
|
5817
|
+
const reason = continued.stderr.split(/\r?\n/u).map((line) => line.trim()).filter((line) => line.length > 0).at(-1);
|
|
5818
|
+
throw new RepositoryActionError("continue-failed", reason === void 0 || reason.length === 0 ? `Git could not continue the ${operation}.` : reason);
|
|
5819
|
+
}
|
|
5820
|
+
const head = await this.#head(git, root);
|
|
5821
|
+
if (!push) return {
|
|
5822
|
+
commit: head,
|
|
5823
|
+
pushed: false
|
|
5824
|
+
};
|
|
5825
|
+
const branch = await this.#run(git, [
|
|
5826
|
+
"symbolic-ref",
|
|
5827
|
+
"--quiet",
|
|
5828
|
+
"--short",
|
|
5829
|
+
"HEAD"
|
|
5830
|
+
], root, GIT_TIMEOUT_MS$1);
|
|
5831
|
+
if (branch.exitCode !== 0 || branch.lossy) throw new RepositoryActionError("detached-head", "The finished operation left a detached HEAD, so nothing was pushed.", head);
|
|
5832
|
+
try {
|
|
5833
|
+
await this.#push(git, root, branch.stdout.trim(), operation === "rebase");
|
|
5834
|
+
} catch (error) {
|
|
5835
|
+
throw new RepositoryActionError("push-failed", error instanceof Error ? error.message : "Git push failed.", head);
|
|
5836
|
+
}
|
|
5837
|
+
this.#invalidate(root);
|
|
5838
|
+
return {
|
|
5839
|
+
commit: head,
|
|
5840
|
+
pushed: true
|
|
5841
|
+
};
|
|
5842
|
+
}
|
|
5843
|
+
async #head(git, root) {
|
|
5844
|
+
const head = await this.#run(git, ["rev-parse", "HEAD"], root, GIT_TIMEOUT_MS$1);
|
|
5845
|
+
return head.exitCode === 0 && !head.lossy ? head.stdout.trim() : "";
|
|
5846
|
+
}
|
|
5730
5847
|
async #preview(cwd) {
|
|
5731
5848
|
const git = await this.#git();
|
|
5732
5849
|
const root = (await this.#mustRun(git, [
|
|
@@ -6012,7 +6129,17 @@ const ACTIONS = /* @__PURE__ */ new Set([
|
|
|
6012
6129
|
"push",
|
|
6013
6130
|
"create-pr",
|
|
6014
6131
|
"merge-pr",
|
|
6015
|
-
"update-branch"
|
|
6132
|
+
"update-branch",
|
|
6133
|
+
"resolve-continue",
|
|
6134
|
+
"resolve-abort"
|
|
6135
|
+
]);
|
|
6136
|
+
/** Actions that commit nothing of their own, so the panel sends no message. */
|
|
6137
|
+
const MESSAGELESS = /* @__PURE__ */ new Set([
|
|
6138
|
+
"push",
|
|
6139
|
+
"merge-pr",
|
|
6140
|
+
"update-branch",
|
|
6141
|
+
"resolve-continue",
|
|
6142
|
+
"resolve-abort"
|
|
6016
6143
|
]);
|
|
6017
6144
|
function record$9(value) {
|
|
6018
6145
|
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
@@ -6051,7 +6178,7 @@ function actionRequest(input) {
|
|
|
6051
6178
|
return {
|
|
6052
6179
|
action,
|
|
6053
6180
|
fingerprint: string$1(input, "fingerprint"),
|
|
6054
|
-
message: action
|
|
6181
|
+
message: MESSAGELESS.has(action) ? optionalString(input, "message") ?? "" : string$1(input, "message"),
|
|
6055
6182
|
includeUnstaged: input.includeUnstaged,
|
|
6056
6183
|
...optionalString(input, "prTitle") === void 0 ? {} : { prTitle: optionalString(input, "prTitle") },
|
|
6057
6184
|
...optionalString(input, "prBody") === void 0 ? {} : { prBody: optionalString(input, "prBody") },
|
|
@@ -6059,6 +6186,9 @@ function actionRequest(input) {
|
|
|
6059
6186
|
...input.draft === void 0 ? {} : typeof input.draft === "boolean" ? { draft: input.draft } : (() => {
|
|
6060
6187
|
throw new RepositoryActionError("invalid-request", "The draft field must be a boolean.");
|
|
6061
6188
|
})(),
|
|
6189
|
+
...input.push === void 0 ? {} : typeof input.push === "boolean" ? { push: input.push } : (() => {
|
|
6190
|
+
throw new RepositoryActionError("invalid-request", "The push field must be a boolean.");
|
|
6191
|
+
})(),
|
|
6062
6192
|
...input.mergeMethod === void 0 ? {} : input.mergeMethod === "merge" || input.mergeMethod === "squash" || input.mergeMethod === "rebase" ? { mergeMethod: input.mergeMethod } : (() => {
|
|
6063
6193
|
throw new RepositoryActionError("invalid-request", "The mergeMethod field is invalid.");
|
|
6064
6194
|
})()
|