dsh-mishu 0.7.2 → 0.7.3

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.
@@ -29,6 +29,11 @@ export type AskRequest = {
29
29
  kind: 'mail-setup';
30
30
  preset: string;
31
31
  error?: string;
32
+ } | {
33
+ kind: 'watch-error';
34
+ watchId: number;
35
+ } | {
36
+ kind: 'watch-setup';
32
37
  };
33
38
  export interface AskOptions {
34
39
  now: number;
@@ -17,7 +17,7 @@ export interface BoardCard {
17
17
  /** Matter id, or series id for a series card. */
18
18
  id: number;
19
19
  target: 'matter' | 'series';
20
- source: 'github' | 'mail' | 'manual';
20
+ source: 'github' | 'mail' | 'manual' | 'watch';
21
21
  title: string;
22
22
  /** One short line: when it is due, or why it is here. */
23
23
  meta: string;
@@ -58,6 +58,12 @@ export interface BoardData {
58
58
  state: 'never' | 'ok' | 'error';
59
59
  text: string;
60
60
  }>;
61
+ watch: Array<{
62
+ id: number;
63
+ label: string;
64
+ state: 'never' | 'ok' | 'error';
65
+ text: string;
66
+ }>;
61
67
  };
62
68
  markRead: MarkReadMode;
63
69
  columns: {
@@ -83,6 +89,11 @@ export interface BoardOptions {
83
89
  id: number;
84
90
  label: string;
85
91
  }>;
92
+ /** Things you watch yourself, for their status line. */
93
+ watches?: Array<{
94
+ id: number;
95
+ label: string;
96
+ }>;
86
97
  }
87
98
  export declare function buildBoard(store: MishuStore, options: BoardOptions): BoardData;
88
99
  export interface MatterDetail {
@@ -27,6 +27,11 @@ export interface BriefOptions {
27
27
  id: number;
28
28
  label: string;
29
29
  }>;
30
+ /** Things you watch yourself, for their status lines. */
31
+ watches?: Array<{
32
+ id: number;
33
+ label: string;
34
+ }>;
30
35
  }
31
36
  export declare function renderBrief(store: MishuStore, options: BriefOptions): string;
32
37
  /** Recently closed matters with reasons, so a wrong closure can be reopened. */
@@ -28,6 +28,9 @@ export { DEFAULT_NOTIFY, deliver, inQuiet, localClock, notifySettings, notifyTic
28
28
  export { restartPlan, scheduleRestart, type RestartPlan, type RestartStarted } from './restart.ts';
29
29
  export { FileSecrets, KeychainOrFile, MemorySecrets, secretEnvName, secretFromEnv, systemSecrets, type SecretStatus, type SecretStore } from './secrets.ts';
30
30
  export { deleteMailAccount, effectiveSettings, mailAccounts, saveMailAccount, saveSettings, type Defaults, type UserSettings } from './settings.ts';
31
+ export { deleteWatch, dueWatches, entryKey, parseKeywords, resolveWatch, saveWatch, watchById, watchReportKey, watchSources, WATCH_SOURCE, type WatchInput, type WatchReport, type WatchSource, } from './watch/source.ts';
32
+ export { describeWatchError, httpFetch, parseFeed, parseJson, parsePage, plainText, readWatchBody, type WatchFetch, type WatchItem } from './watch/fetch.ts';
33
+ export { describeKeywords, describeWatch, diffItems, judgeWatch, syncWatch } from './watch/sync.ts';
31
34
  export declare const DEFAULT_HOME = "~/.mishu";
32
35
  /** `~` and `~/…` expand to the home directory on every platform. */
33
36
  export declare function expandHome(path: string): string;
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Reading a watched address: a feed, a JSON list, or just a page.
3
+ *
4
+ * Whatever comes back is turned into a handful of items — a title, a bit of
5
+ * text and a stable key — so the rest of the code never has to care whether
6
+ * it was RSS, Atom, JSON or plain HTML. No parser library: the shapes that
7
+ * matter here are small, and a wrong guess costs nothing (the page is read
8
+ * as text and still works).
9
+ */
10
+ import { type Bi } from '../i18n.ts';
11
+ import { type WatchSource } from './source.ts';
12
+ export interface WatchItem {
13
+ /** Stable across checks: the same entry twice is the same item. */
14
+ key: string;
15
+ title: string;
16
+ /** The text to look for keywords in, and to quote back. */
17
+ text: string;
18
+ url: string | null;
19
+ /** When the source says it happened; the fetch time when it does not. */
20
+ at: number;
21
+ }
22
+ export interface WatchFetch {
23
+ (url: string): Promise<{
24
+ status: number;
25
+ body: string;
26
+ contentType: string;
27
+ }>;
28
+ }
29
+ /** The real fetch: a plain GET with a short timeout and no cookies. */
30
+ export declare const httpFetch: WatchFetch;
31
+ /** Markup out, whitespace collapsed. */
32
+ export declare function plainText(markup: string): string;
33
+ /** RSS `<item>` and Atom `<entry>` blocks, whichever the document has. */
34
+ export declare function parseFeed(body: string, now: number, base: string): WatchItem[] | undefined;
35
+ /**
36
+ * A JSON body: an array of objects, or an object with one array inside. Each
37
+ * element's title-like and text-like fields become an item.
38
+ */
39
+ export declare function parseJson(body: string, now: number, base: string): WatchItem[] | undefined;
40
+ /** A page with no feed: its title and text, as one item keyed by a hash of the text. */
41
+ export declare function parsePage(body: string, now: number, base: string): WatchItem[];
42
+ /** Whatever the address returned, as items. */
43
+ export declare function readWatchBody(options: {
44
+ body: string;
45
+ contentType: string;
46
+ url: string;
47
+ now: number;
48
+ }): WatchItem[];
49
+ /** The sentence shown when a watch cannot be read. */
50
+ export declare function describeWatchError(error: unknown, source: WatchSource): Bi;
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Things you watch yourself: a page, a feed or an API you care about.
3
+ *
4
+ * GitHub and mail are sources the secretary knows how to read. Everything
5
+ * else a person cares about — a game's patch notes, a job posting, a price,
6
+ * a status page — is a URL. You add it here with the words that matter to
7
+ * you, and the secretary looks at it now and then: when it changes, or when
8
+ * one of your words shows up, it becomes a card like anything else.
9
+ *
10
+ * Only where and what to look for is kept here. What was seen last lives in
11
+ * the notebook's key-value table, so an entry never grows without bound.
12
+ */
13
+ import { type Bi } from '../i18n.ts';
14
+ import type { MishuStore } from '../store.ts';
15
+ export declare const WATCH_SOURCE = "watch";
16
+ export interface WatchSource {
17
+ id: number;
18
+ /** What you call it: "补丁公告", "机票价格". */
19
+ label: string;
20
+ url: string;
21
+ /**
22
+ * Words to watch for, lower-cased. Empty: any change is worth a card.
23
+ * A feed is read per entry; a page is read as one text.
24
+ */
25
+ keywords: string[];
26
+ enabled: boolean;
27
+ /** Minutes between checks. */
28
+ intervalMinutes: number;
29
+ }
30
+ export interface WatchInput {
31
+ id?: number;
32
+ label?: string;
33
+ url?: string;
34
+ keywords?: string | string[];
35
+ enabled?: boolean;
36
+ intervalMinutes?: number;
37
+ }
38
+ export interface WatchReport {
39
+ at: number;
40
+ id: number;
41
+ /** Entries or text pieces the fetch turned up. */
42
+ items: number;
43
+ opened: number;
44
+ closed: number;
45
+ error?: Bi;
46
+ }
47
+ export declare function watchSources(store: MishuStore): WatchSource[];
48
+ export declare function watchById(store: MishuStore, id: number): WatchSource | undefined;
49
+ export declare function watchReportKey(id: number): string;
50
+ /** Where one watch keeps what it has already seen. */
51
+ export declare function watchStateKey(id: number): string;
52
+ export declare function watchSeenKey(id: number): string;
53
+ /** A stable id for a feed entry: its guid, else its link, else its title. */
54
+ export declare function entryKey(entry: {
55
+ id?: string;
56
+ link?: string;
57
+ title?: string;
58
+ }): string;
59
+ /** Split what you typed into words: commas, 、, spaces or new lines all work. */
60
+ export declare function parseKeywords(value: string | string[] | undefined): string[];
61
+ /** Check and complete what the settings page sent; throws a sentence a person can act on. */
62
+ export declare function resolveWatch(input: WatchInput, id: number): WatchSource;
63
+ /** Add (no id) or update one watch. */
64
+ export declare function saveWatch(store: MishuStore, input: WatchInput, now: number): WatchSource;
65
+ /** Remove one watch and close whatever it still has open. */
66
+ export declare function deleteWatch(store: MishuStore, id: number, now: number): boolean;
67
+ /** The watches whose next check is due. */
68
+ export declare function dueWatches(store: MishuStore, now: number): WatchSource[];
@@ -0,0 +1,51 @@
1
+ /**
2
+ * One watch, one check: read the address, compare with what was seen last
3
+ * time, and turn anything new into a card.
4
+ *
5
+ * A card per watch, not per entry: "补丁公告 · 3 条新内容" is what you want to
6
+ * see, not three cards that say the same thing. Marking it done closes it;
7
+ * the next change opens it again with what changed.
8
+ *
9
+ * The first check only writes down what is there now — otherwise adding a
10
+ * feed would hand you every entry it ever published. Words you asked to watch
11
+ * for are the exception: those are shown right away, because you asked.
12
+ */
13
+ import type { MishuStore } from '../store.ts';
14
+ import type { Verdict } from '../github/rules.ts';
15
+ import { type WatchFetch, type WatchItem } from './fetch.ts';
16
+ import { type WatchReport, type WatchSource } from './source.ts';
17
+ interface WatchState {
18
+ /** Keys of entries already reported, newest last. */
19
+ seen: string[];
20
+ /** Fingerprint of the whole page, for addresses that are not feeds. */
21
+ digest?: string;
22
+ /** First check done. */
23
+ primed: boolean;
24
+ }
25
+ /** Which items are new since last time, and whether the whole page changed. */
26
+ export declare function diffItems(items: WatchItem[], state: WatchState): {
27
+ fresh: WatchItem[];
28
+ changed: boolean;
29
+ digest: string;
30
+ };
31
+ export declare function judgeWatch(options: {
32
+ source: WatchSource;
33
+ items: WatchItem[];
34
+ fresh: WatchItem[];
35
+ changed: boolean;
36
+ first: boolean;
37
+ now: number;
38
+ }): Verdict | undefined;
39
+ export declare function syncWatch(options: {
40
+ store: MishuStore;
41
+ source: WatchSource;
42
+ fetcher: WatchFetch;
43
+ now: number;
44
+ }): Promise<WatchReport>;
45
+ /** Everything about one watch the settings page shows. */
46
+ export declare function describeWatch(store: MishuStore, source: WatchSource, lang: 'zh' | 'en'): string;
47
+ /** The words, for showing back to you. */
48
+ export declare function describeKeywords(source: WatchSource, lang: 'zh' | 'en'): string;
49
+ /** Guard for the settings page: an address that cannot be a watch at all. */
50
+ export declare function assertWatchable(source: WatchSource): void;
51
+ export {};
@@ -4,7 +4,7 @@
4
4
  * what you set on the settings page on top), the keychain, and one sync of
5
5
  * every source at a time.
6
6
  */
7
- import { GithubDeviceLogin, type Defaults, type Lang, type MailAccount, type MailFetcher, type MailSyncReport, type MishuStore, type Channel, type ChannelId, type NotifyReport, type RestartPlan, type RestartStarted, type SecretStore, type TokenResult } from '../core/index.ts';
7
+ import { GithubDeviceLogin, type Defaults, type Lang, type MailAccount, type MailFetcher, type MailSyncReport, type MishuStore, type Channel, type ChannelId, type NotifyReport, type RestartPlan, type RestartStarted, type SecretStore, type TokenResult, type WatchFetch, type WatchReport, type WatchSource } from '../core/index.ts';
8
8
  import type { MishuConfig } from './config.ts';
9
9
  interface Logger {
10
10
  info(message: string): void;
@@ -15,6 +15,8 @@ export declare class Runtime {
15
15
  readonly config: MishuConfig;
16
16
  readonly secrets: SecretStore;
17
17
  readonly fetcher: MailFetcher;
18
+ /** How watched addresses are read; tests swap it for a fake. */
19
+ watchFetch: WatchFetch;
18
20
  private readonly logger;
19
21
  private syncing;
20
22
  /** Where reminders can go; tests swap them for recorders. */
@@ -36,6 +38,7 @@ export declare class Runtime {
36
38
  fixedLang(): Lang | undefined;
37
39
  lang(): Lang;
38
40
  accounts(): MailAccount[];
41
+ watches(): WatchSource[];
39
42
  brief(lang?: Lang): string;
40
43
  /** The password for an account: keychain first, then the documented environment variable. */
41
44
  mailPassword(id: number): Promise<string | undefined>;
@@ -44,6 +47,8 @@ export declare class Runtime {
44
47
  private syncGithubNow;
45
48
  /** One account; the result is also kept as its last report. */
46
49
  syncMailAccount(account: MailAccount): Promise<MailSyncReport | undefined>;
50
+ /** One watch; the result is also kept as its last report. */
51
+ syncWatchSource(source: WatchSource): Promise<WatchReport | undefined>;
47
52
  /** The channels that are turned on and work on this computer. */
48
53
  activeChannels(): Promise<Channel[]>;
49
54
  /** The board as the brief describes it. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-mishu",
3
- "version": "0.7.2",
3
+ "version": "0.7.3",
4
4
  "description": "DSH secretary (小秘书) for DeepSeek Harness (dsh): watches GitHub notifications and the dated things you tell it (bills, renewals, documents, birthdays), closes what is already resolved, and keeps a short list of what actually needs you. Bilingual (中文 / English), macOS / Windows / Linux. · DSH 小秘书:提醒、通知、待办、消息、收件箱、秘书。GitHub 通知按当前状态分诊,交代一句就记下到期和周期事项。",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",