@dualityinstitute/blink 0.0.3 → 0.0.5

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 CHANGED
@@ -1,13 +1,12 @@
1
1
  # Blink
2
2
 
3
- Blink is a macOS menu-bar meeting copilot. Monitor answers new screenshots
4
- automatically, while Meeting privately stages the latest transcript and captured
5
- screenshots until you explicitly Ask. Blink Cloud streams every answer into a
6
- floating popup.
3
+ Blink is a macOS menu-bar meeting copilot. Monitor privately stages captured
4
+ screenshots, and Meeting adds the live transcript, until you explicitly Ask.
5
+ Blink Cloud streams every answer into a floating popup.
7
6
 
8
- - Fresh Monitor answers from a watched folder or global capture hotkeys
7
+ - Global capture hotkeys that stage screenshots for the next Ask
9
8
  - Live `Them`/`You` Meeting transcript and private screenshot context
10
- - Explicit Ask button and `Ctrl+Cmd+Return` submission
9
+ - Explicit Ask button and `⇧⌘Return` submission
11
10
  - Presenter notes that remain local to the Mac
12
11
  - Privacy mode for excluding Blink windows from supported screen capture
13
12
  - One-command Bun installation with an interactive terminal setup
@@ -24,7 +23,7 @@ credentials on the Mac.
24
23
  ## Install
25
24
 
26
25
  ```bash
27
- bunx @dualityinstitute/blink install
26
+ bunx @dualityinstitute/blink@latest install
28
27
  ```
29
28
 
30
29
  The terminal UI asks for the access token, writes `~/.blink/config.json`, and
@@ -34,7 +33,7 @@ at its default URL, so only the token is needed.
34
33
  For non-interactive installation:
35
34
 
36
35
  ```bash
37
- bunx @dualityinstitute/blink install --token blink_customer_token
36
+ bunx @dualityinstitute/blink@latest install --token blink_customer_token
38
37
  ```
39
38
 
40
39
  Pass `--api-url` as well to point the app at a different gateway, such as a
@@ -51,19 +50,20 @@ the app bundle, gateway health, and token.
51
50
 
52
51
  ## Monitor
53
52
 
54
- Anything that puts a PNG in `~/blinkDir` is submitted automatically while
55
- Monitor is on. Every Monitor screenshot receives a fresh server-side
56
- conversation. Two global capture shortcuts are registered while Monitor or
57
- Meeting is on:
53
+ Monitor stages screenshots for an explicit Ask. Nothing is sent until you
54
+ submit, so one Ask can carry several captures at once, and every Monitor Ask
55
+ receives a fresh server-side conversation. Two global capture shortcuts are
56
+ registered while Monitor or Meeting is on:
58
57
 
59
58
  | Capture | Shortcut |
60
59
  |---|---|
61
60
  | Select a region | `⇧⌘2` |
62
61
  | Whole main display | `⇧⌘1` |
63
62
 
64
- When Meeting is off, these shortcuts write into the watched directory for an
65
- automatic Monitor answer. When Meeting is active, the same shortcuts stage
66
- private Meeting screenshots instead and never answer automatically.
63
+ Captures are staged privately under `~/.blink/captures/` in both modes and are
64
+ never answered automatically. Select **Ask** or press `⇧⌘Return` to submit every
65
+ staged screenshot in one request; with Meeting active the current transcript is
66
+ included as well.
67
67
 
68
68
  The first capture asks for **Screen & System Audio Recording** permission under
69
69
  the signed **Blink** app identity.
@@ -72,11 +72,13 @@ the signed **Blink** app identity.
72
72
 
73
73
  Enable **Meeting** in the tray or popup. Blink starts a new identity-scoped
74
74
  Meeting conversation, captures system audio as `Them` and microphone audio as
75
- `You`, and keeps a rolling two-minute transcript locally. Captures made with the
75
+ `You`, and keeps a rolling two-minute transcript for the model. The popup widens
76
+ to show the full running transcript beside the answer; scroll back at any point
77
+ and it stops following new speech until you return to the bottom. Captures made with the
76
78
  global shortcuts are retained in a private staging directory. The popup shows
77
79
  which transcript and screenshot context is ready.
78
80
 
79
- Select **Ask** or press `Ctrl+Cmd+Return` to submit one snapshot containing the
81
+ Select **Ask** or press `⇧⌘Return` to submit one snapshot containing the
80
82
  available transcript and staged screenshots. Subsequent Asks reuse the same
81
83
  Meeting conversation. A failed Ask retains its context for retry; after a
82
84
  successful Ask, only the screenshots included in that submission are removed.
@@ -89,19 +91,22 @@ Run `blink ungrant` to clear both permission records and make macOS ask again.
89
91
 
90
92
  ### Saving transcripts
91
93
 
92
- **Save transcript** is off by default. When enabled, the entire Meeting is
93
- appended to a Markdown file under:
94
+ Every Meeting is appended to a Markdown file on your Mac, one per session:
94
95
 
95
96
  ```text
96
97
  ~/.blink/transcripts/2026-08-30-143022.md
97
98
  ```
98
99
 
99
- This local setting is independent from submission: Ask always sends the current
100
- two-minute transcript snapshot when one is available.
100
+ Saving is local only and independent from submission: Ask always sends the
101
+ current two-minute transcript snapshot when one is available, no more.
102
+
103
+ Open **Meeting Transcripts…** from the tray to read, search, and filter past
104
+ sessions by date. Search runs across every saved transcript and reports how many
105
+ times each session matched.
101
106
 
102
107
  ## Answer lifecycle and cancellation
103
108
 
104
- - Monitor answers are always fresh and independent.
109
+ - Monitor Asks are always fresh and independent.
105
110
  - Starting Meeting resets that access identity's Meeting conversation.
106
111
  - Every Ask during the active Meeting continues that conversation.
107
112
  - **Stop** cancels the active request both locally and at the gateway.
@@ -110,9 +115,9 @@ two-minute transcript snapshot when one is available.
110
115
 
111
116
  Subscribe at [duality.institute/blink](https://duality.institute/blink). After
112
117
  Stripe Checkout the claim page shows your access token exactly once; paste it
113
- into `bunx @dualityinstitute/blink install`.
118
+ into `bunx @dualityinstitute/blink@latest install`.
114
119
 
115
- Every Monitor screenshot and every Meeting Ask counts as one answer against
120
+ Every Monitor Ask and every Meeting Ask counts as one answer against
116
121
  your monthly limit. The popup shows `used / limit` for the current period, and
117
122
  `blink doctor` prints the same together with the account email and status.
118
123
  Cancelled or failed answers are not counted. When the limit is reached, or the
@@ -131,16 +136,16 @@ Cloud.
131
136
 
132
137
  Blink Cloud receives:
133
138
 
134
- - A new watched-directory PNG while Monitor is on
135
139
  - The current two-minute transcript and staged screenshots only when Ask is used
136
140
  - The bearer access token needed to authorize and isolate the request
137
141
 
138
142
  The desktop stores the access token in `~/.blink/config.json`. Answer metadata
139
- and text are appended locally to `~/.blink/answers.jsonl`. Presenter Notes stay
140
- local in `~/.blink/presenter.md` and are never submitted. Meeting captures are
141
- staged under `~/.blink/meeting-captures/` and cleared when Meeting ends.
143
+ and text are appended locally to `~/.blink/answers.jsonl`, and meeting
144
+ transcripts to `~/.blink/transcripts/`. Presenter Notes stay
145
+ local in `~/.blink/presenter.md` and are never submitted. Captures are staged
146
+ under `~/.blink/captures/` and cleared when Monitor and Meeting are both off.
142
147
 
143
- Blink Cloud does not retain your content: a Monitor screenshot's conversation is
148
+ Blink Cloud does not retain your content: a Monitor Ask's conversation is
144
149
  deleted as soon as the answer is returned, and a Meeting conversation is deleted
145
150
  when you turn Meeting off (or after two hours without an Ask). Only usage counts
146
151
  and token totals are kept for billing.
@@ -151,11 +156,9 @@ and token totals are kept for billing.
151
156
  |---|---|---|
152
157
  | `apiUrl` | Blink Cloud | Gateway base URL; override only for a self-hosted or local gateway |
153
158
  | `accessToken` | Empty | Bearer token issued for Blink Cloud |
154
- | `screenshotDir` | `~/blinkDir/` | Watched PNG directory |
155
159
  | `hideFromCapture` | `true` | Enables privacy mode |
156
- | `showTranscript` | `true` | Shows the live transcript strip |
157
- | `saveTranscript` | `false` | Saves full meeting transcripts locally |
160
+ | `showTranscript` | `true` | Shows the live transcript panel during a meeting |
158
161
 
159
162
  Blink materializes new defaults without deleting unrelated settings from an
160
- existing config file. The obsolete `sessionMode` and `hotkeyPreset` keys are
161
- removed automatically.
163
+ existing config file. The obsolete `sessionMode`, `hotkeyPreset`, and
164
+ `screenshotDir` keys are removed automatically.
package/dist/cli.js CHANGED
@@ -19862,14 +19862,17 @@ function date4(params) {
19862
19862
  return _coercedDate(ZodDate, params);
19863
19863
  }
19864
19864
  // src/core/cloud-protocol.ts
19865
+ var OptionalPrompt = exports_external.preprocess((value) => value === null || value === "" ? undefined : value, exports_external.string().trim().min(1).max(4000).optional());
19865
19866
  var MonitorRequestSchema = exports_external.object({
19866
19867
  kind: exports_external.literal("monitor"),
19867
- image: exports_external.instanceof(File)
19868
+ images: exports_external.array(exports_external.instanceof(File)).min(1).readonly(),
19869
+ prompt: OptionalPrompt
19868
19870
  });
19869
19871
  var MeetingRequestSchema = exports_external.object({
19870
19872
  kind: exports_external.literal("meeting"),
19871
19873
  text: exports_external.preprocess((value) => value === null ? undefined : value, exports_external.string().trim().min(1).optional()),
19872
- images: exports_external.array(exports_external.instanceof(File)).readonly()
19874
+ images: exports_external.array(exports_external.instanceof(File)).readonly(),
19875
+ prompt: OptionalPrompt
19873
19876
  });
19874
19877
  var CloudAnswerRequestSchema = exports_external.discriminatedUnion("kind", [MonitorRequestSchema, MeetingRequestSchema]).superRefine((request, context) => {
19875
19878
  if (request.kind === "meeting" && request.text === undefined && request.images.length === 0) {
@@ -19962,9 +19965,15 @@ var DEFAULTS = {
19962
19965
  accessToken: "",
19963
19966
  hideFromCapture: true,
19964
19967
  showTranscript: true,
19965
- saveTranscript: false
19968
+ monitorPrompt: "",
19969
+ meetingPrompt: ""
19966
19970
  };
19967
- var OBSOLETE_KEYS = ["sessionMode", "hotkeyPreset"];
19971
+ var OBSOLETE_KEYS = [
19972
+ "sessionMode",
19973
+ "hotkeyPreset",
19974
+ "saveTranscript",
19975
+ "screenshotDir"
19976
+ ];
19968
19977
  var RawConfigSchema = exports_external.record(exports_external.string(), exports_external.unknown());
19969
19978
  var HttpUrlSchema = exports_external.url().refine((value) => {
19970
19979
  const protocol = new URL(value).protocol;
@@ -19980,9 +19989,6 @@ function getBlinkHome() {
19980
19989
  function getConfigPath() {
19981
19990
  return path.join(getBlinkHome(), "config.json");
19982
19991
  }
19983
- function expandTilde(filePath) {
19984
- return filePath.startsWith("~") ? path.join(os.homedir(), filePath.slice(1)) : filePath;
19985
- }
19986
19992
  function parseHttpUrl(value) {
19987
19993
  const parsed = HttpUrlSchema.safeParse(value);
19988
19994
  if (!parsed.success)
@@ -20035,21 +20041,19 @@ function loadConfig() {
20035
20041
  throw new ConfigFileError("accessToken must be a string");
20036
20042
  merged.accessToken = token.data;
20037
20043
  }
20038
- if (user["screenshotDir"] !== undefined) {
20039
- const directory = exports_external.string().safeParse(user["screenshotDir"]);
20040
- if (!directory.success) {
20041
- throw new ConfigFileError("screenshotDir must be a string");
20042
- }
20043
- merged.screenshotDir = expandTilde(directory.data);
20044
- }
20045
20044
  if (user["hideFromCapture"] !== undefined) {
20046
20045
  merged.hideFromCapture = user["hideFromCapture"] === true;
20047
20046
  }
20048
20047
  if (user["showTranscript"] !== undefined) {
20049
20048
  merged.showTranscript = user["showTranscript"] === true;
20050
20049
  }
20051
- if (user["saveTranscript"] !== undefined) {
20052
- merged.saveTranscript = user["saveTranscript"] === true;
20050
+ for (const key of ["monitorPrompt", "meetingPrompt"]) {
20051
+ if (user[key] === undefined)
20052
+ continue;
20053
+ const prompt = exports_external.string().safeParse(user[key]);
20054
+ if (!prompt.success)
20055
+ throw new ConfigFileError(`${key} must be a string`);
20056
+ merged[key] = prompt.data.trim();
20053
20057
  }
20054
20058
  const missing = Object.keys(merged).filter((key) => !(key in persisted));
20055
20059
  if (missing.length > 0 || hadObsolete) {