stickyinc 0.6.0 → 0.6.1

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
@@ -17,7 +17,7 @@
17
17
  </p>
18
18
 
19
19
  <p align="center">
20
- <strong>v0.6.0</strong> · MIT · MCP-first · no backend, ever<br />
20
+ <strong>v0.6.1</strong> · MIT · MCP-first · no backend, ever<br />
21
21
  <em>Bring your own LLM key — or piggyback on Claude Code, ChatGPT, Gemini, or local Ollama. Zero config either way.</em>
22
22
  </p>
23
23
 
@@ -61,6 +61,8 @@ claude mcp add -s user stickyinc -- npx -y stickyinc
61
61
  }
62
62
  ```
63
63
 
64
+ **Faster startup (optional):** `npx` asks npm for the latest StickyInc every time your client starts it, which takes 1–3 seconds. Install it once instead and it starts in about a tenth of a second: run `npm install -g stickyinc`, then use `stickyinc` as the command (`claude mcp add -s user stickyinc -- stickyinc`, or `"command": "stickyinc", "args": []`). Updates are then up to you: `npm install -g stickyinc@latest`.
65
+
64
66
  Restart the client and tell it something you need to do: *"remind me to call the dentist Friday afternoon."* If Claude Desktop or Cursor can't find `npx`, put its full path (from `which npx`) in `command`.
65
67
 
66
68
  **2 · Get the strip.** The server saves tasks to `~/.stickyinc/tasks.db`; the pane is the strip that shows them. [Install the pane](#install-the-pane) for macOS, Windows, or Linux. Its setup wizard can also do step 1 for Claude Code.
@@ -21013,9 +21013,9 @@ var StdioServerTransport = class {
21013
21013
  // src/db.ts
21014
21014
  import { createHash, randomUUID } from "node:crypto";
21015
21015
  import { mkdirSync } from "node:fs";
21016
+ import { createRequire } from "node:module";
21016
21017
  import { homedir } from "node:os";
21017
21018
  import { dirname, join, resolve } from "node:path";
21018
- import { DatabaseSync } from "node:sqlite";
21019
21019
 
21020
21020
  // src/search.ts
21021
21021
  function searchWords(query) {
@@ -21037,6 +21037,17 @@ function dbPath() {
21037
21037
  if (!override) return join(homedir(), ".stickyinc", "tasks.db");
21038
21038
  return resolve(override.replace(/^~(?=$|[\\/])/, homedir()));
21039
21039
  }
21040
+ function loadSqlite() {
21041
+ try {
21042
+ return createRequire(import.meta.url)("node:sqlite");
21043
+ } catch {
21044
+ console.error(
21045
+ `StickyInc needs Node.js 22.13+ (or 23.4+) for its built-in SQLite, but this is Node ${process.version}. Install a current Node from https://nodejs.org and restart your MCP client.`
21046
+ );
21047
+ process.exit(1);
21048
+ }
21049
+ }
21050
+ var { DatabaseSync } = loadSqlite();
21040
21051
  var DB_PATH = dbPath();
21041
21052
  mkdirSync(dirname(DB_PATH), { recursive: true });
21042
21053
  var db = new DatabaseSync(DB_PATH);
@@ -21602,15 +21613,15 @@ var TIMEZONE_ABBR_MAP = {
21602
21613
  YEKT: 360
21603
21614
  };
21604
21615
  function getNthWeekdayOfMonth(year, month, weekday, n, hour = 0) {
21605
- let dayOfMonth = 0;
21616
+ let dayOfMonth2 = 0;
21606
21617
  let i = 0;
21607
21618
  while (i < n) {
21608
- dayOfMonth++;
21609
- const date3 = new Date(year, month - 1, dayOfMonth);
21619
+ dayOfMonth2++;
21620
+ const date3 = new Date(year, month - 1, dayOfMonth2);
21610
21621
  if (date3.getDay() === weekday)
21611
21622
  i++;
21612
21623
  }
21613
- return new Date(year, month - 1, dayOfMonth, hour);
21624
+ return new Date(year, month - 1, dayOfMonth2, hour);
21614
21625
  }
21615
21626
  function getLastWeekdayOfMonth(year, month, weekday, hour = 0) {
21616
21627
  const oneIndexedWeekday = weekday === 0 ? 7 : weekday;
@@ -24205,14 +24216,49 @@ function resolveDue(phrase, ref = /* @__PURE__ */ new Date()) {
24205
24216
  const s = phrase.trim();
24206
24217
  const base = { phrase: s, ref: stamp(ref) };
24207
24218
  if (ISO_DATE.test(s)) return { ...base, at: toStoredDue(s) };
24219
+ const d = endOf(s, ref) ?? fromChrono(s.replace(HOUR_TONIGHT, "at $1 tonight"), ref) ?? dayOfMonth(s, ref);
24220
+ return { ...base, at: d && stamp(d) };
24221
+ }
24222
+ function onDay(day, hour, ref) {
24223
+ const d = new Date(day.getFullYear(), day.getMonth(), day.getDate(), hour);
24224
+ if (d < ref && d.toDateString() === ref.toDateString()) d.setHours(23, 59);
24225
+ return d;
24226
+ }
24227
+ function fromChrono(s, ref) {
24208
24228
  const [result] = parse3(s, ref, { forwardDate: true });
24209
- if (!result) return { ...base, at: null };
24210
- let d = result.start.date();
24211
- if (!result.start.isCertain("hour") && !PART_OF_DAY.test(result.text)) {
24212
- d = new Date(d.getFullYear(), d.getMonth(), d.getDate(), 9);
24213
- if (d < ref && d.toDateString() === ref.toDateString()) d.setHours(23, 59);
24229
+ if (!result) return null;
24230
+ if (result.start.isCertain("hour") || PART_OF_DAY.test(result.text)) return result.start.date();
24231
+ return onDay(result.start.date(), 9, ref);
24232
+ }
24233
+ var HOUR_TONIGHT = /(?<!\bat\s)\b(\d{1,2}(?::\d{2})?)\s+tonight\b/i;
24234
+ var END_OF = /\b(?:end\s+of\s+(?:the\s+)?(day|week|month)|eod|cob|close\s+of\s+business)\b/i;
24235
+ function endOf(s, ref) {
24236
+ const m = END_OF.exec(s);
24237
+ if (!m) return null;
24238
+ const unit = (m[1] ?? "day").toLowerCase();
24239
+ let day = ref;
24240
+ if (unit === "day") {
24241
+ const [other] = parse3(s.replace(m[0], " "), ref, { forwardDate: true });
24242
+ if (other) day = other.start.date();
24243
+ } else if (unit === "week") {
24244
+ day = new Date(ref.getFullYear(), ref.getMonth(), ref.getDate() + (5 - ref.getDay() + 7) % 7);
24245
+ } else {
24246
+ day = new Date(ref.getFullYear(), ref.getMonth() + 1, 0);
24214
24247
  }
24215
- return { ...base, at: stamp(d) };
24248
+ return onDay(day, 17, ref);
24249
+ }
24250
+ var ORDINAL_DAY = /\b(\d{1,2})(?:st|nd|rd|th)\b/i;
24251
+ function dayOfMonth(s, ref) {
24252
+ const m = ORDINAL_DAY.exec(s);
24253
+ const n = m ? Number(m[1]) : 0;
24254
+ if (n < 1 || n > 31) return null;
24255
+ for (let ahead = 0; ahead < 12; ahead++) {
24256
+ const d = new Date(ref.getFullYear(), ref.getMonth() + ahead, n);
24257
+ if (d.getDate() !== n) continue;
24258
+ if (d < ref && d.toDateString() !== ref.toDateString()) continue;
24259
+ return onDay(d, 9, ref);
24260
+ }
24261
+ return null;
24216
24262
  }
24217
24263
  function resolveSince(phrase, ref = /* @__PURE__ */ new Date()) {
24218
24264
  const s = phrase.trim();
@@ -24776,69 +24822,74 @@ async function resolveLLMProvider() {
24776
24822
  cached2 = provider ? { provider, configMtime: mtime } : void 0;
24777
24823
  return provider;
24778
24824
  }
24825
+ async function providerFromConfig(cfg) {
24826
+ switch (cfg.provider) {
24827
+ case "anthropic": {
24828
+ const key = cfg.api_key ?? process.env.ANTHROPIC_API_KEY;
24829
+ if (!key) return null;
24830
+ return new AnthropicProvider({ api_key: key, model: cfg.model, base_url: cfg.base_url });
24831
+ }
24832
+ case "openrouter": {
24833
+ const key = cfg.api_key ?? process.env.OPENROUTER_API_KEY;
24834
+ if (!key) return null;
24835
+ return new OpenAICompatProvider({
24836
+ api_key: key,
24837
+ model: cfg.model ?? "anthropic/claude-haiku-4.5",
24838
+ base_url: cfg.base_url ?? "https://openrouter.ai/api/v1",
24839
+ provider_label: "openrouter",
24840
+ extra_headers: {
24841
+ "HTTP-Referer": "https://github.com/Astralchemist/stickyinc",
24842
+ "X-Title": "StickyInc",
24843
+ ...cfg.extra_headers ?? {}
24844
+ }
24845
+ });
24846
+ }
24847
+ case "openai": {
24848
+ const key = cfg.api_key ?? process.env.OPENAI_API_KEY;
24849
+ if (!key) return null;
24850
+ return new OpenAICompatProvider({
24851
+ api_key: key,
24852
+ model: cfg.model ?? "gpt-4o-mini",
24853
+ base_url: cfg.base_url ?? "https://api.openai.com/v1",
24854
+ provider_label: "openai"
24855
+ });
24856
+ }
24857
+ case "compat": {
24858
+ if (!cfg.api_key || !cfg.model || !cfg.base_url) return null;
24859
+ return new OpenAICompatProvider({
24860
+ api_key: cfg.api_key,
24861
+ model: cfg.model,
24862
+ base_url: cfg.base_url,
24863
+ provider_label: "compat",
24864
+ extra_headers: cfg.extra_headers
24865
+ });
24866
+ }
24867
+ case "claude-code": {
24868
+ const binary = findClaudeBinary();
24869
+ if (!binary) return null;
24870
+ return new ClaudeCodeProvider({ binary, model: cfg.model });
24871
+ }
24872
+ case "codex": {
24873
+ const binary = findCodexBinary();
24874
+ if (!binary) return null;
24875
+ return new CodexProvider({ binary, model: cfg.model });
24876
+ }
24877
+ case "gemini": {
24878
+ const binary = findGeminiBinary();
24879
+ if (!binary) return null;
24880
+ return new GeminiProvider({ binary, model: cfg.model });
24881
+ }
24882
+ case "local": {
24883
+ return probeLocalProvider(cfg.model);
24884
+ }
24885
+ }
24886
+ return void 0;
24887
+ }
24779
24888
  async function resolveInternal() {
24780
24889
  const cfg = readConfigFile();
24781
24890
  if (cfg) {
24782
- switch (cfg.provider) {
24783
- case "anthropic": {
24784
- const key = cfg.api_key ?? process.env.ANTHROPIC_API_KEY;
24785
- if (!key) return null;
24786
- return new AnthropicProvider({ api_key: key, model: cfg.model, base_url: cfg.base_url });
24787
- }
24788
- case "openrouter": {
24789
- const key = cfg.api_key ?? process.env.OPENROUTER_API_KEY;
24790
- if (!key) return null;
24791
- return new OpenAICompatProvider({
24792
- api_key: key,
24793
- model: cfg.model ?? "anthropic/claude-haiku-4.5",
24794
- base_url: cfg.base_url ?? "https://openrouter.ai/api/v1",
24795
- provider_label: "openrouter",
24796
- extra_headers: {
24797
- "HTTP-Referer": "https://github.com/Astralchemist/stickyinc",
24798
- "X-Title": "StickyInc",
24799
- ...cfg.extra_headers ?? {}
24800
- }
24801
- });
24802
- }
24803
- case "openai": {
24804
- const key = cfg.api_key ?? process.env.OPENAI_API_KEY;
24805
- if (!key) return null;
24806
- return new OpenAICompatProvider({
24807
- api_key: key,
24808
- model: cfg.model ?? "gpt-4o-mini",
24809
- base_url: cfg.base_url ?? "https://api.openai.com/v1",
24810
- provider_label: "openai"
24811
- });
24812
- }
24813
- case "compat": {
24814
- if (!cfg.api_key || !cfg.model || !cfg.base_url) return null;
24815
- return new OpenAICompatProvider({
24816
- api_key: cfg.api_key,
24817
- model: cfg.model,
24818
- base_url: cfg.base_url,
24819
- provider_label: "compat",
24820
- extra_headers: cfg.extra_headers
24821
- });
24822
- }
24823
- case "claude-code": {
24824
- const binary = findClaudeBinary();
24825
- if (!binary) return null;
24826
- return new ClaudeCodeProvider({ binary, model: cfg.model });
24827
- }
24828
- case "codex": {
24829
- const binary = findCodexBinary();
24830
- if (!binary) return null;
24831
- return new CodexProvider({ binary, model: cfg.model });
24832
- }
24833
- case "gemini": {
24834
- const binary = findGeminiBinary();
24835
- if (!binary) return null;
24836
- return new GeminiProvider({ binary, model: cfg.model });
24837
- }
24838
- case "local": {
24839
- return probeLocalProvider(cfg.model);
24840
- }
24841
- }
24891
+ const provider = await providerFromConfig(cfg);
24892
+ if (provider !== void 0) return provider;
24842
24893
  }
24843
24894
  if (process.env.OPENROUTER_API_KEY) {
24844
24895
  return new OpenAICompatProvider({
@@ -25354,7 +25405,7 @@ async function handleRoutineImport(args, onChange) {
25354
25405
  // src/index.ts
25355
25406
  var server = new McpServer({
25356
25407
  name: "stickyinc",
25357
- version: "0.6.0"
25408
+ version: "0.6.1"
25358
25409
  });
25359
25410
  var client = () => clientLabel(server.server.getClientVersion());
25360
25411
  server.registerTool(
@@ -25519,3 +25570,6 @@ syncRoutinePrompts();
25519
25570
  setInterval(syncRoutinePrompts, 3e4).unref();
25520
25571
  var transport = new StdioServerTransport();
25521
25572
  await server.connect(transport);
25573
+ setTimeout(() => {
25574
+ for (const phrase of ["tomorrow at 3pm", "Friday", "in 2 hours", "the 30th"]) describeDue(resolveDue(phrase));
25575
+ }, 200).unref();
@@ -22,14 +22,25 @@ import { join as join5 } from "node:path";
22
22
  // src/db.ts
23
23
  import { createHash, randomUUID } from "node:crypto";
24
24
  import { mkdirSync } from "node:fs";
25
+ import { createRequire } from "node:module";
25
26
  import { homedir } from "node:os";
26
27
  import { dirname, join, resolve } from "node:path";
27
- import { DatabaseSync } from "node:sqlite";
28
28
  function dbPath() {
29
29
  const override = process.env.STICKYINC_DB;
30
30
  if (!override) return join(homedir(), ".stickyinc", "tasks.db");
31
31
  return resolve(override.replace(/^~(?=$|[\\/])/, homedir()));
32
32
  }
33
+ function loadSqlite() {
34
+ try {
35
+ return createRequire(import.meta.url)("node:sqlite");
36
+ } catch {
37
+ console.error(
38
+ `StickyInc needs Node.js 22.13+ (or 23.4+) for its built-in SQLite, but this is Node ${process.version}. Install a current Node from https://nodejs.org and restart your MCP client.`
39
+ );
40
+ process.exit(1);
41
+ }
42
+ }
43
+ var { DatabaseSync } = loadSqlite();
33
44
  var DB_PATH = dbPath();
34
45
  mkdirSync(dirname(DB_PATH), { recursive: true });
35
46
  var db = new DatabaseSync(DB_PATH);
@@ -511,15 +522,15 @@ var TIMEZONE_ABBR_MAP = {
511
522
  YEKT: 360
512
523
  };
513
524
  function getNthWeekdayOfMonth(year, month, weekday, n, hour = 0) {
514
- let dayOfMonth = 0;
525
+ let dayOfMonth2 = 0;
515
526
  let i = 0;
516
527
  while (i < n) {
517
- dayOfMonth++;
518
- const date = new Date(year, month - 1, dayOfMonth);
528
+ dayOfMonth2++;
529
+ const date = new Date(year, month - 1, dayOfMonth2);
519
530
  if (date.getDay() === weekday)
520
531
  i++;
521
532
  }
522
- return new Date(year, month - 1, dayOfMonth, hour);
533
+ return new Date(year, month - 1, dayOfMonth2, hour);
523
534
  }
524
535
  function getLastWeekdayOfMonth(year, month, weekday, hour = 0) {
525
536
  const oneIndexedWeekday = weekday === 0 ? 7 : weekday;
@@ -3114,14 +3125,49 @@ function resolveDue(phrase, ref = /* @__PURE__ */ new Date()) {
3114
3125
  const s = phrase.trim();
3115
3126
  const base = { phrase: s, ref: stamp(ref) };
3116
3127
  if (ISO_DATE.test(s)) return { ...base, at: toStoredDue(s) };
3128
+ const d = endOf(s, ref) ?? fromChrono(s.replace(HOUR_TONIGHT, "at $1 tonight"), ref) ?? dayOfMonth(s, ref);
3129
+ return { ...base, at: d && stamp(d) };
3130
+ }
3131
+ function onDay(day, hour, ref) {
3132
+ const d = new Date(day.getFullYear(), day.getMonth(), day.getDate(), hour);
3133
+ if (d < ref && d.toDateString() === ref.toDateString()) d.setHours(23, 59);
3134
+ return d;
3135
+ }
3136
+ function fromChrono(s, ref) {
3117
3137
  const [result] = parse(s, ref, { forwardDate: true });
3118
- if (!result) return { ...base, at: null };
3119
- let d = result.start.date();
3120
- if (!result.start.isCertain("hour") && !PART_OF_DAY.test(result.text)) {
3121
- d = new Date(d.getFullYear(), d.getMonth(), d.getDate(), 9);
3122
- if (d < ref && d.toDateString() === ref.toDateString()) d.setHours(23, 59);
3138
+ if (!result) return null;
3139
+ if (result.start.isCertain("hour") || PART_OF_DAY.test(result.text)) return result.start.date();
3140
+ return onDay(result.start.date(), 9, ref);
3141
+ }
3142
+ var HOUR_TONIGHT = /(?<!\bat\s)\b(\d{1,2}(?::\d{2})?)\s+tonight\b/i;
3143
+ var END_OF = /\b(?:end\s+of\s+(?:the\s+)?(day|week|month)|eod|cob|close\s+of\s+business)\b/i;
3144
+ function endOf(s, ref) {
3145
+ const m = END_OF.exec(s);
3146
+ if (!m) return null;
3147
+ const unit = (m[1] ?? "day").toLowerCase();
3148
+ let day = ref;
3149
+ if (unit === "day") {
3150
+ const [other] = parse(s.replace(m[0], " "), ref, { forwardDate: true });
3151
+ if (other) day = other.start.date();
3152
+ } else if (unit === "week") {
3153
+ day = new Date(ref.getFullYear(), ref.getMonth(), ref.getDate() + (5 - ref.getDay() + 7) % 7);
3154
+ } else {
3155
+ day = new Date(ref.getFullYear(), ref.getMonth() + 1, 0);
3123
3156
  }
3124
- return { ...base, at: stamp(d) };
3157
+ return onDay(day, 17, ref);
3158
+ }
3159
+ var ORDINAL_DAY = /\b(\d{1,2})(?:st|nd|rd|th)\b/i;
3160
+ function dayOfMonth(s, ref) {
3161
+ const m = ORDINAL_DAY.exec(s);
3162
+ const n = m ? Number(m[1]) : 0;
3163
+ if (n < 1 || n > 31) return null;
3164
+ for (let ahead = 0; ahead < 12; ahead++) {
3165
+ const d = new Date(ref.getFullYear(), ref.getMonth() + ahead, n);
3166
+ if (d.getDate() !== n) continue;
3167
+ if (d < ref && d.toDateString() !== ref.toDateString()) continue;
3168
+ return onDay(d, 9, ref);
3169
+ }
3170
+ return null;
3125
3171
  }
3126
3172
 
3127
3173
  // node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/external.js
@@ -7642,69 +7688,74 @@ async function resolveLLMProvider() {
7642
7688
  cached = provider ? { provider, configMtime: mtime } : void 0;
7643
7689
  return provider;
7644
7690
  }
7691
+ async function providerFromConfig(cfg) {
7692
+ switch (cfg.provider) {
7693
+ case "anthropic": {
7694
+ const key = cfg.api_key ?? process.env.ANTHROPIC_API_KEY;
7695
+ if (!key) return null;
7696
+ return new AnthropicProvider({ api_key: key, model: cfg.model, base_url: cfg.base_url });
7697
+ }
7698
+ case "openrouter": {
7699
+ const key = cfg.api_key ?? process.env.OPENROUTER_API_KEY;
7700
+ if (!key) return null;
7701
+ return new OpenAICompatProvider({
7702
+ api_key: key,
7703
+ model: cfg.model ?? "anthropic/claude-haiku-4.5",
7704
+ base_url: cfg.base_url ?? "https://openrouter.ai/api/v1",
7705
+ provider_label: "openrouter",
7706
+ extra_headers: {
7707
+ "HTTP-Referer": "https://github.com/Astralchemist/stickyinc",
7708
+ "X-Title": "StickyInc",
7709
+ ...cfg.extra_headers ?? {}
7710
+ }
7711
+ });
7712
+ }
7713
+ case "openai": {
7714
+ const key = cfg.api_key ?? process.env.OPENAI_API_KEY;
7715
+ if (!key) return null;
7716
+ return new OpenAICompatProvider({
7717
+ api_key: key,
7718
+ model: cfg.model ?? "gpt-4o-mini",
7719
+ base_url: cfg.base_url ?? "https://api.openai.com/v1",
7720
+ provider_label: "openai"
7721
+ });
7722
+ }
7723
+ case "compat": {
7724
+ if (!cfg.api_key || !cfg.model || !cfg.base_url) return null;
7725
+ return new OpenAICompatProvider({
7726
+ api_key: cfg.api_key,
7727
+ model: cfg.model,
7728
+ base_url: cfg.base_url,
7729
+ provider_label: "compat",
7730
+ extra_headers: cfg.extra_headers
7731
+ });
7732
+ }
7733
+ case "claude-code": {
7734
+ const binary = findClaudeBinary();
7735
+ if (!binary) return null;
7736
+ return new ClaudeCodeProvider({ binary, model: cfg.model });
7737
+ }
7738
+ case "codex": {
7739
+ const binary = findCodexBinary();
7740
+ if (!binary) return null;
7741
+ return new CodexProvider({ binary, model: cfg.model });
7742
+ }
7743
+ case "gemini": {
7744
+ const binary = findGeminiBinary();
7745
+ if (!binary) return null;
7746
+ return new GeminiProvider({ binary, model: cfg.model });
7747
+ }
7748
+ case "local": {
7749
+ return probeLocalProvider(cfg.model);
7750
+ }
7751
+ }
7752
+ return void 0;
7753
+ }
7645
7754
  async function resolveInternal() {
7646
7755
  const cfg = readConfigFile();
7647
7756
  if (cfg) {
7648
- switch (cfg.provider) {
7649
- case "anthropic": {
7650
- const key = cfg.api_key ?? process.env.ANTHROPIC_API_KEY;
7651
- if (!key) return null;
7652
- return new AnthropicProvider({ api_key: key, model: cfg.model, base_url: cfg.base_url });
7653
- }
7654
- case "openrouter": {
7655
- const key = cfg.api_key ?? process.env.OPENROUTER_API_KEY;
7656
- if (!key) return null;
7657
- return new OpenAICompatProvider({
7658
- api_key: key,
7659
- model: cfg.model ?? "anthropic/claude-haiku-4.5",
7660
- base_url: cfg.base_url ?? "https://openrouter.ai/api/v1",
7661
- provider_label: "openrouter",
7662
- extra_headers: {
7663
- "HTTP-Referer": "https://github.com/Astralchemist/stickyinc",
7664
- "X-Title": "StickyInc",
7665
- ...cfg.extra_headers ?? {}
7666
- }
7667
- });
7668
- }
7669
- case "openai": {
7670
- const key = cfg.api_key ?? process.env.OPENAI_API_KEY;
7671
- if (!key) return null;
7672
- return new OpenAICompatProvider({
7673
- api_key: key,
7674
- model: cfg.model ?? "gpt-4o-mini",
7675
- base_url: cfg.base_url ?? "https://api.openai.com/v1",
7676
- provider_label: "openai"
7677
- });
7678
- }
7679
- case "compat": {
7680
- if (!cfg.api_key || !cfg.model || !cfg.base_url) return null;
7681
- return new OpenAICompatProvider({
7682
- api_key: cfg.api_key,
7683
- model: cfg.model,
7684
- base_url: cfg.base_url,
7685
- provider_label: "compat",
7686
- extra_headers: cfg.extra_headers
7687
- });
7688
- }
7689
- case "claude-code": {
7690
- const binary = findClaudeBinary();
7691
- if (!binary) return null;
7692
- return new ClaudeCodeProvider({ binary, model: cfg.model });
7693
- }
7694
- case "codex": {
7695
- const binary = findCodexBinary();
7696
- if (!binary) return null;
7697
- return new CodexProvider({ binary, model: cfg.model });
7698
- }
7699
- case "gemini": {
7700
- const binary = findGeminiBinary();
7701
- if (!binary) return null;
7702
- return new GeminiProvider({ binary, model: cfg.model });
7703
- }
7704
- case "local": {
7705
- return probeLocalProvider(cfg.model);
7706
- }
7707
- }
7757
+ const provider = await providerFromConfig(cfg);
7758
+ if (provider !== void 0) return provider;
7708
7759
  }
7709
7760
  if (process.env.OPENROUTER_API_KEY) {
7710
7761
  return new OpenAICompatProvider({
@@ -7815,11 +7866,22 @@ Ignore:
7815
7866
  - Hypotheticals ("I could do X")
7816
7867
  - Rhetorical or past-tense references
7817
7868
  - Generic questions or musings
7869
+ - Requests for the assistant to do something ("fix the build", "can you add a test"): it does those in the conversation. But "remind me to X" is a commitment to X.
7870
+ - When the speaker is the assistant: its own next steps and progress ("I'll run the tests", "Let me check the logs"). Only something it says the user has to do themselves counts.
7818
7871
 
7819
7872
  Output ONLY a JSON object, no prose:
7820
7873
  { "commitments": [{ "text": "...", "due": "<the words that say when, or null>", "quote": "<the sentence it came from, copied exactly>" }] }
7821
7874
 
7822
7875
  Empty array if nothing qualifies. "due" copies the speaker's date/time words ("Friday 3pm", "next week"); don't work out a date. If an hour has no am/pm, add the one the speaker means.`;
7876
+ var MESSAGE_MAX = 4e3;
7877
+ var MESSAGE_HEAD = 1e3;
7878
+ function forModel(text) {
7879
+ const chars = Array.from(text);
7880
+ if (chars.length <= MESSAGE_MAX) return text;
7881
+ return `${chars.slice(0, MESSAGE_HEAD).join("")}
7882
+ [\u2026]
7883
+ ${chars.slice(chars.length - (MESSAGE_MAX - MESSAGE_HEAD)).join("")}`;
7884
+ }
7823
7885
  function stripFences(s) {
7824
7886
  return s.replace(/^\s*```(?:json)?\s*/i, "").replace(/\s*```\s*$/, "").trim();
7825
7887
  }
@@ -7871,7 +7933,7 @@ async function extract(provider, speaker, text, said) {
7871
7933
  content: `Speaker: ${speaker}
7872
7934
 
7873
7935
  Message:
7874
- ${text.slice(0, 4e3)}`
7936
+ ${forModel(text)}`
7875
7937
  }
7876
7938
  ],
7877
7939
  response_format: "json",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "stickyinc",
3
- "version": "0.6.0",
3
+ "version": "0.6.1",
4
4
  "description": "A reification layer between LLM conversations and your commitments. MCP server + floating pane.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -18,7 +18,9 @@
18
18
  "watch": "tsx src/watch-cli.ts",
19
19
  "typecheck": "tsc --noEmit",
20
20
  "test": "tsx --test src/*.test.ts pane/src/*.test.ts extension/*.test.mjs",
21
- "prepublishOnly": "tsc --noEmit && tsx --test src/*.test.ts pane/src/*.test.ts extension/*.test.mjs && node scripts/bundle.mjs && node smoke.mjs dist/bundle/stickyinc-mcp.mjs"
21
+ "eval:extraction": "tsx scripts/eval-extraction.ts",
22
+ "bench": "node scripts/bench.mjs dist/bundle/stickyinc-mcp.mjs",
23
+ "prepublishOnly": "tsc --noEmit && tsx --test src/*.test.ts pane/src/*.test.ts extension/*.test.mjs && node scripts/bundle.mjs && node smoke.mjs dist/bundle/stickyinc-mcp.mjs && node scripts/bench.mjs dist/bundle/stickyinc-mcp.mjs"
22
24
  },
23
25
  "keywords": [
24
26
  "mcp",