harnery 0.41.0 → 0.42.0
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/commander.js +3 -0
- package/dist/commands/agents.d.ts.map +1 -1
- package/dist/commands/agents.js +9 -1
- package/dist/commands/artifacts.d.ts.map +1 -1
- package/dist/commands/artifacts.js +113 -12
- package/dist/commands/browse.d.ts +164 -0
- package/dist/commands/browse.d.ts.map +1 -1
- package/dist/commands/browse.js +263 -51
- package/dist/commands/claude-desktop.d.ts.map +1 -1
- package/dist/commands/claude-desktop.js +52 -0
- package/dist/commands/disk.d.ts +6 -0
- package/dist/commands/disk.d.ts.map +1 -0
- package/dist/commands/disk.js +80 -0
- package/dist/commands/doctor.d.ts +8 -0
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +48 -1
- package/dist/commands/files.d.ts +16 -0
- package/dist/commands/files.d.ts.map +1 -1
- package/dist/commands/files.js +54 -2
- package/dist/commands/init.d.ts +2 -2
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +43 -9
- package/dist/commands/rm.d.ts +4 -0
- package/dist/commands/rm.d.ts.map +1 -0
- package/dist/commands/rm.js +87 -0
- package/dist/commands/servers.d.ts +7 -0
- package/dist/commands/servers.d.ts.map +1 -0
- package/dist/commands/servers.js +394 -0
- package/dist/commands/tunnel.d.ts.map +1 -1
- package/dist/commands/tunnel.js +82 -2
- package/dist/core/adapters/profiles.d.ts +9 -0
- package/dist/core/adapters/profiles.d.ts.map +1 -1
- package/dist/core/adapters/profiles.js +22 -1
- package/dist/core/adapters/types.d.ts +9 -0
- package/dist/core/adapters/types.d.ts.map +1 -1
- package/dist/core/agents/cli.js +13 -6
- package/dist/core/agents/render/session-context.d.ts.map +1 -1
- package/dist/core/agents/render/session-context.js +2 -0
- package/dist/core/agents/state/live-coordination-view.d.ts +20 -0
- package/dist/core/agents/state/live-coordination-view.d.ts.map +1 -1
- package/dist/core/agents/state/live-coordination-view.js +60 -0
- package/dist/core/artifacts/delivery-card.js +9 -2
- package/dist/core/artifacts/index.d.ts +140 -4
- package/dist/core/artifacts/index.d.ts.map +1 -1
- package/dist/core/artifacts/index.js +522 -49
- package/dist/core/config.d.ts +68 -11
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js +128 -3
- package/dist/core/diagnostics/bundle.d.ts.map +1 -1
- package/dist/core/diagnostics/bundle.js +2 -2
- package/dist/core/events/v3/producers/recorder.d.ts.map +1 -1
- package/dist/core/events/v3/producers/recorder.js +16 -0
- package/dist/core/events/v3/projection.js +9 -1
- package/dist/core/hooks/adapter/events.d.ts +17 -0
- package/dist/core/hooks/adapter/events.d.ts.map +1 -1
- package/dist/core/hooks/adapter/events.js +5 -0
- package/dist/core/hooks/adapter/runtime-telemetry.js +3 -1
- package/dist/core/hooks/adapter/wiring.d.ts +37 -7
- package/dist/core/hooks/adapter/wiring.d.ts.map +1 -1
- package/dist/core/hooks/adapter/wiring.js +62 -4
- package/dist/core/hooks/cli.js +45 -6
- package/dist/core/hooks/run-markers.d.ts +37 -0
- package/dist/core/hooks/run-markers.d.ts.map +1 -0
- package/dist/core/hooks/run-markers.js +109 -0
- package/dist/core/servers/index.d.ts +211 -0
- package/dist/core/servers/index.d.ts.map +1 -0
- package/dist/core/servers/index.js +560 -0
- package/dist/core/servers/net.d.ts +58 -0
- package/dist/core/servers/net.d.ts.map +1 -0
- package/dist/core/servers/net.js +270 -0
- package/dist/core/servers/tunnels.d.ts +14 -0
- package/dist/core/servers/tunnels.d.ts.map +1 -0
- package/dist/core/servers/tunnels.js +33 -0
- package/dist/core/storage/builtins.js +1 -0
- package/dist/core/workflow/engine.d.ts.map +1 -1
- package/dist/core/workflow/engine.js +30 -1
- package/dist/core/workflow/sandbox-projection.d.ts +13 -2
- package/dist/core/workflow/sandbox-projection.d.ts.map +1 -1
- package/dist/core/workflow/sandbox-projection.js +23 -0
- package/dist/core/workflow/spawn-claude.d.ts.map +1 -1
- package/dist/core/workflow/spawn-claude.js +6 -1
- package/dist/core/workflow/spawn-codex.d.ts +2 -0
- package/dist/core/workflow/spawn-codex.d.ts.map +1 -1
- package/dist/core/workflow/spawn-codex.js +20 -1
- package/dist/core/workflow/spawn-cursor.d.ts.map +1 -1
- package/dist/core/workflow/spawn-cursor.js +6 -1
- package/dist/core/workflow/spawn-opencode.d.ts.map +1 -1
- package/dist/core/workflow/spawn-opencode.js +4 -1
- package/dist/core/workflow/types.d.ts +47 -2
- package/dist/core/workflow/types.d.ts.map +1 -1
- package/dist/core/workflow/worker-access.d.ts +92 -0
- package/dist/core/workflow/worker-access.d.ts.map +1 -0
- package/dist/core/workflow/worker-access.js +450 -0
- package/dist/lib/browser/batch-steps.d.ts +18 -0
- package/dist/lib/browser/batch-steps.d.ts.map +1 -0
- package/dist/lib/browser/batch-steps.js +82 -0
- package/dist/lib/browser/collect/collector.d.ts +99 -0
- package/dist/lib/browser/collect/collector.d.ts.map +1 -0
- package/dist/lib/browser/collect/collector.js +202 -0
- package/dist/lib/browser/collect/fields.d.ts +31 -0
- package/dist/lib/browser/collect/fields.d.ts.map +1 -0
- package/dist/lib/browser/collect/fields.js +75 -0
- package/dist/lib/browser/collect/index.d.ts +6 -0
- package/dist/lib/browser/collect/index.d.ts.map +1 -0
- package/dist/lib/browser/collect/index.js +5 -0
- package/dist/lib/browser/collect/page-driver.d.ts +14 -0
- package/dist/lib/browser/collect/page-driver.d.ts.map +1 -0
- package/dist/lib/browser/collect/page-driver.js +306 -0
- package/dist/lib/browser/collect/presets.d.ts +52 -0
- package/dist/lib/browser/collect/presets.d.ts.map +1 -0
- package/dist/lib/browser/collect/presets.js +176 -0
- package/dist/lib/browser/collect/scroll-plan.d.ts +85 -0
- package/dist/lib/browser/collect/scroll-plan.d.ts.map +1 -0
- package/dist/lib/browser/collect/scroll-plan.js +118 -0
- package/dist/lib/browser/index.d.ts +1 -1
- package/dist/lib/browser/index.d.ts.map +1 -1
- package/dist/lib/browser/page-review-judge.d.ts +2 -0
- package/dist/lib/browser/page-review-judge.d.ts.map +1 -1
- package/dist/lib/browser/page-review-judge.js +13 -0
- package/dist/lib/browser/qa-run-contracts.d.ts +10 -0
- package/dist/lib/browser/qa-run-contracts.d.ts.map +1 -1
- package/dist/lib/browser/qa-run.d.ts.map +1 -1
- package/dist/lib/browser/qa-run.js +1 -0
- package/dist/lib/browser/windows-chrome.d.ts +59 -0
- package/dist/lib/browser/windows-chrome.d.ts.map +1 -0
- package/dist/lib/browser/windows-chrome.js +179 -0
- package/dist/lib/claude-desktop-share.d.ts +136 -0
- package/dist/lib/claude-desktop-share.d.ts.map +1 -0
- package/dist/lib/claude-desktop-share.js +394 -0
- package/dist/lib/disk-usage.d.ts +50 -0
- package/dist/lib/disk-usage.d.ts.map +1 -0
- package/dist/lib/disk-usage.js +303 -0
- package/dist/lib/docs-lint.d.ts +6 -0
- package/dist/lib/docs-lint.d.ts.map +1 -1
- package/dist/lib/docs-lint.js +8 -3
- package/dist/lib/guarded-remove.d.ts +34 -0
- package/dist/lib/guarded-remove.d.ts.map +1 -0
- package/dist/lib/guarded-remove.js +310 -0
- package/dist/lib/instructions/templates.js +8 -8
- package/dist/lib/tunnel/error-page.d.ts +1 -1
- package/dist/lib/tunnel/error-page.d.ts.map +1 -1
- package/dist/lib/tunnel/error-page.js +19 -8
- package/dist/lib/tunnel/gate.js +20 -1
- package/dist/lib/tunnel/path-scope.d.ts +22 -0
- package/dist/lib/tunnel/path-scope.d.ts.map +1 -0
- package/dist/lib/tunnel/path-scope.js +62 -0
- package/dist/lib/tunnel/state.d.ts +5 -0
- package/dist/lib/tunnel/state.d.ts.map +1 -1
- package/dist/lib/tunnel/state.js +17 -3
- package/package.json +6 -1
- package/schemas/config.schema.json +91 -0
- package/src/commander.ts +19 -0
- package/src/commands/agents.ts +14 -0
- package/src/commands/artifacts.ts +171 -19
- package/src/commands/browse.ts +400 -57
- package/src/commands/claude-desktop.ts +61 -0
- package/src/commands/disk.ts +114 -0
- package/src/commands/doctor.ts +52 -1
- package/src/commands/files.ts +93 -2
- package/src/commands/init.ts +55 -5
- package/src/commands/rm.ts +115 -0
- package/src/commands/servers.ts +506 -0
- package/src/commands/tunnel.ts +102 -2
- package/src/core/adapters/profiles.ts +22 -1
- package/src/core/adapters/types.ts +10 -0
- package/src/core/agents/cli.ts +14 -6
- package/src/core/agents/render/session-context.ts +1 -0
- package/src/core/agents/state/live-coordination-view.ts +73 -0
- package/src/core/artifacts/delivery-card.ts +10 -2
- package/src/core/artifacts/index.ts +697 -44
- package/src/core/config.ts +183 -4
- package/src/core/diagnostics/bundle.ts +2 -1
- package/src/core/events/v3/producers/recorder.ts +16 -0
- package/src/core/events/v3/projection.ts +11 -1
- package/src/core/hooks/adapter/events.ts +19 -0
- package/src/core/hooks/adapter/runtime-telemetry.ts +3 -1
- package/src/core/hooks/adapter/wiring.ts +116 -8
- package/src/core/hooks/cli.ts +47 -6
- package/src/core/hooks/run-markers.ts +132 -0
- package/src/core/servers/index.ts +712 -0
- package/src/core/servers/net.ts +283 -0
- package/src/core/servers/tunnels.ts +39 -0
- package/src/core/storage/builtins.ts +1 -0
- package/src/core/workflow/engine.ts +38 -1
- package/src/core/workflow/sandbox-projection.ts +38 -2
- package/src/core/workflow/spawn-claude.ts +5 -1
- package/src/core/workflow/spawn-codex.ts +18 -1
- package/src/core/workflow/spawn-cursor.ts +5 -1
- package/src/core/workflow/spawn-opencode.ts +4 -1
- package/src/core/workflow/types.ts +62 -2
- package/src/core/workflow/worker-access.ts +525 -0
- package/src/lib/browser/batch-steps.ts +79 -0
- package/src/lib/browser/collect/collector.ts +306 -0
- package/src/lib/browser/collect/fields.ts +87 -0
- package/src/lib/browser/collect/index.ts +47 -0
- package/src/lib/browser/collect/page-driver.ts +327 -0
- package/src/lib/browser/collect/presets.ts +237 -0
- package/src/lib/browser/collect/scroll-plan.ts +179 -0
- package/src/lib/browser/index.ts +1 -0
- package/src/lib/browser/page-review-judge.ts +16 -0
- package/src/lib/browser/qa-run-contracts.ts +11 -0
- package/src/lib/browser/qa-run.ts +1 -0
- package/src/lib/browser/windows-chrome.ts +227 -0
- package/src/lib/claude-desktop-share.ts +511 -0
- package/src/lib/disk-usage.ts +347 -0
- package/src/lib/docs-lint.ts +8 -3
- package/src/lib/guarded-remove.ts +376 -0
- package/src/lib/instructions/templates.ts +8 -8
- package/src/lib/tunnel/error-page.ts +25 -9
- package/src/lib/tunnel/gate.ts +22 -1
- package/src/lib/tunnel/path-scope.ts +73 -0
- package/src/lib/tunnel/state.ts +20 -3
package/dist/commands/browse.js
CHANGED
|
@@ -5,8 +5,11 @@ import { dirname, isAbsolute, resolve } from "node:path";
|
|
|
5
5
|
import { chromium } from "playwright";
|
|
6
6
|
import { PNG } from "pngjs";
|
|
7
7
|
import { resolveBinName } from "../core/config.js";
|
|
8
|
+
import { splitBatchSteps } from "../lib/browser/batch-steps.js";
|
|
8
9
|
import { CAPTURE_FIDELITY_MISMATCH_THRESHOLD, compareBand, cropNativePng, decideFidelity, pngDimensions, } from "../lib/browser/capture-fidelity.js";
|
|
10
|
+
import { createPageDriver, createRng, DEFAULT_COLLECT_LIMITS, PACING_LIMITS, parseProbability, parseRange, presetNames, randomSeed, resolveCollectSpec, runCollect, scrollPacingFromEnv, } from "../lib/browser/collect/index.js";
|
|
9
11
|
import { Browser, browserProxyFromEnv, browserProxyGateFromEnv, captureDevOverlay, chromeMajorFromExecutable, DEFAULT_CRITIQUE_RUBRIC, extractObservedIp, installedChromeChannel, installedChromePath, parseAssertSpec, resolveUserAgent, runCritique, tilesFromFullPage, WEBRTC_PROXY_ONLY_ARG, writeNetscapeCookieFile, wslHeadedLaunchArgs, } from "../lib/browser/index.js";
|
|
12
|
+
import { isWSL } from "../lib/browser/launch-args.js";
|
|
10
13
|
import { allocateTileBudget, validatePageReviewAllocation, } from "../lib/browser/page-review-budget.js";
|
|
11
14
|
import { PAGE_REVIEW_CAPTURE_PLAN_SCHEMA, } from "../lib/browser/page-review-contracts.js";
|
|
12
15
|
import { findPackTile, gateHitsFromEnvelope, readPackContext, tileId, writePackContext, writePackExpandedTile, } from "../lib/browser/page-review-pack.js";
|
|
@@ -19,7 +22,8 @@ import { startBrowserSessionServer, } from "../lib/browser/session-control.js";
|
|
|
19
22
|
import { captureThumbnailFingerprint, registerCaptureThumbnail, } from "../lib/browser/thumbnail-association.js";
|
|
20
23
|
import { reviewCandidateRects } from "../lib/browser/tiling.js";
|
|
21
24
|
import { diffAgainstBaseline, saveBaseline, } from "../lib/browser/visual-diff.js";
|
|
22
|
-
import {
|
|
25
|
+
import { harvestDomain, harvestWindowsChromeCookies, stopWindowsChromeProfile, windowsChromePath, windowsLocalAppData, windowsPlainProfile, wslPathToWindows, } from "../lib/browser/windows-chrome.js";
|
|
26
|
+
import { applyExtraCookies, CookieJar, mergeCookies } from "../lib/cookies/index.js";
|
|
23
27
|
import { commandPaceGate } from "../lib/pace/index.js";
|
|
24
28
|
/**
|
|
25
29
|
* `harn browse <url>`: Playwright-backed page navigation with shared
|
|
@@ -73,21 +77,46 @@ export function registerBrowseCommand(program, emitParam, context, binName = res
|
|
|
73
77
|
.option("--capture-evaluate <js>", "In trio mode, evaluate JS inside the exact screenshot viewport immediately before capture; result is written as captureEval")
|
|
74
78
|
.option("--batch <steps>", "Run multiple steps in one session, semicolon-separated. Each step is one of: " +
|
|
75
79
|
"`click <selector>`, `fill <selector=>value>`, `press <key>`, `wait <selector|ms>`, `eval <js>`, `reload`. " +
|
|
76
|
-
'Example: `--batch "click button; wait 1500; reload; wait 3000"`. `reload` preserves sessionStorage + cookies, which is how to repro sessionStorage-restored UI state.'
|
|
80
|
+
'Example: `--batch "click button; wait 1500; reload; wait 3000"`. `reload` preserves sessionStorage + cookies, which is how to repro sessionStorage-restored UI state. ' +
|
|
81
|
+
"An `eval` step keeps semicolons inside JS quotes and (), [], {}; `\\;` is always a literal semicolon.")
|
|
82
|
+
.option("--collect [preset]", "Scroll an infinite feed like a reader and collect every item that passes through the DOM, " +
|
|
83
|
+
`de-duplicated, as JSON. Presets: ${presetNames().join(", ")} (default generic). ` +
|
|
84
|
+
"Skips the trio, checks and --evaluate; runs after --wait-for and --batch.")
|
|
85
|
+
.option("--collect-item <css>", "Item selector (overrides the preset's).")
|
|
86
|
+
.option("--collect-key <extractor>", "Dedupe key extractor, <selector>[@attr] inside the item. Default: the preset key, else the " +
|
|
87
|
+
"first permalink-like link, else a hash of the item text.")
|
|
88
|
+
.option("--collect-key-pattern <regex>", "Regex a link must match to count as a permalink key.")
|
|
89
|
+
.option("--collect-field <name=extractor>", "Add or override a field: name=<selector>[@attr]. name[]= collects every match. Empty selector " +
|
|
90
|
+
"or :scope is the item itself; no @attr reads visible text (repeatable).", (value, previous = []) => [...previous, value], [])
|
|
91
|
+
.option("--collect-expand [selector]", "Click 'show more' buttons inside visible items before harvesting (preset selector by default). " +
|
|
92
|
+
"At most 3 per step and 50 per run; links and action buttons are never clicked.")
|
|
93
|
+
.option("--collect-max-items <n>", "Stop after this many unique items.")
|
|
94
|
+
.option("--collect-max-scrolls <n>", "Stop after this many scroll steps (default 60).", "60")
|
|
95
|
+
.option("--collect-max-seconds <n>", "Stop after this much wall time (default 300).", "300")
|
|
96
|
+
.option("--collect-idle <n>", "Stop after this many steps with no new items and no page growth (default 5).", "5")
|
|
97
|
+
.option("--collect-step <min-max>", "Scroll distance per step as a viewport fraction (default 0.6-1.1; env HARNERY_COLLECT_STEP).")
|
|
98
|
+
.option("--collect-pause <min-max>", "Pause after each step in ms (default 1200-3500; env HARNERY_COLLECT_PAUSE_MS).")
|
|
99
|
+
.option("--collect-read-chance <p>", "Chance of a longer reading pause per step (default 0.12).")
|
|
100
|
+
.option("--collect-read-pause <min-max>", "Reading pause in ms (default 4000-9000).")
|
|
101
|
+
.option("--collect-back-chance <p>", "Chance a step starts with a small scroll back up (default 0.08).")
|
|
102
|
+
.option("--collect-seed <n>", "Seed for the scroll schedule; the envelope reports the seed used.")
|
|
103
|
+
.option("--collect-format <format>", "json (envelope, default) or jsonl (one item per line).", "json")
|
|
104
|
+
.option("--collect-out <path>", "Write the collection to this file instead of stdout.")
|
|
77
105
|
.option("--network-har <path>", "Record network traffic to a HAR file (finalized on close)")
|
|
78
106
|
.option("--viewport <preset|WxH>", "Viewport: mobile (390x844), tablet (820x1180), desktop (1280x800), hd (1920x1080), or explicit '1920x1080'. Default: real window in headed mode, desktop in headless mode")
|
|
79
107
|
.option("--color-scheme <scheme>", "Emulate prefers-color-scheme for the whole session: light | dark. " +
|
|
80
108
|
"Renders theme-aware pages in that scheme without page-specific toggles. " +
|
|
81
109
|
"Absent = browser default (unchanged behavior).")
|
|
82
|
-
.option("--login", "
|
|
83
|
-
|
|
84
|
-
"
|
|
85
|
-
"
|
|
86
|
-
"
|
|
87
|
-
"
|
|
110
|
+
.option("--login", "Open the URL in the installed Google Chrome with nothing attached (no DevTools protocol, no " +
|
|
111
|
+
"Playwright) so a person can sign in; under WSL this is the Windows host's Chrome. Sign in, then " +
|
|
112
|
+
"close the window or create the --login-close-file; later browse and browse-session runs reuse " +
|
|
113
|
+
"the cookies. Sites cannot tell this window from the person's own browser.")
|
|
114
|
+
.option("--attached", "With --login: open an automation-controlled Chrome that this process and --control-file can drive. " +
|
|
115
|
+
"Sites can detect it and may lock the account, so use it only for agent-driven headed sessions on " +
|
|
116
|
+
"sites you own. Never use it to sign in to a third-party account.")
|
|
88
117
|
.option("--login-close-file <path>", "With --login, wait for this file instead of terminal Enter, then remove it and close cleanly")
|
|
89
|
-
.option("--control-file <path>", "With --login, publish an owner-only descriptor for repeated browse-session control")
|
|
90
|
-
.option("--headed", "
|
|
118
|
+
.option("--control-file <path>", "With --login --attached, publish an owner-only descriptor for repeated browse-session control")
|
|
119
|
+
.option("--headed", "Automation-controlled headed window for one-off visual checks. Not for signing in; use --login.")
|
|
91
120
|
.option("--browser-channel <name>", "Browser to launch: chrome | chrome-beta | msedge | chromium. Headed launches (--login, --headed) " +
|
|
92
121
|
"default to the installed Google Chrome with the automation signals removed, because the bundled " +
|
|
93
122
|
'"Chrome for Testing" build advertises itself and sign-in flows refuse clicks; chromium forces the ' +
|
|
@@ -314,7 +343,8 @@ async function runBrowse(url, opts, context) {
|
|
|
314
343
|
: new CookieJar({ path: opts.store ?? DEFAULT_STORE, source: "harn-browse" });
|
|
315
344
|
if (jar)
|
|
316
345
|
applyExtraCookies(url, jar, context?.extraCookies);
|
|
317
|
-
const
|
|
346
|
+
const loginMode = resolveLoginMode(opts);
|
|
347
|
+
const headed = loginMode === "attached" || opts.headed;
|
|
318
348
|
const viewport = parseViewport(opts.viewport ?? "desktop");
|
|
319
349
|
const deviceScaleFactor = parseDeviceScaleFactor(opts.deviceScaleFactor);
|
|
320
350
|
if (opts.reviewPackAllocation && !opts.reviewPack) {
|
|
@@ -340,23 +370,13 @@ async function runBrowse(url, opts, context) {
|
|
|
340
370
|
}
|
|
341
371
|
const proxy = opts.proxyFromEnv ? browserProxyFromEnv() : undefined;
|
|
342
372
|
const proxyGate = opts.proxyFromEnv ? browserProxyGateFromEnv() : null;
|
|
343
|
-
if (opts.loginCloseFile && !opts.login) {
|
|
344
|
-
throw new Error("--login-close-file requires --login.");
|
|
345
|
-
}
|
|
346
|
-
if (opts.controlFile && !opts.login) {
|
|
347
|
-
throw new Error("--control-file requires --login.");
|
|
348
|
-
}
|
|
349
373
|
const loginCloseFile = opts.loginCloseFile ? resolve(opts.loginCloseFile) : null;
|
|
350
374
|
if (loginCloseFile && existsSync(loginCloseFile)) {
|
|
351
375
|
throw new Error(`Login close signal already exists at ${loginCloseFile}. Remove the stale file before launching.`);
|
|
352
376
|
}
|
|
377
|
+
const collectPlan = opts.collect ? buildCollectPlan(opts) : null;
|
|
353
378
|
const pace = commandPaceGate(opts.pace !== false, (message) => emit.log(message, "info"));
|
|
354
|
-
if (
|
|
355
|
-
if (!opts.login)
|
|
356
|
-
throw new Error("--plain requires --login.");
|
|
357
|
-
if (opts.controlFile) {
|
|
358
|
-
throw new Error("--plain cannot take --control-file: nothing is attached to drive the window.");
|
|
359
|
-
}
|
|
379
|
+
if (loginMode === "plain") {
|
|
360
380
|
const plainExecutable = installedChromePath();
|
|
361
381
|
const plainUa = resolveUserAgent({
|
|
362
382
|
requested: opts.userAgent,
|
|
@@ -364,6 +384,10 @@ async function runBrowse(url, opts, context) {
|
|
|
364
384
|
chromeMajor: plainExecutable ? chromeMajorFromExecutable(plainExecutable) : undefined,
|
|
365
385
|
});
|
|
366
386
|
await pace?.before(url);
|
|
387
|
+
if (useWindowsPlainLogin()) {
|
|
388
|
+
await runWindowsPlainLogin(url, opts.store ?? DEFAULT_STORE, loginCloseFile, emit);
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
367
391
|
await runPlainLogin(url, opts.profile ?? DEFAULT_PROFILE, loginCloseFile, plainUa, emit);
|
|
368
392
|
return;
|
|
369
393
|
}
|
|
@@ -434,6 +458,10 @@ async function runBrowse(url, opts, context) {
|
|
|
434
458
|
if (opts.batch) {
|
|
435
459
|
batchResult = await runBatch(browser, opts.batch, Number.parseInt(opts.timeout, 10));
|
|
436
460
|
}
|
|
461
|
+
if (collectPlan) {
|
|
462
|
+
await runCollectMode(browser, collectPlan);
|
|
463
|
+
return;
|
|
464
|
+
}
|
|
437
465
|
let evalResult;
|
|
438
466
|
if (opts.evaluate) {
|
|
439
467
|
evalResult = await browser.evaluate(opts.evaluate);
|
|
@@ -824,11 +852,11 @@ async function runBrowse(url, opts, context) {
|
|
|
824
852
|
const lifecycleAbort = new AbortController();
|
|
825
853
|
const closeWaits = [];
|
|
826
854
|
if (loginCloseFile) {
|
|
827
|
-
emit.log(`[--login]
|
|
855
|
+
emit.log(`[--login --attached] Automation-controlled Chrome is open. Drive the visible window now. Create ${loginCloseFile} to close cleanly.`, "info");
|
|
828
856
|
closeWaits.push(waitForLoginCloseFile(loginCloseFile, lifecycleAbort.signal));
|
|
829
857
|
}
|
|
830
858
|
else {
|
|
831
|
-
emit.log("[--login]
|
|
859
|
+
emit.log("[--login --attached] Automation-controlled Chrome is open. Drive the visible window now. Press Enter here to close + persist cookies into the profile.", "info");
|
|
832
860
|
closeWaits.push(waitForTerminalEnter(lifecycleAbort.signal));
|
|
833
861
|
}
|
|
834
862
|
if (controlServer) {
|
|
@@ -964,6 +992,137 @@ async function runBrowse(url, opts, context) {
|
|
|
964
992
|
process.exitCode = receivedSignal === "SIGINT" ? 130 : 143;
|
|
965
993
|
}
|
|
966
994
|
}
|
|
995
|
+
function parsePositiveInt(raw, flag) {
|
|
996
|
+
if (!/^\d+$/.test(raw.trim()) || Number.parseInt(raw, 10) < 1) {
|
|
997
|
+
throw new Error(`${flag} must be a positive integer (got "${raw}").`);
|
|
998
|
+
}
|
|
999
|
+
return Number.parseInt(raw, 10);
|
|
1000
|
+
}
|
|
1001
|
+
function buildCollectPlan(opts) {
|
|
1002
|
+
if (opts.login)
|
|
1003
|
+
throw new Error("--collect cannot run with --login.");
|
|
1004
|
+
if (opts.snapshot || opts.html || opts.json) {
|
|
1005
|
+
throw new Error("--collect prints its own JSON; drop --snapshot, --html and --json.");
|
|
1006
|
+
}
|
|
1007
|
+
const presetName = typeof opts.collect === "string" ? opts.collect : "generic";
|
|
1008
|
+
const spec = resolveCollectSpec(presetName, {
|
|
1009
|
+
item: opts.collectItem,
|
|
1010
|
+
key: opts.collectKey,
|
|
1011
|
+
keyPattern: opts.collectKeyPattern,
|
|
1012
|
+
fields: opts.collectField,
|
|
1013
|
+
expand: opts.collectExpand,
|
|
1014
|
+
});
|
|
1015
|
+
const limits = {
|
|
1016
|
+
...DEFAULT_COLLECT_LIMITS,
|
|
1017
|
+
maxItems: opts.collectMaxItems === undefined
|
|
1018
|
+
? null
|
|
1019
|
+
: parsePositiveInt(opts.collectMaxItems, "--collect-max-items"),
|
|
1020
|
+
maxScrolls: parsePositiveInt(opts.collectMaxScrolls ?? "60", "--collect-max-scrolls"),
|
|
1021
|
+
maxMs: parsePositiveInt(opts.collectMaxSeconds ?? "300", "--collect-max-seconds") * 1_000,
|
|
1022
|
+
idleSteps: parsePositiveInt(opts.collectIdle ?? "5", "--collect-idle"),
|
|
1023
|
+
expandPerStep: spec.expand ? 3 : 0,
|
|
1024
|
+
};
|
|
1025
|
+
const pacing = scrollPacingFromEnv();
|
|
1026
|
+
const msBounds = { min: 0, max: PACING_LIMITS.maxPauseMs };
|
|
1027
|
+
if (opts.collectStep) {
|
|
1028
|
+
pacing.step = parseRange(opts.collectStep, "--collect-step", {
|
|
1029
|
+
min: 0.05,
|
|
1030
|
+
max: PACING_LIMITS.maxStepFraction,
|
|
1031
|
+
});
|
|
1032
|
+
}
|
|
1033
|
+
if (opts.collectPause)
|
|
1034
|
+
pacing.pauseMs = parseRange(opts.collectPause, "--collect-pause", msBounds);
|
|
1035
|
+
if (opts.collectReadPause) {
|
|
1036
|
+
pacing.readPauseMs = parseRange(opts.collectReadPause, "--collect-read-pause", msBounds);
|
|
1037
|
+
}
|
|
1038
|
+
if (opts.collectReadChance !== undefined) {
|
|
1039
|
+
pacing.readChance = parseProbability(opts.collectReadChance, "--collect-read-chance");
|
|
1040
|
+
}
|
|
1041
|
+
if (opts.collectBackChance !== undefined) {
|
|
1042
|
+
pacing.backChance = parseProbability(opts.collectBackChance, "--collect-back-chance");
|
|
1043
|
+
}
|
|
1044
|
+
let seed = randomSeed();
|
|
1045
|
+
if (opts.collectSeed !== undefined) {
|
|
1046
|
+
if (!/^\d+$/.test(opts.collectSeed.trim())) {
|
|
1047
|
+
throw new Error(`--collect-seed must be a non-negative integer (got "${opts.collectSeed}").`);
|
|
1048
|
+
}
|
|
1049
|
+
seed = Number(BigInt(opts.collectSeed.trim()) % 4294967296n);
|
|
1050
|
+
}
|
|
1051
|
+
const format = opts.collectFormat ?? "json";
|
|
1052
|
+
if (format !== "json" && format !== "jsonl") {
|
|
1053
|
+
throw new Error(`--collect-format must be json or jsonl (got "${format}").`);
|
|
1054
|
+
}
|
|
1055
|
+
return {
|
|
1056
|
+
spec,
|
|
1057
|
+
limits,
|
|
1058
|
+
pacing,
|
|
1059
|
+
seed,
|
|
1060
|
+
format,
|
|
1061
|
+
out: opts.collectOut ? resolve(opts.collectOut) : null,
|
|
1062
|
+
};
|
|
1063
|
+
}
|
|
1064
|
+
async function runCollectMode(browser, plan) {
|
|
1065
|
+
const page = browser.currentPage;
|
|
1066
|
+
const rng = createRng(plan.seed);
|
|
1067
|
+
let interrupted = false;
|
|
1068
|
+
const onSignal = () => {
|
|
1069
|
+
interrupted = true;
|
|
1070
|
+
};
|
|
1071
|
+
process.on("SIGINT", onSignal);
|
|
1072
|
+
process.on("SIGTERM", onSignal);
|
|
1073
|
+
let result;
|
|
1074
|
+
try {
|
|
1075
|
+
emit.log(`collect: preset ${plan.spec.preset}, item ${plan.spec.item}, seed ${plan.seed}`, "info");
|
|
1076
|
+
const driver = createPageDriver(page, plan.spec, {
|
|
1077
|
+
rng,
|
|
1078
|
+
log: (message) => emit.log(message, "warn"),
|
|
1079
|
+
});
|
|
1080
|
+
result = await runCollect(driver, {
|
|
1081
|
+
limits: plan.limits,
|
|
1082
|
+
pacing: plan.pacing,
|
|
1083
|
+
rng,
|
|
1084
|
+
shouldStop: () => interrupted,
|
|
1085
|
+
onStep: ({ step, items, added }) => emit.log(`collect: step ${step}, ${items} items (+${added})`, "debug"),
|
|
1086
|
+
});
|
|
1087
|
+
}
|
|
1088
|
+
finally {
|
|
1089
|
+
process.removeListener("SIGINT", onSignal);
|
|
1090
|
+
process.removeListener("SIGTERM", onSignal);
|
|
1091
|
+
}
|
|
1092
|
+
const { items, stats } = result;
|
|
1093
|
+
emit.log(`collect: ${items.length} items in ${stats.steps} steps, stopped on ${stats.stopReason}` +
|
|
1094
|
+
(stats.stopDetail ? ` (${stats.stopDetail})` : ""), "info");
|
|
1095
|
+
const envelope = {
|
|
1096
|
+
url: page.url(),
|
|
1097
|
+
preset: plan.spec.preset,
|
|
1098
|
+
itemSelector: plan.spec.item,
|
|
1099
|
+
seed: plan.seed,
|
|
1100
|
+
stopReason: stats.stopReason,
|
|
1101
|
+
stats,
|
|
1102
|
+
items,
|
|
1103
|
+
};
|
|
1104
|
+
if (plan.format === "jsonl") {
|
|
1105
|
+
const body = items.map((item) => JSON.stringify(item)).join("\n");
|
|
1106
|
+
if (plan.out) {
|
|
1107
|
+
mkdirSync(dirname(plan.out), { recursive: true });
|
|
1108
|
+
writeFileSync(plan.out, body ? `${body}\n` : "");
|
|
1109
|
+
emit.file(plan.out, { items: items.length, stopReason: stats.stopReason, stats });
|
|
1110
|
+
}
|
|
1111
|
+
else if (body) {
|
|
1112
|
+
process.stdout.write(`${body}\n`);
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
1115
|
+
else if (plan.out) {
|
|
1116
|
+
mkdirSync(dirname(plan.out), { recursive: true });
|
|
1117
|
+
writeFileSync(plan.out, `${JSON.stringify(envelope, null, 2)}\n`);
|
|
1118
|
+
emit.file(plan.out, { items: items.length, stopReason: stats.stopReason, stats });
|
|
1119
|
+
}
|
|
1120
|
+
else {
|
|
1121
|
+
emit.data(envelope);
|
|
1122
|
+
}
|
|
1123
|
+
if (interrupted)
|
|
1124
|
+
process.exitCode = 130;
|
|
1125
|
+
}
|
|
967
1126
|
async function runBatch(browser, batch, defaultTimeoutMs) {
|
|
968
1127
|
const result = { clipboardReads: [] };
|
|
969
1128
|
const steps = splitBatchSteps(batch);
|
|
@@ -1034,27 +1193,6 @@ async function runBatch(browser, batch, defaultTimeoutMs) {
|
|
|
1034
1193
|
}
|
|
1035
1194
|
return result;
|
|
1036
1195
|
}
|
|
1037
|
-
function splitBatchSteps(input) {
|
|
1038
|
-
const steps = [];
|
|
1039
|
-
let buf = "";
|
|
1040
|
-
for (let i = 0; i < input.length; i++) {
|
|
1041
|
-
const ch = input[i];
|
|
1042
|
-
if (ch === "\\" && input[i + 1] === ";") {
|
|
1043
|
-
buf += ";";
|
|
1044
|
-
i++;
|
|
1045
|
-
continue;
|
|
1046
|
-
}
|
|
1047
|
-
if (ch === ";") {
|
|
1048
|
-
steps.push(buf);
|
|
1049
|
-
buf = "";
|
|
1050
|
-
continue;
|
|
1051
|
-
}
|
|
1052
|
-
buf += ch;
|
|
1053
|
-
}
|
|
1054
|
-
if (buf.trim())
|
|
1055
|
-
steps.push(buf);
|
|
1056
|
-
return steps;
|
|
1057
|
-
}
|
|
1058
1196
|
// ---------------------------------------------------------------------------
|
|
1059
1197
|
// Print mode (--snapshot / --html / --json): stdout, no file writes
|
|
1060
1198
|
// ---------------------------------------------------------------------------
|
|
@@ -2155,7 +2293,26 @@ async function verifyBrowserProxyGate(browser, gate) {
|
|
|
2155
2293
|
emit.log(`browser proxy gate: ${observedIp} · expected IP confirmed`, "info");
|
|
2156
2294
|
}
|
|
2157
2295
|
/**
|
|
2158
|
-
*
|
|
2296
|
+
* How a `browse` run handles sign-in. `--login` opens the installed Google
|
|
2297
|
+
* Chrome with nothing attached, because sites can detect an
|
|
2298
|
+
* automation-controlled browser and disable the account that signed in with
|
|
2299
|
+
* it. The attached window is reachable only through an explicit `--attached`.
|
|
2300
|
+
*/
|
|
2301
|
+
export function resolveLoginMode(opts) {
|
|
2302
|
+
if (opts.attached && !opts.login)
|
|
2303
|
+
throw new Error("--attached requires --login.");
|
|
2304
|
+
if (opts.loginCloseFile && !opts.login)
|
|
2305
|
+
throw new Error("--login-close-file requires --login.");
|
|
2306
|
+
if (opts.controlFile && !(opts.login && opts.attached)) {
|
|
2307
|
+
throw new Error("--control-file requires --login --attached. A sign-in to a third-party account uses plain " +
|
|
2308
|
+
"--login, which nothing can drive.");
|
|
2309
|
+
}
|
|
2310
|
+
if (!opts.login)
|
|
2311
|
+
return "none";
|
|
2312
|
+
return opts.attached ? "attached" : "plain";
|
|
2313
|
+
}
|
|
2314
|
+
/**
|
|
2315
|
+
* `--login`: spawn the installed Google Chrome directly on the shared
|
|
2159
2316
|
* persistent profile, with no DevTools protocol attached. Playwright enables
|
|
2160
2317
|
* its runtime hooks in every frame it drives, and sign-in verification vendors
|
|
2161
2318
|
* (X's is the reported case) detect that and fail the flow with no explanation.
|
|
@@ -2166,7 +2323,8 @@ async function verifyBrowserProxyGate(browser, gate) {
|
|
|
2166
2323
|
async function runPlainLogin(url, profileDir, loginCloseFile, userAgent, emit) {
|
|
2167
2324
|
const executable = installedChromePath();
|
|
2168
2325
|
if (!executable) {
|
|
2169
|
-
throw new Error("--
|
|
2326
|
+
throw new Error("--login needs an installed Google Chrome at a standard path; none was found on this machine. " +
|
|
2327
|
+
"Install Google Chrome; do not fall back to --attached for a third-party sign-in.");
|
|
2170
2328
|
}
|
|
2171
2329
|
mkdirSync(profileDir, { recursive: true });
|
|
2172
2330
|
const child = spawn(executable, [
|
|
@@ -2183,11 +2341,11 @@ async function runPlainLogin(url, profileDir, loginCloseFile, userAgent, emit) {
|
|
|
2183
2341
|
const lifecycleAbort = new AbortController();
|
|
2184
2342
|
const waits = [exited];
|
|
2185
2343
|
if (loginCloseFile) {
|
|
2186
|
-
emit.log(`[--login
|
|
2344
|
+
emit.log(`[--login] Plain Google Chrome is open with nothing attached. Sign in, then close the window or create ${loginCloseFile}.`, "info");
|
|
2187
2345
|
waits.push(waitForLoginCloseFile(loginCloseFile, lifecycleAbort.signal));
|
|
2188
2346
|
}
|
|
2189
2347
|
else {
|
|
2190
|
-
emit.log("[--login
|
|
2348
|
+
emit.log("[--login] Plain Google Chrome is open with nothing attached. Sign in, then close the window or press Enter here.", "info");
|
|
2191
2349
|
waits.push(waitForTerminalEnter(lifecycleAbort.signal));
|
|
2192
2350
|
}
|
|
2193
2351
|
await Promise.race(waits);
|
|
@@ -2196,7 +2354,61 @@ async function runPlainLogin(url, profileDir, loginCloseFile, userAgent, emit) {
|
|
|
2196
2354
|
child.kill("SIGTERM");
|
|
2197
2355
|
await Promise.race([exited, new Promise((r) => setTimeout(r, 5000))]);
|
|
2198
2356
|
}
|
|
2199
|
-
emit.log(`[--login
|
|
2357
|
+
emit.log(`[--login] Chrome closed; cookies persisted in ${profileDir}.`, "info");
|
|
2358
|
+
}
|
|
2359
|
+
/**
|
|
2360
|
+
* Under WSL, `--login` opens the Windows host's Google Chrome when one
|
|
2361
|
+
* is installed. Some sign-in flows reject every browser running inside WSL,
|
|
2362
|
+
* even a plain Linux Chrome, while the Windows desktop Chrome signs in
|
|
2363
|
+
* normally. `HARNERY_BROWSER_WSL_PLAIN=linux` keeps the Linux Chrome.
|
|
2364
|
+
*/
|
|
2365
|
+
function useWindowsPlainLogin() {
|
|
2366
|
+
if (!isWSL())
|
|
2367
|
+
return false;
|
|
2368
|
+
if ((process.env.HARNERY_BROWSER_WSL_PLAIN ?? "").toLowerCase() === "linux")
|
|
2369
|
+
return false;
|
|
2370
|
+
return windowsChromePath() !== undefined;
|
|
2371
|
+
}
|
|
2372
|
+
/**
|
|
2373
|
+
* The WSL form of `--login`: the person signs in through the Windows
|
|
2374
|
+
* Chrome on a dedicated Windows profile with nothing attached. When the window
|
|
2375
|
+
* closes, the profile's cookies for the sign-in site are read and merged into
|
|
2376
|
+
* the shared cookie store, which later `browse` runs attach.
|
|
2377
|
+
*/
|
|
2378
|
+
async function runWindowsPlainLogin(url, storePath, loginCloseFile, emit) {
|
|
2379
|
+
const chromeLinux = windowsChromePath();
|
|
2380
|
+
const localAppData = windowsLocalAppData();
|
|
2381
|
+
if (!chromeLinux || !localAppData) {
|
|
2382
|
+
throw new Error("--login under WSL needs the Windows Google Chrome and %LOCALAPPDATA%; neither was readable.");
|
|
2383
|
+
}
|
|
2384
|
+
const chromeWindows = wslPathToWindows(chromeLinux);
|
|
2385
|
+
const profileWindows = windowsPlainProfile(localAppData);
|
|
2386
|
+
const child = spawn(chromeLinux, [`--user-data-dir=${profileWindows}`, "--no-first-run", "--no-default-browser-check", url], { stdio: "ignore" });
|
|
2387
|
+
const exited = new Promise((resolveExit) => {
|
|
2388
|
+
child.once("exit", () => resolveExit());
|
|
2389
|
+
child.once("error", () => resolveExit());
|
|
2390
|
+
});
|
|
2391
|
+
const lifecycleAbort = new AbortController();
|
|
2392
|
+
const waits = [exited];
|
|
2393
|
+
const how = loginCloseFile ? `create ${loginCloseFile}` : "press Enter here";
|
|
2394
|
+
emit.log(`[--login] Windows Google Chrome is open with nothing attached (profile ${profileWindows}). Sign in, then close the window or ${how}.`, "info");
|
|
2395
|
+
waits.push(loginCloseFile
|
|
2396
|
+
? waitForLoginCloseFile(loginCloseFile, lifecycleAbort.signal)
|
|
2397
|
+
: waitForTerminalEnter(lifecycleAbort.signal));
|
|
2398
|
+
await Promise.race(waits);
|
|
2399
|
+
lifecycleAbort.abort();
|
|
2400
|
+
stopWindowsChromeProfile(profileWindows);
|
|
2401
|
+
await Promise.race([exited, new Promise((r) => setTimeout(r, 5000))]);
|
|
2402
|
+
const domain = harvestDomain(url);
|
|
2403
|
+
const cookies = harvestWindowsChromeCookies({ chromeWindows, profileWindows, domain });
|
|
2404
|
+
if (cookies.length === 0) {
|
|
2405
|
+
emit.log(`[--login] No ${domain} cookies were found in the Windows profile; the sign-in may not have completed.`, "warn");
|
|
2406
|
+
return;
|
|
2407
|
+
}
|
|
2408
|
+
const jar = new CookieJar({ path: storePath, source: "harn-browse" });
|
|
2409
|
+
jar.save(mergeCookies(jar.load(), cookies));
|
|
2410
|
+
const names = [...new Set(cookies.map((cookie) => cookie.name))].sort().join(", ");
|
|
2411
|
+
emit.log(`[--login] Copied ${cookies.length} ${domain} cookies into ${storePath}: ${names}.`, "info");
|
|
2200
2412
|
}
|
|
2201
2413
|
async function waitForLoginCloseFile(path, signal) {
|
|
2202
2414
|
while (!existsSync(path) && !signal?.aborted) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"claude-desktop.d.ts","sourceRoot":"","sources":["../../src/commands/claude-desktop.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,EAAE,WAAW,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"claude-desktop.d.ts","sourceRoot":"","sources":["../../src/commands/claude-desktop.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,EAAE,WAAW,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AAiB1E;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,4BAA4B,CAC1C,OAAO,EAAE,OAAO,EAChB,IAAI,EAAE,WAAW,EACjB,QAAQ,CAAC,EAAE,qBAAqB,GAC/B,IAAI,CAoNN"}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { applyMirror, findDesktopDataDirs, listAccounts, planMirror, readCliAccount, } from "../lib/claude-desktop.js";
|
|
2
|
+
import { applyShare, defaultShareDir, planShare, shareSummary, } from "../lib/claude-desktop-share.js";
|
|
3
|
+
import { resolveMachineLabel } from "../lib/machine.js";
|
|
2
4
|
/**
|
|
3
5
|
* `claude-desktop`: make Claude desktop-app sessions survive account
|
|
4
6
|
* switches.
|
|
@@ -141,6 +143,56 @@ export function registerClaudeDesktopCommand(program, emit, _context) {
|
|
|
141
143
|
});
|
|
142
144
|
}
|
|
143
145
|
});
|
|
146
|
+
cmd
|
|
147
|
+
.command("share")
|
|
148
|
+
.description("Share sessions with your other computers through a synced folder (iCloud Drive by default): " +
|
|
149
|
+
"publish this machine's recent sessions and list + resume every other machine's here. " +
|
|
150
|
+
"Dry-run by default; --yes applies. Restart the desktop app afterward to see new entries.")
|
|
151
|
+
.option("--dir <path>", "Shared folder (default: iCloud Drive/Harnery/claude-sessions)")
|
|
152
|
+
.option("--days <n>", "Only sessions active in the last N days", "14")
|
|
153
|
+
.option("--data-dir <path>", "Explicit desktop-app data directory")
|
|
154
|
+
.option("--yes", "Apply the plan (default is dry-run)")
|
|
155
|
+
.action((opts) => {
|
|
156
|
+
const shareDir = opts.dir ?? process.env.HARNERY_SESSION_SHARE_DIR ?? defaultShareDir();
|
|
157
|
+
if (!shareDir) {
|
|
158
|
+
emit.error({
|
|
159
|
+
code: "no_share_dir",
|
|
160
|
+
message: "no shared folder: pass --dir <path> or set HARNERY_SESSION_SHARE_DIR",
|
|
161
|
+
});
|
|
162
|
+
process.exit(2);
|
|
163
|
+
}
|
|
164
|
+
const cli = readCliAccount();
|
|
165
|
+
if (!cli) {
|
|
166
|
+
emit.error({
|
|
167
|
+
code: "no_account",
|
|
168
|
+
message: "cannot read the signed-in account from ~/.claude.json",
|
|
169
|
+
});
|
|
170
|
+
process.exit(2);
|
|
171
|
+
}
|
|
172
|
+
const [dataDir] = requireDataDirs(emit, opts.dataDir);
|
|
173
|
+
const shareOpts = {
|
|
174
|
+
shareDir,
|
|
175
|
+
machine: resolveMachineLabel(),
|
|
176
|
+
days: Number(opts.days) || 14,
|
|
177
|
+
dataDir: dataDir,
|
|
178
|
+
targetAccountUuid: cli.accountUuid,
|
|
179
|
+
};
|
|
180
|
+
const plan = planShare(shareOpts);
|
|
181
|
+
const summary = { share_dir: shareDir, machine: shareOpts.machine, ...shareSummary(plan) };
|
|
182
|
+
if (!opts.yes) {
|
|
183
|
+
emit.data({ dry_run: true, ...summary, hint: "re-run with --yes to apply" });
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
const result = applyShare(plan, shareOpts);
|
|
187
|
+
emit.data({
|
|
188
|
+
dry_run: false,
|
|
189
|
+
...summary,
|
|
190
|
+
...result,
|
|
191
|
+
hint: result.imported > 0
|
|
192
|
+
? "quit the Claude desktop app fully (Cmd+Q) and reopen it to see the new sessions"
|
|
193
|
+
: "sidebar already current",
|
|
194
|
+
});
|
|
195
|
+
});
|
|
144
196
|
}
|
|
145
197
|
function collect(value, previous) {
|
|
146
198
|
return previous.concat([value]);
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { type Command } from "commander";
|
|
2
|
+
import type { EmitContext } from "../commander.js";
|
|
3
|
+
import { type DiskReport } from "../lib/disk-usage.js";
|
|
4
|
+
export declare function registerDiskCommand(program: Command, emit: EmitContext): void;
|
|
5
|
+
export declare function renderDiskUsage(report: DiskReport): string;
|
|
6
|
+
//# sourceMappingURL=disk.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"disk.d.ts","sourceRoot":"","sources":["../../src/commands/disk.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,OAAO,EAAwB,MAAM,WAAW,CAAC;AAC/D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AACnD,OAAO,EAGL,KAAK,UAAU,EAGhB,MAAM,sBAAsB,CAAC;AAQ9B,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,WAAW,GAAG,IAAI,CA+C7E;AAED,wBAAgB,eAAe,CAAC,MAAM,EAAE,UAAU,GAAG,MAAM,CAgD1D"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { InvalidArgumentError } from "commander";
|
|
2
|
+
import { diskUsage, parseDiskSize, } from "../lib/disk-usage.js";
|
|
3
|
+
function positive(value) {
|
|
4
|
+
if (!/^\d+$/.test(value) || !Number.isSafeInteger(Number(value)) || Number(value) < 1)
|
|
5
|
+
throw new InvalidArgumentError("requires a positive integer");
|
|
6
|
+
return Number(value);
|
|
7
|
+
}
|
|
8
|
+
export function registerDiskCommand(program, emit) {
|
|
9
|
+
program
|
|
10
|
+
.command("disk [path]")
|
|
11
|
+
.description("Measure checkout disk usage, including nested repositories and ignored files")
|
|
12
|
+
.option("--git <states>", "Filter: tracked, untracked, ignored, git-metadata, non-repository, unknown (comma-separated)")
|
|
13
|
+
.option("--type <types>", "File extensions or video, audio, image, media, archive (comma-separated)")
|
|
14
|
+
.option("--exclude <paths>", "Skip directory names at any depth or root-relative paths; repeatable, comma-separated", (value, previous) => [...previous, value], [])
|
|
15
|
+
.option("--group <by>", "Group by directory, repository, extension, git, or files", "directory")
|
|
16
|
+
.option("--depth <n>", "Directory grouping depth; does not limit the scan", positive, 1)
|
|
17
|
+
.option("--top <n>", "Maximum groups and largest files to display", positive, 20)
|
|
18
|
+
.option("--min-size <size>", "Include individual files at least this size (e.g. 10MB, 1GiB)", parseDiskSize)
|
|
19
|
+
.option("--apparent", "Rank and filter by file lengths instead of allocated blocks")
|
|
20
|
+
.option("--json", "Emit the versioned disk usage report")
|
|
21
|
+
.action((path, options) => {
|
|
22
|
+
try {
|
|
23
|
+
const { json, ...filters } = options;
|
|
24
|
+
const report = diskUsage(path, filters);
|
|
25
|
+
if (json) {
|
|
26
|
+
emit.config({ format: "json" });
|
|
27
|
+
emit.data(report);
|
|
28
|
+
}
|
|
29
|
+
else
|
|
30
|
+
emit.text(renderDiskUsage(report));
|
|
31
|
+
if (!report.complete)
|
|
32
|
+
emit.setExitCode(1);
|
|
33
|
+
}
|
|
34
|
+
catch (error) {
|
|
35
|
+
emit.error({
|
|
36
|
+
code: "disk_usage_failed",
|
|
37
|
+
message: error instanceof Error ? error.message : String(error),
|
|
38
|
+
});
|
|
39
|
+
emit.setExitCode(1);
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
export function renderDiskUsage(report) {
|
|
44
|
+
const bytes = (value) => value >= 1e9
|
|
45
|
+
? `${(value / 1e9).toFixed(2)} GB`
|
|
46
|
+
: value >= 1e6
|
|
47
|
+
? `${(value / 1e6).toFixed(2)} MB`
|
|
48
|
+
: value >= 1e3
|
|
49
|
+
? `${(value / 1e3).toFixed(2)} KB`
|
|
50
|
+
: `${value} B`;
|
|
51
|
+
const size = (row) => bytes(report.measurement === "allocated"
|
|
52
|
+
? (row.allocated_bytes ?? row.apparent_bytes)
|
|
53
|
+
: row.apparent_bytes);
|
|
54
|
+
const lines = [
|
|
55
|
+
`Disk usage: ${report.root}`,
|
|
56
|
+
`${size(report.totals)} in ${report.totals.files.toLocaleString()} regular files (${report.measurement})`,
|
|
57
|
+
`File lengths: ${bytes(report.totals.apparent_bytes)}. Directory metadata and symlinks are excluded.`,
|
|
58
|
+
"",
|
|
59
|
+
"Git state:",
|
|
60
|
+
];
|
|
61
|
+
for (const [state, totals] of Object.entries(report.git))
|
|
62
|
+
if (totals.files)
|
|
63
|
+
lines.push(`${size(totals).padStart(12)} ${String(totals.files).padStart(8)} files ${state}`);
|
|
64
|
+
lines.push("", `Largest ${report.filters.group} groups (${report.groups.length} of ${report.group_count}):`);
|
|
65
|
+
for (const row of report.groups)
|
|
66
|
+
lines.push(`${size(row).padStart(12)} ${String(row.files).padStart(8)} files ${row.path}`);
|
|
67
|
+
if (report.filters.group !== "files") {
|
|
68
|
+
lines.push("", "Largest files:");
|
|
69
|
+
for (const row of report.largest_files)
|
|
70
|
+
lines.push(`${size(row).padStart(12)} ${row.git.padEnd(14)} ${row.path}`);
|
|
71
|
+
}
|
|
72
|
+
if (report.skipped.symlinks || report.skipped.special_files || report.skipped.excluded)
|
|
73
|
+
lines.push("", `Skipped: ${report.skipped.symlinks} symlinks, ${report.skipped.special_files} special files, ${report.skipped.excluded} excluded entries.`);
|
|
74
|
+
if (report.issues.length) {
|
|
75
|
+
lines.push("", "Incomplete scan:");
|
|
76
|
+
for (const issue of report.issues)
|
|
77
|
+
lines.push(`${issue.path}: ${issue.message}`);
|
|
78
|
+
}
|
|
79
|
+
return `${lines.join("\n")}\n`;
|
|
80
|
+
}
|
|
@@ -24,6 +24,14 @@ export interface Check {
|
|
|
24
24
|
export declare function registerDoctorCommand(program: Command, emit: EmitContext): void;
|
|
25
25
|
export declare function runChecks(): Promise<Check[]>;
|
|
26
26
|
export declare function codexWslBridgeCheck(status: CodexWslBridgeStatus | null, recentMidFlightCount?: number): Check | null;
|
|
27
|
+
/**
|
|
28
|
+
* Hook runs that started but never finished (ADR 0197). Codex kills a hook at
|
|
29
|
+
* its timeout and the tool proceeds without that hook's checks; the start
|
|
30
|
+
* marker is the only trace such a run leaves.
|
|
31
|
+
*/
|
|
32
|
+
export declare function codexHookRunsCheck(coordRoot: string | null, nowMs?: number, isAlive?: (pid: number) => boolean): Check | null;
|
|
33
|
+
/** Report the per-project Windows bridge opt-in, or an invalid value init ignored. */
|
|
34
|
+
export declare function codexWindowsBridgeCheck(coordRoot: string | null): Check | null;
|
|
27
35
|
/** Count bounded, privacy-safe recovery diagnostics so a healthy environment
|
|
28
36
|
* now does not hide an intermittent Codex-to-WSL identity drop. */
|
|
29
37
|
export declare function countRecentCodexMidFlightOnboardings(coordRoot: string, nowMs?: number, windowMs?: number): number;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../../src/commands/doctor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAMH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AASnD,OAAO,EACL,KAAK,4BAA4B,EAElC,MAAM,sCAAsC,CAAC;AAC9C,OAAO,EACL,KAAK,oBAAoB,EAE1B,MAAM,mCAAmC,CAAC;
|
|
1
|
+
{"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../../src/commands/doctor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAMH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AASnD,OAAO,EACL,KAAK,4BAA4B,EAElC,MAAM,sCAAsC,CAAC;AAC9C,OAAO,EACL,KAAK,oBAAoB,EAE1B,MAAM,mCAAmC,CAAC;AAY3C,KAAK,QAAQ,GAAG,IAAI,GAAG,MAAM,GAAG,MAAM,CAAC;AAEvC,MAAM,WAAW,KAAK;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,QAAQ,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAOD,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,WAAW,GAAG,IAAI,CAmD/E;AAED,wBAAsB,SAAS,IAAI,OAAO,CAAC,KAAK,EAAE,CAAC,CA+BlD;AAgCD,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,oBAAoB,GAAG,IAAI,EACnC,oBAAoB,SAAI,GACvB,KAAK,GAAG,IAAI,CAed;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAChC,SAAS,EAAE,MAAM,GAAG,IAAI,EACxB,KAAK,SAAa,EAClB,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,GACjC,KAAK,GAAG,IAAI,CAad;AAED,sFAAsF;AACtF,wBAAgB,uBAAuB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,GAAG,KAAK,GAAG,IAAI,CAmB9E;AAID;mEACmE;AACnE,wBAAgB,oCAAoC,CAClD,SAAS,EAAE,MAAM,EACjB,KAAK,SAAa,EAClB,QAAQ,SAAqC,GAC5C,MAAM,CA0CR;AAED,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,4BAA4B,GAAG,KAAK,CAoBnF"}
|