@hraness/peopleblade 0.3.4 → 0.3.5
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 +5 -3
- package/dist/peopleblade.js +179 -68
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -15,7 +15,7 @@ Installation includes the pinned runtime dependencies Zod 4.4.3 and Effect 3.22.
|
|
|
15
15
|
## Install
|
|
16
16
|
|
|
17
17
|
```bash
|
|
18
|
-
bun add --global @hraness/peopleblade@0.3.
|
|
18
|
+
bun add --global @hraness/peopleblade@0.3.5
|
|
19
19
|
peopleblade --version
|
|
20
20
|
peopleblade init
|
|
21
21
|
peopleblade stats --json
|
|
@@ -51,7 +51,7 @@ execution runtime.
|
|
|
51
51
|
path. When both variables are set, they must name the same executable. The
|
|
52
52
|
bridge still requires exact Ghostget 0.17.3 before invoking a provider operation.
|
|
53
53
|
|
|
54
|
-
PeopleBlade 0.3.
|
|
54
|
+
PeopleBlade 0.3.5 contains the reviewed Ghostget-backed Beeper, Google, and WhatsApp
|
|
55
55
|
live sync commands. Running them requires the separately installed public
|
|
56
56
|
`@hraness/ghostget` 0.17.3 package. Beeper support is bounded to account-aware
|
|
57
57
|
`contacts.list@3` reads, `contacts.search@1`, `messaging.search@2`, and the separate
|
|
@@ -59,7 +59,9 @@ body-free interaction exporter. `contacts.search@1` executes the official Beeper
|
|
|
59
59
|
CLI 0.6.2 executable pinned by Ghostget; `contacts.list@3` and `messaging.search@2`
|
|
60
60
|
use Ghostget's bounded Desktop-loopback read paths. The exporter is macOS arm64 only
|
|
61
61
|
and reports lower-bound interaction counts from complete one-to-one conversations
|
|
62
|
-
only.
|
|
62
|
+
only. If a history participant is absent from the current contact page, PeopleBlade
|
|
63
|
+
preserves its exact account-scoped coordinate as a relationship-only resource; it
|
|
64
|
+
does not join that resource by name or another fuzzy field. See
|
|
63
65
|
[peopleblade.com/sources](https://peopleblade.com/sources) for the complete provider
|
|
64
66
|
guide. A database previously synced by PeopleBlade 0.1.1 through Wrench 0.16.7
|
|
65
67
|
must run `peopleblade beeper rebind --auth beeper-main --confirm --json` once after
|
package/dist/peopleblade.js
CHANGED
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
var __require = import.meta.require;
|
|
4
4
|
|
|
5
5
|
// src/cli/main.ts
|
|
6
|
-
import { closeSync as closeSync9, constants as constants9, existsSync as
|
|
7
|
-
import { resolve as
|
|
6
|
+
import { closeSync as closeSync9, constants as constants9, existsSync as existsSync6, fstatSync as fstatSync8, mkdirSync as mkdirSync3, openSync as openSync9, readSync as readSync3 } from "fs";
|
|
7
|
+
import { join as join7, resolve as resolve7 } from "path";
|
|
8
8
|
import { ZodError } from "zod";
|
|
9
9
|
|
|
10
10
|
// src/local/contacts.ts
|
|
@@ -11733,6 +11733,57 @@ function addConfirmedPerson(database, args) {
|
|
|
11733
11733
|
}
|
|
11734
11734
|
}
|
|
11735
11735
|
|
|
11736
|
+
// src/local/menubar.ts
|
|
11737
|
+
import { existsSync as existsSync3 } from "fs";
|
|
11738
|
+
import { dirname as dirname4, resolve as resolve3 } from "path";
|
|
11739
|
+
var SETTLE_MS = 400;
|
|
11740
|
+
function resolveMenubarBinary(environment = process.env) {
|
|
11741
|
+
const candidates = [
|
|
11742
|
+
environment.PEOPLEBLADE_DESKTOP,
|
|
11743
|
+
resolve3(dirname4(process.execPath), "peopleblade-menubar"),
|
|
11744
|
+
resolve3(import.meta.dir, "../../desktop/target/release/peopleblade-menubar"),
|
|
11745
|
+
resolve3(import.meta.dir, "../../desktop/target/debug/peopleblade-menubar")
|
|
11746
|
+
];
|
|
11747
|
+
for (const candidate of candidates) {
|
|
11748
|
+
if (candidate !== undefined && candidate !== "" && existsSync3(candidate))
|
|
11749
|
+
return candidate;
|
|
11750
|
+
}
|
|
11751
|
+
return null;
|
|
11752
|
+
}
|
|
11753
|
+
async function launchMenubar(asJson) {
|
|
11754
|
+
const binary = resolveMenubarBinary();
|
|
11755
|
+
if (binary === null) {
|
|
11756
|
+
const message = "The PeopleBlade menu bar is not installed. Build it with `cargo build --release --manifest-path desktop/Cargo.toml` or set PEOPLEBLADE_DESKTOP.";
|
|
11757
|
+
if (asJson)
|
|
11758
|
+
console.log(JSON.stringify({ error: message }));
|
|
11759
|
+
else
|
|
11760
|
+
console.error(message);
|
|
11761
|
+
return 1;
|
|
11762
|
+
}
|
|
11763
|
+
let child;
|
|
11764
|
+
try {
|
|
11765
|
+
child = Bun.spawn([binary], { stdin: "ignore", stdout: "ignore", stderr: "pipe" });
|
|
11766
|
+
} catch {
|
|
11767
|
+
console.error("The PeopleBlade menu bar could not start.");
|
|
11768
|
+
return 1;
|
|
11769
|
+
}
|
|
11770
|
+
child.unref();
|
|
11771
|
+
const settled = await Promise.race([
|
|
11772
|
+
child.exited.then((code) => code),
|
|
11773
|
+
Bun.sleep(SETTLE_MS).then(() => null)
|
|
11774
|
+
]);
|
|
11775
|
+
if (settled !== null && settled !== 0) {
|
|
11776
|
+
console.error("The PeopleBlade menu bar exited during startup.");
|
|
11777
|
+
return 1;
|
|
11778
|
+
}
|
|
11779
|
+
const alreadyRunning = settled === 0;
|
|
11780
|
+
if (asJson)
|
|
11781
|
+
console.log(JSON.stringify({ running: true, alreadyRunning }));
|
|
11782
|
+
else
|
|
11783
|
+
console.log(alreadyRunning ? "PeopleBlade menu bar is already running." : "PeopleBlade menu bar is running.");
|
|
11784
|
+
return 0;
|
|
11785
|
+
}
|
|
11786
|
+
|
|
11736
11787
|
// src/local/providers/apple.ts
|
|
11737
11788
|
import { Database as Database3 } from "bun:sqlite";
|
|
11738
11789
|
import { createHash as createHash3 } from "crypto";
|
|
@@ -11742,7 +11793,7 @@ import {
|
|
|
11742
11793
|
realpathSync as realpathSync4
|
|
11743
11794
|
} from "fs";
|
|
11744
11795
|
import { homedir as homedir2 } from "os";
|
|
11745
|
-
import { join as join5, resolve as
|
|
11796
|
+
import { join as join5, resolve as resolve4 } from "path";
|
|
11746
11797
|
var EMAIL_PATTERN2 = /^(?=[\x21-\x7E]+$)[^@\s]+@[^@\s]+\.[^@\s]+$/u;
|
|
11747
11798
|
function clean(value) {
|
|
11748
11799
|
if (typeof value !== "string")
|
|
@@ -11945,7 +11996,7 @@ function rowsByOwner(database, table, names) {
|
|
|
11945
11996
|
return grouped;
|
|
11946
11997
|
}
|
|
11947
11998
|
function sourceDatabasePaths(directory) {
|
|
11948
|
-
const base = realpathSync4(
|
|
11999
|
+
const base = realpathSync4(resolve4(directory));
|
|
11949
12000
|
const sourceDirectory = join5(base, "Sources");
|
|
11950
12001
|
const sourceIdentity = lstatSync5(sourceDirectory);
|
|
11951
12002
|
if (!sourceIdentity.isDirectory() || sourceIdentity.isSymbolicLink()) {
|
|
@@ -12363,7 +12414,8 @@ var methodSchema = z10.object({
|
|
|
12363
12414
|
var contactSchema = z10.object({
|
|
12364
12415
|
person: personSchema,
|
|
12365
12416
|
resource: resourceSchema,
|
|
12366
|
-
methods: z10.array(methodSchema).max(1000)
|
|
12417
|
+
methods: z10.array(methodSchema).max(1000),
|
|
12418
|
+
existingIdentityPolicy: z10.literal("preserve").optional()
|
|
12367
12419
|
}).strict().superRefine((contact, context) => {
|
|
12368
12420
|
const coordinates = new Set;
|
|
12369
12421
|
contact.methods.forEach((method, index) => {
|
|
@@ -12633,6 +12685,7 @@ function materializeContactSourceSnapshot(database, snapshot, sourceRealmId, sou
|
|
|
12633
12685
|
ordinal += 1;
|
|
12634
12686
|
const prior = getRow3(database, `SELECT id,person_id,source_realm_id FROM provider_resources
|
|
12635
12687
|
WHERE provider=? AND account_key=? AND resource_type=? AND resource_id=?`, snapshot.provider, snapshot.accountKey, contact.resource.type, contact.resource.id);
|
|
12688
|
+
const preserveIdentity = prior !== null && contact.existingIdentityPolicy === "preserve";
|
|
12636
12689
|
let personId;
|
|
12637
12690
|
let providerResourceId;
|
|
12638
12691
|
if (prior === null) {
|
|
@@ -12652,47 +12705,54 @@ function materializeContactSourceSnapshot(database, snapshot, sourceRealmId, sou
|
|
|
12652
12705
|
}
|
|
12653
12706
|
personId = prior.person_id;
|
|
12654
12707
|
providerResourceId = prior.id;
|
|
12655
|
-
|
|
12656
|
-
|
|
12657
|
-
|
|
12658
|
-
|
|
12659
|
-
|
|
12660
|
-
|
|
12661
|
-
|
|
12662
|
-
|
|
12663
|
-
|
|
12664
|
-
|
|
12665
|
-
|
|
12666
|
-
|
|
12667
|
-
|
|
12668
|
-
|
|
12669
|
-
|
|
12670
|
-
|
|
12671
|
-
|
|
12672
|
-
|
|
12673
|
-
|
|
12674
|
-
|
|
12675
|
-
|
|
12676
|
-
|
|
12677
|
-
|
|
12678
|
-
|
|
12679
|
-
|
|
12680
|
-
[
|
|
12681
|
-
|
|
12682
|
-
|
|
12683
|
-
|
|
12684
|
-
|
|
12685
|
-
|
|
12686
|
-
|
|
12687
|
-
|
|
12688
|
-
|
|
12689
|
-
|
|
12690
|
-
|
|
12691
|
-
|
|
12692
|
-
|
|
12693
|
-
|
|
12694
|
-
|
|
12695
|
-
|
|
12708
|
+
if (preserveIdentity) {
|
|
12709
|
+
run2(database, `UPDATE provider_resources SET active=1,last_seen_run_id=?,
|
|
12710
|
+
source_realm_id=coalesce(source_realm_id,?) WHERE id=?`, sourceRunId, sourceRealmId, providerResourceId);
|
|
12711
|
+
} else {
|
|
12712
|
+
const person = contact.person;
|
|
12713
|
+
run2(database, `UPDATE people SET display_name=?,given_name=?,middle_name=?,family_name=?,name_prefix=?,
|
|
12714
|
+
name_suffix=?,nickname=?,organization=?,department=?,title=?,birthday=?,metadata_json=? WHERE id=?`, person.displayName, person.givenName, person.middleName, person.familyName, person.namePrefix, person.nameSuffix, person.nickname, person.organization, person.department, person.title, person.birthday, canonicalJson(person.metadata), personId);
|
|
12715
|
+
run2(database, `UPDATE provider_resources SET username=?,profile_url=?,profile_url_identity_eligible=?,name_identity_eligible=?,display_name=?,active=1,
|
|
12716
|
+
last_seen_run_id=?,metadata_json=?,source_realm_id=coalesce(source_realm_id,?) WHERE id=?`, contact.resource.username, contact.resource.profileUrl, contact.resource.profileUrlIdentityEligible ? 1 : 0, contact.resource.nameIdentityEligible ? 1 : 0, contact.resource.displayName, sourceRunId, canonicalJson(contact.resource.metadata), sourceRealmId, providerResourceId);
|
|
12717
|
+
}
|
|
12718
|
+
}
|
|
12719
|
+
if (!preserveIdentity) {
|
|
12720
|
+
run2(database, "UPDATE contact_methods SET active=0 WHERE provider_resource_id=?", providerResourceId);
|
|
12721
|
+
for (const method of contact.methods) {
|
|
12722
|
+
run2(database, `INSERT INTO contact_methods(
|
|
12723
|
+
person_id,kind,value,normalized_value,label,is_primary,confidence,first_seen_run_id,last_seen_run_id,
|
|
12724
|
+
metadata_json,provider_resource_id,active,identity_eligible
|
|
12725
|
+
) VALUES (?,?,?,?,?,?,?,?,?,?,?,1,?)
|
|
12726
|
+
ON CONFLICT(person_id,kind,normalized_value,label) DO UPDATE SET
|
|
12727
|
+
value=excluded.value,is_primary=excluded.is_primary,confidence=excluded.confidence,
|
|
12728
|
+
last_seen_run_id=excluded.last_seen_run_id,metadata_json=excluded.metadata_json,
|
|
12729
|
+
provider_resource_id=excluded.provider_resource_id,active=1,
|
|
12730
|
+
identity_eligible=excluded.identity_eligible`, personId, method.kind, method.value, method.normalizedValue, method.label, method.primary ? 1 : 0, method.confidence, sourceRunId, sourceRunId, canonicalJson(method.metadata), providerResourceId, method.identityEligible ? 1 : 0);
|
|
12731
|
+
}
|
|
12732
|
+
run2(database, "UPDATE person_field_observations SET active=0 WHERE provider_resource_id=?", providerResourceId);
|
|
12733
|
+
const observedFields = [
|
|
12734
|
+
["display_name", contact.person.displayName],
|
|
12735
|
+
["given_name", contact.person.givenName],
|
|
12736
|
+
["middle_name", contact.person.middleName],
|
|
12737
|
+
["family_name", contact.person.familyName],
|
|
12738
|
+
["name_prefix", contact.person.namePrefix],
|
|
12739
|
+
["name_suffix", contact.person.nameSuffix],
|
|
12740
|
+
["nickname", contact.person.nickname],
|
|
12741
|
+
["organization", contact.person.organization],
|
|
12742
|
+
["department", contact.person.department],
|
|
12743
|
+
["title", contact.person.title],
|
|
12744
|
+
["birthday", contact.person.birthday]
|
|
12745
|
+
];
|
|
12746
|
+
for (const [field, fieldValue] of observedFields) {
|
|
12747
|
+
if (fieldValue === null)
|
|
12748
|
+
continue;
|
|
12749
|
+
run2(database, `INSERT INTO person_field_observations(
|
|
12750
|
+
provider_resource_id,field,value,basis,priority,active,first_seen_run_id,last_seen_run_id,metadata_json
|
|
12751
|
+
) VALUES (?,?,?,?,?,1,?,?,?)
|
|
12752
|
+
ON CONFLICT(provider_resource_id,field,value,basis) DO UPDATE SET
|
|
12753
|
+
priority=excluded.priority,active=1,last_seen_run_id=excluded.last_seen_run_id,
|
|
12754
|
+
metadata_json=excluded.metadata_json`, providerResourceId, field, fieldValue, contact.person.observationBasis, contact.person.observationPriority, sourceRunId, sourceRunId, canonicalJson(contact.person.metadata));
|
|
12755
|
+
}
|
|
12696
12756
|
}
|
|
12697
12757
|
if (writeSourceRecords) {
|
|
12698
12758
|
run2(database, `INSERT INTO source_records(
|
|
@@ -12768,7 +12828,7 @@ function importContactSourceSnapshot(database, value) {
|
|
|
12768
12828
|
source_rows: snapshot.contacts.length,
|
|
12769
12829
|
people_created: peopleCreated,
|
|
12770
12830
|
people_matched: peopleMatched,
|
|
12771
|
-
methods_touched: snapshot.contacts.reduce((sum, contact) => sum + contact.methods.length, 0),
|
|
12831
|
+
methods_touched: snapshot.contacts.reduce((sum, contact) => sum + (contact.existingIdentityPolicy === "preserve" && existingCoordinates.has(`${contact.resource.type}\x00${contact.resource.id}`) ? 0 : contact.methods.length), 0),
|
|
12772
12832
|
interactions_touched: snapshot.interactions.length,
|
|
12773
12833
|
resources_removed: resourcesRemoved,
|
|
12774
12834
|
reconciled: reconcile,
|
|
@@ -12838,7 +12898,7 @@ function sha2563(value) {
|
|
|
12838
12898
|
|
|
12839
12899
|
// node_modules/@hraness/ghostget/dist/client.js
|
|
12840
12900
|
import { spawn, spawnSync } from "child_process";
|
|
12841
|
-
import { existsSync as
|
|
12901
|
+
import { existsSync as existsSync4 } from "fs";
|
|
12842
12902
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
12843
12903
|
import { types as nodeTypes } from "util";
|
|
12844
12904
|
var PORTABLE_OPERATION_IDENTITY_VERSION = 1;
|
|
@@ -13136,10 +13196,10 @@ function boundedMessage(value) {
|
|
|
13136
13196
|
}
|
|
13137
13197
|
function cliSourcePath() {
|
|
13138
13198
|
const besideSource = fileURLToPath2(new URL("./cli.ts", import.meta.url));
|
|
13139
|
-
if (
|
|
13199
|
+
if (existsSync4(besideSource))
|
|
13140
13200
|
return besideSource;
|
|
13141
13201
|
const packagedSource = fileURLToPath2(new URL("../src/cli.ts", import.meta.url));
|
|
13142
|
-
if (
|
|
13202
|
+
if (existsSync4(packagedSource))
|
|
13143
13203
|
return packagedSource;
|
|
13144
13204
|
throw new Error("the installed Ghostget CLI source is unavailable");
|
|
13145
13205
|
}
|
|
@@ -14150,7 +14210,7 @@ var GHOSTGET_VERSION = "0.17.3";
|
|
|
14150
14210
|
|
|
14151
14211
|
// node_modules/@hraness/ghostget/dist/beeper-client.js
|
|
14152
14212
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
14153
|
-
import { existsSync as
|
|
14213
|
+
import { existsSync as existsSync5 } from "fs";
|
|
14154
14214
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
14155
14215
|
import { types as nodeTypes3 } from "util";
|
|
14156
14216
|
import { types as nodeTypes2 } from "util";
|
|
@@ -18596,10 +18656,10 @@ function fail22(message) {
|
|
|
18596
18656
|
}
|
|
18597
18657
|
function cliSourcePath2() {
|
|
18598
18658
|
const besideSource = fileURLToPath3(new URL("./cli.ts", import.meta.url));
|
|
18599
|
-
if (
|
|
18659
|
+
if (existsSync5(besideSource))
|
|
18600
18660
|
return besideSource;
|
|
18601
18661
|
const packagedSource = fileURLToPath3(new URL("../src/cli.ts", import.meta.url));
|
|
18602
|
-
if (
|
|
18662
|
+
if (existsSync5(packagedSource))
|
|
18603
18663
|
return packagedSource;
|
|
18604
18664
|
return fail22("the installed Ghostget CLI source is unavailable");
|
|
18605
18665
|
}
|
|
@@ -19674,6 +19734,28 @@ function contactMethods(contact, service) {
|
|
|
19674
19734
|
});
|
|
19675
19735
|
return methods;
|
|
19676
19736
|
}
|
|
19737
|
+
function contactsWithRelationshipParticipants(directoryContacts, interactions) {
|
|
19738
|
+
const contacts = [...directoryContacts];
|
|
19739
|
+
const known = new Set(contacts.map((contact) => contact.id));
|
|
19740
|
+
const relationshipOnlyIds = new Set;
|
|
19741
|
+
for (const interaction of [...interactions].sort((left, right) => left.contactId.localeCompare(right.contactId))) {
|
|
19742
|
+
if (known.has(interaction.contactId))
|
|
19743
|
+
continue;
|
|
19744
|
+
contacts.push({
|
|
19745
|
+
accountId: interaction.accountId,
|
|
19746
|
+
id: interaction.contactId,
|
|
19747
|
+
fullName: null,
|
|
19748
|
+
username: null,
|
|
19749
|
+
phoneNumber: null,
|
|
19750
|
+
email: null,
|
|
19751
|
+
isSelf: false,
|
|
19752
|
+
cannotMessage: null
|
|
19753
|
+
});
|
|
19754
|
+
known.add(interaction.contactId);
|
|
19755
|
+
relationshipOnlyIds.add(interaction.contactId);
|
|
19756
|
+
}
|
|
19757
|
+
return { contacts, relationshipOnlyIds };
|
|
19758
|
+
}
|
|
19677
19759
|
function sameExecution(left, right) {
|
|
19678
19760
|
return left.authId === right.authId && left.authSha256 === right.authSha256 && left.adapterSha256 === right.adapterSha256 && left.contractSha256 === right.contractSha256;
|
|
19679
19761
|
}
|
|
@@ -19986,8 +20068,12 @@ function syncBeeperContacts(database, options = {}) {
|
|
|
19986
20068
|
if (pageAccount === undefined)
|
|
19987
20069
|
throw new Error("Beeper account disappeared during sequential sync.");
|
|
19988
20070
|
const service = normalizeService(pageAccount);
|
|
19989
|
-
const
|
|
19990
|
-
const skippedSelf = page.contacts.length -
|
|
20071
|
+
const directoryContacts = page.contacts.filter((contact) => contact.isSelf !== true && contact.id !== pageAccount.user.id);
|
|
20072
|
+
const skippedSelf = page.contacts.length - directoryContacts.length;
|
|
20073
|
+
const accountInteractions = interactionExport?.output.interactions.filter((interaction) => interaction.accountId === pageAccount.accountId) ?? [];
|
|
20074
|
+
const relationshipContacts = contactsWithRelationshipParticipants(directoryContacts, accountInteractions);
|
|
20075
|
+
const contacts = relationshipContacts.contacts;
|
|
20076
|
+
const contactsObserved = page.contacts.length + contacts.length - directoryContacts.length;
|
|
19991
20077
|
const relationshipProjection = interactionExport === null ? null : projectBeeperContactInteractions(interactionExport.output, contacts.map((contact) => ({
|
|
19992
20078
|
rawAccountId: pageAccount.accountId,
|
|
19993
20079
|
accountKey: realmAccountKey,
|
|
@@ -20045,7 +20131,9 @@ function syncBeeperContacts(database, options = {}) {
|
|
|
20045
20131
|
sourceRealm: sourceRealm(pageAccount),
|
|
20046
20132
|
contacts: contacts.map((contact) => {
|
|
20047
20133
|
const display = contactDisplayName(contact);
|
|
20134
|
+
const relationshipOnly = relationshipContacts.relationshipOnlyIds.has(contact.id);
|
|
20048
20135
|
return {
|
|
20136
|
+
...relationshipOnly ? { existingIdentityPolicy: "preserve" } : {},
|
|
20049
20137
|
person: {
|
|
20050
20138
|
displayName: display.value,
|
|
20051
20139
|
givenName: null,
|
|
@@ -20060,7 +20148,10 @@ function syncBeeperContacts(database, options = {}) {
|
|
|
20060
20148
|
birthday: null,
|
|
20061
20149
|
observationBasis: display.basis,
|
|
20062
20150
|
observationPriority: display.priority,
|
|
20063
|
-
metadata: {
|
|
20151
|
+
metadata: {
|
|
20152
|
+
createdBy: relationshipOnly ? "beeper-direct-interaction" : "beeper-contact",
|
|
20153
|
+
service
|
|
20154
|
+
}
|
|
20064
20155
|
},
|
|
20065
20156
|
resource: {
|
|
20066
20157
|
type: "user",
|
|
@@ -20073,6 +20164,7 @@ function syncBeeperContacts(database, options = {}) {
|
|
|
20073
20164
|
metadata: {
|
|
20074
20165
|
authority: "beeper",
|
|
20075
20166
|
service,
|
|
20167
|
+
...relationshipOnly ? { relationshipOnly: true } : {},
|
|
20076
20168
|
cannotMessage: contact.cannotMessage,
|
|
20077
20169
|
isSelf: contact.isSelf,
|
|
20078
20170
|
emailVerification: "not-guaranteed",
|
|
@@ -20104,7 +20196,7 @@ function syncBeeperContacts(database, options = {}) {
|
|
|
20104
20196
|
accountResults.push({
|
|
20105
20197
|
realm_id: realm.id,
|
|
20106
20198
|
service,
|
|
20107
|
-
contacts_seen:
|
|
20199
|
+
contacts_seen: contactsObserved,
|
|
20108
20200
|
contacts_imported: imported.people_created,
|
|
20109
20201
|
contacts_matched: imported.people_matched,
|
|
20110
20202
|
contacts_skipped_self: skippedSelf,
|
|
@@ -20113,7 +20205,7 @@ function syncBeeperContacts(database, options = {}) {
|
|
|
20113
20205
|
truncated: !page.localPageComplete,
|
|
20114
20206
|
cached: imported.cached
|
|
20115
20207
|
});
|
|
20116
|
-
contactsSeen +=
|
|
20208
|
+
contactsSeen += contactsObserved;
|
|
20117
20209
|
contactsImported += imported.people_created;
|
|
20118
20210
|
contactsMatched += imported.people_matched;
|
|
20119
20211
|
contactsSkippedSelf += skippedSelf;
|
|
@@ -20128,7 +20220,7 @@ function syncBeeperContacts(database, options = {}) {
|
|
|
20128
20220
|
ordinal: index + 1,
|
|
20129
20221
|
accounts: orderedAccounts.length,
|
|
20130
20222
|
service,
|
|
20131
|
-
contacts:
|
|
20223
|
+
contacts: contactsObserved,
|
|
20132
20224
|
truncated: !page.localPageComplete
|
|
20133
20225
|
});
|
|
20134
20226
|
}
|
|
@@ -21719,7 +21811,7 @@ import { Database as Database4 } from "bun:sqlite";
|
|
|
21719
21811
|
import { createHash as createHash6 } from "crypto";
|
|
21720
21812
|
import { lstatSync as lstatSync6, realpathSync as realpathSync5 } from "fs";
|
|
21721
21813
|
import { homedir as homedir3 } from "os";
|
|
21722
|
-
import { join as join6, resolve as
|
|
21814
|
+
import { join as join6, resolve as resolve5 } from "path";
|
|
21723
21815
|
var DEFAULT_IMESSAGE_DATABASE = join6(homedir3(), "Library", "Messages", "chat.db");
|
|
21724
21816
|
var DEFAULT_IMESSAGE_ACCOUNT = "imessage-main";
|
|
21725
21817
|
var DEFAULT_IMESSAGE_PAGE_SIZE = 1000;
|
|
@@ -22163,7 +22255,7 @@ function syncIMessageRelationships(database, options = {}) {
|
|
|
22163
22255
|
const pageSize = options.pageSize ?? DEFAULT_IMESSAGE_PAGE_SIZE;
|
|
22164
22256
|
if (!Number.isSafeInteger(pageSize) || pageSize < 1 || pageSize > MAX_PAGE_SIZE)
|
|
22165
22257
|
throw new Error(`iMessage page size must be between 1 and ${MAX_PAGE_SIZE}`);
|
|
22166
|
-
const requestedPath =
|
|
22258
|
+
const requestedPath = resolve5(options.messagesDatabase ?? DEFAULT_IMESSAGE_DATABASE);
|
|
22167
22259
|
const identity = lstatSync6(requestedPath);
|
|
22168
22260
|
const uid = typeof process.getuid === "function" ? process.getuid() : null;
|
|
22169
22261
|
if (!identity.isFile() || identity.isSymbolicLink() || identity.size < 1 || identity.size > MAX_DATABASE_BYTES || uid !== null && identity.uid !== uid) {
|
|
@@ -23249,7 +23341,7 @@ function instagramStatsAll(database) {
|
|
|
23249
23341
|
// src/local/providers/x-archive.ts
|
|
23250
23342
|
import { createHash as createHash8 } from "crypto";
|
|
23251
23343
|
import { closeSync as closeSync7, constants as constants7, fstatSync as fstatSync6, openSync as openSync7, readSync as readSync2 } from "fs";
|
|
23252
|
-
import { resolve as
|
|
23344
|
+
import { resolve as resolve6 } from "path";
|
|
23253
23345
|
|
|
23254
23346
|
// src/local/archive/x-zip-file.ts
|
|
23255
23347
|
import { readSync } from "fs";
|
|
@@ -24222,7 +24314,7 @@ function sha256Descriptor(descriptor, size) {
|
|
|
24222
24314
|
async function readArchive2(path) {
|
|
24223
24315
|
if (typeof path !== "string" || path.length < 1 || path.includes("\x00"))
|
|
24224
24316
|
throw new Error("X archive path is invalid");
|
|
24225
|
-
const locator =
|
|
24317
|
+
const locator = resolve6(path);
|
|
24226
24318
|
const descriptor = openSync7(locator, constants7.O_RDONLY | (constants7.O_NOFOLLOW ?? 0));
|
|
24227
24319
|
try {
|
|
24228
24320
|
const before = fstatSync6(descriptor, { bigint: true });
|
|
@@ -26504,7 +26596,7 @@ function syncWhatsAppRelationships(database, options = {}) {
|
|
|
26504
26596
|
}
|
|
26505
26597
|
|
|
26506
26598
|
// src/cli/version.ts
|
|
26507
|
-
var peoplebladeVersion = "0.3.
|
|
26599
|
+
var peoplebladeVersion = "0.3.5";
|
|
26508
26600
|
|
|
26509
26601
|
// src/cli/intro.ts
|
|
26510
26602
|
function terminalIntro(terminal) {
|
|
@@ -26526,6 +26618,8 @@ Usage: peopleblade [--db PATH] <command>
|
|
|
26526
26618
|
|
|
26527
26619
|
Core:
|
|
26528
26620
|
init Create or migrate the local database
|
|
26621
|
+
menubar Launch the detached menu-bar companion
|
|
26622
|
+
outputs Print the agent outputs directory
|
|
26529
26623
|
backup [PATH] Create a consistent private SQLite backup
|
|
26530
26624
|
migrate rolodex --from PATH Back up and migrate a legacy Rolodex database once
|
|
26531
26625
|
list [--search TEXT] [--limit N] Search the local contact book
|
|
@@ -26670,7 +26764,7 @@ function yesNo(value, label) {
|
|
|
26670
26764
|
async function readNoteMarkdown(path) {
|
|
26671
26765
|
const maximum = 65536;
|
|
26672
26766
|
if (path !== "-") {
|
|
26673
|
-
const descriptor = openSync9(
|
|
26767
|
+
const descriptor = openSync9(resolve7(path), constants9.O_RDONLY | constants9.O_NONBLOCK | (constants9.O_NOFOLLOW ?? 0));
|
|
26674
26768
|
try {
|
|
26675
26769
|
const before = fstatSync8(descriptor);
|
|
26676
26770
|
if (!before.isFile() || before.nlink !== 1 || before.size > maximum || typeof process.getuid === "function" && before.uid !== process.getuid()) {
|
|
@@ -26780,13 +26874,30 @@ async function main(argv) {
|
|
|
26780
26874
|
print({ database: databasePath, initialized: true, ...result }, asJson);
|
|
26781
26875
|
return;
|
|
26782
26876
|
}
|
|
26877
|
+
if (command === "menubar") {
|
|
26878
|
+
if (args.length !== 1)
|
|
26879
|
+
fail3("menubar takes no arguments.");
|
|
26880
|
+
process.exitCode = await launchMenubar(asJson);
|
|
26881
|
+
return;
|
|
26882
|
+
}
|
|
26883
|
+
if (command === "outputs") {
|
|
26884
|
+
if (args.length !== 1)
|
|
26885
|
+
fail3("outputs takes no arguments.");
|
|
26886
|
+
const directory = join7(peoplebladeDirectory(), "outputs");
|
|
26887
|
+
mkdirSync3(directory, { recursive: true, mode: 448 });
|
|
26888
|
+
if (asJson)
|
|
26889
|
+
print({ outputs: directory }, true);
|
|
26890
|
+
else
|
|
26891
|
+
console.log(directory);
|
|
26892
|
+
return;
|
|
26893
|
+
}
|
|
26783
26894
|
if (command === "migrate" && subcommand === "rolodex") {
|
|
26784
26895
|
const options = [...rest];
|
|
26785
26896
|
const source = valueAfter(options, "--from") ?? fail3("migrate rolodex requires --from PATH.");
|
|
26786
26897
|
const backup = valueAfter(options, "--backup") ?? standardBackupPath("legacy-rolodex", databasePath);
|
|
26787
26898
|
if (options.length > 0)
|
|
26788
26899
|
fail3(`Unknown argument: ${options[0]}`);
|
|
26789
|
-
if (!
|
|
26900
|
+
if (!existsSync6(source))
|
|
26790
26901
|
fail3(`Legacy Rolodex does not exist: ${source}`);
|
|
26791
26902
|
backupLocalDatabase(source, backup);
|
|
26792
26903
|
print({ database: databasePath, legacyBackup: backup, ...migrateLegacyRolodex(source, databasePath) }, asJson);
|
|
@@ -26820,7 +26931,7 @@ ${verification}`) });
|
|
|
26820
26931
|
print(await signOutCloud(), asJson);
|
|
26821
26932
|
return;
|
|
26822
26933
|
}
|
|
26823
|
-
if (!
|
|
26934
|
+
if (!existsSync6(databasePath))
|
|
26824
26935
|
fail3("Run `peopleblade init` or `peopleblade migrate rolodex --from PATH` first.");
|
|
26825
26936
|
if (command === "backup") {
|
|
26826
26937
|
const destination = subcommand ?? standardBackupPath("manual", databasePath);
|
|
@@ -27258,7 +27369,7 @@ ${verification}`) });
|
|
|
27258
27369
|
const from = valueAfter(options, "--from") ?? fail3("notes import requires --from granola.json or --from -.");
|
|
27259
27370
|
if (options.length)
|
|
27260
27371
|
fail3(`Unknown argument: ${options[0]}`);
|
|
27261
|
-
const payload = from === "-" ? parseGranolaImportJson(await new Response(Bun.stdin).text()) : readGranolaImportFile(
|
|
27372
|
+
const payload = from === "-" ? parseGranolaImportJson(await new Response(Bun.stdin).text()) : readGranolaImportFile(resolve7(from));
|
|
27262
27373
|
printImport(importGranolaMeetings(database, payload), true);
|
|
27263
27374
|
return;
|
|
27264
27375
|
}
|