@pmate/cli 0.25.7 → 0.25.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 +11 -5
- package/dist/index.d.ts +1 -4
- package/dist/index.js +101 -103
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -50,8 +50,9 @@ pmate vercel projects
|
|
|
50
50
|
pmate vercel deploys --project web --last-week
|
|
51
51
|
pmate im1 health
|
|
52
52
|
pmate im1 threads --limit 20
|
|
53
|
+
pmate im1 threads --search "titlte" --limit 5
|
|
53
54
|
pmate im1 messages --thread "<conversationId-or-thread-name>" --limit 50
|
|
54
|
-
pmate im1 reply --
|
|
55
|
+
pmate im1 reply --thread "<conversationId-or-thread-name>" --text "hello"
|
|
55
56
|
```
|
|
56
57
|
|
|
57
58
|
Notes:
|
|
@@ -175,8 +176,9 @@ Useful commands:
|
|
|
175
176
|
pmate im1 health
|
|
176
177
|
pmate im1 threads --limit 20
|
|
177
178
|
pmate im1 threads --q Bug --limit 10
|
|
179
|
+
pmate im1 threads --search "titlte" --limit 5
|
|
178
180
|
pmate im1 messages --thread "<conversationId-or-thread-name>" --limit 50
|
|
179
|
-
pmate im1 reply --
|
|
181
|
+
pmate im1 reply --thread "<conversationId-or-thread-name>" --text "hello"
|
|
180
182
|
```
|
|
181
183
|
|
|
182
184
|
`threads` and `messages` output JSON by default. Thread rows include both
|
|
@@ -184,11 +186,15 @@ pmate im1 reply --source cc --thread "<conversationId-or-thread-name>" --text "h
|
|
|
184
186
|
passing a thread back into `messages`/`reply`. Add `--text` for compact
|
|
185
187
|
human-readable output.
|
|
186
188
|
|
|
189
|
+
Use `pmate im1 threads --search "<title>"` for fuzzy title lookup. It compares
|
|
190
|
+
the lowercased search text against thread titles with edit distance, including
|
|
191
|
+
nearby token/window matches for typos such as `titlte` vs `title`. JSON output
|
|
192
|
+
includes `searchDistance` and `searchScore`.
|
|
193
|
+
|
|
187
194
|
CC sync is owned by the background service in `~/dev/pcs/cc-bot`, which runs
|
|
188
195
|
under PM2, keeps TimescaleDB running, listens for new CC messages, and performs
|
|
189
|
-
bounded 3-day backfill. `pmate im1
|
|
190
|
-
|
|
191
|
-
`pmate im1 collect sync --source yelling` or `--endpoint`.
|
|
196
|
+
bounded 3-day backfill. `pmate im1` is CC-only and does not accept `--source`
|
|
197
|
+
or custom `--endpoint` options.
|
|
192
198
|
|
|
193
199
|
## Notion config
|
|
194
200
|
|
package/dist/index.d.ts
CHANGED
|
@@ -554,22 +554,19 @@ type CollectCommandArgs = {
|
|
|
554
554
|
} | {
|
|
555
555
|
type: Command.Collect;
|
|
556
556
|
action: "threads";
|
|
557
|
-
source?: "cc";
|
|
558
557
|
query?: string;
|
|
558
|
+
search?: string;
|
|
559
559
|
limit?: number;
|
|
560
560
|
json?: boolean;
|
|
561
561
|
} | {
|
|
562
562
|
type: Command.Collect;
|
|
563
563
|
action: "messages";
|
|
564
|
-
source?: "cc";
|
|
565
564
|
thread: string;
|
|
566
565
|
limit?: number;
|
|
567
566
|
json?: boolean;
|
|
568
567
|
} | {
|
|
569
568
|
type: Command.Collect;
|
|
570
569
|
action: "reply";
|
|
571
|
-
source?: "cc" | "yelling";
|
|
572
|
-
endpoint?: string;
|
|
573
570
|
thread: string;
|
|
574
571
|
text: string;
|
|
575
572
|
dryRun: boolean;
|
package/dist/index.js
CHANGED
|
@@ -15748,21 +15748,15 @@ var CollectCommandHandler = class {
|
|
|
15748
15748
|
await this.handleBotMessages(args);
|
|
15749
15749
|
return;
|
|
15750
15750
|
}
|
|
15751
|
-
if (args.source === "cc" && !args.endpoint) {
|
|
15752
|
-
if (args.action === "sync") {
|
|
15753
|
-
throw new Error(
|
|
15754
|
-
'pmate im1 collect sync has moved to cc-bot. Start ~/dev/pcs/cc-bot and use "pmate im1 threads", "pmate im1 messages", or "pmate im1 reply".'
|
|
15755
|
-
);
|
|
15756
|
-
}
|
|
15757
|
-
if (args.action === "reply") {
|
|
15758
|
-
await this.handleBotReply(args);
|
|
15759
|
-
return;
|
|
15760
|
-
}
|
|
15761
|
-
}
|
|
15762
15751
|
if (args.action === "reply") {
|
|
15763
|
-
await this.
|
|
15752
|
+
await this.handleBotReply(args);
|
|
15764
15753
|
return;
|
|
15765
15754
|
}
|
|
15755
|
+
if (args.source === "cc" && !args.endpoint) {
|
|
15756
|
+
throw new Error(
|
|
15757
|
+
'CC sync has moved to cc-bot. Start ~/dev/pcs/cc-bot and use "pmate im1 threads", "pmate im1 messages", or "pmate im1 reply".'
|
|
15758
|
+
);
|
|
15759
|
+
}
|
|
15766
15760
|
await this.handleSync(args);
|
|
15767
15761
|
}
|
|
15768
15762
|
async handleBotHealth() {
|
|
@@ -15771,12 +15765,14 @@ var CollectCommandHandler = class {
|
|
|
15771
15765
|
}
|
|
15772
15766
|
async handleBotThreads(args) {
|
|
15773
15767
|
const client = new Im1BotClient();
|
|
15774
|
-
const
|
|
15768
|
+
const fetchLimit = args.search ? Math.max(args.limit ?? 10, 200) : args.limit;
|
|
15769
|
+
const threads = await client.listThreads({ q: args.query, limit: fetchLimit });
|
|
15770
|
+
const outputThreads = args.search ? rankThreadsByTitleSearch(threads, args.search).slice(0, args.limit ?? 10) : threads;
|
|
15775
15771
|
if (args.json !== false) {
|
|
15776
|
-
ui.plain(JSON.stringify({ ok: true, threads }, null, 2));
|
|
15772
|
+
ui.plain(JSON.stringify({ ok: true, threads: outputThreads }, null, 2));
|
|
15777
15773
|
return;
|
|
15778
15774
|
}
|
|
15779
|
-
for (const thread of
|
|
15775
|
+
for (const thread of outputThreads) {
|
|
15780
15776
|
ui.plain(formatBotThread(thread));
|
|
15781
15777
|
}
|
|
15782
15778
|
}
|
|
@@ -16179,9 +16175,88 @@ function isExternalImageRef(imagePath) {
|
|
|
16179
16175
|
}
|
|
16180
16176
|
function formatBotThread(thread) {
|
|
16181
16177
|
const unread = thread.unread && thread.unread > 0 ? ` unread=${thread.unread}` : "";
|
|
16178
|
+
const distance = typeof thread.searchDistance === "number" ? ` distance=${thread.searchDistance}` : "";
|
|
16182
16179
|
const time = thread.lastMessageAt ? ` ${thread.lastMessageAt}` : "";
|
|
16183
16180
|
const preview = thread.preview ? ` - ${truncateCliPreview(thread.preview, 120)}` : "";
|
|
16184
|
-
return `${thread.name} (${thread.conversationId})${unread}${time}${preview}`;
|
|
16181
|
+
return `${thread.name} (${thread.conversationId})${unread}${distance}${time}${preview}`;
|
|
16182
|
+
}
|
|
16183
|
+
function rankThreadsByTitleSearch(threads, rawSearch) {
|
|
16184
|
+
const search = normalizeSearchTitle(rawSearch);
|
|
16185
|
+
if (!search) {
|
|
16186
|
+
return threads.map((thread) => ({
|
|
16187
|
+
...thread,
|
|
16188
|
+
search: rawSearch,
|
|
16189
|
+
searchDistance: 0,
|
|
16190
|
+
searchScore: 1
|
|
16191
|
+
}));
|
|
16192
|
+
}
|
|
16193
|
+
return threads.map((thread) => {
|
|
16194
|
+
const title = normalizeSearchTitle(thread.name);
|
|
16195
|
+
const distance = fuzzyTitleDistance(search, title);
|
|
16196
|
+
const score = 1 - distance / Math.max(search.replace(/\s+/g, "").length, 1);
|
|
16197
|
+
return {
|
|
16198
|
+
...thread,
|
|
16199
|
+
search: rawSearch,
|
|
16200
|
+
searchDistance: distance,
|
|
16201
|
+
searchScore: Number(Math.max(0, score).toFixed(4))
|
|
16202
|
+
};
|
|
16203
|
+
}).sort((left, right) => {
|
|
16204
|
+
if ((left.searchDistance ?? 0) !== (right.searchDistance ?? 0)) {
|
|
16205
|
+
return (left.searchDistance ?? 0) - (right.searchDistance ?? 0);
|
|
16206
|
+
}
|
|
16207
|
+
if ((left.searchScore ?? 0) !== (right.searchScore ?? 0)) {
|
|
16208
|
+
return (right.searchScore ?? 0) - (left.searchScore ?? 0);
|
|
16209
|
+
}
|
|
16210
|
+
return compareNullableIsoDesc(left.lastMessageAt, right.lastMessageAt);
|
|
16211
|
+
});
|
|
16212
|
+
}
|
|
16213
|
+
function fuzzyTitleDistance(search, title) {
|
|
16214
|
+
if (!search) return 0;
|
|
16215
|
+
if (!title) return search.length;
|
|
16216
|
+
if (title.includes(search)) return 0;
|
|
16217
|
+
const compactSearch = search.replace(/\s+/g, "");
|
|
16218
|
+
const compactTitle = title.replace(/\s+/g, "");
|
|
16219
|
+
const candidates = /* @__PURE__ */ new Set([title, compactTitle]);
|
|
16220
|
+
for (const token of title.split(/\s+/)) {
|
|
16221
|
+
if (token) candidates.add(token);
|
|
16222
|
+
}
|
|
16223
|
+
if (compactSearch && compactTitle.length >= compactSearch.length) {
|
|
16224
|
+
for (let index = 0; index <= compactTitle.length - compactSearch.length; index += 1) {
|
|
16225
|
+
candidates.add(compactTitle.slice(index, index + compactSearch.length));
|
|
16226
|
+
}
|
|
16227
|
+
}
|
|
16228
|
+
let best = Number.POSITIVE_INFINITY;
|
|
16229
|
+
for (const candidate of candidates) {
|
|
16230
|
+
best = Math.min(best, damerauLevenshteinDistance(compactSearch || search, candidate));
|
|
16231
|
+
}
|
|
16232
|
+
return Number.isFinite(best) ? best : search.length;
|
|
16233
|
+
}
|
|
16234
|
+
function normalizeSearchTitle(value) {
|
|
16235
|
+
return value.toLowerCase().normalize("NFKC").replace(/[\p{P}\p{S}]+/gu, " ").replace(/\s+/g, " ").trim();
|
|
16236
|
+
}
|
|
16237
|
+
function damerauLevenshteinDistance(left, right) {
|
|
16238
|
+
const matrix = Array.from({ length: left.length + 1 }, () => Array(right.length + 1).fill(0));
|
|
16239
|
+
for (let row = 0; row <= left.length; row += 1) matrix[row][0] = row;
|
|
16240
|
+
for (let column = 0; column <= right.length; column += 1) matrix[0][column] = column;
|
|
16241
|
+
for (let row = 1; row <= left.length; row += 1) {
|
|
16242
|
+
for (let column = 1; column <= right.length; column += 1) {
|
|
16243
|
+
const cost = left[row - 1] === right[column - 1] ? 0 : 1;
|
|
16244
|
+
matrix[row][column] = Math.min(
|
|
16245
|
+
matrix[row - 1][column] + 1,
|
|
16246
|
+
matrix[row][column - 1] + 1,
|
|
16247
|
+
matrix[row - 1][column - 1] + cost
|
|
16248
|
+
);
|
|
16249
|
+
if (row > 1 && column > 1 && left[row - 1] === right[column - 2] && left[row - 2] === right[column - 1]) {
|
|
16250
|
+
matrix[row][column] = Math.min(matrix[row][column], matrix[row - 2][column - 2] + 1);
|
|
16251
|
+
}
|
|
16252
|
+
}
|
|
16253
|
+
}
|
|
16254
|
+
return matrix[left.length][right.length];
|
|
16255
|
+
}
|
|
16256
|
+
function compareNullableIsoDesc(left, right) {
|
|
16257
|
+
const leftTime = left ? Date.parse(left) : 0;
|
|
16258
|
+
const rightTime = right ? Date.parse(right) : 0;
|
|
16259
|
+
return rightTime - leftTime;
|
|
16185
16260
|
}
|
|
16186
16261
|
function formatBotMessage(message) {
|
|
16187
16262
|
const sender = message.sender ? `${message.sender}: ` : "";
|
|
@@ -25683,10 +25758,7 @@ function parseIm1Args(tokens) {
|
|
|
25683
25758
|
const rest = [...tokens];
|
|
25684
25759
|
const action = rest.shift();
|
|
25685
25760
|
if (!action) {
|
|
25686
|
-
throw new Error("Usage: im1
|
|
25687
|
-
}
|
|
25688
|
-
if (action === "collect") {
|
|
25689
|
-
return parseIm1CollectArgs(rest);
|
|
25761
|
+
throw new Error("Usage: im1 health | im1 threads [--q <keyword>] [--search <title>] | im1 messages --thread <name|conversation-id> | im1 reply --thread <name|conversation-id> --text <text>");
|
|
25690
25762
|
}
|
|
25691
25763
|
if (action === "health") {
|
|
25692
25764
|
return parseIm1HealthArgs(rest);
|
|
@@ -25720,22 +25792,19 @@ function parseIm1HealthArgs(tokens) {
|
|
|
25720
25792
|
function parseIm1ThreadsArgs(tokens) {
|
|
25721
25793
|
const rest = [...tokens];
|
|
25722
25794
|
let query;
|
|
25795
|
+
let search;
|
|
25723
25796
|
let limit;
|
|
25724
|
-
let source = "cc";
|
|
25725
25797
|
let json = true;
|
|
25726
25798
|
while (rest.length > 0) {
|
|
25727
25799
|
const token = rest.shift();
|
|
25728
25800
|
switch (token) {
|
|
25729
|
-
case "--source": {
|
|
25730
|
-
const value = requireValue11(rest, token);
|
|
25731
|
-
if (value !== "cc") throw new Error(`Unsupported im1 threads source: ${value}`);
|
|
25732
|
-
source = value;
|
|
25733
|
-
break;
|
|
25734
|
-
}
|
|
25735
25801
|
case "--q":
|
|
25736
25802
|
case "--query":
|
|
25737
25803
|
query = requireValue11(rest, token);
|
|
25738
25804
|
break;
|
|
25805
|
+
case "--search":
|
|
25806
|
+
search = requireValue11(rest, token);
|
|
25807
|
+
break;
|
|
25739
25808
|
case "--limit":
|
|
25740
25809
|
limit = parsePositiveInt2(requireValue11(rest, token), token);
|
|
25741
25810
|
break;
|
|
@@ -25749,23 +25818,16 @@ function parseIm1ThreadsArgs(tokens) {
|
|
|
25749
25818
|
throw new Error(`Unknown im1 threads option: ${token}`);
|
|
25750
25819
|
}
|
|
25751
25820
|
}
|
|
25752
|
-
return { type: "collect" /* Collect */, action: "threads",
|
|
25821
|
+
return { type: "collect" /* Collect */, action: "threads", query, search, limit, json };
|
|
25753
25822
|
}
|
|
25754
25823
|
function parseIm1MessagesArgs(tokens) {
|
|
25755
25824
|
const rest = [...tokens];
|
|
25756
25825
|
let thread;
|
|
25757
25826
|
let limit;
|
|
25758
|
-
let source = "cc";
|
|
25759
25827
|
let json = true;
|
|
25760
25828
|
while (rest.length > 0) {
|
|
25761
25829
|
const token = rest.shift();
|
|
25762
25830
|
switch (token) {
|
|
25763
|
-
case "--source": {
|
|
25764
|
-
const value = requireValue11(rest, token);
|
|
25765
|
-
if (value !== "cc") throw new Error(`Unsupported im1 messages source: ${value}`);
|
|
25766
|
-
source = value;
|
|
25767
|
-
break;
|
|
25768
|
-
}
|
|
25769
25831
|
case "--thread":
|
|
25770
25832
|
case "--channel":
|
|
25771
25833
|
thread = requireValue11(rest, token);
|
|
@@ -25786,33 +25848,10 @@ function parseIm1MessagesArgs(tokens) {
|
|
|
25786
25848
|
if (!thread) {
|
|
25787
25849
|
throw new Error("Usage: im1 messages --thread <name|conversation-id> [--limit <n>]");
|
|
25788
25850
|
}
|
|
25789
|
-
return { type: "collect" /* Collect */, action: "messages",
|
|
25790
|
-
}
|
|
25791
|
-
function parseIm1CollectArgs(tokens) {
|
|
25792
|
-
const rest = [...tokens];
|
|
25793
|
-
const action = rest.shift();
|
|
25794
|
-
if (action !== "sync") {
|
|
25795
|
-
throw new Error(
|
|
25796
|
-
"Usage: im1 collect sync [--thread <name|conversation-id>] (--source <cc|yelling> | --endpoint <url>)"
|
|
25797
|
-
);
|
|
25798
|
-
}
|
|
25799
|
-
const target = parseTargetOptions(rest);
|
|
25800
|
-
if (!target.source && !target.endpoint) {
|
|
25801
|
-
throw new Error(
|
|
25802
|
-
"Usage: im1 collect sync [--thread <name|conversation-id>] (--source <cc|yelling> | --endpoint <url>)"
|
|
25803
|
-
);
|
|
25804
|
-
}
|
|
25805
|
-
return {
|
|
25806
|
-
type: "collect" /* Collect */,
|
|
25807
|
-
action: "sync",
|
|
25808
|
-
source: target.source,
|
|
25809
|
-
endpoint: target.endpoint,
|
|
25810
|
-
thread: target.thread
|
|
25811
|
-
};
|
|
25851
|
+
return { type: "collect" /* Collect */, action: "messages", thread, limit, json };
|
|
25812
25852
|
}
|
|
25813
25853
|
function parseIm1ReplyArgs(tokens) {
|
|
25814
25854
|
const rest = [...tokens];
|
|
25815
|
-
const targetTokens = [];
|
|
25816
25855
|
let thread;
|
|
25817
25856
|
let text;
|
|
25818
25857
|
let dryRun = false;
|
|
@@ -25829,61 +25868,21 @@ function parseIm1ReplyArgs(tokens) {
|
|
|
25829
25868
|
case "--dry-run":
|
|
25830
25869
|
dryRun = true;
|
|
25831
25870
|
break;
|
|
25832
|
-
case "--source":
|
|
25833
|
-
case "--endpoint":
|
|
25834
|
-
targetTokens.push(token, requireValue11(rest, token));
|
|
25835
|
-
break;
|
|
25836
25871
|
default:
|
|
25837
25872
|
throw new Error(`Unknown im1 reply option: ${token}`);
|
|
25838
25873
|
}
|
|
25839
25874
|
}
|
|
25840
25875
|
if (!thread || !text) {
|
|
25841
|
-
throw new Error("Usage: im1 reply --thread <name> --text <text>
|
|
25842
|
-
}
|
|
25843
|
-
const target = parseTargetOptions(targetTokens);
|
|
25844
|
-
if (!target.source && !target.endpoint) {
|
|
25845
|
-
throw new Error("Usage: im1 reply --thread <name> --text <text> (--source <cc|yelling> | --endpoint <url>)");
|
|
25876
|
+
throw new Error("Usage: im1 reply --thread <name|conversation-id> --text <text>");
|
|
25846
25877
|
}
|
|
25847
25878
|
return {
|
|
25848
25879
|
type: "collect" /* Collect */,
|
|
25849
25880
|
action: "reply",
|
|
25850
|
-
source: target.source,
|
|
25851
|
-
endpoint: target.endpoint,
|
|
25852
25881
|
thread,
|
|
25853
25882
|
text,
|
|
25854
25883
|
dryRun
|
|
25855
25884
|
};
|
|
25856
25885
|
}
|
|
25857
|
-
function parseTargetOptions(tokens) {
|
|
25858
|
-
const rest = [...tokens];
|
|
25859
|
-
const options = {};
|
|
25860
|
-
while (rest.length > 0) {
|
|
25861
|
-
const token = rest.shift();
|
|
25862
|
-
switch (token) {
|
|
25863
|
-
case "--source": {
|
|
25864
|
-
const value = requireValue11(rest, token);
|
|
25865
|
-
if (value !== "cc" && value !== "yelling") {
|
|
25866
|
-
throw new Error(`Unsupported im1 source: ${value}`);
|
|
25867
|
-
}
|
|
25868
|
-
options.source = value;
|
|
25869
|
-
break;
|
|
25870
|
-
}
|
|
25871
|
-
case "--endpoint":
|
|
25872
|
-
options.endpoint = requireValue11(rest, token);
|
|
25873
|
-
break;
|
|
25874
|
-
case "--thread":
|
|
25875
|
-
case "--channel":
|
|
25876
|
-
options.thread = requireValue11(rest, token);
|
|
25877
|
-
break;
|
|
25878
|
-
default:
|
|
25879
|
-
throw new Error(`Unknown im1 option: ${token}`);
|
|
25880
|
-
}
|
|
25881
|
-
}
|
|
25882
|
-
if (options.source && options.endpoint) {
|
|
25883
|
-
throw new Error("Use either --source or --endpoint, not both.");
|
|
25884
|
-
}
|
|
25885
|
-
return options;
|
|
25886
|
-
}
|
|
25887
25886
|
function requireValue11(tokens, optionName) {
|
|
25888
25887
|
const value = tokens.shift();
|
|
25889
25888
|
if (!value || value.startsWith("--")) {
|
|
@@ -26120,10 +26119,9 @@ var HELP_TEXT = [
|
|
|
26120
26119
|
` ${import_chalk4.default.gray(" --easy [--words <n>] [--digits <n>] [--separator <s>] [--count <n>]")}`,
|
|
26121
26120
|
` ${import_chalk4.default.cyan("im1")} Read and reply to local CC messages through cc-bot`,
|
|
26122
26121
|
` ${import_chalk4.default.gray(" health [--json]")}`,
|
|
26123
|
-
` ${import_chalk4.default.gray(" threads [--q <keyword>] [--limit <n>] [--json|--text]")}`,
|
|
26122
|
+
` ${import_chalk4.default.gray(" threads [--q <keyword>] [--search <title>] [--limit <n>] [--json|--text]")}`,
|
|
26124
26123
|
` ${import_chalk4.default.gray(" messages --thread <name|conversation-id> [--limit <n>] [--json|--text]")}`,
|
|
26125
|
-
` ${import_chalk4.default.gray("
|
|
26126
|
-
` ${import_chalk4.default.gray(" reply --thread <name> --text <text> (--source <cc|yelling> | --endpoint <url>)")}`,
|
|
26124
|
+
` ${import_chalk4.default.gray(" reply --thread <name|conversation-id> --text <text> [--dryrun]")}`,
|
|
26127
26125
|
` ${import_chalk4.default.cyan("collect")} Legacy alias for CC/yelling collection`,
|
|
26128
26126
|
` ${import_chalk4.default.gray(" sync [--thread <name|conversation-id>] (--source <cc|yelling> | --endpoint <url>)")}`,
|
|
26129
26127
|
` ${import_chalk4.default.cyan("chat-room")} Run social chat regression commands with PMate bot key JSON`,
|