@joshski/dust 0.1.44 → 0.1.45
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/dust.js +243 -187
- package/package.json +1 -1
package/dist/dust.js
CHANGED
|
@@ -18,129 +18,128 @@ var KNOWN_CHECK_KEYS = new Set([
|
|
|
18
18
|
"hints",
|
|
19
19
|
"timeoutMilliseconds"
|
|
20
20
|
]);
|
|
21
|
-
function
|
|
21
|
+
function validateCheckEntry(check, checkPath) {
|
|
22
22
|
const violations = [];
|
|
23
|
-
|
|
24
|
-
try {
|
|
25
|
-
parsed = JSON.parse(content);
|
|
26
|
-
} catch (error) {
|
|
27
|
-
violations.push({
|
|
28
|
-
message: `Invalid JSON: ${error.message}`
|
|
29
|
-
});
|
|
23
|
+
if (typeof check === "string") {
|
|
30
24
|
return violations;
|
|
31
25
|
}
|
|
32
|
-
if (typeof
|
|
33
|
-
violations.push({
|
|
34
|
-
message: "settings.json must be a JSON object"
|
|
35
|
-
});
|
|
26
|
+
if (typeof check !== "object" || check === null || Array.isArray(check)) {
|
|
27
|
+
violations.push({ message: `${checkPath} must be a string or object` });
|
|
36
28
|
return violations;
|
|
37
29
|
}
|
|
38
|
-
const
|
|
39
|
-
for (const key of Object.keys(
|
|
40
|
-
if (!
|
|
30
|
+
const checkObj = check;
|
|
31
|
+
for (const key of Object.keys(checkObj)) {
|
|
32
|
+
if (!KNOWN_CHECK_KEYS.has(key)) {
|
|
41
33
|
violations.push({
|
|
42
|
-
message: `Unknown key "${key}" in
|
|
34
|
+
message: `Unknown key "${key}" in ${checkPath}. Known keys: ${[...KNOWN_CHECK_KEYS].sort().join(", ")}`
|
|
43
35
|
});
|
|
44
36
|
}
|
|
45
37
|
}
|
|
46
|
-
if ("
|
|
47
|
-
|
|
38
|
+
if (!("name" in checkObj)) {
|
|
39
|
+
violations.push({
|
|
40
|
+
message: `${checkPath} is missing required field "name"`
|
|
41
|
+
});
|
|
42
|
+
} else if (typeof checkObj.name !== "string") {
|
|
43
|
+
violations.push({ message: `${checkPath}.name must be a string` });
|
|
44
|
+
}
|
|
45
|
+
if (!("command" in checkObj)) {
|
|
46
|
+
violations.push({
|
|
47
|
+
message: `${checkPath} is missing required field "command"`
|
|
48
|
+
});
|
|
49
|
+
} else if (typeof checkObj.command !== "string") {
|
|
50
|
+
violations.push({ message: `${checkPath}.command must be a string` });
|
|
51
|
+
}
|
|
52
|
+
if ("hints" in checkObj) {
|
|
53
|
+
if (!Array.isArray(checkObj.hints)) {
|
|
48
54
|
violations.push({
|
|
49
|
-
message:
|
|
55
|
+
message: `${checkPath}.hints must be an array of strings`
|
|
50
56
|
});
|
|
51
57
|
} else {
|
|
52
|
-
for (let
|
|
53
|
-
|
|
54
|
-
const checkPath = `checks[${i}]`;
|
|
55
|
-
if (typeof check === "string") {
|
|
56
|
-
continue;
|
|
57
|
-
}
|
|
58
|
-
if (typeof check !== "object" || check === null || Array.isArray(check)) {
|
|
59
|
-
violations.push({
|
|
60
|
-
message: `${checkPath} must be a string or object`
|
|
61
|
-
});
|
|
62
|
-
continue;
|
|
63
|
-
}
|
|
64
|
-
const checkObj = check;
|
|
65
|
-
for (const key of Object.keys(checkObj)) {
|
|
66
|
-
if (!KNOWN_CHECK_KEYS.has(key)) {
|
|
67
|
-
violations.push({
|
|
68
|
-
message: `Unknown key "${key}" in ${checkPath}. Known keys: ${[...KNOWN_CHECK_KEYS].sort().join(", ")}`
|
|
69
|
-
});
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
if (!("name" in checkObj)) {
|
|
73
|
-
violations.push({
|
|
74
|
-
message: `${checkPath} is missing required field "name"`
|
|
75
|
-
});
|
|
76
|
-
} else if (typeof checkObj.name !== "string") {
|
|
77
|
-
violations.push({
|
|
78
|
-
message: `${checkPath}.name must be a string`
|
|
79
|
-
});
|
|
80
|
-
}
|
|
81
|
-
if (!("command" in checkObj)) {
|
|
58
|
+
for (let j = 0;j < checkObj.hints.length; j++) {
|
|
59
|
+
if (typeof checkObj.hints[j] !== "string") {
|
|
82
60
|
violations.push({
|
|
83
|
-
message: `${checkPath}
|
|
84
|
-
});
|
|
85
|
-
} else if (typeof checkObj.command !== "string") {
|
|
86
|
-
violations.push({
|
|
87
|
-
message: `${checkPath}.command must be a string`
|
|
61
|
+
message: `${checkPath}.hints[${j}] must be a string`
|
|
88
62
|
});
|
|
89
63
|
}
|
|
90
|
-
if ("hints" in checkObj) {
|
|
91
|
-
if (!Array.isArray(checkObj.hints)) {
|
|
92
|
-
violations.push({
|
|
93
|
-
message: `${checkPath}.hints must be an array of strings`
|
|
94
|
-
});
|
|
95
|
-
} else {
|
|
96
|
-
for (let j = 0;j < checkObj.hints.length; j++) {
|
|
97
|
-
if (typeof checkObj.hints[j] !== "string") {
|
|
98
|
-
violations.push({
|
|
99
|
-
message: `${checkPath}.hints[${j}] must be a string`
|
|
100
|
-
});
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
if ("timeoutMilliseconds" in checkObj) {
|
|
106
|
-
if (typeof checkObj.timeoutMilliseconds !== "number" || checkObj.timeoutMilliseconds <= 0) {
|
|
107
|
-
violations.push({
|
|
108
|
-
message: `${checkPath}.timeoutMilliseconds must be a positive number`
|
|
109
|
-
});
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
64
|
}
|
|
113
65
|
}
|
|
114
66
|
}
|
|
115
|
-
if ("
|
|
116
|
-
if (
|
|
67
|
+
if ("timeoutMilliseconds" in checkObj) {
|
|
68
|
+
if (typeof checkObj.timeoutMilliseconds !== "number" || checkObj.timeoutMilliseconds <= 0) {
|
|
117
69
|
violations.push({
|
|
118
|
-
message:
|
|
70
|
+
message: `${checkPath}.timeoutMilliseconds must be a positive number`
|
|
119
71
|
});
|
|
120
|
-
} else {
|
|
121
|
-
for (let i = 0;i < settings.extraDirectories.length; i++) {
|
|
122
|
-
if (typeof settings.extraDirectories[i] !== "string") {
|
|
123
|
-
violations.push({
|
|
124
|
-
message: `extraDirectories[${i}] must be a string`
|
|
125
|
-
});
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
72
|
}
|
|
129
73
|
}
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
74
|
+
return violations;
|
|
75
|
+
}
|
|
76
|
+
function validateChecksConfig(settings) {
|
|
77
|
+
if (!("checks" in settings)) {
|
|
78
|
+
return [];
|
|
134
79
|
}
|
|
135
|
-
if (
|
|
80
|
+
if (!Array.isArray(settings.checks)) {
|
|
81
|
+
return [{ message: '"checks" must be an array' }];
|
|
82
|
+
}
|
|
83
|
+
const violations = [];
|
|
84
|
+
for (let i = 0;i < settings.checks.length; i++) {
|
|
85
|
+
violations.push(...validateCheckEntry(settings.checks[i], `checks[${i}]`));
|
|
86
|
+
}
|
|
87
|
+
return violations;
|
|
88
|
+
}
|
|
89
|
+
function validateExtraDirectories(settings) {
|
|
90
|
+
if (!("extraDirectories" in settings)) {
|
|
91
|
+
return [];
|
|
92
|
+
}
|
|
93
|
+
if (!Array.isArray(settings.extraDirectories)) {
|
|
94
|
+
return [{ message: '"extraDirectories" must be an array of strings' }];
|
|
95
|
+
}
|
|
96
|
+
const violations = [];
|
|
97
|
+
for (let i = 0;i < settings.extraDirectories.length; i++) {
|
|
98
|
+
if (typeof settings.extraDirectories[i] !== "string") {
|
|
99
|
+
violations.push({ message: `extraDirectories[${i}] must be a string` });
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return violations;
|
|
103
|
+
}
|
|
104
|
+
function validateDustEventsUrl(settings) {
|
|
105
|
+
if ("eventsUrl" in settings && typeof settings.eventsUrl !== "string") {
|
|
106
|
+
return [{ message: '"eventsUrl" must be a string' }];
|
|
107
|
+
}
|
|
108
|
+
return [];
|
|
109
|
+
}
|
|
110
|
+
function validateSettingsJson(content) {
|
|
111
|
+
const violations = [];
|
|
112
|
+
let parsed;
|
|
113
|
+
try {
|
|
114
|
+
parsed = JSON.parse(content);
|
|
115
|
+
} catch (error) {
|
|
136
116
|
violations.push({
|
|
137
|
-
message:
|
|
117
|
+
message: `Invalid JSON: ${error.message}`
|
|
138
118
|
});
|
|
119
|
+
return violations;
|
|
139
120
|
}
|
|
140
|
-
if ("
|
|
121
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
141
122
|
violations.push({
|
|
142
|
-
message:
|
|
123
|
+
message: "settings.json must be a JSON object"
|
|
143
124
|
});
|
|
125
|
+
return violations;
|
|
126
|
+
}
|
|
127
|
+
const settings = parsed;
|
|
128
|
+
for (const key of Object.keys(settings)) {
|
|
129
|
+
if (!KNOWN_SETTINGS_KEYS.has(key)) {
|
|
130
|
+
violations.push({
|
|
131
|
+
message: `Unknown key "${key}" in settings.json. Known keys: ${[...KNOWN_SETTINGS_KEYS].sort().join(", ")}`
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
violations.push(...validateChecksConfig(settings));
|
|
136
|
+
violations.push(...validateExtraDirectories(settings));
|
|
137
|
+
violations.push(...validateDustEventsUrl(settings));
|
|
138
|
+
if ("dustCommand" in settings && typeof settings.dustCommand !== "string") {
|
|
139
|
+
violations.push({ message: '"dustCommand" must be a string' });
|
|
140
|
+
}
|
|
141
|
+
if ("installCommand" in settings && typeof settings.installCommand !== "string") {
|
|
142
|
+
violations.push({ message: '"installCommand" must be a string' });
|
|
144
143
|
}
|
|
145
144
|
return violations;
|
|
146
145
|
}
|
|
@@ -973,7 +972,7 @@ import { accessSync, statSync } from "node:fs";
|
|
|
973
972
|
import { chmod, mkdir, readdir, readFile, writeFile } from "node:fs/promises";
|
|
974
973
|
import { createServer as httpCreateServer } from "node:http";
|
|
975
974
|
import { homedir } from "node:os";
|
|
976
|
-
import { join as
|
|
975
|
+
import { join as join8 } from "node:path";
|
|
977
976
|
|
|
978
977
|
// lib/bucket/auth.ts
|
|
979
978
|
import { join as join4 } from "node:path";
|
|
@@ -1046,7 +1045,29 @@ async function authenticate(authDeps) {
|
|
|
1046
1045
|
if (code) {
|
|
1047
1046
|
cleanup();
|
|
1048
1047
|
exchange(code).then(resolve, reject);
|
|
1049
|
-
return new Response(
|
|
1048
|
+
return new Response(`<!DOCTYPE html>
|
|
1049
|
+
<html>
|
|
1050
|
+
<head>
|
|
1051
|
+
<meta charset="utf-8">
|
|
1052
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
1053
|
+
<title>Authorized</title>
|
|
1054
|
+
<style>
|
|
1055
|
+
body { font-family: system-ui, sans-serif; display: flex; justify-content: center; align-items: center; min-height: 100vh; margin: 0; background: #0a0a0a; color: #fafafa; }
|
|
1056
|
+
.card { text-align: center; padding: 2rem; border: 1px solid #333; border-radius: 8px; max-width: 400px; }
|
|
1057
|
+
h1 { font-size: 1.25rem; margin-bottom: 1rem; }
|
|
1058
|
+
p { color: #999; }
|
|
1059
|
+
</style>
|
|
1060
|
+
</head>
|
|
1061
|
+
<body>
|
|
1062
|
+
<div class="card">
|
|
1063
|
+
<svg width="64" height="64" viewBox="0 0 512 512" fill="#fafafa" xmlns="http://www.w3.org/2000/svg" style="margin-bottom: 1rem;">
|
|
1064
|
+
<path d="M392.566 159.4c-.649-79.601-31.851-134.481-80.25-141.944l-1.443-10.127a8.52 8.52 0 0 0-3.339-5.619 8.52 8.52 0 0 0-6.327-1.622l-92.99 13.287c-4.671.666-7.916 4.995-7.25 9.666l1.605 11.229c-.709.179-1.417.307-2.075.692-52.122 30.126-68.688 71.677-77.346 122.859-34.293 13.773-55.008 33.157-55.008 55.316 0 11.997 6.242 23.149 17.24 33.072-.12.922 42.712 207.763 42.712 207.763.06.273.128.555.213.828 10.64 33.678 63.146 57.203 127.693 57.203 66.963 0 118.308-23.107 127.906-58.031 0 0 42.832-206.841 42.712-207.763 10.998-9.922 17.24-21.074 17.24-33.072.001-21.34-19.264-40.083-51.293-53.737m-276.281 51.072a2180 2180 0 0 1-4.022 33.712c-17.326-9.606-27.043-20.545-27.043-31.048 0-11.989 12.723-24.541 34.933-35.044-1.374 10.452-2.612 21.219-3.868 32.38m88.849-158.254.658 4.637a8.52 8.52 0 0 0 3.339 5.619 8.5 8.5 0 0 0 5.123 1.708c.401 0 .811-.026 1.213-.085l92.99-13.287c4.671-.666 7.916-4.995 7.25-9.666l-.837-5.858c35.497 9.077 58.509 53.642 60.482 117.634-32.021-10.477-73.214-16.634-119.35-16.634-43.797 0-83.051 5.593-114.312 15.123 8.205-41.8 23.439-74.573 63.444-99.191m50.867 220.691c-52.891 0-97.217-8.735-127.505-21.1 1.819-13.645 3.339-26.778 4.756-39.425 1.648-14.704 3.228-28.555 5.149-41.653 29.724-10.341 70.208-17.368 117.6-17.368 100.641 0 170.781 31.5 170.781 59.773s-70.14 59.773-170.781 59.773"/>
|
|
1065
|
+
</svg>
|
|
1066
|
+
<h1>Your agent is connected!</h1>
|
|
1067
|
+
<p>You can close this tab.</p>
|
|
1068
|
+
</div>
|
|
1069
|
+
</body>
|
|
1070
|
+
</html>`, { headers: { "Content-Type": "text/html" } });
|
|
1050
1071
|
}
|
|
1051
1072
|
return new Response("Missing code", { status: 400 });
|
|
1052
1073
|
}
|
|
@@ -1120,26 +1141,7 @@ function getLogLines(buffer) {
|
|
|
1120
1141
|
}
|
|
1121
1142
|
|
|
1122
1143
|
// lib/bucket/repository.ts
|
|
1123
|
-
import { dirname as dirname2, join as
|
|
1124
|
-
|
|
1125
|
-
// lib/agent-events.ts
|
|
1126
|
-
function rawEventToAgentEvent(rawEvent) {
|
|
1127
|
-
if (typeof rawEvent.type === "string" && rawEvent.type === "stream_event") {
|
|
1128
|
-
return { type: "agent-session-activity" };
|
|
1129
|
-
}
|
|
1130
|
-
return { type: "claude-event", rawEvent };
|
|
1131
|
-
}
|
|
1132
|
-
function formatAgentEvent(event) {
|
|
1133
|
-
switch (event.type) {
|
|
1134
|
-
case "agent-session-started":
|
|
1135
|
-
return `\uD83E\uDD16 Starting Claude: ${event.title}`;
|
|
1136
|
-
case "agent-session-ended":
|
|
1137
|
-
return event.success ? "\uD83E\uDD16 Claude session ended (success)" : `\uD83E\uDD16 Claude session ended (error: ${event.error})`;
|
|
1138
|
-
case "agent-session-activity":
|
|
1139
|
-
case "claude-event":
|
|
1140
|
-
return null;
|
|
1141
|
-
}
|
|
1142
|
-
}
|
|
1144
|
+
import { dirname as dirname2, join as join7 } from "node:path";
|
|
1143
1145
|
|
|
1144
1146
|
// lib/claude/spawn-claude-code.ts
|
|
1145
1147
|
import { spawn as nodeSpawn } from "node:child_process";
|
|
@@ -1540,11 +1542,74 @@ async function run(prompt, options = {}, dependencies = defaultRunnerDependencie
|
|
|
1540
1542
|
await dependencies.streamEvents(events, sink, onRawEvent);
|
|
1541
1543
|
}
|
|
1542
1544
|
|
|
1545
|
+
// lib/bucket/repository-git.ts
|
|
1546
|
+
import { join as join5 } from "node:path";
|
|
1547
|
+
function getRepoPath(repoName, reposDir) {
|
|
1548
|
+
const safeName = repoName.replace(/[^a-zA-Z0-9-_/]/g, "-");
|
|
1549
|
+
return join5(reposDir, safeName);
|
|
1550
|
+
}
|
|
1551
|
+
async function cloneRepository(repository, targetPath, spawn, context) {
|
|
1552
|
+
return new Promise((resolve) => {
|
|
1553
|
+
const proc = spawn("git", ["clone", repository.gitUrl, targetPath], {
|
|
1554
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
1555
|
+
});
|
|
1556
|
+
let stderr = "";
|
|
1557
|
+
proc.stderr?.on("data", (data) => {
|
|
1558
|
+
stderr += data.toString();
|
|
1559
|
+
});
|
|
1560
|
+
proc.on("close", (code) => {
|
|
1561
|
+
if (code === 0) {
|
|
1562
|
+
resolve(true);
|
|
1563
|
+
} else {
|
|
1564
|
+
context.stderr(`Failed to clone ${repository.name}: ${stderr.trim()}`);
|
|
1565
|
+
resolve(false);
|
|
1566
|
+
}
|
|
1567
|
+
});
|
|
1568
|
+
proc.on("error", (error) => {
|
|
1569
|
+
context.stderr(`Failed to clone ${repository.name}: ${error.message}`);
|
|
1570
|
+
resolve(false);
|
|
1571
|
+
});
|
|
1572
|
+
});
|
|
1573
|
+
}
|
|
1574
|
+
async function removeRepository(path, spawn, context) {
|
|
1575
|
+
return new Promise((resolve) => {
|
|
1576
|
+
const proc = spawn("rm", ["-rf", path], {
|
|
1577
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
1578
|
+
});
|
|
1579
|
+
proc.on("close", (code) => {
|
|
1580
|
+
resolve(code === 0);
|
|
1581
|
+
});
|
|
1582
|
+
proc.on("error", (error) => {
|
|
1583
|
+
context.stderr(`Failed to remove ${path}: ${error.message}`);
|
|
1584
|
+
resolve(false);
|
|
1585
|
+
});
|
|
1586
|
+
});
|
|
1587
|
+
}
|
|
1588
|
+
|
|
1589
|
+
// lib/agent-events.ts
|
|
1590
|
+
function rawEventToAgentEvent(rawEvent) {
|
|
1591
|
+
if (typeof rawEvent.type === "string" && rawEvent.type === "stream_event") {
|
|
1592
|
+
return { type: "agent-session-activity" };
|
|
1593
|
+
}
|
|
1594
|
+
return { type: "claude-event", rawEvent };
|
|
1595
|
+
}
|
|
1596
|
+
function formatAgentEvent(event) {
|
|
1597
|
+
switch (event.type) {
|
|
1598
|
+
case "agent-session-started":
|
|
1599
|
+
return `\uD83E\uDD16 Starting Claude: ${event.title}`;
|
|
1600
|
+
case "agent-session-ended":
|
|
1601
|
+
return event.success ? "\uD83E\uDD16 Claude session ended (success)" : `\uD83E\uDD16 Claude session ended (error: ${event.error})`;
|
|
1602
|
+
case "agent-session-activity":
|
|
1603
|
+
case "claude-event":
|
|
1604
|
+
return null;
|
|
1605
|
+
}
|
|
1606
|
+
}
|
|
1607
|
+
|
|
1543
1608
|
// lib/cli/commands/loop.ts
|
|
1544
1609
|
import { spawn as nodeSpawn2 } from "node:child_process";
|
|
1545
1610
|
import { readFileSync } from "node:fs";
|
|
1546
1611
|
import os from "node:os";
|
|
1547
|
-
import { dirname, join as
|
|
1612
|
+
import { dirname, join as join6 } from "node:path";
|
|
1548
1613
|
import { fileURLToPath } from "node:url";
|
|
1549
1614
|
|
|
1550
1615
|
// lib/workflow-tasks.ts
|
|
@@ -1688,8 +1753,8 @@ async function next(dependencies) {
|
|
|
1688
1753
|
var __dirname2 = dirname(fileURLToPath(import.meta.url));
|
|
1689
1754
|
function getDustVersion() {
|
|
1690
1755
|
const candidates = [
|
|
1691
|
-
|
|
1692
|
-
|
|
1756
|
+
join6(__dirname2, "../../../package.json"),
|
|
1757
|
+
join6(__dirname2, "../package.json")
|
|
1693
1758
|
];
|
|
1694
1759
|
for (const candidate of candidates) {
|
|
1695
1760
|
try {
|
|
@@ -1965,68 +2030,8 @@ async function loopClaude(dependencies, loopDependencies = createDefaultDependen
|
|
|
1965
2030
|
return { exitCode: 0 };
|
|
1966
2031
|
}
|
|
1967
2032
|
|
|
1968
|
-
// lib/bucket/repository.ts
|
|
1969
|
-
var
|
|
1970
|
-
function parseRepository(data) {
|
|
1971
|
-
if (typeof data === "string") {
|
|
1972
|
-
return { name: data, gitUrl: data };
|
|
1973
|
-
}
|
|
1974
|
-
if (typeof data === "object" && data !== null && "name" in data && "gitUrl" in data) {
|
|
1975
|
-
const repositoryData = data;
|
|
1976
|
-
if (typeof repositoryData.name === "string" && typeof repositoryData.gitUrl === "string") {
|
|
1977
|
-
const repo = {
|
|
1978
|
-
name: repositoryData.name,
|
|
1979
|
-
gitUrl: repositoryData.gitUrl
|
|
1980
|
-
};
|
|
1981
|
-
if (typeof repositoryData.url === "string") {
|
|
1982
|
-
repo.url = repositoryData.url;
|
|
1983
|
-
}
|
|
1984
|
-
return repo;
|
|
1985
|
-
}
|
|
1986
|
-
}
|
|
1987
|
-
return null;
|
|
1988
|
-
}
|
|
1989
|
-
function getRepoPath(repoName, reposDir) {
|
|
1990
|
-
const safeName = repoName.replace(/[^a-zA-Z0-9-_/]/g, "-");
|
|
1991
|
-
return join6(reposDir, safeName);
|
|
1992
|
-
}
|
|
1993
|
-
async function cloneRepository(repository, targetPath, spawn, context) {
|
|
1994
|
-
return new Promise((resolve) => {
|
|
1995
|
-
const proc = spawn("git", ["clone", repository.gitUrl, targetPath], {
|
|
1996
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
1997
|
-
});
|
|
1998
|
-
let stderr = "";
|
|
1999
|
-
proc.stderr?.on("data", (data) => {
|
|
2000
|
-
stderr += data.toString();
|
|
2001
|
-
});
|
|
2002
|
-
proc.on("close", (code) => {
|
|
2003
|
-
if (code === 0) {
|
|
2004
|
-
resolve(true);
|
|
2005
|
-
} else {
|
|
2006
|
-
context.stderr(`Failed to clone ${repository.name}: ${stderr.trim()}`);
|
|
2007
|
-
resolve(false);
|
|
2008
|
-
}
|
|
2009
|
-
});
|
|
2010
|
-
proc.on("error", (error) => {
|
|
2011
|
-
context.stderr(`Failed to clone ${repository.name}: ${error.message}`);
|
|
2012
|
-
resolve(false);
|
|
2013
|
-
});
|
|
2014
|
-
});
|
|
2015
|
-
}
|
|
2016
|
-
async function removeRepository(path, spawn, context) {
|
|
2017
|
-
return new Promise((resolve) => {
|
|
2018
|
-
const proc = spawn("rm", ["-rf", path], {
|
|
2019
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
2020
|
-
});
|
|
2021
|
-
proc.on("close", (code) => {
|
|
2022
|
-
resolve(code === 0);
|
|
2023
|
-
});
|
|
2024
|
-
proc.on("error", (error) => {
|
|
2025
|
-
context.stderr(`Failed to remove ${path}: ${error.message}`);
|
|
2026
|
-
resolve(false);
|
|
2027
|
-
});
|
|
2028
|
-
});
|
|
2029
|
-
}
|
|
2033
|
+
// lib/bucket/repository-loop.ts
|
|
2034
|
+
var FALLBACK_TIMEOUT_MS = 300000;
|
|
2030
2035
|
function createNoOpGlobScanner() {
|
|
2031
2036
|
return {
|
|
2032
2037
|
scan: async function* () {}
|
|
@@ -2120,11 +2125,42 @@ async function runRepositoryLoop(repoState, repoDeps, sendEvent, sessionId) {
|
|
|
2120
2125
|
}
|
|
2121
2126
|
});
|
|
2122
2127
|
if (result === "no_tasks") {
|
|
2123
|
-
await
|
|
2128
|
+
await new Promise((resolve) => {
|
|
2129
|
+
repoState.wakeUp = () => {
|
|
2130
|
+
repoState.wakeUp = undefined;
|
|
2131
|
+
resolve();
|
|
2132
|
+
};
|
|
2133
|
+
sleep(FALLBACK_TIMEOUT_MS).then(() => {
|
|
2134
|
+
if (repoState.wakeUp) {
|
|
2135
|
+
repoState.wakeUp = undefined;
|
|
2136
|
+
resolve();
|
|
2137
|
+
}
|
|
2138
|
+
});
|
|
2139
|
+
});
|
|
2124
2140
|
}
|
|
2125
2141
|
}
|
|
2126
2142
|
appendLogLine(repoState.logBuffer, createLogLine(`Stopped loop for ${repoName}`, "stdout"));
|
|
2127
2143
|
}
|
|
2144
|
+
// lib/bucket/repository.ts
|
|
2145
|
+
function parseRepository(data) {
|
|
2146
|
+
if (typeof data === "string") {
|
|
2147
|
+
return { name: data, gitUrl: data };
|
|
2148
|
+
}
|
|
2149
|
+
if (typeof data === "object" && data !== null && "name" in data && "gitUrl" in data) {
|
|
2150
|
+
const repositoryData = data;
|
|
2151
|
+
if (typeof repositoryData.name === "string" && typeof repositoryData.gitUrl === "string") {
|
|
2152
|
+
const repo = {
|
|
2153
|
+
name: repositoryData.name,
|
|
2154
|
+
gitUrl: repositoryData.gitUrl
|
|
2155
|
+
};
|
|
2156
|
+
if (typeof repositoryData.url === "string") {
|
|
2157
|
+
repo.url = repositoryData.url;
|
|
2158
|
+
}
|
|
2159
|
+
return repo;
|
|
2160
|
+
}
|
|
2161
|
+
}
|
|
2162
|
+
return null;
|
|
2163
|
+
}
|
|
2128
2164
|
async function addRepository(repository, manager, repoDeps, context) {
|
|
2129
2165
|
if (manager.repositories.has(repository.name)) {
|
|
2130
2166
|
return;
|
|
@@ -2169,6 +2205,7 @@ async function removeRepositoryFromManager(repoName, manager, repoDeps, context)
|
|
|
2169
2205
|
return;
|
|
2170
2206
|
}
|
|
2171
2207
|
repoState.stopRequested = true;
|
|
2208
|
+
repoState.wakeUp?.();
|
|
2172
2209
|
if (repoState.loopPromise) {
|
|
2173
2210
|
await Promise.race([repoState.loopPromise, repoDeps.sleep(5000)]);
|
|
2174
2211
|
}
|
|
@@ -2730,7 +2767,7 @@ function createDefaultBucketDependencies() {
|
|
|
2730
2767
|
writeStdout: defaultWriteStdout,
|
|
2731
2768
|
isTTY: process.stdout.isTTY ?? false,
|
|
2732
2769
|
sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
|
|
2733
|
-
getReposDir: () => process.env.DUST_REPOS_DIR ||
|
|
2770
|
+
getReposDir: () => process.env.DUST_REPOS_DIR || join8(homedir(), ".dust", "repos"),
|
|
2734
2771
|
auth: {
|
|
2735
2772
|
createServer: defaultCreateServer,
|
|
2736
2773
|
openBrowser: defaultOpenBrowser,
|
|
@@ -2876,6 +2913,10 @@ function connectWebSocket(token, state, bucketDependencies, context, fileSystem,
|
|
|
2876
2913
|
state.emit(disconnectEvent);
|
|
2877
2914
|
logMessage(state, context, useTUI, formatBucketEvent(disconnectEvent));
|
|
2878
2915
|
state.ws = null;
|
|
2916
|
+
if (event.code === 4000) {
|
|
2917
|
+
logMessage(state, context, useTUI, "Another connection replaced this one. Not reconnecting.");
|
|
2918
|
+
return;
|
|
2919
|
+
}
|
|
2879
2920
|
if (!state.shuttingDown) {
|
|
2880
2921
|
logMessage(state, context, useTUI, `Reconnecting in ${state.reconnectDelay / 1000} seconds...`);
|
|
2881
2922
|
state.reconnectTimer = setTimeout(() => {
|
|
@@ -2896,9 +2937,23 @@ function connectWebSocket(token, state, bucketDependencies, context, fileSystem,
|
|
|
2896
2937
|
syncUIWithRepoList(state, repos);
|
|
2897
2938
|
const repoDeps = toRepositoryDependencies(bucketDependencies, fileSystem);
|
|
2898
2939
|
const repoContext = createTUIContext(state, context, useTUI);
|
|
2899
|
-
handleRepositoryList(repos, state, repoDeps, repoContext).then(() =>
|
|
2940
|
+
handleRepositoryList(repos, state, repoDeps, repoContext).then(() => {
|
|
2941
|
+
syncTUI(state);
|
|
2942
|
+
for (const repoData of repos) {
|
|
2943
|
+
if (typeof repoData === "object" && repoData !== null && "name" in repoData && "hasTask" in repoData && repoData.hasTask) {
|
|
2944
|
+
const repoState = state.repositories.get(repoData.name);
|
|
2945
|
+
repoState?.wakeUp?.();
|
|
2946
|
+
}
|
|
2947
|
+
}
|
|
2948
|
+
}).catch((error) => {
|
|
2900
2949
|
logMessage(state, context, useTUI, `Failed to handle repository list: ${error.message}`, "stderr");
|
|
2901
2950
|
});
|
|
2951
|
+
} else if (message.type === "task-available") {
|
|
2952
|
+
const repoName = message.repository;
|
|
2953
|
+
if (typeof repoName === "string") {
|
|
2954
|
+
const repoState = state.repositories.get(repoName);
|
|
2955
|
+
repoState?.wakeUp?.();
|
|
2956
|
+
}
|
|
2902
2957
|
}
|
|
2903
2958
|
} catch {
|
|
2904
2959
|
logMessage(state, context, useTUI, `Failed to parse WebSocket message: ${event.data}`, "stderr");
|
|
@@ -2920,6 +2975,7 @@ async function shutdown(state, bucketDeps, context) {
|
|
|
2920
2975
|
}
|
|
2921
2976
|
for (const repoState of state.repositories.values()) {
|
|
2922
2977
|
repoState.stopRequested = true;
|
|
2978
|
+
repoState.wakeUp?.();
|
|
2923
2979
|
}
|
|
2924
2980
|
const loopPromises = Array.from(state.repositories.values()).map((rs) => rs.loopPromise).filter((p) => p !== null);
|
|
2925
2981
|
await Promise.all(loopPromises.map((p) => p.catch(() => {})));
|
|
@@ -3099,7 +3155,7 @@ function runBufferedProcess(spawnFn, command, commandArguments, cwd, shell, time
|
|
|
3099
3155
|
}
|
|
3100
3156
|
|
|
3101
3157
|
// lib/cli/commands/lint-markdown.ts
|
|
3102
|
-
import { dirname as dirname3, join as
|
|
3158
|
+
import { dirname as dirname3, join as join9, resolve } from "node:path";
|
|
3103
3159
|
var REQUIRED_HEADINGS = ["## Goals", "## Blocked By", "## Definition of Done"];
|
|
3104
3160
|
var REQUIRED_GOAL_HEADINGS = ["## Parent Goal", "## Sub-Goals"];
|
|
3105
3161
|
var SLUG_PATTERN = /^[a-z0-9]+(-[a-z0-9]+)*\.md$/;
|
|
@@ -3636,7 +3692,7 @@ async function lintMarkdown(dependencies) {
|
|
|
3636
3692
|
const violations = [];
|
|
3637
3693
|
context.stdout("Validating directory structure...");
|
|
3638
3694
|
violations.push(...await validateDirectoryStructure(dustPath, fileSystem, dependencies.settings.extraDirectories));
|
|
3639
|
-
const settingsPath =
|
|
3695
|
+
const settingsPath = join9(dustPath, "config", "settings.json");
|
|
3640
3696
|
if (fileSystem.exists(settingsPath)) {
|
|
3641
3697
|
context.stdout("Validating settings.json...");
|
|
3642
3698
|
try {
|