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 +3 -1
- package/dist/bundle/stickyinc-mcp.mjs +126 -72
- package/dist/bundle/stickyinc-watch.mjs +134 -72
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
</p>
|
|
18
18
|
|
|
19
19
|
<p align="center">
|
|
20
|
-
<strong>v0.6.
|
|
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
|
|
21616
|
+
let dayOfMonth2 = 0;
|
|
21606
21617
|
let i = 0;
|
|
21607
21618
|
while (i < n) {
|
|
21608
|
-
|
|
21609
|
-
const date3 = new Date(year, month - 1,
|
|
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,
|
|
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
|
|
24210
|
-
|
|
24211
|
-
|
|
24212
|
-
|
|
24213
|
-
|
|
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
|
|
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
|
-
|
|
24783
|
-
|
|
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.
|
|
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
|
|
525
|
+
let dayOfMonth2 = 0;
|
|
515
526
|
let i = 0;
|
|
516
527
|
while (i < n) {
|
|
517
|
-
|
|
518
|
-
const date = new Date(year, month - 1,
|
|
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,
|
|
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
|
|
3119
|
-
|
|
3120
|
-
|
|
3121
|
-
|
|
3122
|
-
|
|
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
|
|
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
|
-
|
|
7649
|
-
|
|
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
|
|
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.
|
|
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
|
-
"
|
|
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",
|