@joshski/dust 0.1.82 → 0.1.84
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/bucket/repository.d.ts +3 -0
- package/dist/config/settings.d.ts +1 -1
- package/dist/dust.js +106 -42
- package/package.json +1 -1
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
import { spawn as nodeSpawn } from 'node:child_process';
|
|
8
8
|
import { run as claudeRun } from '../claude/run';
|
|
9
9
|
import type { CommandDependencies, FileSystem } from '../cli/types';
|
|
10
|
+
import type { DockerDependencies } from '../docker/docker-agent';
|
|
10
11
|
import { type BucketEmitFn, type SendEventFn } from './events';
|
|
11
12
|
import { type LogBuffer } from './log-buffer';
|
|
12
13
|
export { cloneRepository, getRepoPath, removeRepository, } from './repository-git';
|
|
@@ -47,6 +48,8 @@ export interface RepositoryDependencies {
|
|
|
47
48
|
fileSystem: FileSystem;
|
|
48
49
|
sleep: (ms: number) => Promise<void>;
|
|
49
50
|
getReposDir: () => string;
|
|
51
|
+
/** Optional overrides for Docker dependency functions (for testing) */
|
|
52
|
+
dockerDeps?: Partial<DockerDependencies>;
|
|
50
53
|
}
|
|
51
54
|
/**
|
|
52
55
|
* Start (or restart) the per-repository loop and keep loopPromise state accurate.
|
|
@@ -12,7 +12,7 @@ export declare function validateSettingsJson(content: string): SettingsViolation
|
|
|
12
12
|
/**
|
|
13
13
|
* Detects the appropriate dust command based on lockfiles and environment.
|
|
14
14
|
* Priority:
|
|
15
|
-
* 1. bun.lockb exists → bunx dust
|
|
15
|
+
* 1. bun.lock or bun.lockb exists → bunx dust
|
|
16
16
|
* 2. pnpm-lock.yaml exists → pnpx dust
|
|
17
17
|
* 3. package-lock.json exists → npx dust
|
|
18
18
|
* 4. No lockfile + BUN_INSTALL env var set → bunx dust
|
package/dist/dust.js
CHANGED
|
@@ -185,7 +185,7 @@ var DEFAULT_SETTINGS = {
|
|
|
185
185
|
dustCommand: "npx dust"
|
|
186
186
|
};
|
|
187
187
|
function detectDustCommand(cwd, fileSystem) {
|
|
188
|
-
if (fileSystem.exists(join(cwd, "bun.lockb"))) {
|
|
188
|
+
if (fileSystem.exists(join(cwd, "bun.lock")) || fileSystem.exists(join(cwd, "bun.lockb"))) {
|
|
189
189
|
return "bunx dust";
|
|
190
190
|
}
|
|
191
191
|
if (fileSystem.exists(join(cwd, "pnpm-lock.yaml"))) {
|
|
@@ -200,6 +200,7 @@ function detectDustCommand(cwd, fileSystem) {
|
|
|
200
200
|
return "npx dust";
|
|
201
201
|
}
|
|
202
202
|
var LOCKFILE_COMMANDS = [
|
|
203
|
+
{ file: "bun.lock", command: "bun install", ecosystem: "js" },
|
|
203
204
|
{ file: "bun.lockb", command: "bun install", ecosystem: "js" },
|
|
204
205
|
{ file: "pnpm-lock.yaml", command: "pnpm install", ecosystem: "js" },
|
|
205
206
|
{ file: "package-lock.json", command: "npm install", ecosystem: "js" },
|
|
@@ -318,7 +319,7 @@ async function loadSettings(cwd, fileSystem) {
|
|
|
318
319
|
}
|
|
319
320
|
|
|
320
321
|
// lib/version.ts
|
|
321
|
-
var DUST_VERSION = "0.1.
|
|
322
|
+
var DUST_VERSION = "0.1.84";
|
|
322
323
|
|
|
323
324
|
// lib/session.ts
|
|
324
325
|
var DUST_UNATTENDED = "DUST_UNATTENDED";
|
|
@@ -2043,16 +2044,25 @@ function buildDockerRunArguments(docker, claudeArguments, env) {
|
|
|
2043
2044
|
"-w",
|
|
2044
2045
|
"/workspace",
|
|
2045
2046
|
"-v",
|
|
2046
|
-
`${docker.homeDir}/.claude:/
|
|
2047
|
+
`${docker.homeDir}/.claude:/home/user/.claude`,
|
|
2047
2048
|
"-v",
|
|
2048
|
-
`${docker.homeDir}/.
|
|
2049
|
+
`${docker.homeDir}/.claude.json:/home/user/.claude.json`,
|
|
2050
|
+
"-v",
|
|
2051
|
+
`${docker.homeDir}/.ssh:/home/user/.ssh:ro`,
|
|
2052
|
+
"-e",
|
|
2053
|
+
"HOME=/home/user"
|
|
2049
2054
|
];
|
|
2050
2055
|
if (docker.hasGitconfig) {
|
|
2051
|
-
dockerArguments.push("-v", `${docker.homeDir}/.gitconfig:/
|
|
2056
|
+
dockerArguments.push("-v", `${docker.homeDir}/.gitconfig:/home/user/.gitconfig:ro`);
|
|
2052
2057
|
}
|
|
2053
2058
|
for (const [key, value] of Object.entries(env)) {
|
|
2054
2059
|
dockerArguments.push("-e", `${key}=${value}`);
|
|
2055
2060
|
}
|
|
2061
|
+
for (const key of ["CLAUDE_CODE_OAUTH_TOKEN", "OPENAI_API_KEY"]) {
|
|
2062
|
+
if (process.env[key] && !(key in env)) {
|
|
2063
|
+
dockerArguments.push("-e", `${key}=${process.env[key]}`);
|
|
2064
|
+
}
|
|
2065
|
+
}
|
|
2056
2066
|
dockerArguments.push(docker.imageTag);
|
|
2057
2067
|
dockerArguments.push("claude");
|
|
2058
2068
|
dockerArguments.push(...claudeArguments);
|
|
@@ -2535,6 +2545,11 @@ async function removeRepository(path, spawn, context) {
|
|
|
2535
2545
|
});
|
|
2536
2546
|
}
|
|
2537
2547
|
|
|
2548
|
+
// lib/bucket/repository-loop.ts
|
|
2549
|
+
import { existsSync as fsExistsSync } from "node:fs";
|
|
2550
|
+
import os2 from "node:os";
|
|
2551
|
+
import path3 from "node:path";
|
|
2552
|
+
|
|
2538
2553
|
// lib/agent-events.ts
|
|
2539
2554
|
function rawEventToAgentEvent(rawEvent) {
|
|
2540
2555
|
if (typeof rawEvent.type === "string" && rawEvent.type === "stream_event") {
|
|
@@ -3075,7 +3090,7 @@ Make sure the repository is in a clean state and synced with remote before finis
|
|
|
3075
3090
|
log2(`found ${tasks.length} task(s), picking: ${task.title ?? task.path}`);
|
|
3076
3091
|
onLoopEvent({ type: "loop.tasks_found" });
|
|
3077
3092
|
const taskContent = await dependencies.fileSystem.readFile(`${dependencies.context.cwd}/${task.path}`);
|
|
3078
|
-
const { dustCommand, installCommand
|
|
3093
|
+
const { dustCommand, installCommand } = dependencies.settings;
|
|
3079
3094
|
const instructions = buildImplementationInstructions(dustCommand, hooksInstalled, task.title ?? undefined, task.path, installCommand);
|
|
3080
3095
|
const prompt = `Implement the task at \`${task.path}\`:
|
|
3081
3096
|
|
|
@@ -3166,8 +3181,10 @@ async function loopClaude(dependencies, loopDependencies = createDefaultDependen
|
|
|
3166
3181
|
homedir: loopDependencies.dockerDeps?.homedir ?? os.homedir,
|
|
3167
3182
|
existsSync: loopDependencies.dockerDeps?.existsSync ?? existsSync
|
|
3168
3183
|
};
|
|
3184
|
+
log2(`checking for .dust/Dockerfile in ${context.cwd}`);
|
|
3169
3185
|
if (hasDockerfile(context.cwd, dockerDeps)) {
|
|
3170
3186
|
const imageTag = generateImageTag(context.cwd);
|
|
3187
|
+
log2(`Dockerfile found, image tag: ${imageTag}`);
|
|
3171
3188
|
onLoopEvent({ type: "loop.docker_detected", imageTag });
|
|
3172
3189
|
if (!await isDockerAvailable(dockerDeps)) {
|
|
3173
3190
|
context.stderr("Docker not available. Install Docker or remove .dust/Dockerfile to run without Docker.");
|
|
@@ -3188,6 +3205,12 @@ async function loopClaude(dependencies, loopDependencies = createDefaultDependen
|
|
|
3188
3205
|
homeDir,
|
|
3189
3206
|
hasGitconfig: existsSync(path2.join(homeDir, ".gitconfig"))
|
|
3190
3207
|
};
|
|
3208
|
+
if (!process.env.CLAUDE_CODE_OAUTH_TOKEN) {
|
|
3209
|
+
context.stderr("Docker mode requires CLAUDE_CODE_OAUTH_TOKEN. Run `claude setup-token` and export the token.");
|
|
3210
|
+
return { exitCode: 1 };
|
|
3211
|
+
}
|
|
3212
|
+
} else {
|
|
3213
|
+
log2("no .dust/Dockerfile found, running without Docker");
|
|
3191
3214
|
}
|
|
3192
3215
|
log2(`starting loop, maxIterations=${maxIterations}, sessionId=${sessionId}`);
|
|
3193
3216
|
onLoopEvent({ type: "loop.warning" });
|
|
@@ -3518,6 +3541,46 @@ async function runRepositoryLoop(repoState, repoDeps, sendEvent, sessionId) {
|
|
|
3518
3541
|
loopState
|
|
3519
3542
|
});
|
|
3520
3543
|
const hooksInstalled = await manageGitHooks(commandDeps);
|
|
3544
|
+
let dockerConfig;
|
|
3545
|
+
const dockerDeps = {
|
|
3546
|
+
spawn: repoDeps.dockerDeps?.spawn ?? spawn,
|
|
3547
|
+
homedir: repoDeps.dockerDeps?.homedir ?? os2.homedir,
|
|
3548
|
+
existsSync: repoDeps.dockerDeps?.existsSync ?? fsExistsSync
|
|
3549
|
+
};
|
|
3550
|
+
log3(`checking for .dust/Dockerfile in ${repoState.path}`);
|
|
3551
|
+
if (hasDockerfile(repoState.path, dockerDeps)) {
|
|
3552
|
+
const imageTag = generateImageTag(repoState.path);
|
|
3553
|
+
log3(`Dockerfile found, image tag: ${imageTag}`);
|
|
3554
|
+
onLoopEvent({ type: "loop.docker_detected", imageTag });
|
|
3555
|
+
if (!await isDockerAvailable(dockerDeps)) {
|
|
3556
|
+
log3("Docker not available");
|
|
3557
|
+
appendLogLine(repoState.logBuffer, createLogLine("Docker not available. Install Docker or remove .dust/Dockerfile.", "stderr"));
|
|
3558
|
+
} else {
|
|
3559
|
+
onLoopEvent({ type: "loop.docker_building", imageTag });
|
|
3560
|
+
const buildResult = await buildDockerImage({ repoPath: repoState.path, imageTag }, dockerDeps);
|
|
3561
|
+
if (!buildResult.success) {
|
|
3562
|
+
onLoopEvent({ type: "loop.docker_error", error: buildResult.error });
|
|
3563
|
+
log3(`Docker build failed: ${buildResult.error}`);
|
|
3564
|
+
} else {
|
|
3565
|
+
onLoopEvent({ type: "loop.docker_built", imageTag });
|
|
3566
|
+
const homeDir = dockerDeps.homedir();
|
|
3567
|
+
dockerConfig = {
|
|
3568
|
+
imageTag,
|
|
3569
|
+
repoPath: repoState.path,
|
|
3570
|
+
homeDir,
|
|
3571
|
+
hasGitconfig: dockerDeps.existsSync(path3.join(homeDir, ".gitconfig"))
|
|
3572
|
+
};
|
|
3573
|
+
log3(`Docker config ready: ${JSON.stringify(dockerConfig)}`);
|
|
3574
|
+
if (!process.env.CLAUDE_CODE_OAUTH_TOKEN) {
|
|
3575
|
+
log3("CLAUDE_CODE_OAUTH_TOKEN is not set, cannot run in Docker mode");
|
|
3576
|
+
appendLogLine(repoState.logBuffer, createLogLine("Docker mode requires CLAUDE_CODE_OAUTH_TOKEN. Run `claude setup-token` and export the token.", "stderr"));
|
|
3577
|
+
return;
|
|
3578
|
+
}
|
|
3579
|
+
}
|
|
3580
|
+
}
|
|
3581
|
+
} else {
|
|
3582
|
+
log3("no .dust/Dockerfile found, running without Docker");
|
|
3583
|
+
}
|
|
3521
3584
|
log3(`loop started for ${repoName} at ${repoState.path}`);
|
|
3522
3585
|
while (!repoState.stopRequested) {
|
|
3523
3586
|
loopState.agentSessionId = crypto.randomUUID();
|
|
@@ -3530,7 +3593,8 @@ async function runRepositoryLoop(repoState, repoDeps, sendEvent, sessionId) {
|
|
|
3530
3593
|
hooksInstalled,
|
|
3531
3594
|
signal: abortController.signal,
|
|
3532
3595
|
repositoryId: repoState.repository.id.toString(),
|
|
3533
|
-
onRawEvent: createHeartbeatThrottler(onAgentEvent)
|
|
3596
|
+
onRawEvent: createHeartbeatThrottler(onAgentEvent),
|
|
3597
|
+
docker: dockerConfig
|
|
3534
3598
|
});
|
|
3535
3599
|
} catch (error) {
|
|
3536
3600
|
const msg = error instanceof Error ? error.message : String(error);
|
|
@@ -4212,27 +4276,27 @@ function defaultWriteStdout(data) {
|
|
|
4212
4276
|
}
|
|
4213
4277
|
function createAuthFileSystem(dependencies) {
|
|
4214
4278
|
return {
|
|
4215
|
-
exists: (
|
|
4279
|
+
exists: (path4) => {
|
|
4216
4280
|
try {
|
|
4217
|
-
dependencies.accessSync(
|
|
4281
|
+
dependencies.accessSync(path4);
|
|
4218
4282
|
return true;
|
|
4219
4283
|
} catch {
|
|
4220
4284
|
return false;
|
|
4221
4285
|
}
|
|
4222
4286
|
},
|
|
4223
|
-
isDirectory: (
|
|
4287
|
+
isDirectory: (path4) => {
|
|
4224
4288
|
try {
|
|
4225
|
-
return dependencies.statSync(
|
|
4289
|
+
return dependencies.statSync(path4).isDirectory();
|
|
4226
4290
|
} catch {
|
|
4227
4291
|
return false;
|
|
4228
4292
|
}
|
|
4229
4293
|
},
|
|
4230
|
-
getFileCreationTime: (
|
|
4231
|
-
readFile: (
|
|
4232
|
-
writeFile: (
|
|
4233
|
-
mkdir: (
|
|
4234
|
-
readdir: (
|
|
4235
|
-
chmod: (
|
|
4294
|
+
getFileCreationTime: (path4) => dependencies.statSync(path4).birthtimeMs,
|
|
4295
|
+
readFile: (path4) => dependencies.readFile(path4, "utf8"),
|
|
4296
|
+
writeFile: (path4, content) => dependencies.writeFile(path4, content, "utf8"),
|
|
4297
|
+
mkdir: (path4, options) => dependencies.mkdir(path4, options).then(() => {}),
|
|
4298
|
+
readdir: (path4) => dependencies.readdir(path4),
|
|
4299
|
+
chmod: (path4, mode) => dependencies.chmod(path4, mode),
|
|
4236
4300
|
rename: (oldPath, newPath) => dependencies.rename(oldPath, newPath)
|
|
4237
4301
|
};
|
|
4238
4302
|
}
|
|
@@ -4699,16 +4763,16 @@ function createDefaultUploadDependencies() {
|
|
|
4699
4763
|
getHomeDir: () => homedir2(),
|
|
4700
4764
|
fileSystem: authFileSystem
|
|
4701
4765
|
},
|
|
4702
|
-
readFileBytes: async (
|
|
4703
|
-
const buffer = await Bun.file(
|
|
4766
|
+
readFileBytes: async (path4) => {
|
|
4767
|
+
const buffer = await Bun.file(path4).arrayBuffer();
|
|
4704
4768
|
return new Uint8Array(buffer);
|
|
4705
4769
|
},
|
|
4706
|
-
getFileSize: async (
|
|
4707
|
-
const file = Bun.file(
|
|
4770
|
+
getFileSize: async (path4) => {
|
|
4771
|
+
const file = Bun.file(path4);
|
|
4708
4772
|
return file.size;
|
|
4709
4773
|
},
|
|
4710
|
-
fileExists: async (
|
|
4711
|
-
const file = Bun.file(
|
|
4774
|
+
fileExists: async (path4) => {
|
|
4775
|
+
const file = Bun.file(path4);
|
|
4712
4776
|
return file.exists();
|
|
4713
4777
|
},
|
|
4714
4778
|
uploadFile: async (url, token, fileBytes, contentType, fileName) => {
|
|
@@ -5502,12 +5566,12 @@ function validateNoCycles(allPrincipleRelationships) {
|
|
|
5502
5566
|
}
|
|
5503
5567
|
for (const rel of allPrincipleRelationships) {
|
|
5504
5568
|
const visited = new Set;
|
|
5505
|
-
const
|
|
5569
|
+
const path4 = [];
|
|
5506
5570
|
let current = rel.filePath;
|
|
5507
5571
|
while (current) {
|
|
5508
5572
|
if (visited.has(current)) {
|
|
5509
|
-
const cycleStart =
|
|
5510
|
-
const cyclePath =
|
|
5573
|
+
const cycleStart = path4.indexOf(current);
|
|
5574
|
+
const cyclePath = path4.slice(cycleStart).concat(current);
|
|
5511
5575
|
violations.push({
|
|
5512
5576
|
file: rel.filePath,
|
|
5513
5577
|
message: `Cycle detected in principle hierarchy: ${cyclePath.join(" -> ")}`
|
|
@@ -5515,7 +5579,7 @@ function validateNoCycles(allPrincipleRelationships) {
|
|
|
5515
5579
|
break;
|
|
5516
5580
|
}
|
|
5517
5581
|
visited.add(current);
|
|
5518
|
-
|
|
5582
|
+
path4.push(current);
|
|
5519
5583
|
const currentRel = relationshipMap.get(current);
|
|
5520
5584
|
if (currentRel && currentRel.parentPrinciples.length > 0) {
|
|
5521
5585
|
current = currentRel.parentPrinciples[0];
|
|
@@ -6502,8 +6566,8 @@ function parseGitDiffNameStatus(output) {
|
|
|
6502
6566
|
const parts = line.split("\t");
|
|
6503
6567
|
if (parts.length >= 2) {
|
|
6504
6568
|
const statusChar = parts[0].charAt(0);
|
|
6505
|
-
const
|
|
6506
|
-
changes.push({ status: statusChar, path:
|
|
6569
|
+
const path4 = parts.length > 2 ? parts[2] : parts[1];
|
|
6570
|
+
changes.push({ status: statusChar, path: path4 });
|
|
6507
6571
|
}
|
|
6508
6572
|
}
|
|
6509
6573
|
return changes;
|
|
@@ -6564,12 +6628,12 @@ async function getUncommittedFiles(cwd, gitRunner) {
|
|
|
6564
6628
|
`).filter((line) => line.length > 0);
|
|
6565
6629
|
for (const line of lines) {
|
|
6566
6630
|
if (line.length > 3) {
|
|
6567
|
-
const
|
|
6568
|
-
const arrowIndex =
|
|
6631
|
+
const path4 = line.substring(3);
|
|
6632
|
+
const arrowIndex = path4.indexOf(" -> ");
|
|
6569
6633
|
if (arrowIndex !== -1) {
|
|
6570
|
-
files.push(
|
|
6634
|
+
files.push(path4.substring(arrowIndex + 4));
|
|
6571
6635
|
} else {
|
|
6572
|
-
files.push(
|
|
6636
|
+
files.push(path4);
|
|
6573
6637
|
}
|
|
6574
6638
|
}
|
|
6575
6639
|
}
|
|
@@ -6720,24 +6784,24 @@ async function main(options) {
|
|
|
6720
6784
|
function createFileSystem(primitives) {
|
|
6721
6785
|
return {
|
|
6722
6786
|
exists: primitives.existsSync,
|
|
6723
|
-
isDirectory: (
|
|
6787
|
+
isDirectory: (path4) => {
|
|
6724
6788
|
try {
|
|
6725
|
-
return primitives.statSync(
|
|
6789
|
+
return primitives.statSync(path4).isDirectory();
|
|
6726
6790
|
} catch {
|
|
6727
6791
|
return false;
|
|
6728
6792
|
}
|
|
6729
6793
|
},
|
|
6730
|
-
readFile: (
|
|
6731
|
-
writeFile: (
|
|
6794
|
+
readFile: (path4) => primitives.readFile(path4, "utf-8"),
|
|
6795
|
+
writeFile: (path4, content, options) => primitives.writeFile(path4, content, {
|
|
6732
6796
|
encoding: "utf-8",
|
|
6733
6797
|
flag: options?.flag
|
|
6734
6798
|
}),
|
|
6735
|
-
mkdir: async (
|
|
6736
|
-
await primitives.mkdir(
|
|
6799
|
+
mkdir: async (path4, options) => {
|
|
6800
|
+
await primitives.mkdir(path4, options);
|
|
6737
6801
|
},
|
|
6738
|
-
getFileCreationTime: (
|
|
6739
|
-
readdir: (
|
|
6740
|
-
chmod: (
|
|
6802
|
+
getFileCreationTime: (path4) => primitives.statSync(path4).birthtimeMs,
|
|
6803
|
+
readdir: (path4) => primitives.readdir(path4),
|
|
6804
|
+
chmod: (path4, mode) => primitives.chmod(path4, mode),
|
|
6741
6805
|
rename: (oldPath, newPath) => primitives.rename(oldPath, newPath)
|
|
6742
6806
|
};
|
|
6743
6807
|
}
|