@pmate/cli 0.25.7 → 0.25.9
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 +42 -4
- package/dist/index.js +391 -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
|
@@ -452,6 +452,47 @@ type AccountCommandArgs = {
|
|
|
452
452
|
role?: "practitioner" | "mate";
|
|
453
453
|
baseUrl?: string;
|
|
454
454
|
json: boolean;
|
|
455
|
+
} | {
|
|
456
|
+
type: Command.Account;
|
|
457
|
+
action: "access-key-create";
|
|
458
|
+
app: string;
|
|
459
|
+
name: string;
|
|
460
|
+
namespace?: string;
|
|
461
|
+
scopes: string[];
|
|
462
|
+
expiresAt: string;
|
|
463
|
+
out?: string;
|
|
464
|
+
baseUrl?: string;
|
|
465
|
+
json: boolean;
|
|
466
|
+
} | {
|
|
467
|
+
type: Command.Account;
|
|
468
|
+
action: "access-key-list";
|
|
469
|
+
app?: string;
|
|
470
|
+
namespace?: string;
|
|
471
|
+
baseUrl?: string;
|
|
472
|
+
json: boolean;
|
|
473
|
+
} | {
|
|
474
|
+
type: Command.Account;
|
|
475
|
+
action: "access-key-disable";
|
|
476
|
+
id: string;
|
|
477
|
+
baseUrl?: string;
|
|
478
|
+
json: boolean;
|
|
479
|
+
} | {
|
|
480
|
+
type: Command.Account;
|
|
481
|
+
action: "access-key-rotate";
|
|
482
|
+
id: string;
|
|
483
|
+
expiresAt: string;
|
|
484
|
+
gracePeriodSeconds?: number;
|
|
485
|
+
out?: string;
|
|
486
|
+
baseUrl?: string;
|
|
487
|
+
json: boolean;
|
|
488
|
+
} | {
|
|
489
|
+
type: Command.Account;
|
|
490
|
+
action: "access-key-login";
|
|
491
|
+
app: string;
|
|
492
|
+
key?: string;
|
|
493
|
+
keyFile?: string;
|
|
494
|
+
baseUrl?: string;
|
|
495
|
+
json: boolean;
|
|
455
496
|
};
|
|
456
497
|
type OrgCommandArgs = {
|
|
457
498
|
type: Command.Org;
|
|
@@ -554,22 +595,19 @@ type CollectCommandArgs = {
|
|
|
554
595
|
} | {
|
|
555
596
|
type: Command.Collect;
|
|
556
597
|
action: "threads";
|
|
557
|
-
source?: "cc";
|
|
558
598
|
query?: string;
|
|
599
|
+
search?: string;
|
|
559
600
|
limit?: number;
|
|
560
601
|
json?: boolean;
|
|
561
602
|
} | {
|
|
562
603
|
type: Command.Collect;
|
|
563
604
|
action: "messages";
|
|
564
|
-
source?: "cc";
|
|
565
605
|
thread: string;
|
|
566
606
|
limit?: number;
|
|
567
607
|
json?: boolean;
|
|
568
608
|
} | {
|
|
569
609
|
type: Command.Collect;
|
|
570
610
|
action: "reply";
|
|
571
|
-
source?: "cc" | "yelling";
|
|
572
|
-
endpoint?: string;
|
|
573
611
|
thread: string;
|
|
574
612
|
text: string;
|
|
575
613
|
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}: ` : "";
|
|
@@ -22228,6 +22303,88 @@ var AccountCommandHandler = class {
|
|
|
22228
22303
|
this.renderJson(profile, args.json);
|
|
22229
22304
|
return;
|
|
22230
22305
|
}
|
|
22306
|
+
case "access-key-create": {
|
|
22307
|
+
const token = this.readToken();
|
|
22308
|
+
const baseUrl = this.resolveAuthBaseUrl(args.baseUrl);
|
|
22309
|
+
const result = await this.request(
|
|
22310
|
+
baseUrl,
|
|
22311
|
+
token,
|
|
22312
|
+
"POST",
|
|
22313
|
+
"/access-keys",
|
|
22314
|
+
{
|
|
22315
|
+
app: args.app,
|
|
22316
|
+
name: args.name,
|
|
22317
|
+
namespace: args.namespace,
|
|
22318
|
+
scopes: args.scopes,
|
|
22319
|
+
expiresAt: args.expiresAt
|
|
22320
|
+
}
|
|
22321
|
+
);
|
|
22322
|
+
this.persistAccessKeySecret(result, args.out);
|
|
22323
|
+
this.renderAccessKeyCreateResult(result, args.json, args.out);
|
|
22324
|
+
return;
|
|
22325
|
+
}
|
|
22326
|
+
case "access-key-list": {
|
|
22327
|
+
const token = this.readToken();
|
|
22328
|
+
const baseUrl = this.resolveAuthBaseUrl(args.baseUrl);
|
|
22329
|
+
const result = await this.request(
|
|
22330
|
+
baseUrl,
|
|
22331
|
+
token,
|
|
22332
|
+
"GET",
|
|
22333
|
+
`/access-keys?${this.buildAccessKeyListQuery(args)}`
|
|
22334
|
+
);
|
|
22335
|
+
this.renderAccessKeys(result.items, args.json);
|
|
22336
|
+
return;
|
|
22337
|
+
}
|
|
22338
|
+
case "access-key-disable": {
|
|
22339
|
+
const token = this.readToken();
|
|
22340
|
+
const baseUrl = this.resolveAuthBaseUrl(args.baseUrl);
|
|
22341
|
+
const item = await this.request(
|
|
22342
|
+
baseUrl,
|
|
22343
|
+
token,
|
|
22344
|
+
"DELETE",
|
|
22345
|
+
`/access-keys/${encodeURIComponent(args.id)}`
|
|
22346
|
+
);
|
|
22347
|
+
if (!args.json) {
|
|
22348
|
+
ui.success(`Disabled access key ${item.id}`);
|
|
22349
|
+
}
|
|
22350
|
+
this.renderJson(item, args.json);
|
|
22351
|
+
return;
|
|
22352
|
+
}
|
|
22353
|
+
case "access-key-rotate": {
|
|
22354
|
+
const token = this.readToken();
|
|
22355
|
+
const baseUrl = this.resolveAuthBaseUrl(args.baseUrl);
|
|
22356
|
+
const result = await this.request(
|
|
22357
|
+
baseUrl,
|
|
22358
|
+
token,
|
|
22359
|
+
"POST",
|
|
22360
|
+
`/access-keys/${encodeURIComponent(args.id)}/rotate`,
|
|
22361
|
+
{
|
|
22362
|
+
expiresAt: args.expiresAt,
|
|
22363
|
+
gracePeriodSeconds: args.gracePeriodSeconds
|
|
22364
|
+
}
|
|
22365
|
+
);
|
|
22366
|
+
this.persistAccessKeySecret(result, args.out);
|
|
22367
|
+
this.renderAccessKeyCreateResult(result, args.json, args.out);
|
|
22368
|
+
return;
|
|
22369
|
+
}
|
|
22370
|
+
case "access-key-login": {
|
|
22371
|
+
const baseUrl = this.resolveAuthBaseUrl(args.baseUrl);
|
|
22372
|
+
const secret = args.key ?? this.readAccessKeyFile(args.keyFile, args.app).secret;
|
|
22373
|
+
const login = await this.request(
|
|
22374
|
+
baseUrl,
|
|
22375
|
+
secret,
|
|
22376
|
+
"POST",
|
|
22377
|
+
"/access-keys/session",
|
|
22378
|
+
{ app: args.app }
|
|
22379
|
+
);
|
|
22380
|
+
const profileId = await this.resolveLoginProfileId(login, args.app, baseUrl);
|
|
22381
|
+
const saved = saveLoginSession({ ...login, profileId }, args.app);
|
|
22382
|
+
if (!args.json) {
|
|
22383
|
+
ui.success(`Logged in access key ${login.session.identity.accessKeyId ?? "unknown"} to ${saved.app}`);
|
|
22384
|
+
}
|
|
22385
|
+
this.renderJson(login, args.json);
|
|
22386
|
+
return;
|
|
22387
|
+
}
|
|
22231
22388
|
}
|
|
22232
22389
|
}
|
|
22233
22390
|
resolveAuthBaseUrl(baseUrl) {
|
|
@@ -22269,6 +22426,12 @@ var AccountCommandHandler = class {
|
|
|
22269
22426
|
if (args.namespace) params.set("namespace", args.namespace);
|
|
22270
22427
|
return params.toString();
|
|
22271
22428
|
}
|
|
22429
|
+
buildAccessKeyListQuery(args) {
|
|
22430
|
+
const params = new URLSearchParams();
|
|
22431
|
+
if (args.app) params.set("app", args.app);
|
|
22432
|
+
if (args.namespace) params.set("namespace", args.namespace);
|
|
22433
|
+
return params.toString();
|
|
22434
|
+
}
|
|
22272
22435
|
async request(baseUrl, token, method, pathname, body) {
|
|
22273
22436
|
const response = await fetch(`${baseUrl}${pathname}`, {
|
|
22274
22437
|
method,
|
|
@@ -22426,6 +22589,9 @@ var AccountCommandHandler = class {
|
|
|
22426
22589
|
resolveDefaultBotKeyPath(app, name) {
|
|
22427
22590
|
return import_node_path26.default.join(this.homeDir(), ".pmate", "bots", app, `${name}.bot-key.json`);
|
|
22428
22591
|
}
|
|
22592
|
+
resolveDefaultAccessKeyPath(app, name) {
|
|
22593
|
+
return import_node_path26.default.join(this.homeDir(), ".pmate", "access-keys", app, `${name}.access-key.json`);
|
|
22594
|
+
}
|
|
22429
22595
|
homeDir() {
|
|
22430
22596
|
return process.env.HOME || process.env.USERPROFILE || process.cwd();
|
|
22431
22597
|
}
|
|
@@ -22462,6 +22628,89 @@ var AccountCommandHandler = class {
|
|
|
22462
22628
|
}
|
|
22463
22629
|
return parsed;
|
|
22464
22630
|
}
|
|
22631
|
+
readAccessKeyFile(filePath, expectedApp) {
|
|
22632
|
+
if (!filePath) {
|
|
22633
|
+
throw new Error("Missing access key file");
|
|
22634
|
+
}
|
|
22635
|
+
const absolutePath = this.resolveFilePath(filePath);
|
|
22636
|
+
if (!(0, import_node_fs22.existsSync)(absolutePath)) {
|
|
22637
|
+
throw new Error(`Access key file not found: ${absolutePath}`);
|
|
22638
|
+
}
|
|
22639
|
+
const parsed = JSON.parse((0, import_node_fs22.readFileSync)(absolutePath, "utf8"));
|
|
22640
|
+
if (parsed.type !== "pmate-access-key" || parsed.version !== 1 || !parsed.id || !parsed.app || !parsed.secret || !parsed.expiresAt) {
|
|
22641
|
+
throw new Error(`Invalid PMate access key file: ${absolutePath}`);
|
|
22642
|
+
}
|
|
22643
|
+
if (expectedApp && parsed.app !== expectedApp) {
|
|
22644
|
+
throw new Error(`Access key app mismatch. expected=${expectedApp}, received=${parsed.app}`);
|
|
22645
|
+
}
|
|
22646
|
+
return parsed;
|
|
22647
|
+
}
|
|
22648
|
+
persistAccessKeySecret(result, out) {
|
|
22649
|
+
const keyFile = {
|
|
22650
|
+
type: "pmate-access-key",
|
|
22651
|
+
version: 1,
|
|
22652
|
+
id: result.key.id,
|
|
22653
|
+
app: result.key.app,
|
|
22654
|
+
name: result.key.name,
|
|
22655
|
+
namespace: result.key.namespace,
|
|
22656
|
+
secret: result.secret,
|
|
22657
|
+
expiresAt: result.key.expiresAt,
|
|
22658
|
+
createdAt: result.key.createdAt
|
|
22659
|
+
};
|
|
22660
|
+
const outPath = out ? this.resolveFilePath(out) : this.resolveDefaultAccessKeyPath(result.key.app, result.key.name);
|
|
22661
|
+
(0, import_node_fs22.mkdirSync)(import_node_path26.default.dirname(outPath), { recursive: true });
|
|
22662
|
+
(0, import_node_fs22.writeFileSync)(outPath, JSON.stringify(keyFile, null, 2), { mode: 384 });
|
|
22663
|
+
(0, import_node_fs22.chmodSync)(outPath, 384);
|
|
22664
|
+
}
|
|
22665
|
+
renderAccessKeyCreateResult(result, asJson, out) {
|
|
22666
|
+
if (asJson) {
|
|
22667
|
+
this.renderJson(result, true);
|
|
22668
|
+
return;
|
|
22669
|
+
}
|
|
22670
|
+
const outPath = out ? this.resolveFilePath(out) : this.resolveDefaultAccessKeyPath(result.key.app, result.key.name);
|
|
22671
|
+
ui.success(`Access key saved to ${outPath}`);
|
|
22672
|
+
ui.info(result.key.id, {
|
|
22673
|
+
app: result.key.app,
|
|
22674
|
+
name: result.key.name,
|
|
22675
|
+
namespace: result.key.namespace,
|
|
22676
|
+
scopes: result.key.scopes,
|
|
22677
|
+
expiresAt: result.key.expiresAt
|
|
22678
|
+
});
|
|
22679
|
+
}
|
|
22680
|
+
renderAccessKeys(items, asJson) {
|
|
22681
|
+
if (asJson) {
|
|
22682
|
+
this.renderJson({ items }, true);
|
|
22683
|
+
return;
|
|
22684
|
+
}
|
|
22685
|
+
if (items.length === 0) {
|
|
22686
|
+
ui.warn("No access keys found.");
|
|
22687
|
+
return;
|
|
22688
|
+
}
|
|
22689
|
+
for (const item of items) {
|
|
22690
|
+
ui.info(item.id, {
|
|
22691
|
+
app: item.app,
|
|
22692
|
+
name: item.name,
|
|
22693
|
+
namespace: item.namespace,
|
|
22694
|
+
scopes: item.scopes,
|
|
22695
|
+
status: item.status,
|
|
22696
|
+
expiresAt: item.expiresAt,
|
|
22697
|
+
lastUsedAt: item.lastUsedAt,
|
|
22698
|
+
rotationExpiresAt: item.rotationExpiresAt
|
|
22699
|
+
});
|
|
22700
|
+
}
|
|
22701
|
+
}
|
|
22702
|
+
async resolveLoginProfileId(login, app, authApiBaseUrl) {
|
|
22703
|
+
try {
|
|
22704
|
+
return await fetchDefaultProfileId({
|
|
22705
|
+
token: login.token,
|
|
22706
|
+
app,
|
|
22707
|
+
accountId: login.session.identity.accountId,
|
|
22708
|
+
authApiBaseUrl
|
|
22709
|
+
});
|
|
22710
|
+
} catch {
|
|
22711
|
+
return login.session.identity.accessKeyId ?? login.session.identity.accountId;
|
|
22712
|
+
}
|
|
22713
|
+
}
|
|
22465
22714
|
signBotChallenge(challenge, privateKey) {
|
|
22466
22715
|
const key = (0, import_node_crypto5.createPrivateKey)({
|
|
22467
22716
|
key: Buffer.from(privateKey, "base64url"),
|
|
@@ -25264,6 +25513,104 @@ function parseAccountArgs(tokens) {
|
|
|
25264
25513
|
}
|
|
25265
25514
|
throw new Error(`Unknown account bot action: ${botAction}`);
|
|
25266
25515
|
}
|
|
25516
|
+
if (action === "access-key") {
|
|
25517
|
+
const keyAction = rest.shift();
|
|
25518
|
+
if (!keyAction) {
|
|
25519
|
+
throw new Error("Missing account access-key action. Use: account access-key create|list|rotate|disable|login");
|
|
25520
|
+
}
|
|
25521
|
+
let app;
|
|
25522
|
+
let id;
|
|
25523
|
+
let name;
|
|
25524
|
+
let namespace2;
|
|
25525
|
+
const scopes = [];
|
|
25526
|
+
let expiresAt;
|
|
25527
|
+
let out;
|
|
25528
|
+
let key;
|
|
25529
|
+
let keyFile;
|
|
25530
|
+
let gracePeriodSeconds;
|
|
25531
|
+
let baseUrl2;
|
|
25532
|
+
let json2 = false;
|
|
25533
|
+
while (rest.length > 0) {
|
|
25534
|
+
const token = rest.shift();
|
|
25535
|
+
switch (token) {
|
|
25536
|
+
case "--app":
|
|
25537
|
+
app = parseNamespacedRef(requireValue8(rest, token), {
|
|
25538
|
+
entityLabel: "app ref"
|
|
25539
|
+
}).fullId;
|
|
25540
|
+
break;
|
|
25541
|
+
case "--name":
|
|
25542
|
+
name = requireValue8(rest, token);
|
|
25543
|
+
break;
|
|
25544
|
+
case "--namespace":
|
|
25545
|
+
namespace2 = requireValue8(rest, token);
|
|
25546
|
+
break;
|
|
25547
|
+
case "--scope":
|
|
25548
|
+
scopes.push(requireValue8(rest, token));
|
|
25549
|
+
break;
|
|
25550
|
+
case "--expires-at":
|
|
25551
|
+
expiresAt = requireValue8(rest, token);
|
|
25552
|
+
break;
|
|
25553
|
+
case "--expires-in":
|
|
25554
|
+
expiresAt = parseExpiresIn(requireValue8(rest, token));
|
|
25555
|
+
break;
|
|
25556
|
+
case "--grace-period":
|
|
25557
|
+
gracePeriodSeconds = parseDurationSeconds(requireValue8(rest, token), token);
|
|
25558
|
+
break;
|
|
25559
|
+
case "--out":
|
|
25560
|
+
out = requireValue8(rest, token);
|
|
25561
|
+
break;
|
|
25562
|
+
case "--key":
|
|
25563
|
+
key = requireValue8(rest, token);
|
|
25564
|
+
break;
|
|
25565
|
+
case "--key-file":
|
|
25566
|
+
keyFile = requireValue8(rest, token);
|
|
25567
|
+
break;
|
|
25568
|
+
case "--base-url":
|
|
25569
|
+
baseUrl2 = requireValue8(rest, token);
|
|
25570
|
+
break;
|
|
25571
|
+
case "--json":
|
|
25572
|
+
json2 = true;
|
|
25573
|
+
break;
|
|
25574
|
+
default:
|
|
25575
|
+
if ((keyAction === "rotate" || keyAction === "disable") && !token.startsWith("-")) {
|
|
25576
|
+
if (id) {
|
|
25577
|
+
throw new Error(`Unexpected account access-key id: ${token}`);
|
|
25578
|
+
}
|
|
25579
|
+
id = token;
|
|
25580
|
+
continue;
|
|
25581
|
+
}
|
|
25582
|
+
throw new Error(`Unknown account access-key option: ${token}`);
|
|
25583
|
+
}
|
|
25584
|
+
}
|
|
25585
|
+
if (keyAction === "create") {
|
|
25586
|
+
if (!app || !name || scopes.length === 0 || !expiresAt) {
|
|
25587
|
+
throw new Error("Usage: account access-key create --app <ns:app> --name <name> --scope <scope>... (--expires-at <iso> | --expires-in <duration>) [--namespace <ns>] [--out <path>] [--base-url <url>] [--json]");
|
|
25588
|
+
}
|
|
25589
|
+
return { type: "account" /* Account */, action: "access-key-create", app, name, namespace: namespace2, scopes, expiresAt, out, baseUrl: baseUrl2, json: json2 };
|
|
25590
|
+
}
|
|
25591
|
+
if (keyAction === "list") {
|
|
25592
|
+
return { type: "account" /* Account */, action: "access-key-list", app, namespace: namespace2, baseUrl: baseUrl2, json: json2 };
|
|
25593
|
+
}
|
|
25594
|
+
if (keyAction === "disable") {
|
|
25595
|
+
if (!id) {
|
|
25596
|
+
throw new Error("Usage: account access-key disable <id> [--base-url <url>] [--json]");
|
|
25597
|
+
}
|
|
25598
|
+
return { type: "account" /* Account */, action: "access-key-disable", id, baseUrl: baseUrl2, json: json2 };
|
|
25599
|
+
}
|
|
25600
|
+
if (keyAction === "rotate") {
|
|
25601
|
+
if (!id || !expiresAt) {
|
|
25602
|
+
throw new Error("Usage: account access-key rotate <id> (--expires-at <iso> | --expires-in <duration>) [--grace-period <duration>] [--out <path>] [--base-url <url>] [--json]");
|
|
25603
|
+
}
|
|
25604
|
+
return { type: "account" /* Account */, action: "access-key-rotate", id, expiresAt, gracePeriodSeconds, out, baseUrl: baseUrl2, json: json2 };
|
|
25605
|
+
}
|
|
25606
|
+
if (keyAction === "login") {
|
|
25607
|
+
if (!app || !key && !keyFile || key && keyFile) {
|
|
25608
|
+
throw new Error("Usage: account access-key login --app <ns:app> (--key <secret> | --key-file <path>) [--base-url <url>] [--json]");
|
|
25609
|
+
}
|
|
25610
|
+
return { type: "account" /* Account */, action: "access-key-login", app, key, keyFile, baseUrl: baseUrl2, json: json2 };
|
|
25611
|
+
}
|
|
25612
|
+
throw new Error(`Unknown account access-key action: ${keyAction}`);
|
|
25613
|
+
}
|
|
25267
25614
|
if (action !== "role") {
|
|
25268
25615
|
throw new Error(`Unknown account action: ${action}`);
|
|
25269
25616
|
}
|
|
@@ -25345,6 +25692,19 @@ function parsePositiveInt(value, optionName) {
|
|
|
25345
25692
|
}
|
|
25346
25693
|
return parsed;
|
|
25347
25694
|
}
|
|
25695
|
+
function parseExpiresIn(value) {
|
|
25696
|
+
return new Date(Date.now() + parseDurationSeconds(value, "--expires-in") * 1e3).toISOString();
|
|
25697
|
+
}
|
|
25698
|
+
function parseDurationSeconds(value, optionName) {
|
|
25699
|
+
const match = /^(\d+)(s|m|h|d)?$/.exec(value.trim());
|
|
25700
|
+
if (!match) {
|
|
25701
|
+
throw new Error(`${optionName} must be a duration like 3600s, 60m, 24h, or 30d`);
|
|
25702
|
+
}
|
|
25703
|
+
const amount = Number(match[1]);
|
|
25704
|
+
const unit = match[2] ?? "s";
|
|
25705
|
+
const multiplier = unit === "d" ? 86400 : unit === "h" ? 3600 : unit === "m" ? 60 : 1;
|
|
25706
|
+
return amount * multiplier;
|
|
25707
|
+
}
|
|
25348
25708
|
|
|
25349
25709
|
// src/orgCommandParser.ts
|
|
25350
25710
|
init_namespacedRef();
|
|
@@ -25683,10 +26043,7 @@ function parseIm1Args(tokens) {
|
|
|
25683
26043
|
const rest = [...tokens];
|
|
25684
26044
|
const action = rest.shift();
|
|
25685
26045
|
if (!action) {
|
|
25686
|
-
throw new Error("Usage: im1
|
|
25687
|
-
}
|
|
25688
|
-
if (action === "collect") {
|
|
25689
|
-
return parseIm1CollectArgs(rest);
|
|
26046
|
+
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
26047
|
}
|
|
25691
26048
|
if (action === "health") {
|
|
25692
26049
|
return parseIm1HealthArgs(rest);
|
|
@@ -25720,22 +26077,19 @@ function parseIm1HealthArgs(tokens) {
|
|
|
25720
26077
|
function parseIm1ThreadsArgs(tokens) {
|
|
25721
26078
|
const rest = [...tokens];
|
|
25722
26079
|
let query;
|
|
26080
|
+
let search;
|
|
25723
26081
|
let limit;
|
|
25724
|
-
let source = "cc";
|
|
25725
26082
|
let json = true;
|
|
25726
26083
|
while (rest.length > 0) {
|
|
25727
26084
|
const token = rest.shift();
|
|
25728
26085
|
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
26086
|
case "--q":
|
|
25736
26087
|
case "--query":
|
|
25737
26088
|
query = requireValue11(rest, token);
|
|
25738
26089
|
break;
|
|
26090
|
+
case "--search":
|
|
26091
|
+
search = requireValue11(rest, token);
|
|
26092
|
+
break;
|
|
25739
26093
|
case "--limit":
|
|
25740
26094
|
limit = parsePositiveInt2(requireValue11(rest, token), token);
|
|
25741
26095
|
break;
|
|
@@ -25749,23 +26103,16 @@ function parseIm1ThreadsArgs(tokens) {
|
|
|
25749
26103
|
throw new Error(`Unknown im1 threads option: ${token}`);
|
|
25750
26104
|
}
|
|
25751
26105
|
}
|
|
25752
|
-
return { type: "collect" /* Collect */, action: "threads",
|
|
26106
|
+
return { type: "collect" /* Collect */, action: "threads", query, search, limit, json };
|
|
25753
26107
|
}
|
|
25754
26108
|
function parseIm1MessagesArgs(tokens) {
|
|
25755
26109
|
const rest = [...tokens];
|
|
25756
26110
|
let thread;
|
|
25757
26111
|
let limit;
|
|
25758
|
-
let source = "cc";
|
|
25759
26112
|
let json = true;
|
|
25760
26113
|
while (rest.length > 0) {
|
|
25761
26114
|
const token = rest.shift();
|
|
25762
26115
|
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
26116
|
case "--thread":
|
|
25770
26117
|
case "--channel":
|
|
25771
26118
|
thread = requireValue11(rest, token);
|
|
@@ -25786,33 +26133,10 @@ function parseIm1MessagesArgs(tokens) {
|
|
|
25786
26133
|
if (!thread) {
|
|
25787
26134
|
throw new Error("Usage: im1 messages --thread <name|conversation-id> [--limit <n>]");
|
|
25788
26135
|
}
|
|
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
|
-
};
|
|
26136
|
+
return { type: "collect" /* Collect */, action: "messages", thread, limit, json };
|
|
25812
26137
|
}
|
|
25813
26138
|
function parseIm1ReplyArgs(tokens) {
|
|
25814
26139
|
const rest = [...tokens];
|
|
25815
|
-
const targetTokens = [];
|
|
25816
26140
|
let thread;
|
|
25817
26141
|
let text;
|
|
25818
26142
|
let dryRun = false;
|
|
@@ -25829,61 +26153,21 @@ function parseIm1ReplyArgs(tokens) {
|
|
|
25829
26153
|
case "--dry-run":
|
|
25830
26154
|
dryRun = true;
|
|
25831
26155
|
break;
|
|
25832
|
-
case "--source":
|
|
25833
|
-
case "--endpoint":
|
|
25834
|
-
targetTokens.push(token, requireValue11(rest, token));
|
|
25835
|
-
break;
|
|
25836
26156
|
default:
|
|
25837
26157
|
throw new Error(`Unknown im1 reply option: ${token}`);
|
|
25838
26158
|
}
|
|
25839
26159
|
}
|
|
25840
26160
|
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>)");
|
|
26161
|
+
throw new Error("Usage: im1 reply --thread <name|conversation-id> --text <text>");
|
|
25846
26162
|
}
|
|
25847
26163
|
return {
|
|
25848
26164
|
type: "collect" /* Collect */,
|
|
25849
26165
|
action: "reply",
|
|
25850
|
-
source: target.source,
|
|
25851
|
-
endpoint: target.endpoint,
|
|
25852
26166
|
thread,
|
|
25853
26167
|
text,
|
|
25854
26168
|
dryRun
|
|
25855
26169
|
};
|
|
25856
26170
|
}
|
|
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
26171
|
function requireValue11(tokens, optionName) {
|
|
25888
26172
|
const value = tokens.shift();
|
|
25889
26173
|
if (!value || value.startsWith("--")) {
|
|
@@ -26120,10 +26404,9 @@ var HELP_TEXT = [
|
|
|
26120
26404
|
` ${import_chalk4.default.gray(" --easy [--words <n>] [--digits <n>] [--separator <s>] [--count <n>]")}`,
|
|
26121
26405
|
` ${import_chalk4.default.cyan("im1")} Read and reply to local CC messages through cc-bot`,
|
|
26122
26406
|
` ${import_chalk4.default.gray(" health [--json]")}`,
|
|
26123
|
-
` ${import_chalk4.default.gray(" threads [--q <keyword>] [--limit <n>] [--json|--text]")}`,
|
|
26407
|
+
` ${import_chalk4.default.gray(" threads [--q <keyword>] [--search <title>] [--limit <n>] [--json|--text]")}`,
|
|
26124
26408
|
` ${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>)")}`,
|
|
26409
|
+
` ${import_chalk4.default.gray(" reply --thread <name|conversation-id> --text <text> [--dryrun]")}`,
|
|
26127
26410
|
` ${import_chalk4.default.cyan("collect")} Legacy alias for CC/yelling collection`,
|
|
26128
26411
|
` ${import_chalk4.default.gray(" sync [--thread <name|conversation-id>] (--source <cc|yelling> | --endpoint <url>)")}`,
|
|
26129
26412
|
` ${import_chalk4.default.cyan("chat-room")} Run social chat regression commands with PMate bot key JSON`,
|
|
@@ -26175,6 +26458,11 @@ var HELP_TEXT = [
|
|
|
26175
26458
|
` ${import_chalk4.default.gray(" bot list --app <ns:app> [--base-url <url>] [--json]")}`,
|
|
26176
26459
|
` ${import_chalk4.default.gray(" bot login --app <ns:app> --name <name> (--key-file <path> | --private-key <key>) [--base-url <url>] [--json]")}`,
|
|
26177
26460
|
` ${import_chalk4.default.gray(" bot profile create --app <ns:app> --name <name> --nickname <nickname> [--role <role>] [--base-url <url>] [--json]")}`,
|
|
26461
|
+
` ${import_chalk4.default.gray(" access-key create --app <ns:app> --name <name> --scope <scope>... (--expires-at <iso> | --expires-in <duration>) [--namespace <ns>] [--out <path>] [--base-url <url>] [--json]")}`,
|
|
26462
|
+
` ${import_chalk4.default.gray(" access-key list [--app <ns:app>] [--namespace <ns>] [--base-url <url>] [--json]")}`,
|
|
26463
|
+
` ${import_chalk4.default.gray(" access-key rotate <id> (--expires-at <iso> | --expires-in <duration>) [--grace-period <duration>] [--out <path>] [--base-url <url>] [--json]")}`,
|
|
26464
|
+
` ${import_chalk4.default.gray(" access-key disable <id> [--base-url <url>] [--json]")}`,
|
|
26465
|
+
` ${import_chalk4.default.gray(" access-key login --app <ns:app> (--key <secret> | --key-file <path>) [--base-url <url>] [--json]")}`,
|
|
26178
26466
|
` ${import_chalk4.default.gray(" default base URL: https://auth-api-v2.pmate.chat")}`,
|
|
26179
26467
|
` ${import_chalk4.default.cyan("org")} Manage orgs, org members, and org visible apps via apps-api`,
|
|
26180
26468
|
` ${import_chalk4.default.gray(" list [--base-url <url>] [--json]")}`,
|