@joshski/dust 0.1.62 → 0.1.64

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 CHANGED
@@ -3,14 +3,14 @@ import { createRequire } from "node:module";
3
3
  var __require = /* @__PURE__ */ createRequire(import.meta.url);
4
4
 
5
5
  // lib/cli/run.ts
6
- import { existsSync, statSync as statSync2 } from "node:fs";
6
+ import { existsSync, statSync as statSync3 } from "node:fs";
7
7
  import {
8
- chmod as chmod2,
9
- mkdir as mkdir2,
10
- readdir as readdir2,
11
- readFile as readFile2,
8
+ chmod as chmod3,
9
+ mkdir as mkdir3,
10
+ readdir as readdir3,
11
+ readFile as readFile3,
12
12
  rename,
13
- writeFile as writeFile2
13
+ writeFile as writeFile3
14
14
  } from "node:fs/promises";
15
15
 
16
16
  // lib/git/file-sorter.ts
@@ -274,6 +274,9 @@ async function loadSettings(cwd, fileSystem) {
274
274
  }
275
275
  }
276
276
 
277
+ // lib/version.ts
278
+ var DUST_VERSION = "0.1.64";
279
+
277
280
  // lib/cli/dedent.ts
278
281
  function dedent(strings, ...values) {
279
282
  const result = strings.reduce((acc, part, index) => acc + part + (values[index] ?? ""), "");
@@ -902,6 +905,16 @@ function loadStockAudits() {
902
905
  });
903
906
  }
904
907
 
908
+ // lib/audits/index.ts
909
+ function transformAuditContent(content) {
910
+ const titleMatch = content.match(/^#\s+(.+)$/m);
911
+ if (!titleMatch) {
912
+ return content;
913
+ }
914
+ const originalTitle = titleMatch[1];
915
+ return content.replace(/^#\s+.+$/m, `# Audit: ${originalTitle}`);
916
+ }
917
+
905
918
  // lib/cli/colors.ts
906
919
  var ANSI_COLORS = {
907
920
  reset: "\x1B[0m",
@@ -936,14 +949,6 @@ function getColors() {
936
949
  }
937
950
 
938
951
  // lib/cli/commands/audit.ts
939
- function transformAuditContent(content) {
940
- const titleMatch = content.match(/^#\s+(.+)$/m);
941
- if (!titleMatch) {
942
- return content;
943
- }
944
- const originalTitle = titleMatch[1];
945
- return content.replace(/^#\s+.+$/m, `# Audit: ${originalTitle}`);
946
- }
947
952
  async function addAudit(auditName, dependencies) {
948
953
  const { context, fileSystem, settings } = dependencies;
949
954
  const dustPath = `${context.cwd}/.dust`;
@@ -1029,12 +1034,10 @@ async function audit(dependencies) {
1029
1034
  }
1030
1035
 
1031
1036
  // lib/cli/commands/bucket.ts
1032
- import { spawn as nodeSpawn3 } from "node:child_process";
1037
+ import { spawn as nodeSpawn4 } from "node:child_process";
1033
1038
  import { accessSync, statSync } from "node:fs";
1034
1039
  import { chmod, mkdir, readdir, readFile, writeFile } from "node:fs/promises";
1035
- import { createServer as httpCreateServer } from "node:http";
1036
1040
  import { homedir } from "node:os";
1037
- import { join as join9 } from "node:path";
1038
1041
 
1039
1042
  // lib/bucket/auth.ts
1040
1043
  import { join as join4 } from "node:path";
@@ -1152,6 +1155,40 @@ async function authenticate(authDeps) {
1152
1155
  });
1153
1156
  }
1154
1157
 
1158
+ // lib/bucket/auth-server.ts
1159
+ import { spawn as nodeSpawn } from "node:child_process";
1160
+ import { createServer as httpCreateServer } from "node:http";
1161
+ function createLocalServer(handler) {
1162
+ let resolvedPort = 0;
1163
+ const server = httpCreateServer(async (nodeRequest, nodeResponse) => {
1164
+ const url = new URL(nodeRequest.url ?? "/", `http://localhost:${resolvedPort}`);
1165
+ const request = new Request(url.toString(), {
1166
+ method: nodeRequest.method ?? "GET"
1167
+ });
1168
+ const response = handler(request);
1169
+ const body = await response.text();
1170
+ nodeResponse.writeHead(response.status, {
1171
+ "Content-Type": response.headers.get("content-type") ?? "text/plain"
1172
+ });
1173
+ nodeResponse.end(body);
1174
+ });
1175
+ server.listen(0, () => {
1176
+ const addr2 = server.address();
1177
+ if (addr2 && typeof addr2 === "object") {
1178
+ resolvedPort = addr2.port;
1179
+ }
1180
+ });
1181
+ const addr = server.address();
1182
+ if (addr && typeof addr === "object") {
1183
+ resolvedPort = addr.port;
1184
+ }
1185
+ return { port: resolvedPort, stop: () => server.close() };
1186
+ }
1187
+ function openBrowser(url) {
1188
+ const cmd = process.platform === "darwin" ? "open" : "xdg-open";
1189
+ nodeSpawn(cmd, [url], { stdio: "ignore", detached: true }).unref();
1190
+ }
1191
+
1155
1192
  // lib/bucket/events.ts
1156
1193
  var WS_OPEN = 1;
1157
1194
  function formatBucketEvent(event) {
@@ -1202,14 +1239,20 @@ function getLogLines(buffer) {
1202
1239
  return buffer.lines;
1203
1240
  }
1204
1241
 
1242
+ // lib/bucket/paths.ts
1243
+ import { join as join5 } from "node:path";
1244
+ function getReposDir(env, homeDir) {
1245
+ return env.DUST_REPOS_DIR || join5(homeDir, ".dust", "repos");
1246
+ }
1247
+
1205
1248
  // lib/bucket/repository.ts
1206
- import { dirname as dirname3, join as join8 } from "node:path";
1249
+ import { dirname as dirname2 } from "node:path";
1207
1250
 
1208
1251
  // lib/claude/spawn-claude-code.ts
1209
- import { spawn as nodeSpawn } from "node:child_process";
1252
+ import { spawn as nodeSpawn2 } from "node:child_process";
1210
1253
  import { createInterface as nodeCreateInterface } from "node:readline";
1211
1254
  var defaultDependencies = {
1212
- spawn: nodeSpawn,
1255
+ spawn: nodeSpawn2,
1213
1256
  createInterface: nodeCreateInterface
1214
1257
  };
1215
1258
  async function* spawnClaudeCode(prompt, options = {}, dependencies = defaultDependencies) {
@@ -1622,7 +1665,7 @@ async function run(prompt, options = {}, dependencies = defaultRunnerDependencie
1622
1665
  }
1623
1666
 
1624
1667
  // lib/logging/index.ts
1625
- import { join as join5 } from "node:path";
1668
+ import { join as join6 } from "node:path";
1626
1669
 
1627
1670
  // lib/logging/match.ts
1628
1671
  function parsePatterns(debug) {
@@ -1706,8 +1749,8 @@ function createLoggingService() {
1706
1749
  return {
1707
1750
  enableFileLogs(scope, sinkForTesting) {
1708
1751
  const existing = process.env[DUST_LOG_FILE];
1709
- const logDir = process.env.DUST_LOG_DIR ?? join5(process.cwd(), "log");
1710
- const path = existing ?? join5(logDir, `${scope}.log`);
1752
+ const logDir = process.env.DUST_LOG_DIR ?? join6(process.cwd(), "log");
1753
+ const path = existing ?? join6(logDir, `${scope}.log`);
1711
1754
  if (!existing) {
1712
1755
  process.env[DUST_LOG_FILE] = path;
1713
1756
  }
@@ -1747,10 +1790,10 @@ var createLogger = defaultService.createLogger.bind(defaultService);
1747
1790
  var isEnabled = defaultService.isEnabled.bind(defaultService);
1748
1791
 
1749
1792
  // lib/bucket/repository-git.ts
1750
- import { join as join6 } from "node:path";
1793
+ import { join as join7 } from "node:path";
1751
1794
  function getRepoPath(repoName, reposDir) {
1752
1795
  const safeName = repoName.replace(/[^a-zA-Z0-9-_/]/g, "-");
1753
- return join6(reposDir, safeName);
1796
+ return join7(reposDir, safeName);
1754
1797
  }
1755
1798
  async function cloneRepository(repository, targetPath, spawn, context) {
1756
1799
  return new Promise((resolve) => {
@@ -1817,11 +1860,8 @@ function formatAgentEvent(event) {
1817
1860
  }
1818
1861
 
1819
1862
  // lib/cli/commands/loop.ts
1820
- import { spawn as nodeSpawn2 } from "node:child_process";
1821
- import { readFileSync } from "node:fs";
1863
+ import { spawn as nodeSpawn3 } from "node:child_process";
1822
1864
  import os from "node:os";
1823
- import { dirname as dirname2, join as join7 } from "node:path";
1824
- import { fileURLToPath } from "node:url";
1825
1865
 
1826
1866
  // lib/artifacts/workflow-tasks.ts
1827
1867
  var IDEA_TRANSITION_PREFIXES = [
@@ -1970,26 +2010,12 @@ async function next(dependencies) {
1970
2010
  }
1971
2011
 
1972
2012
  // lib/cli/commands/loop.ts
1973
- var __dirname2 = dirname2(fileURLToPath(import.meta.url));
1974
- function getDustVersion() {
1975
- const candidates = [
1976
- join7(__dirname2, "../../../package.json"),
1977
- join7(__dirname2, "../package.json")
1978
- ];
1979
- for (const candidate of candidates) {
1980
- try {
1981
- const packageJson = JSON.parse(readFileSync(candidate, "utf-8"));
1982
- return packageJson.version ?? "unknown";
1983
- } catch {}
1984
- }
1985
- return "unknown";
1986
- }
1987
2013
  function getEnvironmentContext(cwd) {
1988
2014
  return {
1989
2015
  machineName: os.hostname(),
1990
2016
  cwd,
1991
2017
  platform: `${os.platform()} ${os.release()}`,
1992
- dustVersion: getDustVersion(),
2018
+ dustVersion: DUST_VERSION,
1993
2019
  runtimeVersion: process.version
1994
2020
  };
1995
2021
  }
@@ -2018,19 +2044,21 @@ function formatLoopEvent(event) {
2018
2044
  return `\uD83C\uDFC1 Reached max iterations (${event.maxIterations}). Exiting.`;
2019
2045
  }
2020
2046
  }
2021
- async function defaultPostEvent(url, payload) {
2022
- await fetch(url, {
2023
- method: "POST",
2024
- headers: { "Content-Type": "application/json" },
2025
- body: JSON.stringify(payload)
2026
- });
2047
+ function createPostEvent(fetchFn) {
2048
+ return async (url, payload) => {
2049
+ await fetchFn(url, {
2050
+ method: "POST",
2051
+ headers: { "Content-Type": "application/json" },
2052
+ body: JSON.stringify(payload)
2053
+ });
2054
+ };
2027
2055
  }
2028
2056
  function createDefaultDependencies() {
2029
2057
  return {
2030
- spawn: nodeSpawn2,
2058
+ spawn: nodeSpawn3,
2031
2059
  run,
2032
2060
  sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
2033
- postEvent: defaultPostEvent
2061
+ postEvent: createPostEvent(fetch)
2034
2062
  };
2035
2063
  }
2036
2064
  function createWireEventSender(eventsUrl, sessionId, postEvent, onError, getAgentSessionId, repository = "") {
@@ -2098,7 +2126,20 @@ async function runOneIteration(dependencies, loopDependencies, onLoopEvent, onAg
2098
2126
  const { context } = dependencies;
2099
2127
  const { spawn, run: run2 } = loopDependencies;
2100
2128
  const agentName = loopDependencies.agentType === "codex" ? "Codex" : "Claude";
2101
- const { onRawEvent, hooksInstalled = false, signal } = options;
2129
+ const {
2130
+ onRawEvent,
2131
+ hooksInstalled = false,
2132
+ signal,
2133
+ logger = log,
2134
+ repositoryId
2135
+ } = options;
2136
+ const baseEnv = {
2137
+ DUST_UNATTENDED: "1",
2138
+ DUST_SKIP_AGENT: "1"
2139
+ };
2140
+ if (repositoryId) {
2141
+ baseEnv.DUST_REPOSITORY_ID = repositoryId;
2142
+ }
2102
2143
  log("syncing with remote");
2103
2144
  onLoopEvent({ type: "loop.syncing" });
2104
2145
  const pullResult = await gitPull(context.cwd, spawn);
@@ -2133,7 +2174,7 @@ Make sure the repository is in a clean state and synced with remote before finis
2133
2174
  spawnOptions: {
2134
2175
  cwd: context.cwd,
2135
2176
  dangerouslySkipPermissions: true,
2136
- env: { DUST_UNATTENDED: "1", DUST_SKIP_AGENT: "1" },
2177
+ env: baseEnv,
2137
2178
  signal
2138
2179
  },
2139
2180
  onRawEvent
@@ -2189,7 +2230,7 @@ ${instructions}`;
2189
2230
  spawnOptions: {
2190
2231
  cwd: context.cwd,
2191
2232
  dangerouslySkipPermissions: true,
2192
- env: { DUST_UNATTENDED: "1", DUST_SKIP_AGENT: "1" },
2233
+ env: baseEnv,
2193
2234
  signal
2194
2235
  },
2195
2236
  onRawEvent
@@ -2199,7 +2240,7 @@ ${instructions}`;
2199
2240
  return "ran_claude";
2200
2241
  } catch (error) {
2201
2242
  const errorMessage = error instanceof Error ? error.message : String(error);
2202
- log(`${agentName} error on task ${task.title ?? task.path}: ${errorMessage}`);
2243
+ logger(`${agentName} error on task ${task.title ?? task.path}: ${errorMessage}`);
2203
2244
  context.stderr(`${agentName} exited with error: ${errorMessage}`);
2204
2245
  onAgentEvent?.({
2205
2246
  type: "agent-session-ended",
@@ -2286,6 +2327,45 @@ async function loopClaude(dependencies, loopDependencies = createDefaultDependen
2286
2327
  // lib/bucket/repository-loop.ts
2287
2328
  var log2 = createLogger("dust:bucket:repository-loop");
2288
2329
  var FALLBACK_TIMEOUT_MS = 300000;
2330
+ function createLogCallbacks(logBuffer) {
2331
+ return {
2332
+ stdout: (msg) => appendLogLine(logBuffer, createLogLine(msg, "stdout")),
2333
+ stderr: (msg) => appendLogLine(logBuffer, createLogLine(msg, "stderr"))
2334
+ };
2335
+ }
2336
+ function flushAndLogMultiLine(partialLine, text, logBuffer) {
2337
+ if (partialLine) {
2338
+ appendLogLine(logBuffer, createLogLine(partialLine, "stdout"));
2339
+ }
2340
+ for (const segment of text.split(`
2341
+ `)) {
2342
+ appendLogLine(logBuffer, createLogLine(segment, "stdout"));
2343
+ }
2344
+ return "";
2345
+ }
2346
+ function buildEventMessage(parameters) {
2347
+ const msg = {
2348
+ sequence: parameters.sequence,
2349
+ timestamp: new Date().toISOString(),
2350
+ sessionId: parameters.sessionId,
2351
+ repository: parameters.repository,
2352
+ event: parameters.event
2353
+ };
2354
+ if (parameters.agentSessionId) {
2355
+ msg.agentSessionId = parameters.agentSessionId;
2356
+ }
2357
+ return msg;
2358
+ }
2359
+ function createWakeUpHandler(repoState, resolve) {
2360
+ const handler = () => {
2361
+ if (repoState.wakeUp !== handler) {
2362
+ return;
2363
+ }
2364
+ repoState.wakeUp = undefined;
2365
+ resolve();
2366
+ };
2367
+ return handler;
2368
+ }
2289
2369
  function createNoOpGlobScanner() {
2290
2370
  return {
2291
2371
  scan: async function* () {}
@@ -2295,12 +2375,13 @@ async function runRepositoryLoop(repoState, repoDeps, sendEvent, sessionId) {
2295
2375
  const { spawn, run: run2, fileSystem, sleep } = repoDeps;
2296
2376
  const repoName = repoState.repository.name;
2297
2377
  const settings = await loadSettings(repoState.path, fileSystem);
2378
+ const logCallbacks = createLogCallbacks(repoState.logBuffer);
2298
2379
  const commandDeps = {
2299
2380
  arguments: [],
2300
2381
  context: {
2301
2382
  cwd: repoState.path,
2302
- stdout: (msg) => appendLogLine(repoState.logBuffer, createLogLine(msg, "stdout")),
2303
- stderr: (msg) => appendLogLine(repoState.logBuffer, createLogLine(msg, "stderr"))
2383
+ stdout: (msg) => logCallbacks.stdout(msg),
2384
+ stderr: (msg) => logCallbacks.stderr(msg)
2304
2385
  },
2305
2386
  fileSystem,
2306
2387
  globScanner: createNoOpGlobScanner(),
@@ -2320,14 +2401,7 @@ async function runRepositoryLoop(repoState, repoDeps, sendEvent, sessionId) {
2320
2401
  partialLine = lines[lines.length - 1];
2321
2402
  },
2322
2403
  line: (text) => {
2323
- if (partialLine) {
2324
- appendLogLine(repoState.logBuffer, createLogLine(partialLine, "stdout"));
2325
- partialLine = "";
2326
- }
2327
- for (const segment of text.split(`
2328
- `)) {
2329
- appendLogLine(repoState.logBuffer, createLogLine(segment, "stdout"));
2330
- }
2404
+ partialLine = flushAndLogMultiLine(partialLine, text, repoState.logBuffer);
2331
2405
  }
2332
2406
  })
2333
2407
  };
@@ -2358,17 +2432,13 @@ async function runRepositoryLoop(repoState, repoDeps, sendEvent, sessionId) {
2358
2432
  }
2359
2433
  if (sendEvent && sessionId) {
2360
2434
  sequence++;
2361
- const msg = {
2435
+ sendEvent(buildEventMessage({
2362
2436
  sequence,
2363
- timestamp: new Date().toISOString(),
2364
2437
  sessionId,
2365
2438
  repository: repoName,
2366
- event
2367
- };
2368
- if (agentSessionId) {
2369
- msg.agentSessionId = agentSessionId;
2370
- }
2371
- sendEvent(msg);
2439
+ event,
2440
+ agentSessionId
2441
+ }));
2372
2442
  }
2373
2443
  };
2374
2444
  const hooksInstalled = await manageGitHooks(commandDeps);
@@ -2386,6 +2456,7 @@ async function runRepositoryLoop(repoState, repoDeps, sendEvent, sessionId) {
2386
2456
  result = await runOneIteration(commandDeps, loopDeps, onLoopEvent, onAgentEvent, {
2387
2457
  hooksInstalled,
2388
2458
  signal: abortController.signal,
2459
+ repositoryId: repoState.repository.id,
2389
2460
  onRawEvent: (rawEvent) => {
2390
2461
  onAgentEvent(rawEventToAgentEvent(rawEvent));
2391
2462
  }
@@ -2411,13 +2482,7 @@ async function runRepositoryLoop(repoState, repoDeps, sendEvent, sessionId) {
2411
2482
  log2(`${repoName}: no tasks available, waiting`);
2412
2483
  logLine("Waiting for tasks...");
2413
2484
  await new Promise((resolve) => {
2414
- const wakeUpForThisWait = () => {
2415
- if (repoState.wakeUp !== wakeUpForThisWait) {
2416
- return;
2417
- }
2418
- repoState.wakeUp = undefined;
2419
- resolve();
2420
- };
2485
+ const wakeUpForThisWait = createWakeUpHandler(repoState, resolve);
2421
2486
  repoState.wakeUp = wakeUpForThisWait;
2422
2487
  sleep(FALLBACK_TIMEOUT_MS).then(() => {
2423
2488
  if (repoState.wakeUp === wakeUpForThisWait) {
@@ -2463,6 +2528,9 @@ function parseRepository(data) {
2463
2528
  if (typeof repositoryData.url === "string") {
2464
2529
  repo.url = repositoryData.url;
2465
2530
  }
2531
+ if (typeof repositoryData.id === "string") {
2532
+ repo.id = repositoryData.id;
2533
+ }
2466
2534
  return repo;
2467
2535
  }
2468
2536
  }
@@ -2475,7 +2543,7 @@ async function addRepository(repository, manager, repoDeps, context) {
2475
2543
  }
2476
2544
  log3(`adding repository ${repository.name}`);
2477
2545
  const repoPath = getRepoPath(repository.name, repoDeps.getReposDir());
2478
- await repoDeps.fileSystem.mkdir(dirname3(repoPath), { recursive: true });
2546
+ await repoDeps.fileSystem.mkdir(dirname2(repoPath), { recursive: true });
2479
2547
  if (repoDeps.fileSystem.exists(repoPath)) {
2480
2548
  await removeRepository(repoPath, repoDeps.spawn, context);
2481
2549
  }
@@ -3026,41 +3094,11 @@ function defaultGetTerminalSize() {
3026
3094
  function defaultWriteStdout(data) {
3027
3095
  process.stdout.write(data);
3028
3096
  }
3029
- function defaultCreateServer(handler) {
3030
- let resolvedPort = 0;
3031
- const server = httpCreateServer(async (nodeRequest, nodeResponse) => {
3032
- const url = new URL(nodeRequest.url ?? "/", `http://localhost:${resolvedPort}`);
3033
- const request = new Request(url.toString(), {
3034
- method: nodeRequest.method ?? "GET"
3035
- });
3036
- const response = handler(request);
3037
- const body = await response.text();
3038
- nodeResponse.writeHead(response.status, {
3039
- "Content-Type": response.headers.get("content-type") ?? "text/plain"
3040
- });
3041
- nodeResponse.end(body);
3042
- });
3043
- server.listen(0, () => {
3044
- const addr2 = server.address();
3045
- if (addr2 && typeof addr2 === "object") {
3046
- resolvedPort = addr2.port;
3047
- }
3048
- });
3049
- const addr = server.address();
3050
- if (addr && typeof addr === "object") {
3051
- resolvedPort = addr.port;
3052
- }
3053
- return { port: resolvedPort, stop: () => server.close() };
3054
- }
3055
- function defaultOpenBrowser(url) {
3056
- const cmd = process.platform === "darwin" ? "open" : "xdg-open";
3057
- nodeSpawn3(cmd, [url], { stdio: "ignore", detached: true }).unref();
3058
- }
3059
- function createDefaultBucketDependencies() {
3060
- const authFileSystem = {
3097
+ function createAuthFileSystem(dependencies) {
3098
+ return {
3061
3099
  exists: (path) => {
3062
3100
  try {
3063
- accessSync(path);
3101
+ dependencies.accessSync(path);
3064
3102
  return true;
3065
3103
  } catch {
3066
3104
  return false;
@@ -3068,21 +3106,33 @@ function createDefaultBucketDependencies() {
3068
3106
  },
3069
3107
  isDirectory: (path) => {
3070
3108
  try {
3071
- return statSync(path).isDirectory();
3109
+ return dependencies.statSync(path).isDirectory();
3072
3110
  } catch {
3073
3111
  return false;
3074
3112
  }
3075
3113
  },
3076
- getFileCreationTime: (path) => statSync(path).birthtimeMs,
3077
- readFile: (path) => readFile(path, "utf8"),
3078
- writeFile: (path, content) => writeFile(path, content, "utf8"),
3079
- mkdir: (path, options) => mkdir(path, options).then(() => {}),
3080
- readdir: (path) => readdir(path),
3081
- chmod: (path, mode) => chmod(path, mode),
3082
- rename: (oldPath, newPath) => import("node:fs/promises").then((mod) => mod.rename(oldPath, newPath))
3114
+ getFileCreationTime: (path) => dependencies.statSync(path).birthtimeMs,
3115
+ readFile: (path) => dependencies.readFile(path, "utf8"),
3116
+ writeFile: (path, content) => dependencies.writeFile(path, content, "utf8"),
3117
+ mkdir: (path, options) => dependencies.mkdir(path, options).then(() => {}),
3118
+ readdir: (path) => dependencies.readdir(path),
3119
+ chmod: (path, mode) => dependencies.chmod(path, mode),
3120
+ rename: (oldPath, newPath) => dependencies.rename(oldPath, newPath)
3083
3121
  };
3122
+ }
3123
+ function createDefaultBucketDependencies() {
3124
+ const authFileSystem = createAuthFileSystem({
3125
+ accessSync,
3126
+ statSync,
3127
+ readFile,
3128
+ writeFile,
3129
+ mkdir,
3130
+ readdir,
3131
+ chmod,
3132
+ rename: (oldPath, newPath) => import("node:fs/promises").then((mod) => mod.rename(oldPath, newPath))
3133
+ });
3084
3134
  return {
3085
- spawn: nodeSpawn3,
3135
+ spawn: nodeSpawn4,
3086
3136
  createWebSocket: defaultCreateWebSocket,
3087
3137
  setupKeypress: defaultSetupKeypress,
3088
3138
  setupSignals: defaultSetupSignals,
@@ -3091,10 +3141,10 @@ function createDefaultBucketDependencies() {
3091
3141
  writeStdout: defaultWriteStdout,
3092
3142
  isTTY: process.stdout.isTTY ?? false,
3093
3143
  sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
3094
- getReposDir: () => process.env.DUST_REPOS_DIR || join9(homedir(), ".dust", "repos"),
3144
+ getReposDir: () => getReposDir(process.env, homedir()),
3095
3145
  auth: {
3096
- createServer: defaultCreateServer,
3097
- openBrowser: defaultOpenBrowser,
3146
+ createServer: createLocalServer,
3147
+ openBrowser,
3098
3148
  getHomeDir: () => homedir(),
3099
3149
  fileSystem: authFileSystem
3100
3150
  }
@@ -3459,6 +3509,176 @@ async function bucket(dependencies, bucketDeps = createDefaultBucketDependencies
3459
3509
  return { exitCode: 0 };
3460
3510
  }
3461
3511
 
3512
+ // lib/cli/commands/bucket-asset-upload.ts
3513
+ import { accessSync as accessSync2, statSync as statSync2 } from "node:fs";
3514
+ import { chmod as chmod2, mkdir as mkdir2, readdir as readdir2, readFile as readFile2, writeFile as writeFile2 } from "node:fs/promises";
3515
+ import { homedir as homedir2 } from "node:os";
3516
+ import { extname } from "node:path";
3517
+ var MAX_FILE_SIZE_BYTES = 10 * 1024 * 1024;
3518
+ var ALLOWED_EXTENSIONS = new Set([
3519
+ ".png",
3520
+ ".jpg",
3521
+ ".jpeg",
3522
+ ".gif",
3523
+ ".webp",
3524
+ ".svg",
3525
+ ".pdf",
3526
+ ".txt",
3527
+ ".json",
3528
+ ".csv",
3529
+ ".md",
3530
+ ".html",
3531
+ ".xml"
3532
+ ]);
3533
+ var MIME_TYPES = {
3534
+ ".png": "image/png",
3535
+ ".jpg": "image/jpeg",
3536
+ ".jpeg": "image/jpeg",
3537
+ ".gif": "image/gif",
3538
+ ".webp": "image/webp",
3539
+ ".svg": "image/svg+xml",
3540
+ ".pdf": "application/pdf",
3541
+ ".txt": "text/plain",
3542
+ ".json": "application/json",
3543
+ ".csv": "text/csv",
3544
+ ".md": "text/markdown",
3545
+ ".html": "text/html",
3546
+ ".xml": "application/xml"
3547
+ };
3548
+ function createDefaultUploadDependencies() {
3549
+ const authFileSystemDeps = {
3550
+ accessSync: accessSync2,
3551
+ statSync: statSync2,
3552
+ readFile: readFile2,
3553
+ writeFile: writeFile2,
3554
+ mkdir: mkdir2,
3555
+ readdir: readdir2,
3556
+ chmod: chmod2,
3557
+ rename: (oldPath, newPath) => import("node:fs/promises").then((mod) => mod.rename(oldPath, newPath))
3558
+ };
3559
+ const authFileSystem = createAuthFileSystem(authFileSystemDeps);
3560
+ return {
3561
+ auth: {
3562
+ createServer: createLocalServer,
3563
+ openBrowser,
3564
+ getHomeDir: () => homedir2(),
3565
+ fileSystem: authFileSystem
3566
+ },
3567
+ readFileBytes: async (path) => {
3568
+ const buffer = await Bun.file(path).arrayBuffer();
3569
+ return new Uint8Array(buffer);
3570
+ },
3571
+ getFileSize: async (path) => {
3572
+ const file = Bun.file(path);
3573
+ return file.size;
3574
+ },
3575
+ fileExists: async (path) => {
3576
+ const file = Bun.file(path);
3577
+ return file.exists();
3578
+ },
3579
+ uploadFile: async (url, token, fileBytes, contentType) => {
3580
+ const response = await fetch(url, {
3581
+ method: "POST",
3582
+ headers: {
3583
+ Authorization: `Bearer ${token}`,
3584
+ "Content-Type": contentType
3585
+ },
3586
+ body: new Blob([fileBytes])
3587
+ });
3588
+ if (!response.ok) {
3589
+ const text = await response.text();
3590
+ throw new Error(`Upload failed (${response.status}): ${text || response.statusText}`);
3591
+ }
3592
+ const body = await response.json();
3593
+ if (typeof body.url !== "string") {
3594
+ throw new Error("Server response missing URL");
3595
+ }
3596
+ return { url: body.url };
3597
+ }
3598
+ };
3599
+ }
3600
+ async function resolveToken2(authDeps, context) {
3601
+ const envToken = process.env.DUST_BUCKET_TOKEN;
3602
+ if (envToken) {
3603
+ return envToken;
3604
+ }
3605
+ const stored = await loadStoredToken(authDeps.fileSystem, authDeps.getHomeDir());
3606
+ if (stored) {
3607
+ return stored;
3608
+ }
3609
+ context.stdout("Opening browser to authenticate with dustbucket...");
3610
+ try {
3611
+ const token = await authenticate(authDeps);
3612
+ await storeToken(authDeps.fileSystem, authDeps.getHomeDir(), token);
3613
+ context.stdout("Authenticated successfully");
3614
+ return token;
3615
+ } catch (error) {
3616
+ context.stderr(`Authentication failed: ${error.message}`);
3617
+ return null;
3618
+ }
3619
+ }
3620
+ function getContentType(filePath) {
3621
+ const ext = extname(filePath).toLowerCase();
3622
+ return MIME_TYPES[ext] || "application/octet-stream";
3623
+ }
3624
+ function isAllowedExtension(filePath) {
3625
+ const ext = extname(filePath).toLowerCase();
3626
+ return ALLOWED_EXTENSIONS.has(ext);
3627
+ }
3628
+ function formatFileSize(bytes) {
3629
+ if (bytes < 1024)
3630
+ return `${bytes} bytes`;
3631
+ if (bytes < 1048576)
3632
+ return `${(bytes / 1024).toFixed(1)} KB`;
3633
+ return `${(bytes / 1048576).toFixed(1)} MB`;
3634
+ }
3635
+ async function bucketAssetUpload(dependencies, uploadDeps = createDefaultUploadDependencies(), env = process.env) {
3636
+ const { context } = dependencies;
3637
+ const filePath = dependencies.arguments[0];
3638
+ if (!filePath) {
3639
+ context.stderr("Usage: dust bucket asset upload <file-path>");
3640
+ return { exitCode: 1 };
3641
+ }
3642
+ const repositoryId = env.DUST_REPOSITORY_ID;
3643
+ if (!repositoryId) {
3644
+ context.stderr("Error: DUST_REPOSITORY_ID environment variable is not set.");
3645
+ context.stderr("This command must be run within a repository context (via `dust bucket`).");
3646
+ return { exitCode: 1 };
3647
+ }
3648
+ const exists = await uploadDeps.fileExists(filePath);
3649
+ if (!exists) {
3650
+ context.stderr(`File not found: ${filePath}`);
3651
+ return { exitCode: 1 };
3652
+ }
3653
+ if (!isAllowedExtension(filePath)) {
3654
+ const ext = extname(filePath).toLowerCase() || "(no extension)";
3655
+ const allowed = Array.from(ALLOWED_EXTENSIONS).join(", ");
3656
+ context.stderr(`Unsupported file type: ${ext}`);
3657
+ context.stderr(`Allowed types: ${allowed}`);
3658
+ return { exitCode: 1 };
3659
+ }
3660
+ const fileSize = await uploadDeps.getFileSize(filePath);
3661
+ if (fileSize > MAX_FILE_SIZE_BYTES) {
3662
+ context.stderr(`File too large: ${formatFileSize(fileSize)} (max ${formatFileSize(MAX_FILE_SIZE_BYTES)})`);
3663
+ return { exitCode: 1 };
3664
+ }
3665
+ const token = await resolveToken2(uploadDeps.auth, context);
3666
+ if (!token) {
3667
+ return { exitCode: 1 };
3668
+ }
3669
+ const fileBytes = await uploadDeps.readFileBytes(filePath);
3670
+ const contentType = getContentType(filePath);
3671
+ const uploadUrl = `${getDustbucketHost()}/api/assets?repositoryId=${encodeURIComponent(repositoryId)}`;
3672
+ try {
3673
+ const result = await uploadDeps.uploadFile(uploadUrl, token, fileBytes, contentType);
3674
+ context.stdout(result.url);
3675
+ return { exitCode: 0 };
3676
+ } catch (error) {
3677
+ context.stderr(`Upload failed: ${error.message}`);
3678
+ return { exitCode: 1 };
3679
+ }
3680
+ }
3681
+
3462
3682
  // lib/cli/process-runner.ts
3463
3683
  import { spawn } from "node:child_process";
3464
3684
  function createShellRunner(spawnFn) {
@@ -3517,7 +3737,7 @@ function runBufferedProcess(spawnFn, command, commandArguments, cwd, shell, time
3517
3737
  }
3518
3738
 
3519
3739
  // lib/cli/commands/lint-markdown.ts
3520
- import { join as join10 } from "node:path";
3740
+ import { join as join8 } from "node:path";
3521
3741
 
3522
3742
  // lib/lint/validators/content-validator.ts
3523
3743
  var REQUIRED_HEADINGS = [
@@ -3807,7 +4027,7 @@ function validateIdeaTransitionTitle(filePath, content, ideasPath, fileSystem) {
3807
4027
  }
3808
4028
 
3809
4029
  // lib/lint/validators/link-validator.ts
3810
- import { dirname as dirname4, resolve } from "node:path";
4030
+ import { dirname as dirname3, resolve } from "node:path";
3811
4031
  var SEMANTIC_RULES = [
3812
4032
  {
3813
4033
  section: "## Principles",
@@ -3824,7 +4044,7 @@ function validateLinks(filePath, content, fileSystem) {
3824
4044
  const violations = [];
3825
4045
  const lines = content.split(`
3826
4046
  `);
3827
- const fileDir = dirname4(filePath);
4047
+ const fileDir = dirname3(filePath);
3828
4048
  for (let i = 0;i < lines.length; i++) {
3829
4049
  const line = lines[i];
3830
4050
  const linkPattern = new RegExp(MARKDOWN_LINK_PATTERN.source, "g");
@@ -3851,7 +4071,7 @@ function validateSemanticLinks(filePath, content) {
3851
4071
  const violations = [];
3852
4072
  const lines = content.split(`
3853
4073
  `);
3854
- const fileDir = dirname4(filePath);
4074
+ const fileDir = dirname3(filePath);
3855
4075
  let currentSection = null;
3856
4076
  for (let i = 0;i < lines.length; i++) {
3857
4077
  const line = lines[i];
@@ -3902,7 +4122,7 @@ function validatePrincipleHierarchyLinks(filePath, content) {
3902
4122
  const violations = [];
3903
4123
  const lines = content.split(`
3904
4124
  `);
3905
- const fileDir = dirname4(filePath);
4125
+ const fileDir = dirname3(filePath);
3906
4126
  let currentSection = null;
3907
4127
  for (let i = 0;i < lines.length; i++) {
3908
4128
  const line = lines[i];
@@ -3951,7 +4171,7 @@ function validatePrincipleHierarchyLinks(filePath, content) {
3951
4171
  }
3952
4172
 
3953
4173
  // lib/lint/validators/principle-hierarchy.ts
3954
- import { dirname as dirname5, resolve as resolve2 } from "node:path";
4174
+ import { dirname as dirname4, resolve as resolve2 } from "node:path";
3955
4175
  var REQUIRED_PRINCIPLE_HEADINGS = ["## Parent Principle", "## Sub-Principles"];
3956
4176
  function validatePrincipleHierarchySections(filePath, content) {
3957
4177
  const violations = [];
@@ -3968,7 +4188,7 @@ function validatePrincipleHierarchySections(filePath, content) {
3968
4188
  function extractPrincipleRelationships(filePath, content) {
3969
4189
  const lines = content.split(`
3970
4190
  `);
3971
- const fileDir = dirname5(filePath);
4191
+ const fileDir = dirname4(filePath);
3972
4192
  const parentPrinciples = [];
3973
4193
  const subPrinciples = [];
3974
4194
  let currentSection = null;
@@ -4089,7 +4309,7 @@ async function lintMarkdown(dependencies) {
4089
4309
  const violations = [];
4090
4310
  context.stdout("Validating directory structure...");
4091
4311
  violations.push(...await validateDirectoryStructure(dustPath, fileSystem, dependencies.settings.extraDirectories));
4092
- const settingsPath = join10(dustPath, "config", "settings.json");
4312
+ const settingsPath = join8(dustPath, "config", "settings.json");
4093
4313
  if (fileSystem.exists(settingsPath)) {
4094
4314
  context.stdout("Validating settings.json...");
4095
4315
  try {
@@ -4419,7 +4639,7 @@ async function check(dependencies, shellRunner = defaultShellRunner) {
4419
4639
  function generateHelpText(settings) {
4420
4640
  const bin = settings.dustCommand;
4421
4641
  return dedent`
4422
- 💨 dust - Flow state for AI coding agents.
4642
+ dust - Flow state for AI coding agents.
4423
4643
 
4424
4644
  Usage: ${bin} <command> [options]
4425
4645
 
@@ -4705,10 +4925,10 @@ async function list(dependencies) {
4705
4925
  }
4706
4926
 
4707
4927
  // lib/codex/spawn-codex.ts
4708
- import { spawn as nodeSpawn4 } from "node:child_process";
4928
+ import { spawn as nodeSpawn5 } from "node:child_process";
4709
4929
  import { createInterface as nodeCreateInterface2 } from "node:readline";
4710
4930
  var defaultDependencies2 = {
4711
- spawn: nodeSpawn4,
4931
+ spawn: nodeSpawn5,
4712
4932
  createInterface: nodeCreateInterface2
4713
4933
  };
4714
4934
  async function* spawnCodex(prompt, options = {}, dependencies = defaultDependencies2) {
@@ -5227,6 +5447,7 @@ var commandRegistry = {
5227
5447
  agent,
5228
5448
  audit,
5229
5449
  bucket,
5450
+ "bucket asset upload": bucketAssetUpload,
5230
5451
  focus,
5231
5452
  "new task": newTask,
5232
5453
  "new principle": newPrinciple,
@@ -5243,6 +5464,9 @@ var COMMANDS = Object.keys(commandRegistry).filter((cmd) => !cmd.includes(" "));
5243
5464
  function isHelpRequest(command) {
5244
5465
  return !command || command === "help" || command === "--help" || command === "-h";
5245
5466
  }
5467
+ function isVersionRequest(command) {
5468
+ return command === "--version" || command === "-v";
5469
+ }
5246
5470
  function isValidCommand(command) {
5247
5471
  return command in commandRegistry;
5248
5472
  }
@@ -5262,6 +5486,10 @@ async function main(options) {
5262
5486
  const { commandArguments, context, fileSystem, glob, directoryFileSorter } = options;
5263
5487
  const settings = await loadSettings(context.cwd, fileSystem);
5264
5488
  const helpText = generateHelpText(settings);
5489
+ if (isVersionRequest(commandArguments[0])) {
5490
+ context.stdout(DUST_VERSION);
5491
+ return { exitCode: 0 };
5492
+ }
5265
5493
  if (isHelpRequest(commandArguments[0])) {
5266
5494
  context.stdout(helpText);
5267
5495
  return { exitCode: 0 };
@@ -5308,10 +5536,10 @@ function createFileSystem(primitives) {
5308
5536
  rename: (oldPath, newPath) => primitives.rename(oldPath, newPath)
5309
5537
  };
5310
5538
  }
5311
- function createGlobScanner(readdir2) {
5539
+ function createGlobScanner(readdir3) {
5312
5540
  return {
5313
5541
  scan: async function* (dir) {
5314
- for (const entry of await readdir2(dir, { recursive: true })) {
5542
+ for (const entry of await readdir3(dir, { recursive: true })) {
5315
5543
  if (entry.endsWith(".md"))
5316
5544
  yield entry;
5317
5545
  }
@@ -5338,7 +5566,7 @@ async function wireEntry(fsPrimitives, processPrimitives, consolePrimitives) {
5338
5566
  }
5339
5567
 
5340
5568
  // lib/cli/run.ts
5341
- await wireEntry({ existsSync, statSync: statSync2, readFile: readFile2, writeFile: writeFile2, mkdir: mkdir2, readdir: readdir2, chmod: chmod2, rename }, {
5569
+ await wireEntry({ existsSync, statSync: statSync3, readFile: readFile3, writeFile: writeFile3, mkdir: mkdir3, readdir: readdir3, chmod: chmod3, rename }, {
5342
5570
  argv: process.argv,
5343
5571
  cwd: () => process.cwd(),
5344
5572
  exit: (code) => {
@@ -5350,4 +5578,4 @@ await wireEntry({ existsSync, statSync: statSync2, readFile: readFile2, writeFil
5350
5578
  process.stdout.write(message);
5351
5579
  },
5352
5580
  error: console.error
5353
- });
5581
+ });