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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "slack-term",
3
- "version": "1.25.0",
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; shared?: boolean; count: number; json: boolean; cookie?: 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
- if (opts.shared) queryOpts.shared = true;
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) console.error(`(query: ${query})`);
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; one search per run)",
2417
+ "List everyone's tasks (has: — read-only, one search per run)",
2369
2418
  (y2) => y2
2370
- .option("state", {
2371
- type: "string",
2372
- choices: ["open", "untriaged", "pending", "doing", "done", "dropped", "stuck"],
2373
- default: "open",
2374
- describe: "open (default) = marked and not done/dropped; untriaged = marked with no progress; stuck = unfinished with a reason flag",
2375
- })
2376
- .option("in", { type: "string", describe: "Limit to a channel (#chan)" })
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",