@joshski/dust 0.1.43 → 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 +251 -193
- 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") {
|
|
58
|
+
for (let j = 0;j < checkObj.hints.length; j++) {
|
|
59
|
+
if (typeof checkObj.hints[j] !== "string") {
|
|
77
60
|
violations.push({
|
|
78
|
-
message: `${checkPath}.
|
|
61
|
+
message: `${checkPath}.hints[${j}] must be a string`
|
|
79
62
|
});
|
|
80
63
|
}
|
|
81
|
-
if (!("command" in checkObj)) {
|
|
82
|
-
violations.push({
|
|
83
|
-
message: `${checkPath} is missing required field "command"`
|
|
84
|
-
});
|
|
85
|
-
} else if (typeof checkObj.command !== "string") {
|
|
86
|
-
violations.push({
|
|
87
|
-
message: `${checkPath}.command must be a string`
|
|
88
|
-
});
|
|
89
|
-
}
|
|
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
|
}
|
|
@@ -972,7 +971,8 @@ import { spawn as nodeSpawn3 } from "node:child_process";
|
|
|
972
971
|
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
|
-
import { homedir
|
|
974
|
+
import { homedir } from "node:os";
|
|
975
|
+
import { join as join8 } from "node:path";
|
|
976
976
|
|
|
977
977
|
// lib/bucket/auth.ts
|
|
978
978
|
import { join as join4 } from "node:path";
|
|
@@ -1045,7 +1045,29 @@ async function authenticate(authDeps) {
|
|
|
1045
1045
|
if (code) {
|
|
1046
1046
|
cleanup();
|
|
1047
1047
|
exchange(code).then(resolve, reject);
|
|
1048
|
-
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" } });
|
|
1049
1071
|
}
|
|
1050
1072
|
return new Response("Missing code", { status: 400 });
|
|
1051
1073
|
}
|
|
@@ -1119,26 +1141,7 @@ function getLogLines(buffer) {
|
|
|
1119
1141
|
}
|
|
1120
1142
|
|
|
1121
1143
|
// lib/bucket/repository.ts
|
|
1122
|
-
import { join as
|
|
1123
|
-
|
|
1124
|
-
// lib/agent-events.ts
|
|
1125
|
-
function rawEventToAgentEvent(rawEvent) {
|
|
1126
|
-
if (typeof rawEvent.type === "string" && rawEvent.type === "stream_event") {
|
|
1127
|
-
return { type: "agent-session-activity" };
|
|
1128
|
-
}
|
|
1129
|
-
return { type: "claude-event", rawEvent };
|
|
1130
|
-
}
|
|
1131
|
-
function formatAgentEvent(event) {
|
|
1132
|
-
switch (event.type) {
|
|
1133
|
-
case "agent-session-started":
|
|
1134
|
-
return `\uD83E\uDD16 Starting Claude: ${event.title}`;
|
|
1135
|
-
case "agent-session-ended":
|
|
1136
|
-
return event.success ? "\uD83E\uDD16 Claude session ended (success)" : `\uD83E\uDD16 Claude session ended (error: ${event.error})`;
|
|
1137
|
-
case "agent-session-activity":
|
|
1138
|
-
case "claude-event":
|
|
1139
|
-
return null;
|
|
1140
|
-
}
|
|
1141
|
-
}
|
|
1144
|
+
import { dirname as dirname2, join as join7 } from "node:path";
|
|
1142
1145
|
|
|
1143
1146
|
// lib/claude/spawn-claude-code.ts
|
|
1144
1147
|
import { spawn as nodeSpawn } from "node:child_process";
|
|
@@ -1539,11 +1542,74 @@ async function run(prompt, options = {}, dependencies = defaultRunnerDependencie
|
|
|
1539
1542
|
await dependencies.streamEvents(events, sink, onRawEvent);
|
|
1540
1543
|
}
|
|
1541
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
|
+
|
|
1542
1608
|
// lib/cli/commands/loop.ts
|
|
1543
1609
|
import { spawn as nodeSpawn2 } from "node:child_process";
|
|
1544
1610
|
import { readFileSync } from "node:fs";
|
|
1545
1611
|
import os from "node:os";
|
|
1546
|
-
import { dirname, join as
|
|
1612
|
+
import { dirname, join as join6 } from "node:path";
|
|
1547
1613
|
import { fileURLToPath } from "node:url";
|
|
1548
1614
|
|
|
1549
1615
|
// lib/workflow-tasks.ts
|
|
@@ -1687,8 +1753,8 @@ async function next(dependencies) {
|
|
|
1687
1753
|
var __dirname2 = dirname(fileURLToPath(import.meta.url));
|
|
1688
1754
|
function getDustVersion() {
|
|
1689
1755
|
const candidates = [
|
|
1690
|
-
|
|
1691
|
-
|
|
1756
|
+
join6(__dirname2, "../../../package.json"),
|
|
1757
|
+
join6(__dirname2, "../package.json")
|
|
1692
1758
|
];
|
|
1693
1759
|
for (const candidate of candidates) {
|
|
1694
1760
|
try {
|
|
@@ -1964,68 +2030,8 @@ async function loopClaude(dependencies, loopDependencies = createDefaultDependen
|
|
|
1964
2030
|
return { exitCode: 0 };
|
|
1965
2031
|
}
|
|
1966
2032
|
|
|
1967
|
-
// lib/bucket/repository.ts
|
|
1968
|
-
var
|
|
1969
|
-
function parseRepository(data) {
|
|
1970
|
-
if (typeof data === "string") {
|
|
1971
|
-
return { name: data, gitUrl: data };
|
|
1972
|
-
}
|
|
1973
|
-
if (typeof data === "object" && data !== null && "name" in data && "gitUrl" in data) {
|
|
1974
|
-
const repositoryData = data;
|
|
1975
|
-
if (typeof repositoryData.name === "string" && typeof repositoryData.gitUrl === "string") {
|
|
1976
|
-
const repo = {
|
|
1977
|
-
name: repositoryData.name,
|
|
1978
|
-
gitUrl: repositoryData.gitUrl
|
|
1979
|
-
};
|
|
1980
|
-
if (typeof repositoryData.url === "string") {
|
|
1981
|
-
repo.url = repositoryData.url;
|
|
1982
|
-
}
|
|
1983
|
-
return repo;
|
|
1984
|
-
}
|
|
1985
|
-
}
|
|
1986
|
-
return null;
|
|
1987
|
-
}
|
|
1988
|
-
function getRepoTempPath(repoName, tempDir) {
|
|
1989
|
-
const safeName = repoName.replace(/[^a-zA-Z0-9-_]/g, "-");
|
|
1990
|
-
return join6(tempDir, `dust-bucket-${safeName}`);
|
|
1991
|
-
}
|
|
1992
|
-
async function cloneRepository(repository, targetPath, spawn, context) {
|
|
1993
|
-
return new Promise((resolve) => {
|
|
1994
|
-
const proc = spawn("git", ["clone", repository.gitUrl, targetPath], {
|
|
1995
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
1996
|
-
});
|
|
1997
|
-
let stderr = "";
|
|
1998
|
-
proc.stderr?.on("data", (data) => {
|
|
1999
|
-
stderr += data.toString();
|
|
2000
|
-
});
|
|
2001
|
-
proc.on("close", (code) => {
|
|
2002
|
-
if (code === 0) {
|
|
2003
|
-
resolve(true);
|
|
2004
|
-
} else {
|
|
2005
|
-
context.stderr(`Failed to clone ${repository.name}: ${stderr.trim()}`);
|
|
2006
|
-
resolve(false);
|
|
2007
|
-
}
|
|
2008
|
-
});
|
|
2009
|
-
proc.on("error", (error) => {
|
|
2010
|
-
context.stderr(`Failed to clone ${repository.name}: ${error.message}`);
|
|
2011
|
-
resolve(false);
|
|
2012
|
-
});
|
|
2013
|
-
});
|
|
2014
|
-
}
|
|
2015
|
-
async function removeRepository(path, spawn, context) {
|
|
2016
|
-
return new Promise((resolve) => {
|
|
2017
|
-
const proc = spawn("rm", ["-rf", path], {
|
|
2018
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
2019
|
-
});
|
|
2020
|
-
proc.on("close", (code) => {
|
|
2021
|
-
resolve(code === 0);
|
|
2022
|
-
});
|
|
2023
|
-
proc.on("error", (error) => {
|
|
2024
|
-
context.stderr(`Failed to remove ${path}: ${error.message}`);
|
|
2025
|
-
resolve(false);
|
|
2026
|
-
});
|
|
2027
|
-
});
|
|
2028
|
-
}
|
|
2033
|
+
// lib/bucket/repository-loop.ts
|
|
2034
|
+
var FALLBACK_TIMEOUT_MS = 300000;
|
|
2029
2035
|
function createNoOpGlobScanner() {
|
|
2030
2036
|
return {
|
|
2031
2037
|
scan: async function* () {}
|
|
@@ -2119,16 +2125,48 @@ async function runRepositoryLoop(repoState, repoDeps, sendEvent, sessionId) {
|
|
|
2119
2125
|
}
|
|
2120
2126
|
});
|
|
2121
2127
|
if (result === "no_tasks") {
|
|
2122
|
-
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
|
+
});
|
|
2123
2140
|
}
|
|
2124
2141
|
}
|
|
2125
2142
|
appendLogLine(repoState.logBuffer, createLogLine(`Stopped loop for ${repoName}`, "stdout"));
|
|
2126
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
|
+
}
|
|
2127
2164
|
async function addRepository(repository, manager, repoDeps, context) {
|
|
2128
2165
|
if (manager.repositories.has(repository.name)) {
|
|
2129
2166
|
return;
|
|
2130
2167
|
}
|
|
2131
|
-
const repoPath =
|
|
2168
|
+
const repoPath = getRepoPath(repository.name, repoDeps.getReposDir());
|
|
2169
|
+
await repoDeps.fileSystem.mkdir(dirname2(repoPath), { recursive: true });
|
|
2132
2170
|
if (repoDeps.fileSystem.exists(repoPath)) {
|
|
2133
2171
|
await removeRepository(repoPath, repoDeps.spawn, context);
|
|
2134
2172
|
}
|
|
@@ -2167,6 +2205,7 @@ async function removeRepositoryFromManager(repoName, manager, repoDeps, context)
|
|
|
2167
2205
|
return;
|
|
2168
2206
|
}
|
|
2169
2207
|
repoState.stopRequested = true;
|
|
2208
|
+
repoState.wakeUp?.();
|
|
2170
2209
|
if (repoState.loopPromise) {
|
|
2171
2210
|
await Promise.race([repoState.loopPromise, repoDeps.sleep(5000)]);
|
|
2172
2211
|
}
|
|
@@ -2728,7 +2767,7 @@ function createDefaultBucketDependencies() {
|
|
|
2728
2767
|
writeStdout: defaultWriteStdout,
|
|
2729
2768
|
isTTY: process.stdout.isTTY ?? false,
|
|
2730
2769
|
sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
|
|
2731
|
-
|
|
2770
|
+
getReposDir: () => process.env.DUST_REPOS_DIR || join8(homedir(), ".dust", "repos"),
|
|
2732
2771
|
auth: {
|
|
2733
2772
|
createServer: defaultCreateServer,
|
|
2734
2773
|
openBrowser: defaultOpenBrowser,
|
|
@@ -2766,7 +2805,7 @@ function toRepositoryDependencies(bucketDeps, fileSystem) {
|
|
|
2766
2805
|
run,
|
|
2767
2806
|
fileSystem,
|
|
2768
2807
|
sleep: bucketDeps.sleep,
|
|
2769
|
-
|
|
2808
|
+
getReposDir: bucketDeps.getReposDir
|
|
2770
2809
|
};
|
|
2771
2810
|
}
|
|
2772
2811
|
function syncUIWithRepoList(state, repos) {
|
|
@@ -2874,6 +2913,10 @@ function connectWebSocket(token, state, bucketDependencies, context, fileSystem,
|
|
|
2874
2913
|
state.emit(disconnectEvent);
|
|
2875
2914
|
logMessage(state, context, useTUI, formatBucketEvent(disconnectEvent));
|
|
2876
2915
|
state.ws = null;
|
|
2916
|
+
if (event.code === 4000) {
|
|
2917
|
+
logMessage(state, context, useTUI, "Another connection replaced this one. Not reconnecting.");
|
|
2918
|
+
return;
|
|
2919
|
+
}
|
|
2877
2920
|
if (!state.shuttingDown) {
|
|
2878
2921
|
logMessage(state, context, useTUI, `Reconnecting in ${state.reconnectDelay / 1000} seconds...`);
|
|
2879
2922
|
state.reconnectTimer = setTimeout(() => {
|
|
@@ -2894,9 +2937,23 @@ function connectWebSocket(token, state, bucketDependencies, context, fileSystem,
|
|
|
2894
2937
|
syncUIWithRepoList(state, repos);
|
|
2895
2938
|
const repoDeps = toRepositoryDependencies(bucketDependencies, fileSystem);
|
|
2896
2939
|
const repoContext = createTUIContext(state, context, useTUI);
|
|
2897
|
-
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) => {
|
|
2898
2949
|
logMessage(state, context, useTUI, `Failed to handle repository list: ${error.message}`, "stderr");
|
|
2899
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
|
+
}
|
|
2900
2957
|
}
|
|
2901
2958
|
} catch {
|
|
2902
2959
|
logMessage(state, context, useTUI, `Failed to parse WebSocket message: ${event.data}`, "stderr");
|
|
@@ -2918,6 +2975,7 @@ async function shutdown(state, bucketDeps, context) {
|
|
|
2918
2975
|
}
|
|
2919
2976
|
for (const repoState of state.repositories.values()) {
|
|
2920
2977
|
repoState.stopRequested = true;
|
|
2978
|
+
repoState.wakeUp?.();
|
|
2921
2979
|
}
|
|
2922
2980
|
const loopPromises = Array.from(state.repositories.values()).map((rs) => rs.loopPromise).filter((p) => p !== null);
|
|
2923
2981
|
await Promise.all(loopPromises.map((p) => p.catch(() => {})));
|
|
@@ -3097,7 +3155,7 @@ function runBufferedProcess(spawnFn, command, commandArguments, cwd, shell, time
|
|
|
3097
3155
|
}
|
|
3098
3156
|
|
|
3099
3157
|
// lib/cli/commands/lint-markdown.ts
|
|
3100
|
-
import { dirname as
|
|
3158
|
+
import { dirname as dirname3, join as join9, resolve } from "node:path";
|
|
3101
3159
|
var REQUIRED_HEADINGS = ["## Goals", "## Blocked By", "## Definition of Done"];
|
|
3102
3160
|
var REQUIRED_GOAL_HEADINGS = ["## Parent Goal", "## Sub-Goals"];
|
|
3103
3161
|
var SLUG_PATTERN = /^[a-z0-9]+(-[a-z0-9]+)*\.md$/;
|
|
@@ -3199,7 +3257,7 @@ function validateLinks(filePath, content, fileSystem) {
|
|
|
3199
3257
|
const violations = [];
|
|
3200
3258
|
const lines = content.split(`
|
|
3201
3259
|
`);
|
|
3202
|
-
const fileDir =
|
|
3260
|
+
const fileDir = dirname3(filePath);
|
|
3203
3261
|
for (let i = 0;i < lines.length; i++) {
|
|
3204
3262
|
const line = lines[i];
|
|
3205
3263
|
const linkPattern = new RegExp(MARKDOWN_LINK_PATTERN.source, "g");
|
|
@@ -3316,7 +3374,7 @@ function validateSemanticLinks(filePath, content) {
|
|
|
3316
3374
|
const violations = [];
|
|
3317
3375
|
const lines = content.split(`
|
|
3318
3376
|
`);
|
|
3319
|
-
const fileDir =
|
|
3377
|
+
const fileDir = dirname3(filePath);
|
|
3320
3378
|
let currentSection = null;
|
|
3321
3379
|
for (let i = 0;i < lines.length; i++) {
|
|
3322
3380
|
const line = lines[i];
|
|
@@ -3399,7 +3457,7 @@ function validateGoalHierarchyLinks(filePath, content) {
|
|
|
3399
3457
|
const violations = [];
|
|
3400
3458
|
const lines = content.split(`
|
|
3401
3459
|
`);
|
|
3402
|
-
const fileDir =
|
|
3460
|
+
const fileDir = dirname3(filePath);
|
|
3403
3461
|
let currentSection = null;
|
|
3404
3462
|
for (let i = 0;i < lines.length; i++) {
|
|
3405
3463
|
const line = lines[i];
|
|
@@ -3449,7 +3507,7 @@ function validateGoalHierarchyLinks(filePath, content) {
|
|
|
3449
3507
|
function extractGoalRelationships(filePath, content) {
|
|
3450
3508
|
const lines = content.split(`
|
|
3451
3509
|
`);
|
|
3452
|
-
const fileDir =
|
|
3510
|
+
const fileDir = dirname3(filePath);
|
|
3453
3511
|
const parentGoals = [];
|
|
3454
3512
|
const subGoals = [];
|
|
3455
3513
|
let currentSection = null;
|
|
@@ -3634,7 +3692,7 @@ async function lintMarkdown(dependencies) {
|
|
|
3634
3692
|
const violations = [];
|
|
3635
3693
|
context.stdout("Validating directory structure...");
|
|
3636
3694
|
violations.push(...await validateDirectoryStructure(dustPath, fileSystem, dependencies.settings.extraDirectories));
|
|
3637
|
-
const settingsPath =
|
|
3695
|
+
const settingsPath = join9(dustPath, "config", "settings.json");
|
|
3638
3696
|
if (fileSystem.exists(settingsPath)) {
|
|
3639
3697
|
context.stdout("Validating settings.json...");
|
|
3640
3698
|
try {
|