@usepowerplant/cli 0.4.7 → 0.4.8
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/README.md +10 -5
- package/dist/cli.js +480 -133
- package/dist/menubar/Powerplant.app.zip +0 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -22,8 +22,11 @@ re-run at any time — finished steps are skipped with a note:
|
|
|
22
22
|
2. **Install the background service.** The sync service watches your local
|
|
23
23
|
coding-agent sessions (Claude Code, Codex, and Cursor) and uploads the
|
|
24
24
|
ones in scope. If recent sessions name repositories under a macOS-protected
|
|
25
|
-
location such as Documents, init lists the location and asks before
|
|
26
|
-
Git metadata there; declining leaves those sessions local
|
|
25
|
+
location such as Documents, init lists the location and asks once before
|
|
26
|
+
reading Git metadata there; declining leaves those sessions local, and a
|
|
27
|
+
yes covers locations that appear later (init still explains before macOS
|
|
28
|
+
asks). A location macOS refused is re-checked on every init until you
|
|
29
|
+
enable it in System Settings. The service
|
|
27
30
|
starts on login and keeps itself running. On macOS, init also installs
|
|
28
31
|
the Powerplant menu bar app from this package into `~/Applications` and
|
|
29
32
|
launches it; the app registers itself to start at login (turn that off
|
|
@@ -131,9 +134,11 @@ then restarts itself onto it. The choice is per machine and only an
|
|
|
131
134
|
explicit yes counts: a machine set up before the question existed stays
|
|
132
135
|
off until someone turns it on. `powerplant status` and `powerplant doctor`
|
|
133
136
|
report the current choice, and `powerplant update --automatic on` or
|
|
134
|
-
`--automatic off`, or the menu bar's More menu, change it.
|
|
135
|
-
|
|
136
|
-
|
|
137
|
+
`--automatic off`, or the menu bar's More menu, change it. Turning it on
|
|
138
|
+
(there, or at init's question) makes the sync check within seconds, so a
|
|
139
|
+
release already out installs now rather than at the next hourly check. A
|
|
140
|
+
release that fails to install is not retried by itself; the menu bar shows
|
|
141
|
+
the failure with a retry and the update log, and the next release clears it.
|
|
137
142
|
|
|
138
143
|
Powerplant also publishes the oldest version it still supports. When yours
|
|
139
144
|
is older, `powerplant status`, `powerplant doctor`, the menu bar app, and
|
package/dist/cli.js
CHANGED
|
@@ -55485,20 +55485,210 @@ function workspaceClaimsAnyRepo(policy) {
|
|
|
55485
55485
|
return policy.repos !== null ? policy.repos.length > 0 : policy.owners.length > 0;
|
|
55486
55486
|
}
|
|
55487
55487
|
|
|
55488
|
+
// ../core/src/encodedImages.ts
|
|
55489
|
+
var BASE64_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
55490
|
+
function isEncodedImage(data, mediaType) {
|
|
55491
|
+
if (data.length < 16 || data.length % 4 !== 0) return false;
|
|
55492
|
+
const padding = data.endsWith("==") ? 2 : data.endsWith("=") ? 1 : 0;
|
|
55493
|
+
const body = data.slice(0, data.length - padding);
|
|
55494
|
+
if (/[^A-Za-z0-9+/]/.test(body)) return false;
|
|
55495
|
+
const last = BASE64_ALPHABET.indexOf(body.at(-1));
|
|
55496
|
+
if (padding === 2 && (last & 15) !== 0 || padding === 1 && (last & 3) !== 0) return false;
|
|
55497
|
+
switch (mediaType) {
|
|
55498
|
+
case "image/png":
|
|
55499
|
+
return isPng(atob(data));
|
|
55500
|
+
case "image/jpeg":
|
|
55501
|
+
return isJpeg(atob(data));
|
|
55502
|
+
case "image/gif":
|
|
55503
|
+
return isGif(atob(data));
|
|
55504
|
+
case "image/webp":
|
|
55505
|
+
return isWebp(atob(data));
|
|
55506
|
+
default:
|
|
55507
|
+
return false;
|
|
55508
|
+
}
|
|
55509
|
+
}
|
|
55510
|
+
function u16(data, offset) {
|
|
55511
|
+
return data.charCodeAt(offset) << 8 | data.charCodeAt(offset + 1);
|
|
55512
|
+
}
|
|
55513
|
+
function u32(data, offset) {
|
|
55514
|
+
return u16(data, offset) * 65536 + u16(data, offset + 2) >>> 0;
|
|
55515
|
+
}
|
|
55516
|
+
function little32(data, offset) {
|
|
55517
|
+
return (data.charCodeAt(offset) | data.charCodeAt(offset + 1) << 8 | data.charCodeAt(offset + 2) << 16 | data.charCodeAt(offset + 3) << 24) >>> 0;
|
|
55518
|
+
}
|
|
55519
|
+
var CRC_TABLE = Array.from({ length: 256 }, (_, value) => {
|
|
55520
|
+
for (let bit = 0; bit < 8; bit++) value = value & 1 ? 3988292384 ^ value >>> 1 : value >>> 1;
|
|
55521
|
+
return value >>> 0;
|
|
55522
|
+
});
|
|
55523
|
+
function crc32(data, start, end) {
|
|
55524
|
+
let crc = 4294967295;
|
|
55525
|
+
for (let i = start; i < end; i++)
|
|
55526
|
+
crc = CRC_TABLE[(crc ^ data.charCodeAt(i)) & 255] ^ crc >>> 8;
|
|
55527
|
+
return (crc ^ 4294967295) >>> 0;
|
|
55528
|
+
}
|
|
55529
|
+
function isPng(data) {
|
|
55530
|
+
if (!data.startsWith("\x89PNG\r\n\n")) return false;
|
|
55531
|
+
let offset = 8;
|
|
55532
|
+
let imageData = false;
|
|
55533
|
+
let dataEnded = false;
|
|
55534
|
+
let palette = false;
|
|
55535
|
+
let indexed = false;
|
|
55536
|
+
while (offset + 12 <= data.length) {
|
|
55537
|
+
const size = u32(data, offset);
|
|
55538
|
+
const type = data.slice(offset + 4, offset + 8);
|
|
55539
|
+
const end = offset + 8 + size;
|
|
55540
|
+
if (size > 2147483647 || end + 4 > data.length || !/^[A-Za-z]{2}[A-Z][A-Za-z]$/.test(type))
|
|
55541
|
+
return false;
|
|
55542
|
+
if (u32(data, end) !== crc32(data, offset + 4, end)) return false;
|
|
55543
|
+
if (offset === 8) {
|
|
55544
|
+
if (type !== "IHDR" || size !== 13 || !u32(data, offset + 8) || !u32(data, offset + 12))
|
|
55545
|
+
return false;
|
|
55546
|
+
const depth = data.charCodeAt(offset + 16);
|
|
55547
|
+
const color = data.charCodeAt(offset + 17);
|
|
55548
|
+
const validDepth = color === 0 ? [1, 2, 4, 8, 16] : color === 3 ? [1, 2, 4, 8] : [8, 16];
|
|
55549
|
+
if (![0, 2, 3, 4, 6].includes(color) || !validDepth.includes(depth)) return false;
|
|
55550
|
+
if (data.charCodeAt(offset + 18) !== 0 || data.charCodeAt(offset + 19) !== 0 || data.charCodeAt(offset + 20) > 1)
|
|
55551
|
+
return false;
|
|
55552
|
+
indexed = color === 3;
|
|
55553
|
+
} else if (type === "IHDR") return false;
|
|
55554
|
+
else if (type === "PLTE") {
|
|
55555
|
+
if (imageData || palette || size === 0 || size > 768 || size % 3 !== 0) return false;
|
|
55556
|
+
palette = true;
|
|
55557
|
+
} else if (type === "IDAT") {
|
|
55558
|
+
if (dataEnded || indexed && !palette) return false;
|
|
55559
|
+
imageData = imageData || size > 0;
|
|
55560
|
+
} else if (type === "IEND") return imageData && size === 0 && end + 4 === data.length;
|
|
55561
|
+
else if (type[0] === type[0].toUpperCase()) return false;
|
|
55562
|
+
if (imageData && type !== "IDAT") dataEnded = true;
|
|
55563
|
+
offset = end + 4;
|
|
55564
|
+
}
|
|
55565
|
+
return false;
|
|
55566
|
+
}
|
|
55567
|
+
function isJpeg(data) {
|
|
55568
|
+
if (!data.startsWith("\xFF\xD8")) return false;
|
|
55569
|
+
let offset = 2;
|
|
55570
|
+
let frame = false;
|
|
55571
|
+
let scan = false;
|
|
55572
|
+
while (offset < data.length) {
|
|
55573
|
+
if (data.charCodeAt(offset++) !== 255) return false;
|
|
55574
|
+
while (data.charCodeAt(offset) === 255) offset++;
|
|
55575
|
+
const marker = data.charCodeAt(offset++);
|
|
55576
|
+
if (marker === 217) return frame && scan && offset === data.length;
|
|
55577
|
+
if (marker === 0 || marker === 216 || marker >= 208 && marker <= 215) return false;
|
|
55578
|
+
if (marker === 1) continue;
|
|
55579
|
+
if (offset + 2 > data.length) return false;
|
|
55580
|
+
const size = u16(data, offset);
|
|
55581
|
+
if (size < 2 || offset + size > data.length) return false;
|
|
55582
|
+
if (marker >= 192 && marker <= 207 && ![196, 200, 204].includes(marker)) {
|
|
55583
|
+
if (size < 11 || size !== 8 + 3 * data.charCodeAt(offset + 7) || !u16(data, offset + 3) || !u16(data, offset + 5))
|
|
55584
|
+
return false;
|
|
55585
|
+
frame = true;
|
|
55586
|
+
}
|
|
55587
|
+
if (marker !== 218) {
|
|
55588
|
+
offset += size;
|
|
55589
|
+
continue;
|
|
55590
|
+
}
|
|
55591
|
+
if (!frame || size < 8 || size !== 6 + 2 * data.charCodeAt(offset + 2)) return false;
|
|
55592
|
+
offset += size;
|
|
55593
|
+
const start = offset;
|
|
55594
|
+
for (; ; ) {
|
|
55595
|
+
const next = data.indexOf("\xFF", offset);
|
|
55596
|
+
if (next < 0 || next + 1 >= data.length) return false;
|
|
55597
|
+
const following = data.charCodeAt(next + 1);
|
|
55598
|
+
if (following === 0 || following >= 208 && following <= 215) offset = next + 2;
|
|
55599
|
+
else if (following === 255) offset = next + 1;
|
|
55600
|
+
else {
|
|
55601
|
+
offset = next;
|
|
55602
|
+
break;
|
|
55603
|
+
}
|
|
55604
|
+
}
|
|
55605
|
+
if (offset <= start) return false;
|
|
55606
|
+
scan = true;
|
|
55607
|
+
}
|
|
55608
|
+
return false;
|
|
55609
|
+
}
|
|
55610
|
+
function skipGifBlocks(data, offset) {
|
|
55611
|
+
while (offset < data.length) {
|
|
55612
|
+
const size = data.charCodeAt(offset++);
|
|
55613
|
+
if (size === 0) return offset;
|
|
55614
|
+
offset += size;
|
|
55615
|
+
}
|
|
55616
|
+
return -1;
|
|
55617
|
+
}
|
|
55618
|
+
function isGif(data) {
|
|
55619
|
+
if (!data.startsWith("GIF87a") && !data.startsWith("GIF89a") || data.length < 13) return false;
|
|
55620
|
+
if (!(data.charCodeAt(6) | data.charCodeAt(7)) || !(data.charCodeAt(8) | data.charCodeAt(9)))
|
|
55621
|
+
return false;
|
|
55622
|
+
const packed = data.charCodeAt(10);
|
|
55623
|
+
let offset = 13 + (packed & 128 ? 3 * (2 << (packed & 7)) : 0);
|
|
55624
|
+
let image = false;
|
|
55625
|
+
while (offset < data.length) {
|
|
55626
|
+
const marker = data.charCodeAt(offset++);
|
|
55627
|
+
if (marker === 59) return image && offset === data.length;
|
|
55628
|
+
if (marker === 33) offset = skipGifBlocks(data, offset + 1);
|
|
55629
|
+
else if (marker === 44) {
|
|
55630
|
+
if (offset + 9 > data.length || !(data.charCodeAt(offset + 4) | data.charCodeAt(offset + 5)) || !(data.charCodeAt(offset + 6) | data.charCodeAt(offset + 7)))
|
|
55631
|
+
return false;
|
|
55632
|
+
const local = data.charCodeAt(offset + 8);
|
|
55633
|
+
offset += 9 + (local & 128 ? 3 * (2 << (local & 7)) : 0);
|
|
55634
|
+
const codeSize = data.charCodeAt(offset++);
|
|
55635
|
+
if (codeSize < 2 || codeSize > 8 || offset >= data.length || data.charCodeAt(offset) === 0)
|
|
55636
|
+
return false;
|
|
55637
|
+
offset = skipGifBlocks(data, offset);
|
|
55638
|
+
image = true;
|
|
55639
|
+
} else return false;
|
|
55640
|
+
if (offset < 0) return false;
|
|
55641
|
+
}
|
|
55642
|
+
return false;
|
|
55643
|
+
}
|
|
55644
|
+
function isWebp(data) {
|
|
55645
|
+
return data.startsWith("RIFF") && data.slice(8, 12) === "WEBP" && little32(data, 4) + 8 === data.length && webpChunks(data, 12, data.length, true);
|
|
55646
|
+
}
|
|
55647
|
+
function webpChunks(data, start, end, allowFrames) {
|
|
55648
|
+
let image = false;
|
|
55649
|
+
let offset = start;
|
|
55650
|
+
while (offset + 8 <= end) {
|
|
55651
|
+
const type = data.slice(offset, offset + 4);
|
|
55652
|
+
const size = little32(data, offset + 4);
|
|
55653
|
+
const body = offset + 8;
|
|
55654
|
+
const next = body + size + size % 2;
|
|
55655
|
+
if (next > end) return false;
|
|
55656
|
+
if (type === "VP8 ") {
|
|
55657
|
+
if (size < 11 || data.charCodeAt(body) & 1 || data.slice(body + 3, body + 6) !== "\x9D*")
|
|
55658
|
+
return false;
|
|
55659
|
+
if (!((data.charCodeAt(body + 6) | data.charCodeAt(body + 7) << 8) & 16383) || !((data.charCodeAt(body + 8) | data.charCodeAt(body + 9) << 8) & 16383))
|
|
55660
|
+
return false;
|
|
55661
|
+
image = true;
|
|
55662
|
+
} else if (type === "VP8L") {
|
|
55663
|
+
if (size < 6 || data.charCodeAt(body) !== 47 || data.charCodeAt(body + 4) & 224)
|
|
55664
|
+
return false;
|
|
55665
|
+
image = true;
|
|
55666
|
+
} else if (type === "VP8X") {
|
|
55667
|
+
if (size !== 10 || !allowFrames) return false;
|
|
55668
|
+
} else if (type === "ANMF") {
|
|
55669
|
+
if (!allowFrames || size < 16 || !webpChunks(data, body + 16, body + size, false))
|
|
55670
|
+
return false;
|
|
55671
|
+
image = true;
|
|
55672
|
+
}
|
|
55673
|
+
offset = next;
|
|
55674
|
+
}
|
|
55675
|
+
return image && offset === end;
|
|
55676
|
+
}
|
|
55677
|
+
|
|
55488
55678
|
// ../core/src/transcriptImages.ts
|
|
55489
55679
|
var IMAGE_DATA_URI_RE = /^data:(image\/[\w.+-]+);base64,([A-Za-z0-9+/=_-]*)$/;
|
|
55490
55680
|
function isImageDataUri(value) {
|
|
55491
|
-
|
|
55681
|
+
if (!value.startsWith("data:image/")) return false;
|
|
55682
|
+
const match = IMAGE_DATA_URI_RE.exec(value);
|
|
55683
|
+
return match !== null && isEncodedImage(match[2], match[1]);
|
|
55492
55684
|
}
|
|
55493
55685
|
function isImageBase64Payload(record2, key) {
|
|
55494
|
-
|
|
55495
|
-
if (
|
|
55496
|
-
if (key === "base64") return
|
|
55686
|
+
const data = record2[key];
|
|
55687
|
+
if (typeof data !== "string") return false;
|
|
55688
|
+
if (key === "data" && record2.type === "base64") return isEncodedImage(data, record2.media_type);
|
|
55689
|
+
if (key === "base64") return isEncodedImage(data, record2.type);
|
|
55497
55690
|
return false;
|
|
55498
55691
|
}
|
|
55499
|
-
function isImageMediaType(value) {
|
|
55500
|
-
return typeof value === "string" && value.startsWith("image/");
|
|
55501
|
-
}
|
|
55502
55692
|
function describeImageBlock(block2) {
|
|
55503
55693
|
if (block2.type !== "image") return null;
|
|
55504
55694
|
const source = asRecord(block2.source);
|
|
@@ -56388,6 +56578,38 @@ function scrubBreadcrumb(breadcrumb) {
|
|
|
56388
56578
|
return breadcrumb;
|
|
56389
56579
|
}
|
|
56390
56580
|
|
|
56581
|
+
// ../core/src/freshness.ts
|
|
56582
|
+
function describeDataAge(generatedAt, now) {
|
|
56583
|
+
const ageMs = now.getTime() - generatedAt.getTime();
|
|
56584
|
+
if (ageMs < 0) return "just now";
|
|
56585
|
+
const minutes = Math.floor(ageMs / 6e4);
|
|
56586
|
+
if (minutes < 1) return "just now";
|
|
56587
|
+
if (minutes < 60) return `${minutes}m ago`;
|
|
56588
|
+
const hours = Math.floor(minutes / 60);
|
|
56589
|
+
if (hours < 24) return `${hours}h ago`;
|
|
56590
|
+
const days = Math.floor(hours / 24);
|
|
56591
|
+
return `${days}d ago`;
|
|
56592
|
+
}
|
|
56593
|
+
|
|
56594
|
+
// ../core/src/onboarding.ts
|
|
56595
|
+
var SAME_HOUR_MS = 60 * 60 * 1e3;
|
|
56596
|
+
function describeSyncSignals(member, now, noUpload = "no sessions uploaded yet") {
|
|
56597
|
+
const session = member.lastSessionActivityAt;
|
|
56598
|
+
const poll = member.lastCheckedInAt;
|
|
56599
|
+
const error62 = member.lastSyncErrorAt;
|
|
56600
|
+
const paused = member.syncPaused === true;
|
|
56601
|
+
if (session === null && poll === null && error62 === null) return null;
|
|
56602
|
+
const age = (at) => describeDataAge(new Date(at), now);
|
|
56603
|
+
const apart = (a, b2) => b2 === null || Math.abs(Date.parse(a) - Date.parse(b2)) > SAME_HOUR_MS;
|
|
56604
|
+
const parts = [session === null ? noUpload : `last uploaded session ${age(session)}`];
|
|
56605
|
+
if (error62 !== null) parts.push(`reported sync error ${age(error62)}`);
|
|
56606
|
+
if (paused) parts.push("syncing paused");
|
|
56607
|
+
const pollSays = error62 !== null ? apart(poll ?? error62, error62) : paused || apart(poll ?? "", session);
|
|
56608
|
+
if (poll !== null && pollSays) parts.push(`last poll ${age(poll)}`);
|
|
56609
|
+
const line = parts.join(", ");
|
|
56610
|
+
return `${line.charAt(0).toUpperCase()}${line.slice(1)}.`;
|
|
56611
|
+
}
|
|
56612
|
+
|
|
56391
56613
|
// ../core/src/sessionIngest.ts
|
|
56392
56614
|
var SESSION_SOURCES = [
|
|
56393
56615
|
"claude-code",
|
|
@@ -83335,9 +83557,20 @@ var storedCredentialsSchema = external_exports.object({
|
|
|
83335
83557
|
* (`powerplant folders add`). */
|
|
83336
83558
|
scopePaths: external_exports.array(external_exports.string()).optional(),
|
|
83337
83559
|
/** macOS privacy roots the member explicitly allowed Powerplant to inspect
|
|
83338
|
-
* for Git metadata. This does not widen
|
|
83339
|
-
* which discovered repositories upload. */
|
|
83560
|
+
* for Git metadata, and that macOS let it read. This does not widen
|
|
83561
|
+
* capture: org policy still decides which discovered repositories upload. */
|
|
83340
83562
|
repoDiscoveryRoots: external_exports.array(external_exports.string()).optional(),
|
|
83563
|
+
/** When the member answered yes to Git metadata scans of macOS-protected
|
|
83564
|
+
* session folders (ISO 8601). One answer for this enrollment: later init
|
|
83565
|
+
* runs scan a newly seen root after an explanation, without asking again.
|
|
83566
|
+
* Absent = never asked, or never answered yes. */
|
|
83567
|
+
repoDiscoveryConsentedAt: external_exports.string().optional(),
|
|
83568
|
+
/** Protected roots the member allowed but macOS refused to let Powerplant
|
|
83569
|
+
* read (a declined Files and Folders dialog). macOS remembers that refusal
|
|
83570
|
+
* and shows no second dialog, so init re-reads these roots on every run
|
|
83571
|
+
* without a question and moves a root to repoDiscoveryRoots once the
|
|
83572
|
+
* member enables it in System Settings. */
|
|
83573
|
+
repoDiscoveryBlockedRoots: external_exports.array(external_exports.string()).optional(),
|
|
83341
83574
|
/** When this enrollment's deep first sync completed; absent = still owed,
|
|
83342
83575
|
* so the next sync sweeps DEEP_FIRST_SYNC_DAYS instead of the rolling window. */
|
|
83343
83576
|
deepSyncedAt: external_exports.string().optional()
|
|
@@ -83423,7 +83656,7 @@ import os6 from "node:os";
|
|
|
83423
83656
|
// ../../apps/collector/src/http.ts
|
|
83424
83657
|
var import_undici = __toESM(require_undici(), 1);
|
|
83425
83658
|
var CLI_VERSION_HEADER = "x-powerplant-cli-version";
|
|
83426
|
-
var cliVersion = true ? "0.4.
|
|
83659
|
+
var cliVersion = true ? "0.4.8" : "0.0.0-checkout";
|
|
83427
83660
|
function cliVersionHeaders() {
|
|
83428
83661
|
return { [CLI_VERSION_HEADER]: cliVersion, "user-agent": `powerplant-cli/${cliVersion}` };
|
|
83429
83662
|
}
|
|
@@ -83608,8 +83841,8 @@ function unconsentedProtectedRoot(candidate, environment = {}) {
|
|
|
83608
83841
|
);
|
|
83609
83842
|
return consented ? null : protectedRoot;
|
|
83610
83843
|
}
|
|
83611
|
-
var DENIED = { kind: "denied" };
|
|
83612
83844
|
var NONE = { kind: "none" };
|
|
83845
|
+
var deniedAt = (p) => ({ kind: "denied", path: p });
|
|
83613
83846
|
function consentGate(candidate, environment) {
|
|
83614
83847
|
const root = unconsentedProtectedRoot(candidate, environment);
|
|
83615
83848
|
return root === null ? null : { kind: "unconsented", root, path: path2.resolve(candidate) };
|
|
@@ -83618,7 +83851,7 @@ function readProbe(p) {
|
|
|
83618
83851
|
try {
|
|
83619
83852
|
return { kind: "found", value: readFileSync3(p, "utf8") };
|
|
83620
83853
|
} catch (cause) {
|
|
83621
|
-
return isPermissionError(cause) ?
|
|
83854
|
+
return isPermissionError(cause) ? deniedAt(p) : NONE;
|
|
83622
83855
|
}
|
|
83623
83856
|
}
|
|
83624
83857
|
function gitDirReferenceProbe(cwd, environment) {
|
|
@@ -83628,7 +83861,7 @@ function gitDirReferenceProbe(cwd, environment) {
|
|
|
83628
83861
|
for (; ; ) {
|
|
83629
83862
|
const gitPath = path2.join(dir, ".git");
|
|
83630
83863
|
const kind = statKind(gitPath);
|
|
83631
|
-
if (kind === "denied") return
|
|
83864
|
+
if (kind === "denied") return deniedAt(gitPath);
|
|
83632
83865
|
if (kind === "dir") return { kind: "found", value: gitPath };
|
|
83633
83866
|
if (kind === "file") {
|
|
83634
83867
|
const pointer = readProbe(gitPath);
|
|
@@ -83652,7 +83885,7 @@ function gitDirsProbe(cwd, environment) {
|
|
|
83652
83885
|
const gitDirGate = consentGate(gitDir, environment);
|
|
83653
83886
|
if (gitDirGate) return gitDirGate;
|
|
83654
83887
|
const commondir = readProbe(path2.join(gitDir, "commondir"));
|
|
83655
|
-
if (commondir.kind === "denied") return
|
|
83888
|
+
if (commondir.kind === "denied") return commondir;
|
|
83656
83889
|
const pointer = commondir.kind === "found" ? commondir.value.trim() : "";
|
|
83657
83890
|
const commonDir = pointer ? path2.resolve(gitDir, pointer) : gitDir;
|
|
83658
83891
|
const commonDirGate = consentGate(commonDir, environment);
|
|
@@ -83887,10 +84120,15 @@ function tokenRejected(route) {
|
|
|
83887
84120
|
`${route} rejected this machine's token (revoked or unknown). Re-enroll this machine (powerplant enroll, or pnpm collector enroll from the checkout).`
|
|
83888
84121
|
);
|
|
83889
84122
|
}
|
|
83890
|
-
|
|
84123
|
+
function capturePolicyUrl(apiUrl, request2) {
|
|
84124
|
+
const url2 = new URL(`${apiUrl}/api/collector/capture-policy`);
|
|
84125
|
+
if (request2.paused !== void 0) url2.searchParams.set("paused", String(request2.paused));
|
|
84126
|
+
return url2.toString();
|
|
84127
|
+
}
|
|
84128
|
+
async function fetchCapturePolicy(apiUrl, machineToken, request2 = {}) {
|
|
83891
84129
|
const response = await fetchWithRetry(
|
|
83892
84130
|
"fetching the org's capture policy",
|
|
83893
|
-
|
|
84131
|
+
capturePolicyUrl(apiUrl, request2),
|
|
83894
84132
|
{ headers: { authorization: `Bearer ${machineToken}` } }
|
|
83895
84133
|
);
|
|
83896
84134
|
if (response.status === 401) {
|
|
@@ -83921,6 +84159,19 @@ async function fetchCapturePolicy(apiUrl, machineToken) {
|
|
|
83921
84159
|
);
|
|
83922
84160
|
return result.data;
|
|
83923
84161
|
}
|
|
84162
|
+
var CHECK_IN_INTERVAL_MS = 30 * 6e4;
|
|
84163
|
+
async function reportCheckIn(apiUrl, machineToken, paused, fetcher = fetch) {
|
|
84164
|
+
try {
|
|
84165
|
+
const response = await fetcher(capturePolicyUrl(apiUrl, { paused }), {
|
|
84166
|
+
headers: { ...cliVersionHeaders(), authorization: `Bearer ${machineToken}` },
|
|
84167
|
+
signal: AbortSignal.timeout(3e3)
|
|
84168
|
+
});
|
|
84169
|
+
await response.body?.cancel();
|
|
84170
|
+
return response.ok;
|
|
84171
|
+
} catch {
|
|
84172
|
+
return false;
|
|
84173
|
+
}
|
|
84174
|
+
}
|
|
83924
84175
|
async function uploadSessions(apiUrl, machineToken, items) {
|
|
83925
84176
|
let upserted = 0;
|
|
83926
84177
|
let unchanged = 0;
|
|
@@ -84461,7 +84712,6 @@ function removeFolder(config2, rawPath, enrollCommand, log3, deps = defaultDeps)
|
|
|
84461
84712
|
|
|
84462
84713
|
// ../../apps/collector/src/diagnostics.ts
|
|
84463
84714
|
import { createHmac } from "node:crypto";
|
|
84464
|
-
var DIAGNOSTIC_INTERVAL_MS = 30 * 6e4;
|
|
84465
84715
|
function observeFileDiagnostic(observer, file2) {
|
|
84466
84716
|
try {
|
|
84467
84717
|
observer?.(file2);
|
|
@@ -84579,8 +84829,7 @@ function createDiagnosticsReporter(credentials, send = createDiagnosticSender())
|
|
|
84579
84829
|
let pending = Promise.resolve();
|
|
84580
84830
|
let enrollment = null;
|
|
84581
84831
|
let lastIssue = null;
|
|
84582
|
-
|
|
84583
|
-
const report2 = (detailed) => {
|
|
84832
|
+
const report2 = () => {
|
|
84584
84833
|
try {
|
|
84585
84834
|
const current = credentials();
|
|
84586
84835
|
if (!current) return;
|
|
@@ -84589,29 +84838,20 @@ function createDiagnosticsReporter(credentials, send = createDiagnosticSender())
|
|
|
84589
84838
|
lastIssue = null;
|
|
84590
84839
|
}
|
|
84591
84840
|
const matching = pass !== null && passEnrollments.get(pass) === current.token ? pass : null;
|
|
84592
|
-
if (
|
|
84593
|
-
|
|
84594
|
-
|
|
84595
|
-
|
|
84596
|
-
|
|
84597
|
-
}
|
|
84598
|
-
const body = collectorDiagnosticSchema.parse({
|
|
84599
|
-
state: matching?.outcome === "running" ? "syncing" : paused ? "paused" : "idle",
|
|
84600
|
-
pass: detailed ? matching : null
|
|
84601
|
-
});
|
|
84841
|
+
if (matching === null) return;
|
|
84842
|
+
const issue2 = issueSignature(matching);
|
|
84843
|
+
if (issue2 === lastIssue) return;
|
|
84844
|
+
lastIssue = issue2;
|
|
84845
|
+
const body = collectorDiagnosticSchema.parse({ state: "idle", pass: matching });
|
|
84602
84846
|
pending = send(current, body).catch(() => void 0);
|
|
84603
84847
|
} catch {
|
|
84604
84848
|
}
|
|
84605
84849
|
};
|
|
84606
84850
|
return {
|
|
84607
|
-
heartbeat: (isPaused = false) => {
|
|
84608
|
-
paused = isPaused;
|
|
84609
|
-
report2(false);
|
|
84610
|
-
},
|
|
84611
84851
|
flush: () => pending,
|
|
84612
84852
|
onDiagnostics: (diagnostic) => {
|
|
84613
84853
|
pass = diagnostic;
|
|
84614
|
-
if (diagnostic.outcome !== "running") report2(
|
|
84854
|
+
if (diagnostic.outcome !== "running") report2();
|
|
84615
84855
|
}
|
|
84616
84856
|
};
|
|
84617
84857
|
}
|
|
@@ -86296,25 +86536,33 @@ function groupProtectedSessions(sessions, environment = {}) {
|
|
|
86296
86536
|
if (!session.cwd) continue;
|
|
86297
86537
|
const cwd = path10.resolve(session.cwd);
|
|
86298
86538
|
const directRoot = macPrivacyProtectedRoot(cwd, environment);
|
|
86299
|
-
|
|
86300
|
-
const
|
|
86301
|
-
|
|
86302
|
-
|
|
86303
|
-
|
|
86539
|
+
if (directRoot !== null) tally.add(directRoot, cwd, cwd);
|
|
86540
|
+
const gitDir = resolveGitDirReference(cwd, environment);
|
|
86541
|
+
const checkoutRoot = gitDir ? macPrivacyProtectedRoot(gitDir, environment) : null;
|
|
86542
|
+
if (checkoutRoot !== null && checkoutRoot !== directRoot) {
|
|
86543
|
+
tally.add(checkoutRoot, checkoutOfGitDir(gitDir), cwd);
|
|
86544
|
+
}
|
|
86304
86545
|
}
|
|
86305
86546
|
return tally.entries();
|
|
86306
86547
|
}
|
|
86307
86548
|
var defaultAllowDeps = {
|
|
86308
86549
|
load: loadCredentials,
|
|
86309
86550
|
persist: saveCredentials,
|
|
86310
|
-
probeRepo: repoProber
|
|
86551
|
+
probeRepo: repoProber,
|
|
86552
|
+
now: Date.now
|
|
86311
86553
|
};
|
|
86554
|
+
function protectedRootReview(credentials, root) {
|
|
86555
|
+
const resolved = path10.resolve(root);
|
|
86556
|
+
if ((credentials.repoDiscoveryRoots ?? []).includes(resolved)) return "allowed";
|
|
86557
|
+
if ((credentials.repoDiscoveryBlockedRoots ?? []).includes(resolved)) return "blocked";
|
|
86558
|
+
return "unreviewed";
|
|
86559
|
+
}
|
|
86312
86560
|
function allowProtectedRepoDiscovery(config2, found, environment = {}, deps = defaultAllowDeps) {
|
|
86313
86561
|
const enrollment = deps.load(config2.appEnv);
|
|
86314
86562
|
if (enrollment === null) {
|
|
86315
86563
|
throw new ExpectedCliError("This machine must be enrolled before protected folders can be reviewed.");
|
|
86316
86564
|
}
|
|
86317
|
-
const candidateRoots = found
|
|
86565
|
+
const candidateRoots = rootsOf(found);
|
|
86318
86566
|
const invalid = candidateRoots.find(
|
|
86319
86567
|
(root) => macPrivacyProtectedRoot(root, environment) !== root
|
|
86320
86568
|
);
|
|
@@ -86335,49 +86583,88 @@ function allowProtectedRepoDiscovery(config2, found, environment = {}, deps = de
|
|
|
86335
86583
|
needsReview: []
|
|
86336
86584
|
};
|
|
86337
86585
|
const needsReview = new ProtectedRootTally(environment);
|
|
86586
|
+
const refusedElsewhere = new ProtectedRootTally(environment);
|
|
86338
86587
|
for (const entry of found) {
|
|
86588
|
+
const root = path10.resolve(entry.root);
|
|
86339
86589
|
const probes = entry.folders.flatMap(
|
|
86340
86590
|
(folder) => folder.cwds.map((cwd) => ({ cwd, probe: probeOf(cwd), folder }))
|
|
86341
86591
|
);
|
|
86592
|
+
let deniedHere = false;
|
|
86342
86593
|
let unconsented = 0;
|
|
86343
86594
|
const countedFolderTargets = /* @__PURE__ */ new Set();
|
|
86595
|
+
const countOnce = (target, sessionCount) => {
|
|
86596
|
+
const counted = countedFolderTargets.has(target);
|
|
86597
|
+
countedFolderTargets.add(target);
|
|
86598
|
+
return counted ? 0 : sessionCount;
|
|
86599
|
+
};
|
|
86344
86600
|
for (const { cwd, probe, folder } of probes) {
|
|
86601
|
+
if (probe.kind === "denied") {
|
|
86602
|
+
const refusedRoot = macPrivacyProtectedRoot(probe.path, environment);
|
|
86603
|
+
if (refusedRoot === root) deniedHere = true;
|
|
86604
|
+
else if (refusedRoot !== null) {
|
|
86605
|
+
const checkout2 = checkoutOfGitDir(probe.path);
|
|
86606
|
+
const target2 = [folder.path, refusedRoot, checkout2].join("\0");
|
|
86607
|
+
refusedElsewhere.add(refusedRoot, checkout2, cwd, countOnce(target2, folder.sessionCount));
|
|
86608
|
+
}
|
|
86609
|
+
continue;
|
|
86610
|
+
}
|
|
86345
86611
|
if (probe.kind !== "unconsented") continue;
|
|
86346
86612
|
unconsented += 1;
|
|
86347
86613
|
const checkout = checkoutOfGitDir(probe.path);
|
|
86348
86614
|
const target = [folder.path, probe.root, checkout].join("\0");
|
|
86349
|
-
needsReview.add(
|
|
86350
|
-
probe.root,
|
|
86351
|
-
checkout,
|
|
86352
|
-
cwd,
|
|
86353
|
-
countedFolderTargets.has(target) ? 0 : folder.sessionCount
|
|
86354
|
-
);
|
|
86355
|
-
countedFolderTargets.add(target);
|
|
86615
|
+
needsReview.add(probe.root, checkout, cwd, countOnce(target, folder.sessionCount));
|
|
86356
86616
|
}
|
|
86357
86617
|
if (probes.some(({ probe }) => probe.kind === "found")) outcome.enabled.push(entry);
|
|
86358
|
-
else if (
|
|
86618
|
+
else if (deniedHere) outcome.blocked.push(entry);
|
|
86359
86619
|
else if (unconsented > 0) outcome.unreviewed.push(entry);
|
|
86360
86620
|
else outcome.unmatched.push(entry);
|
|
86361
86621
|
}
|
|
86622
|
+
const blockedRoots = new Set(rootsOf(outcome.blocked));
|
|
86623
|
+
outcome.blocked.push(
|
|
86624
|
+
...refusedElsewhere.entries().filter((entry) => !blockedRoots.has(entry.root))
|
|
86625
|
+
);
|
|
86362
86626
|
outcome.needsReview = needsReview.entries();
|
|
86363
|
-
const consented = [...outcome.enabled, ...outcome.unmatched];
|
|
86364
|
-
if (consented.length === 0) return outcome;
|
|
86365
86627
|
const credentials = deps.load(config2.appEnv);
|
|
86366
86628
|
if (credentials === null || credentials.token !== enrollment.token || credentials.enrolledAt !== enrollment.enrolledAt) {
|
|
86367
86629
|
throw new ExpectedCliError(
|
|
86368
|
-
"This machine's enrollment changed while the folders were being reviewed, so
|
|
86630
|
+
"This machine's enrollment changed while the folders were being reviewed, so nothing was saved, run `powerplant init` again to review them."
|
|
86369
86631
|
);
|
|
86370
86632
|
}
|
|
86371
|
-
const
|
|
86633
|
+
const next = credentialsAfterRepoDiscovery(credentials, outcome, deps.now);
|
|
86634
|
+
if (next !== null) deps.persist(config2.appEnv, next);
|
|
86635
|
+
return outcome;
|
|
86636
|
+
}
|
|
86637
|
+
function rootsOf(entries) {
|
|
86638
|
+
return entries.map((entry) => path10.resolve(entry.root));
|
|
86639
|
+
}
|
|
86640
|
+
function credentialsAfterRepoDiscovery(credentials, outcome, now) {
|
|
86641
|
+
const readable = rootsOf([...outcome.enabled, ...outcome.unmatched]);
|
|
86642
|
+
const denied = rootsOf(outcome.blocked).filter((root) => !readable.includes(root));
|
|
86643
|
+
const allowedRoots = [
|
|
86372
86644
|
.../* @__PURE__ */ new Set([
|
|
86373
|
-
...credentials.repoDiscoveryRoots ?? [],
|
|
86374
|
-
...
|
|
86645
|
+
...(credentials.repoDiscoveryRoots ?? []).filter((root) => !denied.includes(root)),
|
|
86646
|
+
...readable
|
|
86375
86647
|
])
|
|
86376
86648
|
];
|
|
86377
|
-
|
|
86378
|
-
|
|
86379
|
-
|
|
86380
|
-
|
|
86649
|
+
const blockedRoots = [
|
|
86650
|
+
.../* @__PURE__ */ new Set([
|
|
86651
|
+
...(credentials.repoDiscoveryBlockedRoots ?? []).filter((root) => !readable.includes(root)),
|
|
86652
|
+
...denied
|
|
86653
|
+
])
|
|
86654
|
+
];
|
|
86655
|
+
const unchanged = credentials.repoDiscoveryConsentedAt !== void 0 && sameList(allowedRoots, credentials.repoDiscoveryRoots ?? []) && sameList(blockedRoots, credentials.repoDiscoveryBlockedRoots ?? []);
|
|
86656
|
+
if (unchanged) return null;
|
|
86657
|
+
return {
|
|
86658
|
+
...credentials,
|
|
86659
|
+
repoDiscoveryConsentedAt: credentials.repoDiscoveryConsentedAt ?? new Date(now()).toISOString(),
|
|
86660
|
+
// Both lists clear on empty: keeping a stale allowed list would read a
|
|
86661
|
+
// root as consent after macOS refused it.
|
|
86662
|
+
repoDiscoveryRoots: allowedRoots.length > 0 ? allowedRoots : void 0,
|
|
86663
|
+
repoDiscoveryBlockedRoots: blockedRoots.length > 0 ? blockedRoots : void 0
|
|
86664
|
+
};
|
|
86665
|
+
}
|
|
86666
|
+
function sameList(a, b2) {
|
|
86667
|
+
return a.length === b2.length && a.every((root, index) => root === b2[index]);
|
|
86381
86668
|
}
|
|
86382
86669
|
|
|
86383
86670
|
// ../../apps/collector/src/hookInstall.ts
|
|
@@ -86709,6 +86996,9 @@ var collectorStatusSchema = external_exports.object({
|
|
|
86709
86996
|
/** Last pass that completed without error — everything eligible had been
|
|
86710
86997
|
* uploaded as of this time. */
|
|
86711
86998
|
lastSyncAt: external_exports.string().optional(),
|
|
86999
|
+
/** Last pass that upserted at least one session; missing = no session
|
|
87000
|
+
* has uploaded in this daemon's lifetime, not "never". */
|
|
87001
|
+
lastUploadAt: external_exports.string().optional(),
|
|
86712
87002
|
consecutiveFailures: external_exports.number().int().nonnegative()
|
|
86713
87003
|
});
|
|
86714
87004
|
var controlSchema = external_exports.object({
|
|
@@ -86725,7 +87015,8 @@ var controlSchema = external_exports.object({
|
|
|
86725
87015
|
function interpretControlChange(previous, current) {
|
|
86726
87016
|
const transitioned = previous.paused !== current.paused;
|
|
86727
87017
|
const nudge = !current.paused && (transitioned || current.resumedAt !== previous.resumedAt);
|
|
86728
|
-
|
|
87018
|
+
const updateOptIn = current.autoUpdate === true && previous.autoUpdate !== true;
|
|
87019
|
+
return { transitioned, nudge, updateOptIn };
|
|
86729
87020
|
}
|
|
86730
87021
|
function collectorStateDir(homeDir = os14.homedir()) {
|
|
86731
87022
|
return path13.join(homeDir, ".config", "powerplant", "state");
|
|
@@ -86748,6 +87039,10 @@ function writeAtomically(file2, payload) {
|
|
|
86748
87039
|
function writeCollectorStatus(appEnv, status, homeDir) {
|
|
86749
87040
|
writeAtomically(statusFilePath(appEnv, homeDir), JSON.stringify(status, null, 2));
|
|
86750
87041
|
}
|
|
87042
|
+
function carriedUploadClock(previous, machineId) {
|
|
87043
|
+
if (previous.file !== "ok" || machineId === void 0) return void 0;
|
|
87044
|
+
return previous.status.machineId === machineId ? previous.status.lastUploadAt : void 0;
|
|
87045
|
+
}
|
|
86751
87046
|
function readCollectorStatus(appEnv, homeDir) {
|
|
86752
87047
|
let raw;
|
|
86753
87048
|
try {
|
|
@@ -87856,6 +88151,10 @@ function startWatch(config2, homeDir, runtime = { version: "checkout" }) {
|
|
|
87856
88151
|
const presentRoots = storeRoots.filter(({ dir }) => existsSync6(dir)).map(({ dir }) => dir);
|
|
87857
88152
|
const initialControl = readCollectorControl(config2.appEnv, home);
|
|
87858
88153
|
const pausedAtMs = initialControl.paused ? Date.parse(initialControl.pausedAt ?? "") : Number.NaN;
|
|
88154
|
+
const lastUploadAt = carriedUploadClock(
|
|
88155
|
+
readCollectorStatus(config2.appEnv, home),
|
|
88156
|
+
credentials?.machineId
|
|
88157
|
+
);
|
|
87859
88158
|
let status = {
|
|
87860
88159
|
pid: process.pid,
|
|
87861
88160
|
version: runtime.version,
|
|
@@ -87871,6 +88170,7 @@ function startWatch(config2, homeDir, runtime = { version: "checkout" }) {
|
|
|
87871
88170
|
},
|
|
87872
88171
|
watchedRoots: presentRoots,
|
|
87873
88172
|
paused: initialControl.paused,
|
|
88173
|
+
...lastUploadAt !== void 0 && { lastUploadAt },
|
|
87874
88174
|
consecutiveFailures: 0
|
|
87875
88175
|
};
|
|
87876
88176
|
let statusWriteFailed = false;
|
|
@@ -87889,9 +88189,19 @@ function startWatch(config2, homeDir, runtime = { version: "checkout" }) {
|
|
|
87889
88189
|
};
|
|
87890
88190
|
writeStatus({});
|
|
87891
88191
|
const diagnostics = createDiagnosticsReporter(() => loadCredentials(config2.appEnv));
|
|
87892
|
-
|
|
87893
|
-
|
|
87894
|
-
|
|
88192
|
+
const checkIn = () => {
|
|
88193
|
+
let current;
|
|
88194
|
+
try {
|
|
88195
|
+
current = loadCredentials(config2.appEnv);
|
|
88196
|
+
} catch {
|
|
88197
|
+
return;
|
|
88198
|
+
}
|
|
88199
|
+
if (current === null) return;
|
|
88200
|
+
void reportCheckIn(current.apiUrl, current.token, status.paused);
|
|
88201
|
+
};
|
|
88202
|
+
checkIn();
|
|
88203
|
+
const checkInTimer = setInterval(checkIn, CHECK_IN_INTERVAL_MS);
|
|
88204
|
+
checkInTimer.unref?.();
|
|
87895
88205
|
const heartbeat = setInterval(() => writeStatus({}), HEARTBEAT_INTERVAL_MS);
|
|
87896
88206
|
heartbeat.unref?.();
|
|
87897
88207
|
const noticeIfUnsupported = async () => {
|
|
@@ -87989,6 +88299,7 @@ function startWatch(config2, homeDir, runtime = { version: "checkout" }) {
|
|
|
87989
88299
|
...result.diagnostic && { diagnostic: result.diagnostic }
|
|
87990
88300
|
},
|
|
87991
88301
|
...result.ok && { lastSyncAt: new Date(result.at).toISOString() },
|
|
88302
|
+
...result.ok && result.upserted > 0 && { lastUploadAt: new Date(result.at).toISOString() },
|
|
87992
88303
|
consecutiveFailures: result.ok ? 0 : status.consecutiveFailures + 1
|
|
87993
88304
|
}),
|
|
87994
88305
|
reportError: reportCollectorError,
|
|
@@ -88020,16 +88331,21 @@ function startWatch(config2, homeDir, runtime = { version: "checkout" }) {
|
|
|
88020
88331
|
let lastControl = initialControl;
|
|
88021
88332
|
const controlPoll = setInterval(() => {
|
|
88022
88333
|
const control = readCollectorControl(config2.appEnv, home);
|
|
88023
|
-
const { transitioned, nudge } = interpretControlChange(lastControl, control);
|
|
88334
|
+
const { transitioned, nudge, updateOptIn } = interpretControlChange(lastControl, control);
|
|
88024
88335
|
lastControl = control;
|
|
88025
88336
|
if (transitioned) {
|
|
88026
88337
|
log3(control.paused ? "syncing paused via control file" : "syncing resumed");
|
|
88027
88338
|
writeStatus({ paused: control.paused });
|
|
88339
|
+
checkIn();
|
|
88028
88340
|
}
|
|
88029
88341
|
if (nudge) {
|
|
88030
88342
|
log3("running a catch-up pass to cover the pause");
|
|
88031
88343
|
loop.nudge();
|
|
88032
88344
|
}
|
|
88345
|
+
if (updateOptIn) {
|
|
88346
|
+
log3("automatic updates turned on, checking for a release now");
|
|
88347
|
+
void checkForUpdate();
|
|
88348
|
+
}
|
|
88033
88349
|
}, CONTROL_POLL_MS);
|
|
88034
88350
|
controlPoll.unref?.();
|
|
88035
88351
|
if (runtime.entryPath) {
|
|
@@ -89852,7 +90168,7 @@ import { accessSync, constants as constants2 } from "node:fs";
|
|
|
89852
90168
|
import path23 from "node:path";
|
|
89853
90169
|
|
|
89854
90170
|
// src/version.ts
|
|
89855
|
-
var CLI_VERSION = true ? "0.4.
|
|
90171
|
+
var CLI_VERSION = true ? "0.4.8" : "0.0.0-checkout";
|
|
89856
90172
|
var CLI_RELEASE_BUILD = true ? true : false;
|
|
89857
90173
|
var CLI_PACKAGE_NAME = "@usepowerplant/cli";
|
|
89858
90174
|
|
|
@@ -90424,6 +90740,9 @@ async function confirmResume(deps, log3, indent) {
|
|
|
90424
90740
|
}
|
|
90425
90741
|
}
|
|
90426
90742
|
|
|
90743
|
+
// src/protectedFolders.ts
|
|
90744
|
+
var MACOS_BLOCKED_FOLDER_REMEDY = "To allow it, open System Settings > Privacy & Security > Files and Folders, enable the folder for node, then run `powerplant init`.";
|
|
90745
|
+
|
|
90427
90746
|
// src/init.ts
|
|
90428
90747
|
function syncNote(credentials) {
|
|
90429
90748
|
const org = credentials.orgName ?? "your organization";
|
|
@@ -90468,7 +90787,7 @@ async function runInit(deps) {
|
|
|
90468
90787
|
}
|
|
90469
90788
|
log3("");
|
|
90470
90789
|
const orgName = credentials.orgName;
|
|
90471
|
-
let
|
|
90790
|
+
let repoDiscoveryDidWork = false;
|
|
90472
90791
|
let protectedFoldersNeedAttention = false;
|
|
90473
90792
|
let installed = false;
|
|
90474
90793
|
let menubarDidWork = false;
|
|
@@ -90481,56 +90800,68 @@ async function runInit(deps) {
|
|
|
90481
90800
|
"Checking recent session locations\u2026",
|
|
90482
90801
|
deps.findProtectedSessions
|
|
90483
90802
|
);
|
|
90484
|
-
const
|
|
90485
|
-
const
|
|
90486
|
-
|
|
90487
|
-
|
|
90488
|
-
|
|
90489
|
-
|
|
90490
|
-
|
|
90491
|
-
|
|
90492
|
-
|
|
90493
|
-
|
|
90494
|
-
|
|
90495
|
-
|
|
90496
|
-
|
|
90803
|
+
const reviewOf = (entry) => protectedRootReview(credentials, entry.root);
|
|
90804
|
+
const blocked = protectedSessions.filter((entry) => reviewOf(entry) === "blocked");
|
|
90805
|
+
const fresh = protectedSessions.filter((entry) => reviewOf(entry) === "unreviewed");
|
|
90806
|
+
if (blocked.length > 0 || fresh.length > 0) {
|
|
90807
|
+
const org = orgName ?? "your organization";
|
|
90808
|
+
const consented = credentials.repoDiscoveryConsentedAt !== void 0;
|
|
90809
|
+
const toProbe = [];
|
|
90810
|
+
let needsAttention = false;
|
|
90811
|
+
if (fresh.length > 0) {
|
|
90812
|
+
log3(" Coding agents recorded recent sessions in folders macOS is currently hiding from Powerplant:");
|
|
90813
|
+
logProtectedFolders(log3, fresh);
|
|
90814
|
+
if (!deps.promptAllowed) {
|
|
90815
|
+
needsAttention = true;
|
|
90816
|
+
logProtectedSessionsStayLocal(log3, fresh);
|
|
90817
|
+
log3(` Run \`powerplant init\` in a terminal to ${consented ? "scan" : "review"} them.`);
|
|
90818
|
+
} else {
|
|
90497
90819
|
log3(
|
|
90498
|
-
` If
|
|
90820
|
+
consented ? ` You already allowed Git metadata scans. Checking those folders for ${org}'s work repositories.` : ` If you allow it, Powerplant reads only Git metadata there, to find ${org}'s work repositories.`
|
|
90499
90821
|
);
|
|
90500
|
-
|
|
90822
|
+
if (consented || await confirmProtectedRepoDiscovery(deps)) toProbe.push(...fresh);
|
|
90823
|
+
else {
|
|
90824
|
+
needsAttention = true;
|
|
90825
|
+
logProtectedSessionsStayLocal(log3, fresh);
|
|
90826
|
+
}
|
|
90827
|
+
}
|
|
90828
|
+
}
|
|
90829
|
+
if (blocked.length > 0) {
|
|
90830
|
+
if (deps.promptAllowed) {
|
|
90831
|
+
log3(` Checking whether macOS still blocks ${displayRoots(blocked)}.`);
|
|
90832
|
+
toProbe.push(...blocked);
|
|
90833
|
+
} else {
|
|
90834
|
+
needsAttention = true;
|
|
90835
|
+
logBlockedByMacOS(log3, blocked);
|
|
90836
|
+
}
|
|
90837
|
+
}
|
|
90838
|
+
if (toProbe.length > 0) {
|
|
90839
|
+
log3(' macOS may label Powerplant as "node" because Powerplant runs on Node.js.');
|
|
90840
|
+
const probe = async (roots) => {
|
|
90841
|
+
const result2 = await deps.activity(
|
|
90501
90842
|
"Scanning recorded session folders for Git repositories\u2026",
|
|
90502
|
-
() => deps.allowProtectedRepoDiscovery(
|
|
90843
|
+
() => deps.allowProtectedRepoDiscovery(roots)
|
|
90503
90844
|
);
|
|
90504
|
-
|
|
90505
|
-
|
|
90506
|
-
if (
|
|
90507
|
-
log3(
|
|
90508
|
-
|
|
90509
|
-
|
|
90510
|
-
|
|
90511
|
-
|
|
90512
|
-
|
|
90513
|
-
|
|
90514
|
-
|
|
90515
|
-
|
|
90516
|
-
|
|
90517
|
-
|
|
90518
|
-
|
|
90519
|
-
|
|
90520
|
-
}
|
|
90521
|
-
} else {
|
|
90522
|
-
logProtectedSessionsStayLocal(log3, result.needsReview);
|
|
90523
|
-
needsAttention = true;
|
|
90524
|
-
}
|
|
90845
|
+
needsAttention = reportProtectedRepoDiscovery(log3, result2) || needsAttention;
|
|
90846
|
+
const unblocked = result2.unmatched.filter((entry) => reviewOf(entry) === "blocked");
|
|
90847
|
+
if (unblocked.length > 0) {
|
|
90848
|
+
log3(` \u2713 macOS now lets Powerplant read ${displayRoots(unblocked)}. No work repositories were found there.`);
|
|
90849
|
+
}
|
|
90850
|
+
if (result2.enabled.length > 0 || unblocked.length > 0) repoDiscoveryDidWork = true;
|
|
90851
|
+
return result2;
|
|
90852
|
+
};
|
|
90853
|
+
const result = await probe(toProbe);
|
|
90854
|
+
if (result.needsReview.length > 0) {
|
|
90855
|
+
log3(" Some of those sessions use Git checkouts in other folders macOS is hiding from Powerplant:");
|
|
90856
|
+
logProtectedFolders(log3, result.needsReview);
|
|
90857
|
+
const second = await probe([...result.unreviewed, ...result.needsReview]);
|
|
90858
|
+
if (second.unreviewed.length > 0) {
|
|
90859
|
+
logProtectedSessionsStayLocal(log3, second.unreviewed);
|
|
90860
|
+
needsAttention = true;
|
|
90525
90861
|
}
|
|
90526
|
-
protectedFoldersNeedAttention = needsAttention;
|
|
90527
|
-
} else {
|
|
90528
|
-
logProtectedSessionsStayLocal(log3, needsReview);
|
|
90529
90862
|
}
|
|
90530
|
-
} else {
|
|
90531
|
-
logProtectedSessionsStayLocal(log3, needsReview);
|
|
90532
|
-
log3(" Run `powerplant init` in a terminal to review them.");
|
|
90533
90863
|
}
|
|
90864
|
+
protectedFoldersNeedAttention = needsAttention;
|
|
90534
90865
|
}
|
|
90535
90866
|
}
|
|
90536
90867
|
if (deps.platform !== "darwin") {
|
|
@@ -90570,7 +90901,7 @@ async function runInit(deps) {
|
|
|
90570
90901
|
log3("");
|
|
90571
90902
|
const connectDidWork = outcome !== null && outcome.connected + outcome.configured > outcome.unchanged;
|
|
90572
90903
|
const connectSettled = outcome !== null && outcome.found > 0 && outcome.pending === 0 && outcome.failed === 0 && outcome.skipped === 0 && outcome.unchanged === outcome.found && !connectDidWork;
|
|
90573
|
-
if (!enrolled && !installed && !menubarDidWork && !
|
|
90904
|
+
if (!enrolled && !installed && !menubarDidWork && !repoDiscoveryDidWork && !protectedFoldersNeedAttention && pauseOffer === "not-paused" && connectSettled) {
|
|
90574
90905
|
log3("Everything is already set up.");
|
|
90575
90906
|
return;
|
|
90576
90907
|
}
|
|
@@ -90647,24 +90978,22 @@ function logProtectedFolders(log3, roots) {
|
|
|
90647
90978
|
}
|
|
90648
90979
|
function reportProtectedRepoDiscovery(log3, result) {
|
|
90649
90980
|
if (result.enabled.length > 0) {
|
|
90650
|
-
log3(
|
|
90651
|
-
` \u2713 Repository scanning enabled for ${result.enabled.map((entry) => entry.displayRoot).join(", ")}`
|
|
90652
|
-
);
|
|
90653
|
-
}
|
|
90654
|
-
if (result.blocked.length > 0) {
|
|
90655
|
-
log3(
|
|
90656
|
-
` ! macOS blocked Powerplant from reading ${result.blocked.map((entry) => entry.displayRoot).join(", ")}.`
|
|
90657
|
-
);
|
|
90658
|
-
log3(
|
|
90659
|
-
" Sessions there will stay local. To allow it, open System Settings > Privacy & Security > Files and Folders, enable the folder for node, then rerun `powerplant init`."
|
|
90660
|
-
);
|
|
90981
|
+
log3(` \u2713 Repository scanning enabled for ${displayRoots(result.enabled)}`);
|
|
90661
90982
|
}
|
|
90983
|
+
if (result.blocked.length > 0) logBlockedByMacOS(log3, result.blocked);
|
|
90662
90984
|
return result.blocked.length > 0;
|
|
90663
90985
|
}
|
|
90986
|
+
function logBlockedByMacOS(log3, roots) {
|
|
90987
|
+
log3(` ! macOS blocked Powerplant from reading ${displayRoots(roots)}.`);
|
|
90988
|
+
log3(` Sessions there will stay local. ${MACOS_BLOCKED_FOLDER_REMEDY}`);
|
|
90989
|
+
}
|
|
90990
|
+
function displayRoots(roots) {
|
|
90991
|
+
return roots.map((entry) => entry.displayRoot).join(", ");
|
|
90992
|
+
}
|
|
90664
90993
|
function logProtectedSessionsStayLocal(log3, found) {
|
|
90665
90994
|
const total = found.reduce((sum2, entry) => sum2 + entry.sessionCount, 0);
|
|
90666
90995
|
log3(
|
|
90667
|
-
` ! Repository matching for ${total} recent session${total === 1 ? "" : "s"} under ${found
|
|
90996
|
+
` ! Repository matching for ${total} recent session${total === 1 ? "" : "s"} under ${displayRoots(found)} was not enabled.`
|
|
90668
90997
|
);
|
|
90669
90998
|
log3(" Sessions outside folders you explicitly added will stay local.");
|
|
90670
90999
|
}
|
|
@@ -91313,8 +91642,9 @@ function renderStatus(i) {
|
|
|
91313
91642
|
);
|
|
91314
91643
|
lines.push(`Syncing: ${describeStoredCapture(i.credentials)}.`);
|
|
91315
91644
|
for (const entry of i.protectedSessions ?? []) {
|
|
91645
|
+
const sessions = `${entry.sessionCount} recent agent session${entry.sessionCount === 1 ? "" : "s"}`;
|
|
91316
91646
|
lines.push(
|
|
91317
|
-
`\u26A0
|
|
91647
|
+
protectedRootReview(i.credentials, entry.root) === "blocked" ? `\u26A0 macOS is blocking Powerplant from reading ${entry.displayRoot} (${sessions}). Those sessions are staying local. ${MACOS_BLOCKED_FOLDER_REMEDY}` : `\u26A0 Repository matching is not enabled for ${sessions} under ${entry.displayRoot}. Sessions outside folders you explicitly added are staying local, run \`powerplant init\` to review Git repository scanning.`
|
|
91318
91648
|
);
|
|
91319
91649
|
}
|
|
91320
91650
|
}
|
|
@@ -91366,9 +91696,17 @@ function renderDaemon(i) {
|
|
|
91366
91696
|
` The daemon is on ${status.version}, this CLI is ${i.cliVersion}, it restarts onto the installed version within minutes, or now via: powerplant restart`
|
|
91367
91697
|
);
|
|
91368
91698
|
}
|
|
91369
|
-
|
|
91370
|
-
|
|
91699
|
+
const signals = describeSyncSignals(
|
|
91700
|
+
{
|
|
91701
|
+
lastSessionActivityAt: status.lastUploadAt ?? null,
|
|
91702
|
+
lastCheckedInAt: status.lastPass?.at ?? null,
|
|
91703
|
+
syncPaused: null,
|
|
91704
|
+
lastSyncErrorAt: status.lastPass && !status.lastPass.ok ? status.lastPass.at : null
|
|
91705
|
+
},
|
|
91706
|
+
new Date(i.now),
|
|
91707
|
+
"last upload unmeasured"
|
|
91371
91708
|
);
|
|
91709
|
+
lines.push(signals === null ? " No pass has run yet in this run." : ` ${signals}`);
|
|
91372
91710
|
if (status.lastPass && !status.lastPass.ok) {
|
|
91373
91711
|
lines.push(
|
|
91374
91712
|
` \u26A0 Last pass failed: ${status.lastPass.error ?? "unknown error"} (${status.consecutiveFailures} consecutive failure${status.consecutiveFailures === 1 ? "" : "s"}).`
|
|
@@ -91395,7 +91733,10 @@ function statusJson(i) {
|
|
|
91395
91733
|
},
|
|
91396
91734
|
protectedRepoDiscoveryNeeded: (i.protectedSessions ?? []).map((entry) => ({
|
|
91397
91735
|
displayRoot: entry.displayRoot,
|
|
91398
|
-
sessionCount: entry.sessionCount
|
|
91736
|
+
sessionCount: entry.sessionCount,
|
|
91737
|
+
// The member said yes and macOS refused: System Settings, not init,
|
|
91738
|
+
// is the fix, so a tool rendering this must not say "review".
|
|
91739
|
+
blockedByMacOS: i.credentials !== null && protectedRootReview(i.credentials, entry.root) === "blocked"
|
|
91399
91740
|
})),
|
|
91400
91741
|
...i.credentialsError !== void 0 && { credentialsError: i.credentialsError },
|
|
91401
91742
|
service: i.service,
|
|
@@ -91990,7 +92331,11 @@ ${USAGE}`);
|
|
|
91990
92331
|
// Enrollment's own narration (org, token location, capture boundary)
|
|
91991
92332
|
// indents under stage 1.
|
|
91992
92333
|
enroll: () => enroll2((line) => terminal.log(` ${line}`)),
|
|
91993
|
-
|
|
92334
|
+
// Discovery may read a worktree pointer under a root the store
|
|
92335
|
+
// already allows, so a revoked checkout root still surfaces.
|
|
92336
|
+
findProtectedSessions: () => findRecentProtectedSessions({
|
|
92337
|
+
environment: { allowedProtectedRoots: loadCredentials(config2.appEnv)?.repoDiscoveryRoots ?? [] }
|
|
92338
|
+
}),
|
|
91994
92339
|
promptAllowed: pickerOn,
|
|
91995
92340
|
promptLine: realPromptLine,
|
|
91996
92341
|
allowProtectedRepoDiscovery: (found) => allowProtectedRepoDiscovery(config2, found),
|
|
@@ -92064,12 +92409,14 @@ The access you granted these agents stays on your Powerplant account until you r
|
|
|
92064
92409
|
}
|
|
92065
92410
|
case "status": {
|
|
92066
92411
|
const stored = loadCredentialsSafely(config2);
|
|
92067
|
-
const
|
|
92412
|
+
const credentials = stored.credentials;
|
|
92068
92413
|
const [latestVersion, oldestSupportedVersion, protectedSessions] = await Promise.all([
|
|
92069
|
-
fetchAvailableVersion(config2,
|
|
92070
|
-
oldestSupportedFor(
|
|
92071
|
-
process.platform === "darwin" &&
|
|
92072
|
-
|
|
92414
|
+
fetchAvailableVersion(config2, credentials),
|
|
92415
|
+
oldestSupportedFor(credentials),
|
|
92416
|
+
process.platform === "darwin" && credentials ? findRecentProtectedSessions({
|
|
92417
|
+
environment: { allowedProtectedRoots: credentials.repoDiscoveryRoots ?? [] }
|
|
92418
|
+
}).then(
|
|
92419
|
+
(found) => found.filter((entry) => protectedRootReview(credentials, entry.root) !== "allowed")
|
|
92073
92420
|
) : []
|
|
92074
92421
|
]);
|
|
92075
92422
|
const inputs = {
|
|
@@ -92212,7 +92559,7 @@ The access you granted these agents stays on your Powerplant account until you r
|
|
|
92212
92559
|
const on = parseAutomaticFlag(values.automatic);
|
|
92213
92560
|
setAutomaticUpdates(config2.appEnv, on);
|
|
92214
92561
|
console.log(
|
|
92215
|
-
on ? "Automatic updates on: the background sync installs each new
|
|
92562
|
+
on ? "Automatic updates on: the background sync checks for a release now and installs each new one within an hour of publication." : "Automatic updates off: powerplant status and the menu bar still say when a release is available."
|
|
92216
92563
|
);
|
|
92217
92564
|
return;
|
|
92218
92565
|
}
|
|
Binary file
|