slack-term 1.25.0 → 1.26.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +30 -8
- package/SKILL.md +20 -7
- package/dist/cli.js +86 -86
- package/package.json +3 -2
- package/ts/cli.ts +78 -29
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "slack-term",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.26.0",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "https://github.com/snomiao/slack-term.git"
|
|
@@ -66,8 +66,9 @@
|
|
|
66
66
|
"record": "bun run tests/record.ts",
|
|
67
67
|
"anonymize": "bun run tests/anonymize.ts",
|
|
68
68
|
"scan-pii": "bash .githooks/pre-commit",
|
|
69
|
+
"scan-pii:all": "bash .githooks/pre-commit --all",
|
|
69
70
|
"prepare": "git config core.hooksPath .githooks 2>/dev/null || true",
|
|
70
|
-
"prepublishOnly": "bun run typecheck && bun run build"
|
|
71
|
+
"prepublishOnly": "bun run scan-pii:all && bun run typecheck && bun run build"
|
|
71
72
|
},
|
|
72
73
|
"type": "module",
|
|
73
74
|
"dependencies": {
|
package/ts/cli.ts
CHANGED
|
@@ -6,7 +6,7 @@ import { existsSync, readFileSync, mkdirSync, writeFileSync } from "node:fs";
|
|
|
6
6
|
import { homedir } from "node:os";
|
|
7
7
|
import { join } from "node:path";
|
|
8
8
|
|
|
9
|
-
import yargs from "yargs";
|
|
9
|
+
import yargs, { type Options } from "yargs";
|
|
10
10
|
import { hideBin } from "yargs/helpers";
|
|
11
11
|
import { listProfiles, removeProfile, resolveBotToken, resolveCookie, resolveToken, useProfile, type Profile } from "./profiles.ts";
|
|
12
12
|
import { diagnoseBotMessaging, formatDiagnosis } from "./botdoctor.ts";
|
|
@@ -545,15 +545,26 @@ async function todoTarget(
|
|
|
545
545
|
|
|
546
546
|
async function cmdTodoLs(
|
|
547
547
|
token: string,
|
|
548
|
-
opts: { state: LsState; in?: string; from?: string;
|
|
548
|
+
opts: { state: LsState; in?: string; from?: string; mine: boolean; count: number; json: boolean; cookie?: string },
|
|
549
549
|
): Promise<void> {
|
|
550
550
|
const cfg = loadTodoConfig();
|
|
551
551
|
const queryOpts: LsQueryOpts = {};
|
|
552
|
-
|
|
552
|
+
// `mine` is the narrow view (`hasmy:` — only reactions this token's user made).
|
|
553
|
+
// Anything else searches anyone's reactions (`has:`).
|
|
554
|
+
if (!opts.mine) queryOpts.shared = true;
|
|
553
555
|
if (opts.in) queryOpts.in = opts.in;
|
|
554
556
|
if (opts.from) queryOpts.from = opts.from;
|
|
555
557
|
const query = buildTodoQuery(opts.state, cfg, queryOpts);
|
|
556
|
-
if (!opts.json)
|
|
558
|
+
if (!opts.json) {
|
|
559
|
+
// Name the identity the listing is relative to. `hasmy:` resolves against
|
|
560
|
+
// whoever the token is, so "my tasks" is only meaningful once you know who
|
|
561
|
+
// "my" is — a wrong profile silently lists a different person's tasks.
|
|
562
|
+
// Same From: line the write gates print, for the same reason.
|
|
563
|
+
const self = await selfIdentity(token, opts.cookie);
|
|
564
|
+
const scope = opts.mine ? "your reactions only [hasmy:]" : "anyone's reactions [has:]";
|
|
565
|
+
console.error(`(From: ${senderLabel(self)} · matching ${scope})`);
|
|
566
|
+
console.error(`(query: ${query})`);
|
|
567
|
+
}
|
|
557
568
|
// One search per invocation — search.messages is Tier 2 (~20 req/min), so the
|
|
558
569
|
// priority rule is encoded as negations in the query, not as extra searches.
|
|
559
570
|
await withRateLimitRetry("search.messages", () => cmdSearch(token, query, opts.count, opts.json, opts.cookie));
|
|
@@ -1846,6 +1857,44 @@ async function main(): Promise<void> {
|
|
|
1846
1857
|
const tok = (a: W) => resolveToken(a.workspace);
|
|
1847
1858
|
const ck = (a: W) => resolveCookie(a.workspace);
|
|
1848
1859
|
|
|
1860
|
+
// `todo ls` and `mytodo ls` are the same listing over two different search
|
|
1861
|
+
// scopes — `has:` (anyone's reactions) vs `hasmy:` (only this token's). They
|
|
1862
|
+
// share one definition so the flags can never drift apart; `mine` is the only
|
|
1863
|
+
// difference. Splitting them into separate commands is deliberate: the old
|
|
1864
|
+
// single `todo ls` defaulted to `hasmy:` with a `--shared` opt-in, and "is
|
|
1865
|
+
// this everyone's list or just mine?" is not a question a flag answers at a
|
|
1866
|
+
// glance.
|
|
1867
|
+
const TODO_LS_OPTIONS: Record<string, Options> = {
|
|
1868
|
+
state: {
|
|
1869
|
+
type: "string",
|
|
1870
|
+
choices: ["open", "untriaged", "pending", "doing", "done", "dropped", "stuck"],
|
|
1871
|
+
default: "open",
|
|
1872
|
+
describe: "open (default) = marked and not done/dropped; untriaged = marked with no progress; stuck = unfinished with a reason flag",
|
|
1873
|
+
},
|
|
1874
|
+
in: { type: "string", describe: "Limit to a channel (#chan)" },
|
|
1875
|
+
from: { type: "string", describe: "Limit to a sender (@user, or 'me') — usually the person a task is waiting on" },
|
|
1876
|
+
n: { alias: "count", type: "number", default: 50, describe: "Max results" },
|
|
1877
|
+
json: { type: "boolean", default: false },
|
|
1878
|
+
};
|
|
1879
|
+
// Runs both listings. `mine` fixes the search scope per command; `--mine` on
|
|
1880
|
+
// `todo ls` is the one-off escape hatch to the narrow view.
|
|
1881
|
+
const runTodoLs = async (
|
|
1882
|
+
argv: W & { state?: string; in?: string; from?: string; mine?: boolean; n?: number; json?: boolean },
|
|
1883
|
+
mine: boolean,
|
|
1884
|
+
) => {
|
|
1885
|
+
const opts: { state: LsState; in?: string; from?: string; mine: boolean; count: number; json: boolean; cookie?: string } = {
|
|
1886
|
+
state: (argv.state ?? "open") as LsState,
|
|
1887
|
+
mine: mine || argv.mine === true,
|
|
1888
|
+
count: argv.n ?? 50,
|
|
1889
|
+
json: argv.json === true,
|
|
1890
|
+
};
|
|
1891
|
+
if (argv.in) opts.in = argv.in;
|
|
1892
|
+
if (argv.from) opts.from = argv.from;
|
|
1893
|
+
const cookie = ck(argv);
|
|
1894
|
+
if (cookie) opts.cookie = cookie;
|
|
1895
|
+
await cmdTodoLs(tok(argv), opts);
|
|
1896
|
+
};
|
|
1897
|
+
|
|
1849
1898
|
await yargs(hideBin(process.argv))
|
|
1850
1899
|
.scriptName("slack")
|
|
1851
1900
|
// A thrown handler error (e.g. a Slack API failure) is a runtime error, not a usage
|
|
@@ -2365,32 +2414,15 @@ async function main(): Promise<void> {
|
|
|
2365
2414
|
.middleware((argv) => { if (argv.cache === false) setCacheEnabled(false); })
|
|
2366
2415
|
.command(
|
|
2367
2416
|
["ls", "list"],
|
|
2368
|
-
"List tasks (read-only
|
|
2417
|
+
"List everyone's tasks (has: — read-only, one search per run)",
|
|
2369
2418
|
(y2) => y2
|
|
2370
|
-
.
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
})
|
|
2376
|
-
|
|
2377
|
-
.option("from", { type: "string", describe: "Limit to a sender (@user, or 'me') — usually the person a task is waiting on" })
|
|
2378
|
-
.option("shared", { type: "boolean", default: false, describe: "Match anyone's reactions (has:) instead of only yours (hasmy:)" })
|
|
2379
|
-
.option("n", { alias: "count", type: "number", default: 50, describe: "Max results" })
|
|
2380
|
-
.option("json", { type: "boolean", default: false }),
|
|
2381
|
-
async (argv) => {
|
|
2382
|
-
const opts: { state: LsState; in?: string; from?: string; shared?: boolean; count: number; json: boolean; cookie?: string } = {
|
|
2383
|
-
state: argv.state as LsState,
|
|
2384
|
-
count: argv.n,
|
|
2385
|
-
json: argv.json,
|
|
2386
|
-
};
|
|
2387
|
-
if (argv.in) opts.in = argv.in;
|
|
2388
|
-
if (argv.from) opts.from = argv.from;
|
|
2389
|
-
if (argv.shared) opts.shared = true;
|
|
2390
|
-
const cookie = ck(argv as W);
|
|
2391
|
-
if (cookie) opts.cookie = cookie;
|
|
2392
|
-
await cmdTodoLs(tok(argv as W), opts);
|
|
2393
|
-
},
|
|
2419
|
+
.options(TODO_LS_OPTIONS)
|
|
2420
|
+
.option("mine", { type: "boolean", default: false, describe: "Only your own reactions (hasmy:) — same as `slack mytodo ls`" })
|
|
2421
|
+
// Accepted for compatibility: it used to opt in to `has:`, which is
|
|
2422
|
+
// now the default here. .strict() rejects unknown flags, so scripts
|
|
2423
|
+
// still passing --shared keep working instead of hard-failing.
|
|
2424
|
+
.option("shared", { type: "boolean", default: false, hidden: true, describe: "Deprecated — anyone's reactions is the default for `todo ls`" }),
|
|
2425
|
+
async (argv) => { await runTodoLs(argv as W & { state?: string }, false); },
|
|
2394
2426
|
)
|
|
2395
2427
|
.command(
|
|
2396
2428
|
"set <target> <state>",
|
|
@@ -2468,6 +2500,23 @@ async function main(): Promise<void> {
|
|
|
2468
2500
|
.demandCommand(1, "Specify a todo subcommand (ls, set, flag, doctor)"),
|
|
2469
2501
|
() => {},
|
|
2470
2502
|
)
|
|
2503
|
+
.command(
|
|
2504
|
+
"mytodo",
|
|
2505
|
+
"Tasks carrying YOUR reactions (hasmy:) — the my-only half of `todo`",
|
|
2506
|
+
(y) => y
|
|
2507
|
+
.option("cache", { type: "boolean", default: true, describe: "Use ~/.config/slack-cli/cache.json for channel/user lookups (--no-cache to bypass)" })
|
|
2508
|
+
.middleware((argv) => { if (argv.cache === false) setCacheEnabled(false); })
|
|
2509
|
+
.command(
|
|
2510
|
+
["ls", "list"],
|
|
2511
|
+
"List tasks you have reacted to (hasmy: — read-only, one search per run)",
|
|
2512
|
+
(y2) => y2.options(TODO_LS_OPTIONS),
|
|
2513
|
+
async (argv) => { await runTodoLs(argv as W & { state?: string }, true); },
|
|
2514
|
+
)
|
|
2515
|
+
// Only `ls` is split. set/flag/doctor act on one identified message, so
|
|
2516
|
+
// "mine vs everyone's" does not apply to them — they stay under `todo`.
|
|
2517
|
+
.demandCommand(1, "Specify a mytodo subcommand (ls)"),
|
|
2518
|
+
() => {},
|
|
2519
|
+
)
|
|
2471
2520
|
.command(
|
|
2472
2521
|
"upload <target> <file..>",
|
|
2473
2522
|
"Upload one or more files to a channel or DM",
|