@wenrwa/marketplace-cli 0.8.11 → 0.8.13

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.
Files changed (29) hide show
  1. package/dist/__tests__/bridge-cleanup-frame.test.d.ts +1 -0
  2. package/dist/__tests__/bridge-cleanup-frame.test.js +217 -0
  3. package/dist/__tests__/bridge-clone-grow-quote.test.d.ts +1 -0
  4. package/dist/__tests__/bridge-clone-grow-quote.test.js +145 -0
  5. package/dist/__tests__/bridge-disk-pressure-frame.test.d.ts +1 -0
  6. package/dist/__tests__/bridge-disk-pressure-frame.test.js +179 -0
  7. package/dist/__tests__/bridge-preview-checkout.test.js +9 -0
  8. package/dist/__tests__/bridge-storage-cleanup-real-fs.test.d.ts +1 -0
  9. package/dist/__tests__/bridge-storage-cleanup-real-fs.test.js +372 -0
  10. package/dist/__tests__/git-token-refresh.test.d.ts +1 -0
  11. package/dist/__tests__/git-token-refresh.test.js +231 -0
  12. package/dist/bridge/diskPreflight.d.ts +4 -1
  13. package/dist/bridge/diskPreflight.js +5 -3
  14. package/dist/bridge/gitTokenBroker.d.ts +28 -0
  15. package/dist/bridge/gitTokenBroker.js +124 -0
  16. package/dist/bridge/previewCheckout.d.ts +11 -0
  17. package/dist/bridge/previewCheckout.js +18 -1
  18. package/dist/bridge/relayClient.d.ts +47 -0
  19. package/dist/bridge/relayClient.js +150 -2
  20. package/dist/bridge/repoCheckout.d.ts +3 -1
  21. package/dist/bridge/repoCheckout.js +6 -1
  22. package/dist/bridge/storageCleanup.d.ts +66 -0
  23. package/dist/bridge/storageCleanup.js +337 -0
  24. package/dist/bridge/storageQuotaFetch.js +12 -0
  25. package/dist/bridge/storageQuotaPreflight.d.ts +12 -0
  26. package/dist/commands/bridge.d.ts +10 -0
  27. package/dist/commands/bridge.js +59 -3
  28. package/dist/index.js +0 -0
  29. package/package.json +1 -1
@@ -0,0 +1,337 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.BUILD_OUTPUT_DIRS = exports.CLEANUP_VERSION = exports.CLEANUP_CATEGORIES = void 0;
37
+ exports.defaultCacheDirs = defaultCacheDirs;
38
+ exports.createStorageCleaner = createStorageCleaner;
39
+ /**
40
+ * stc-05 (docs/prd/storage-billed-in-credits.md, ADR-0018; ADR-0008's floor) —
41
+ * measure and delete ONLY data the box can rebuild by itself:
42
+ *
43
+ * caches npm / uv / pip caches (the user's home, and the volume)
44
+ * previews Team preview slots (`<previews>/<ws>/{a,b}`) of a preview
45
+ * that is not running, and the shared uv cache under
46
+ * `<previews>/.tools/uv-cache` when no preview runs at all
47
+ * removedCheckouts a `<repos>/<ws>` directory with no repo left in it and
48
+ * nothing but empty dirs / build output — what an
49
+ * interrupted removal leaves behind
50
+ * buildOutput gitignored build directories (node_modules, .next, dist,
51
+ * build, target, .venv, ...) inside a CLEAN checkout
52
+ *
53
+ * Never: source, `.env` (or any other file a repo ignores that is not a known
54
+ * build directory), a checkout with uncommitted / untracked / unpushed /
55
+ * stashed work (checkoutInventory.ts's inspectDirty — the one dirty
56
+ * definition), or anything belonging to a workspace with an attached session.
57
+ * Attachment, dirtiness and preview activity are re-checked IMMEDIATELY before
58
+ * each delete (after the target is measured), so a session that attaches or a
59
+ * file written mid-run aborts that target (ADR-0008: "the attachment check
60
+ * happens immediately before removal").
61
+ *
62
+ * The figure the storage view shows ("N.N GB can be freed") is `measure()`;
63
+ * `clean()` frees exactly the targets it would have counted, each measured
64
+ * right before its delete, so the two agree unless something changed between.
65
+ *
66
+ * Automatic cleanup (stc-06, before a disk grows) is `runAutomaticCleanup()`
67
+ * and sits behind a kill switch that defaults OFF; when on it touches only the
68
+ * caches and preview categories. The user's own Clean up button (`clean()`)
69
+ * works regardless of the switch.
70
+ *
71
+ * HOSTED LANE ONLY — the local bridge never constructs one (HiveOp does not
72
+ * garbage-collect a laptop it does not own).
73
+ */
74
+ const fs = __importStar(require("fs"));
75
+ const path = __importStar(require("path"));
76
+ const bundleInstaller_1 = require("./bundleInstaller");
77
+ const checkoutInventory_1 = require("./checkoutInventory");
78
+ exports.CLEANUP_CATEGORIES = ['caches', 'previews', 'removedCheckouts', 'buildOutput'];
79
+ /** Stamped on every report so the backend/arena can tell a bridge that can clean. */
80
+ exports.CLEANUP_VERSION = 1;
81
+ /** Directory names that are build output by convention — deleted only when the repo ignores them. */
82
+ exports.BUILD_OUTPUT_DIRS = new Set([
83
+ 'node_modules',
84
+ '.next',
85
+ '.nuxt',
86
+ '.turbo',
87
+ 'dist',
88
+ 'build',
89
+ 'target',
90
+ '.venv',
91
+ '__pycache__',
92
+ ]);
93
+ /** npm / uv / pip caches under each home-like root, de-duplicated. */
94
+ function defaultCacheDirs(roots) {
95
+ const out = new Set();
96
+ for (const r of roots) {
97
+ if (!r)
98
+ continue;
99
+ for (const rel of ['.npm/_cacache', '.npm/_npx', '.cache/pip', '.cache/uv'])
100
+ out.add(path.join(r, rel));
101
+ }
102
+ return [...out];
103
+ }
104
+ function zeros() {
105
+ return { caches: 0, previews: 0, removedCheckouts: 0, buildOutput: 0 };
106
+ }
107
+ async function walkBytes(p) {
108
+ let st;
109
+ try {
110
+ st = await fs.promises.lstat(p);
111
+ }
112
+ catch {
113
+ return 0;
114
+ }
115
+ const own = typeof st.blocks === 'number' ? st.blocks * 512 : st.size;
116
+ if (!st.isDirectory())
117
+ return own;
118
+ let entries = [];
119
+ try {
120
+ entries = await fs.promises.readdir(p);
121
+ }
122
+ catch {
123
+ return own;
124
+ }
125
+ let total = own;
126
+ for (const e of entries)
127
+ total += await walkBytes(path.join(p, e));
128
+ return total;
129
+ }
130
+ const defaultRm = (p) => fs.promises.rm(p, { recursive: true, force: true });
131
+ /** A real directory (not a symlink to one). */
132
+ function isDir(p) {
133
+ try {
134
+ return fs.lstatSync(p).isDirectory();
135
+ }
136
+ catch {
137
+ return false;
138
+ }
139
+ }
140
+ function childDirs(dir) {
141
+ try {
142
+ return fs.readdirSync(dir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
143
+ }
144
+ catch {
145
+ return [];
146
+ }
147
+ }
148
+ /** Nothing in here but directories and build-output directories — no file anyone could need. */
149
+ function holdsOnlyRebuildable(dir) {
150
+ let entries;
151
+ try {
152
+ entries = fs.readdirSync(dir, { withFileTypes: true });
153
+ }
154
+ catch {
155
+ return false;
156
+ }
157
+ for (const e of entries) {
158
+ if (!e.isDirectory())
159
+ return false; // a file, symlink, socket... keep
160
+ if (exports.BUILD_OUTPUT_DIRS.has(e.name))
161
+ continue;
162
+ if (!holdsOnlyRebuildable(path.join(dir, e.name)))
163
+ return false;
164
+ }
165
+ return true;
166
+ }
167
+ function createStorageCleaner(deps) {
168
+ const sizeBytes = deps.sizeBytes ?? walkBytes;
169
+ const rm = deps.rm ?? defaultRm;
170
+ let running = false;
171
+ const activity = () => deps.previewActivity?.() ?? { opInFlight: false, activeWorkspaces: [] };
172
+ function previewHasPidfile(ws) {
173
+ return !!deps.previewsBaseDir && fs.existsSync(path.join(deps.previewsBaseDir, ws, 'preview.pid'));
174
+ }
175
+ /** Why a preview slot of `ws` (or the shared tools cache, ws=null) must stay — null when it may go. */
176
+ function previewHold(ws) {
177
+ const a = activity();
178
+ if (a.opInFlight)
179
+ return 'a preview is starting or updating';
180
+ if (ws === null) {
181
+ if (a.activeWorkspaces.length > 0)
182
+ return 'a preview is running';
183
+ const anyPid = deps.previewsBaseDir ? childDirs(deps.previewsBaseDir).some((d) => previewHasPidfile(d)) : false;
184
+ return anyPid ? 'a preview may still be running' : null;
185
+ }
186
+ if (a.activeWorkspaces.includes(ws))
187
+ return 'this preview is running';
188
+ if (previewHasPidfile(ws))
189
+ return 'this preview may still be running';
190
+ return null;
191
+ }
192
+ async function checkoutIsClean(dir) {
193
+ try {
194
+ const d = await (0, checkoutInventory_1.inspectDirty)(dir, deps.run, deps.readText);
195
+ return d !== 'unparseable' && !d.dirty;
196
+ }
197
+ catch {
198
+ return false; // inconclusive is never clean
199
+ }
200
+ }
201
+ async function ignoredBuildDirs(dir) {
202
+ let out;
203
+ try {
204
+ const r = await deps.run('git', ['-C', dir, 'ls-files', '--others', '--ignored', '--exclude-standard', '--directory', '-z']);
205
+ if (r.code !== 0)
206
+ return [];
207
+ out = r.stdout;
208
+ }
209
+ catch {
210
+ return [];
211
+ }
212
+ const root = path.resolve(dir);
213
+ const found = [];
214
+ for (const rel of out.split('\0')) {
215
+ if (!rel.endsWith('/'))
216
+ continue;
217
+ const clean = rel.replace(/\/+$/, '');
218
+ if (!exports.BUILD_OUTPUT_DIRS.has(path.basename(clean)))
219
+ continue;
220
+ if (clean.split('/').includes('.git'))
221
+ continue;
222
+ const abs = path.resolve(root, clean);
223
+ if (!abs.startsWith(root + path.sep))
224
+ continue;
225
+ if (isDir(abs))
226
+ found.push(abs);
227
+ }
228
+ return found;
229
+ }
230
+ async function plan(categories) {
231
+ const want = new Set(categories);
232
+ const targets = [];
233
+ if (want.has('caches')) {
234
+ for (const c of deps.cacheDirs)
235
+ if (isDir(c))
236
+ targets.push({ category: 'caches', path: c });
237
+ }
238
+ if (want.has('previews') && deps.previewsBaseDir) {
239
+ const base = deps.previewsBaseDir;
240
+ for (const name of childDirs(base)) {
241
+ if (name === '.tools') {
242
+ const uvCache = path.join(base, '.tools', 'uv-cache');
243
+ if (isDir(uvCache) && previewHold(null) === null)
244
+ targets.push({ category: 'previews', path: uvCache, workspaceId: null });
245
+ continue;
246
+ }
247
+ if (!(0, bundleInstaller_1.isSafeWorkspaceId)(name) || previewHold(name) !== null)
248
+ continue;
249
+ for (const slot of ['a', 'b']) {
250
+ const p = path.join(base, name, slot);
251
+ if (isDir(p))
252
+ targets.push({ category: 'previews', path: p, workspaceId: name });
253
+ }
254
+ }
255
+ }
256
+ if (want.has('removedCheckouts') || want.has('buildOutput')) {
257
+ for (const ws of childDirs(deps.reposBaseDir)) {
258
+ if (ws.startsWith('.') || !(0, bundleInstaller_1.isSafeWorkspaceId)(ws) || deps.isAttached(ws))
259
+ continue;
260
+ const dir = path.join(deps.reposBaseDir, ws);
261
+ if (fs.existsSync(path.join(dir, '.git'))) {
262
+ if (!want.has('buildOutput') || !(await checkoutIsClean(dir)))
263
+ continue;
264
+ for (const p of await ignoredBuildDirs(dir))
265
+ targets.push({ category: 'buildOutput', path: p, workspaceId: ws, checkout: dir });
266
+ }
267
+ else if (want.has('removedCheckouts') && holdsOnlyRebuildable(dir)) {
268
+ targets.push({ category: 'removedCheckouts', path: dir, workspaceId: ws });
269
+ }
270
+ }
271
+ }
272
+ // Never count (or delete) a path twice: drop any target inside another.
273
+ return targets.filter((t) => !targets.some((o) => o !== t && t.path.startsWith(o.path + path.sep)));
274
+ }
275
+ /** Re-derived right before the delete. null = still safe to remove. */
276
+ async function hold(t) {
277
+ switch (t.category) {
278
+ case 'caches':
279
+ return null;
280
+ case 'previews':
281
+ return previewHold(t.workspaceId);
282
+ case 'removedCheckouts':
283
+ if (deps.isAttached(t.workspaceId))
284
+ return 'a session attached';
285
+ if (fs.existsSync(path.join(t.path, '.git')))
286
+ return 'a checkout appeared';
287
+ return holdsOnlyRebuildable(t.path) ? null : 'files appeared';
288
+ case 'buildOutput':
289
+ if (deps.isAttached(t.workspaceId))
290
+ return 'a session attached';
291
+ return (await checkoutIsClean(t.checkout)) ? null : 'the checkout has unsaved work';
292
+ }
293
+ }
294
+ async function measure() {
295
+ const byCategory = zeros();
296
+ for (const t of await plan(exports.CLEANUP_CATEGORIES))
297
+ byCategory[t.category] += await sizeBytes(t.path).catch(() => 0);
298
+ return { reclaimableBytes: exports.CLEANUP_CATEGORIES.reduce((s, c) => s + byCategory[c], 0), byCategory };
299
+ }
300
+ async function clean(categories = exports.CLEANUP_CATEGORIES) {
301
+ if (running)
302
+ return { busy: true, freedBytes: 0, byCategory: zeros(), skipped: [] };
303
+ running = true;
304
+ try {
305
+ const byCategory = zeros();
306
+ const skipped = [];
307
+ for (const t of await plan(categories)) {
308
+ const bytes = await sizeBytes(t.path).catch(() => 0);
309
+ const why = await hold(t);
310
+ if (why) {
311
+ skipped.push({ category: t.category, path: t.path, reason: why });
312
+ continue;
313
+ }
314
+ try {
315
+ await rm(t.path);
316
+ byCategory[t.category] += bytes;
317
+ }
318
+ catch (err) {
319
+ skipped.push({ category: t.category, path: t.path, reason: err instanceof Error ? err.message : String(err) });
320
+ }
321
+ }
322
+ return { busy: false, freedBytes: exports.CLEANUP_CATEGORIES.reduce((s, c) => s + byCategory[c], 0), byCategory, skipped };
323
+ }
324
+ finally {
325
+ running = false;
326
+ }
327
+ }
328
+ async function runAutomaticCleanup() {
329
+ if (!deps.autoCleanupEnabled)
330
+ return { ran: false, reason: 'disabled' };
331
+ const r = await clean(['caches', 'previews']);
332
+ if (r.busy)
333
+ return { ran: false, reason: 'busy' };
334
+ return { ran: true, ...r };
335
+ }
336
+ return { measure, clean, runAutomaticCleanup };
337
+ }
@@ -7,6 +7,16 @@ function optString(v) {
7
7
  function isFiniteNumber(v) {
8
8
  return typeof v === 'number' && Number.isFinite(v);
9
9
  }
10
+ /** stc-04 — all three numbers finite and positive, or no quote at all. */
11
+ function parseQuote(v) {
12
+ if (!v || typeof v !== 'object')
13
+ return null;
14
+ const q = v;
15
+ const ok = (n) => isFiniteNumber(n) && n > 0;
16
+ if (!ok(q.addGb) || !ok(q.newDiskGb) || !isFiniteNumber(q.newCreditsPerDay) || q.newCreditsPerDay < 0)
17
+ return null;
18
+ return { addGb: q.addGb, newDiskGb: q.newDiskGb, newCreditsPerDay: q.newCreditsPerDay };
19
+ }
10
20
  async function fetchStorageQuotaFact(opts) {
11
21
  const fetchImpl = opts.fetchImpl ?? globalThis.fetch;
12
22
  if (!fetchImpl)
@@ -39,6 +49,7 @@ async function fetchStorageQuotaFact(opts) {
39
49
  isFiniteNumber(err.capGb) &&
40
50
  isFiniteNumber(err.usedGb) &&
41
51
  isFiniteNumber(err.additionalGb)) {
52
+ const quote = parseQuote(err.quote);
42
53
  return {
43
54
  allowed: false,
44
55
  tier: err.tier,
@@ -49,6 +60,7 @@ async function fetchStorageQuotaFact(opts) {
49
60
  suggestedCapGb: isFiniteNumber(err.suggestedCapGb) ? err.suggestedCapGb : null,
50
61
  tierName: optString(err.tierName),
51
62
  suggestedTierName: optString(err.suggestedTierName),
63
+ ...(quote ? { quote } : {}),
52
64
  };
53
65
  }
54
66
  }
@@ -68,6 +68,18 @@ export interface StorageQuotaFact {
68
68
  * Absent from an older backend — the raw id is the fallback. */
69
69
  tierName?: string | null;
70
70
  suggestedTierName?: string | null;
71
+ /** stc-04 — the backend's price for growing the disk to fit this clone
72
+ * (present only while storage billing is on and growth is possible).
73
+ * Carried to the pane untouched; the bridge never grows the disk itself —
74
+ * the grow route spends money, so the arena's Grow click makes that call
75
+ * with the user's own session. */
76
+ quote?: GrowQuote;
77
+ }
78
+ /** stc-04 — `POST /me/hive/storage/grow`'s price, quoted by the clone check. */
79
+ export interface GrowQuote {
80
+ addGb: number;
81
+ newDiskGb: number;
82
+ newCreditsPerDay: number;
71
83
  }
72
84
  /** "about 4.0 GB", or "under 0.1 GB" for a positive sliver — never a raw
73
85
  * float, never "about 0.0 GB" for something real. */
@@ -2,6 +2,16 @@ import { Command } from 'commander';
2
2
  import { CompanionForwarder } from '../bridge/companionForwarder';
3
3
  export declare const DEFAULT_BRIDGE_PORT = 8377;
4
4
  export declare function bridgeApiUrl(): string;
5
+ /**
6
+ * stc-06 — free and total GB of the volume holding `p` (the SAME statfs as
7
+ * readReposVolumeFreeMb), for the disk-pressure report on the `sessions`
8
+ * frame. null = unknown (no statfsSync, read failure): the field is omitted
9
+ * and the backend never acts on a guess.
10
+ */
11
+ export declare function readVolumeSpaceGb(p: string): {
12
+ freeGb: number;
13
+ totalGb: number;
14
+ } | null;
5
15
  export type RelayCliOpts = {
6
16
  relay?: string;
7
17
  relayToken?: string;
@@ -38,6 +38,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
38
38
  Object.defineProperty(exports, "__esModule", { value: true });
39
39
  exports.bridgeCommand = exports.DEFAULT_BRIDGE_PORT = void 0;
40
40
  exports.bridgeApiUrl = bridgeApiUrl;
41
+ exports.readVolumeSpaceGb = readVolumeSpaceGb;
41
42
  exports.resolveRelayMode = resolveRelayMode;
42
43
  exports.buildBrowserDriverOptions = buildBrowserDriverOptions;
43
44
  exports.buildCompanionForwarderEnvOptions = buildCompanionForwarderEnvOptions;
@@ -63,6 +64,7 @@ const chalk_1 = __importDefault(require("chalk"));
63
64
  const sessionManager_1 = require("../bridge/sessionManager");
64
65
  const server_1 = require("../bridge/server");
65
66
  const relayClient_1 = require("../bridge/relayClient");
67
+ const gitTokenBroker_1 = require("../bridge/gitTokenBroker");
66
68
  const browserDriver_1 = require("../bridge/browserDriver");
67
69
  const browserHost_1 = require("../bridge/browserHost");
68
70
  const browserIdle_1 = require("../bridge/browserIdle");
@@ -124,6 +126,7 @@ const companionForwarder_1 = require("../bridge/companionForwarder");
124
126
  const previewForwarder_1 = require("../bridge/previewForwarder");
125
127
  // tp-05 — the Team preview checkout runner (hosted lane only, ADR-0014).
126
128
  const previewCheckout_1 = require("../bridge/previewCheckout");
129
+ const storageCleanup_1 = require("../bridge/storageCleanup");
127
130
  /** lws-12 — real fs binding for writeGitignore.ts: mirrors realLocalContextWriterDeps
128
131
  * above, but a `.gitignore` may already hold user content, so this is a plain
129
132
  * read + overwrite (append), never atomic-rename — there's no concurrent
@@ -320,6 +323,28 @@ function readReposVolumeUsedGb(p) {
320
323
  return null;
321
324
  }
322
325
  }
326
+ /**
327
+ * stc-06 — free and total GB of the volume holding `p` (the SAME statfs as
328
+ * readReposVolumeFreeMb), for the disk-pressure report on the `sessions`
329
+ * frame. null = unknown (no statfsSync, read failure): the field is omitted
330
+ * and the backend never acts on a guess.
331
+ */
332
+ function readVolumeSpaceGb(p) {
333
+ try {
334
+ const statfsSync = fs.statfsSync;
335
+ if (!statfsSync)
336
+ return null;
337
+ const stats = statfsSync(p);
338
+ const GB = 1024 * 1024 * 1024;
339
+ const totalGb = (stats.blocks * stats.bsize) / GB;
340
+ if (!(totalGb > 0))
341
+ return null;
342
+ return { freeGb: (stats.bavail * stats.bsize) / GB, totalGb };
343
+ }
344
+ catch {
345
+ return null;
346
+ }
347
+ }
323
348
  /**
324
349
  * hcs-05/hcs-07 — recursive+force removal, shared by checkoutReclaimDeps.rm
325
350
  * (removing a RECLAIM-verdict checkout) and resolveWorkspaceCwd's cleanup of
@@ -650,17 +675,28 @@ exports.bridgeCommand = new commander_1.Command('bridge')
650
675
  // .env, served on 127.0.0.1:<preview port> (the port the forwarder
651
676
  // above targets). Container branch only; the local bridge's relay
652
677
  // client answers every preview frame `unsupported / local_lane`.
678
+ const previewsBaseDir = process.env.WENRWA_PREVIEWS_DIR || path.join(path.dirname(reposBaseDir), 'previews');
653
679
  const previewRunner = new previewCheckout_1.PreviewRunner({
654
- previewsBaseDir: process.env.WENRWA_PREVIEWS_DIR || path.join(path.dirname(reposBaseDir), 'previews'),
680
+ previewsBaseDir,
655
681
  workingCopyDir: (workspaceId) => path.join(reposBaseDir, workspaceId),
656
682
  port: (0, previewForwarder_1.resolvePreviewPorts)(process.env).previewPort,
657
683
  });
658
684
  // cvt-07: the box's one shared Chromium, created lazily by the first tab.
659
685
  let browserHost;
660
- (0, relayClient_1.startRelayClient)({
686
+ // a9be9720 — agents' git credentials stay fresh past the hour the
687
+ // injected one lives: git-credential-wenrwa asks this socket, the broker
688
+ // asks the relay. Hosted lane only — a laptop pushes with its own git.
689
+ const gitTokenSocket = process.env.WENRWA_GIT_TOKEN_SOCKET || path.join(os.homedir(), '.wenrwa', 'git-token.sock');
690
+ let relayClient = null;
691
+ const gitTokenBroker = new gitTokenBroker_1.GitTokenBroker((workspaceId) => relayClient
692
+ ? relayClient.requestGitToken(workspaceId)
693
+ : Promise.reject(Object.assign(new Error('The bridge is starting'), { code: 'OFFLINE' })));
694
+ (0, gitTokenBroker_1.startGitTokenSocket)({ path: gitTokenSocket, broker: gitTokenBroker }).catch((err) => console.error(`[bridge] git token socket failed to start — agents fall back to the token from open: ${err instanceof Error ? err.message : String(err)}`));
695
+ relayClient = (0, relayClient_1.startRelayClient)({
661
696
  relayUrl: relayMode.relayUrl,
662
697
  relayToken: relayMode.relayToken,
663
698
  sessions,
699
+ gitTokenSocket,
664
700
  // Rock-solid sleep/wake: this is the HOSTED container bridge, the only
665
701
  // process that knows which agent PTYs are actually running. The backend
666
702
  // reconciles claude_code_agents.lifecycle_state against this report —
@@ -743,12 +779,32 @@ exports.bridgeCommand = new commander_1.Command('bridge')
743
779
  exists: (p) => fs.existsSync(p),
744
780
  run: execRunner,
745
781
  },
782
+ // stc-05 — Clean up: measure + delete only rebuildable data (npm/uv/
783
+ // pip caches in the home dir and on the volume, preview slots not in
784
+ // use, removed-checkout leftovers, build output in clean repos). The
785
+ // button always works; AUTOMATIC cleanup (stc-06, before a disk
786
+ // grows) stays off unless HIVEOP_AUTO_CLEANUP=1 — the ADR-0008 kill
787
+ // switch. HOSTED LANE ONLY, like enableReclaim.
788
+ enableStorageCleanup: true,
789
+ storageCleanupDeps: {
790
+ previewsBaseDir,
791
+ cacheDirs: (0, storageCleanup_1.defaultCacheDirs)([os.homedir(), process.env.HOME, path.dirname(reposBaseDir)]),
792
+ previewActivity: () => previewRunner.activity(),
793
+ run: execRunner,
794
+ sizeBytes: duSizeBytes,
795
+ rm: rmCheckoutDir,
796
+ autoCleanupEnabled: process.env.HIVEOP_AUTO_CLEANUP === '1',
797
+ },
746
798
  // Rock-solid sleep/wake — refuse a PTY spawn the box can't hold,
747
799
  // rather than OOM-killing it behind an indefinite "Waking the
748
800
  // machine…" (production 2026-08-14: MemAvailable: 0 kB). Linux-only
749
801
  // by nature; a missing /proc/meminfo parses to null, which is UNKNOWN
750
802
  // and spawns exactly as before.
751
803
  readMemInfo: readContainerMemInfo,
804
+ // stc-06 — free space on the checkouts' volume rides every `sessions`
805
+ // frame; the backend grows a nearly-full disk while agents work
806
+ // (cleanup first, kill switch above). HOSTED LANE ONLY.
807
+ readDiskSpace: () => readVolumeSpaceGb(reposBaseDir),
752
808
  checkLoginValid: () => (0, loginStatus_1.checkClaudeLoginValid)({
753
809
  claudeDir,
754
810
  exists: (p) => fs.existsSync(p),
@@ -776,7 +832,7 @@ exports.bridgeCommand = new commander_1.Command('bridge')
776
832
  // failure here gets. `result.code` is threaded through so the
777
833
  // frame the pane receives carries the RIGHT code — free-space
778
834
  // and quota refusals must never collapse onto the same one.
779
- throw new diskPreflight_1.ClonePreflightRefusalError(result.message, result.code);
835
+ throw new diskPreflight_1.ClonePreflightRefusalError(result.message, result.code, result.quote);
780
836
  }
781
837
  // Playtest 2026-10-01 — Claude Code opens a fresh checkout on its
782
838
  // folder-trust dialog (default "No, exit") even in bypass mode. The
package/dist/index.js CHANGED
File without changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wenrwa/marketplace-cli",
3
- "version": "0.8.11",
3
+ "version": "0.8.13",
4
4
  "description": "CLI tool for the Wenrwa Agent Marketplace — monitor bounties, agents, workspaces, and treasury from your terminal",
5
5
  "author": "Wenrwa <dev@wenrwa.com>",
6
6
  "license": "MIT",