granola-toolkit 0.47.0 → 0.48.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/dist/cli.js +189 -10
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -449,7 +449,7 @@ function asRecord(value) {
|
|
|
449
449
|
function stringValue(value) {
|
|
450
450
|
return typeof value === "string" ? value : "";
|
|
451
451
|
}
|
|
452
|
-
function stringArray$
|
|
452
|
+
function stringArray$2(value) {
|
|
453
453
|
if (!Array.isArray(value)) return [];
|
|
454
454
|
return value.filter((item) => typeof item === "string");
|
|
455
455
|
}
|
|
@@ -2612,6 +2612,7 @@ function defaultGranolaToolkitPersistenceLayout(options = {}) {
|
|
|
2612
2612
|
const targetPlatform = options.platform ?? platform();
|
|
2613
2613
|
const dataDirectory = defaultGranolaToolkitDataDirectory(targetPlatform, options.homeDirectory ?? homedir());
|
|
2614
2614
|
return {
|
|
2615
|
+
agentHarnessesFile: join(dataDirectory, "agent-harnesses.json"),
|
|
2615
2616
|
automationMatchesFile: join(dataDirectory, "automation-matches.jsonl"),
|
|
2616
2617
|
automationRulesFile: join(dataDirectory, "automation-rules.json"),
|
|
2617
2618
|
automationRunsFile: join(dataDirectory, "automation-runs.jsonl"),
|
|
@@ -2627,6 +2628,145 @@ function defaultGranolaToolkitPersistenceLayout(options = {}) {
|
|
|
2627
2628
|
};
|
|
2628
2629
|
}
|
|
2629
2630
|
//#endregion
|
|
2631
|
+
//#region src/agent-harnesses.ts
|
|
2632
|
+
function stringArray$1(value) {
|
|
2633
|
+
if (!Array.isArray(value)) return;
|
|
2634
|
+
const values = value.filter((item) => typeof item === "string" && item.trim().length > 0).map((item) => item.trim());
|
|
2635
|
+
return values.length > 0 ? [...new Set(values)] : void 0;
|
|
2636
|
+
}
|
|
2637
|
+
function parseMatch(value) {
|
|
2638
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return;
|
|
2639
|
+
const record = value;
|
|
2640
|
+
return {
|
|
2641
|
+
calendarEventIds: stringArray$1(record.calendarEventIds),
|
|
2642
|
+
eventKinds: stringArray$1(record.eventKinds),
|
|
2643
|
+
folderIds: stringArray$1(record.folderIds),
|
|
2644
|
+
folderNames: stringArray$1(record.folderNames),
|
|
2645
|
+
meetingIds: stringArray$1(record.meetingIds),
|
|
2646
|
+
recurringEventIds: stringArray$1(record.recurringEventIds),
|
|
2647
|
+
tags: stringArray$1(record.tags),
|
|
2648
|
+
titleIncludes: stringArray$1(record.titleIncludes),
|
|
2649
|
+
titleMatches: typeof record.titleMatches === "string" && record.titleMatches.trim() ? record.titleMatches.trim() : void 0,
|
|
2650
|
+
transcriptLoaded: typeof record.transcriptLoaded === "boolean" ? record.transcriptLoaded : void 0
|
|
2651
|
+
};
|
|
2652
|
+
}
|
|
2653
|
+
function parseHarness(value) {
|
|
2654
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return;
|
|
2655
|
+
const record = value;
|
|
2656
|
+
const id = typeof record.id === "string" && record.id.trim() ? record.id.trim() : void 0;
|
|
2657
|
+
const name = typeof record.name === "string" && record.name.trim() ? record.name.trim() : void 0;
|
|
2658
|
+
if (!id || !name) return;
|
|
2659
|
+
const provider = record.provider === "codex" || record.provider === "openai" || record.provider === "openrouter" ? record.provider : void 0;
|
|
2660
|
+
const prompt = typeof record.prompt === "string" && record.prompt.trim() ? record.prompt.trim() : void 0;
|
|
2661
|
+
const promptFile = typeof record.promptFile === "string" && record.promptFile.trim() ? record.promptFile.trim() : void 0;
|
|
2662
|
+
if (!prompt && !promptFile) return;
|
|
2663
|
+
return {
|
|
2664
|
+
cwd: typeof record.cwd === "string" && record.cwd.trim() ? record.cwd.trim() : void 0,
|
|
2665
|
+
id,
|
|
2666
|
+
match: parseMatch(record.match),
|
|
2667
|
+
model: typeof record.model === "string" && record.model.trim() ? record.model.trim() : void 0,
|
|
2668
|
+
name,
|
|
2669
|
+
priority: typeof record.priority === "number" && Number.isFinite(record.priority) ? record.priority : typeof record.priority === "string" && /^-?\d+$/.test(record.priority) ? Number(record.priority) : void 0,
|
|
2670
|
+
prompt,
|
|
2671
|
+
promptFile,
|
|
2672
|
+
provider,
|
|
2673
|
+
systemPrompt: typeof record.systemPrompt === "string" && record.systemPrompt.trim() ? record.systemPrompt.trim() : void 0,
|
|
2674
|
+
systemPromptFile: typeof record.systemPromptFile === "string" && record.systemPromptFile.trim() ? record.systemPromptFile.trim() : void 0
|
|
2675
|
+
};
|
|
2676
|
+
}
|
|
2677
|
+
function cloneHarness(harness) {
|
|
2678
|
+
return {
|
|
2679
|
+
...harness,
|
|
2680
|
+
match: harness.match ? {
|
|
2681
|
+
...harness.match,
|
|
2682
|
+
calendarEventIds: harness.match.calendarEventIds ? [...harness.match.calendarEventIds] : void 0,
|
|
2683
|
+
eventKinds: harness.match.eventKinds ? [...harness.match.eventKinds] : void 0,
|
|
2684
|
+
folderIds: harness.match.folderIds ? [...harness.match.folderIds] : void 0,
|
|
2685
|
+
folderNames: harness.match.folderNames ? [...harness.match.folderNames] : void 0,
|
|
2686
|
+
meetingIds: harness.match.meetingIds ? [...harness.match.meetingIds] : void 0,
|
|
2687
|
+
recurringEventIds: harness.match.recurringEventIds ? [...harness.match.recurringEventIds] : void 0,
|
|
2688
|
+
tags: harness.match.tags ? [...harness.match.tags] : void 0,
|
|
2689
|
+
titleIncludes: harness.match.titleIncludes ? [...harness.match.titleIncludes] : void 0
|
|
2690
|
+
} : void 0
|
|
2691
|
+
};
|
|
2692
|
+
}
|
|
2693
|
+
function includesIgnoreCase$1(candidate, values) {
|
|
2694
|
+
const lowerCandidate = candidate.toLowerCase();
|
|
2695
|
+
return values.some((value) => lowerCandidate.includes(value.toLowerCase()));
|
|
2696
|
+
}
|
|
2697
|
+
function harnessSpecificity(match) {
|
|
2698
|
+
if (!match) return 0;
|
|
2699
|
+
return [
|
|
2700
|
+
match.calendarEventIds?.length ?? 0,
|
|
2701
|
+
match.eventKinds?.length ?? 0,
|
|
2702
|
+
match.folderIds?.length ?? 0,
|
|
2703
|
+
match.folderNames?.length ?? 0,
|
|
2704
|
+
match.meetingIds?.length ?? 0,
|
|
2705
|
+
match.recurringEventIds?.length ?? 0,
|
|
2706
|
+
match.tags?.length ?? 0,
|
|
2707
|
+
match.titleIncludes?.length ?? 0,
|
|
2708
|
+
match.titleMatches ? 1 : 0,
|
|
2709
|
+
match.transcriptLoaded != null ? 1 : 0
|
|
2710
|
+
].reduce((total, count) => total + count, 0);
|
|
2711
|
+
}
|
|
2712
|
+
function matchesHarness(harness, context) {
|
|
2713
|
+
const match = harness.match;
|
|
2714
|
+
if (!match) return true;
|
|
2715
|
+
if (match.eventKinds?.length && !match.eventKinds.includes(context.match.eventKind)) return false;
|
|
2716
|
+
if (match.meetingIds?.length && !match.meetingIds.includes(context.match.meetingId)) return false;
|
|
2717
|
+
if (match.folderIds?.length && !context.match.folders.some((folder) => match.folderIds?.includes(folder.id))) return false;
|
|
2718
|
+
if (match.folderNames?.length && !context.match.folders.some((folder) => match.folderNames?.includes(folder.name))) return false;
|
|
2719
|
+
if (match.tags?.length && !context.match.tags.some((tag) => match.tags?.includes(tag))) return false;
|
|
2720
|
+
if (match.transcriptLoaded != null && context.match.transcriptLoaded !== match.transcriptLoaded) return false;
|
|
2721
|
+
if (match.titleIncludes?.length && !includesIgnoreCase$1(context.match.title, match.titleIncludes)) return false;
|
|
2722
|
+
if (match.titleMatches) try {
|
|
2723
|
+
if (!new RegExp(match.titleMatches, "i").test(context.match.title)) return false;
|
|
2724
|
+
} catch {
|
|
2725
|
+
return false;
|
|
2726
|
+
}
|
|
2727
|
+
const calendarEventId = context.bundle?.document.calendarEvent?.id;
|
|
2728
|
+
if (match.calendarEventIds?.length && !calendarEventId) return false;
|
|
2729
|
+
if (match.calendarEventIds?.length && !match.calendarEventIds.includes(calendarEventId)) return false;
|
|
2730
|
+
const recurringEventId = context.bundle?.document.calendarEvent?.recurringEventId;
|
|
2731
|
+
if (match.recurringEventIds?.length && !recurringEventId) return false;
|
|
2732
|
+
if (match.recurringEventIds?.length && !match.recurringEventIds.includes(recurringEventId)) return false;
|
|
2733
|
+
return true;
|
|
2734
|
+
}
|
|
2735
|
+
function matchAgentHarnesses(harnesses, context) {
|
|
2736
|
+
return harnesses.filter((harness) => matchesHarness(harness, context)).slice().sort((left, right) => {
|
|
2737
|
+
const priority = (right.priority ?? 0) - (left.priority ?? 0);
|
|
2738
|
+
if (priority !== 0) return priority;
|
|
2739
|
+
return harnessSpecificity(right.match) - harnessSpecificity(left.match);
|
|
2740
|
+
}).map(cloneHarness);
|
|
2741
|
+
}
|
|
2742
|
+
function resolveAgentHarness(harnesses, context, harnessId) {
|
|
2743
|
+
if (harnessId?.trim()) {
|
|
2744
|
+
const harness = harnesses.find((candidate) => candidate.id === harnessId.trim());
|
|
2745
|
+
if (!harness) throw new Error(`agent harness not found: ${harnessId.trim()}`);
|
|
2746
|
+
return cloneHarness(harness);
|
|
2747
|
+
}
|
|
2748
|
+
return matchAgentHarnesses(harnesses, context)[0];
|
|
2749
|
+
}
|
|
2750
|
+
var FileAgentHarnessStore = class {
|
|
2751
|
+
constructor(filePath = defaultAgentHarnessesFilePath()) {
|
|
2752
|
+
this.filePath = filePath;
|
|
2753
|
+
}
|
|
2754
|
+
async readHarnesses() {
|
|
2755
|
+
try {
|
|
2756
|
+
const parsed = parseJsonString(await readFile(this.filePath, "utf8"));
|
|
2757
|
+
return (Array.isArray(parsed) ? parsed : parsed && typeof parsed === "object" && Array.isArray(parsed.harnesses) ? parsed.harnesses : []).map((harness) => parseHarness(harness)).filter((harness) => Boolean(harness)).map(cloneHarness);
|
|
2758
|
+
} catch {
|
|
2759
|
+
return [];
|
|
2760
|
+
}
|
|
2761
|
+
}
|
|
2762
|
+
};
|
|
2763
|
+
function defaultAgentHarnessesFilePath() {
|
|
2764
|
+
return defaultGranolaToolkitPersistenceLayout().agentHarnessesFile;
|
|
2765
|
+
}
|
|
2766
|
+
function createDefaultAgentHarnessStore(filePath) {
|
|
2767
|
+
return new FileAgentHarnessStore(filePath);
|
|
2768
|
+
}
|
|
2769
|
+
//#endregion
|
|
2630
2770
|
//#region src/client/auth.ts
|
|
2631
2771
|
const execFileAsync$1 = promisify(execFile);
|
|
2632
2772
|
const DEFAULT_CLIENT_ID = "client_GranolaMac";
|
|
@@ -3514,13 +3654,15 @@ function parseAction(value, index) {
|
|
|
3514
3654
|
const provider = record.provider === "codex" || record.provider === "openai" || record.provider === "openrouter" ? record.provider : void 0;
|
|
3515
3655
|
const prompt = typeof record.prompt === "string" && record.prompt.trim() ? record.prompt.trim() : void 0;
|
|
3516
3656
|
const promptFile = typeof record.promptFile === "string" && record.promptFile.trim() ? record.promptFile.trim() : void 0;
|
|
3657
|
+
const harnessId = typeof record.harnessId === "string" && record.harnessId.trim() ? record.harnessId.trim() : void 0;
|
|
3517
3658
|
const systemPrompt = typeof record.systemPrompt === "string" && record.systemPrompt.trim() ? record.systemPrompt.trim() : void 0;
|
|
3518
3659
|
const systemPromptFile = typeof record.systemPromptFile === "string" && record.systemPromptFile.trim() ? record.systemPromptFile.trim() : void 0;
|
|
3519
|
-
if (!prompt && !promptFile) return;
|
|
3660
|
+
if (!prompt && !promptFile && !harnessId) return;
|
|
3520
3661
|
return {
|
|
3521
3662
|
cwd: typeof record.cwd === "string" && record.cwd.trim() ? record.cwd.trim() : void 0,
|
|
3522
3663
|
dryRun: typeof record.dryRun === "boolean" ? record.dryRun : void 0,
|
|
3523
3664
|
enabled,
|
|
3665
|
+
harnessId,
|
|
3524
3666
|
id,
|
|
3525
3667
|
kind,
|
|
3526
3668
|
model: typeof record.model === "string" && record.model.trim() ? record.model.trim() : void 0,
|
|
@@ -3912,17 +4054,39 @@ function parseLastViewedPanel(value) {
|
|
|
3912
4054
|
updatedAt: stringValue(panel.updated_at)
|
|
3913
4055
|
};
|
|
3914
4056
|
}
|
|
4057
|
+
function parseCalendarEvent(value) {
|
|
4058
|
+
const record = asRecord(value);
|
|
4059
|
+
if (!record) return;
|
|
4060
|
+
const id = stringValue(record.id);
|
|
4061
|
+
const recurringEventId = stringValue(record.recurring_event_id) || stringValue(record.recurringEventId);
|
|
4062
|
+
const calendarId = stringValue(record.calendar_id) || stringValue(record.calendarId);
|
|
4063
|
+
const url = stringValue(record.url);
|
|
4064
|
+
const htmlLink = stringValue(record.html_link) || stringValue(record.htmlLink);
|
|
4065
|
+
const startTime = stringValue(record.start_time) || stringValue(record.startTime);
|
|
4066
|
+
const endTime = stringValue(record.end_time) || stringValue(record.endTime);
|
|
4067
|
+
if (!id && !recurringEventId && !calendarId && !url && !htmlLink && !startTime && !endTime) return;
|
|
4068
|
+
return {
|
|
4069
|
+
calendarId: calendarId || void 0,
|
|
4070
|
+
endTime: endTime || void 0,
|
|
4071
|
+
htmlLink: htmlLink || void 0,
|
|
4072
|
+
id: id || void 0,
|
|
4073
|
+
recurringEventId: recurringEventId || void 0,
|
|
4074
|
+
startTime: startTime || void 0,
|
|
4075
|
+
url: url || void 0
|
|
4076
|
+
};
|
|
4077
|
+
}
|
|
3915
4078
|
function parseDocument(value) {
|
|
3916
4079
|
const record = asRecord(value);
|
|
3917
4080
|
if (!record) throw new Error("document payload is not an object");
|
|
3918
4081
|
return {
|
|
4082
|
+
calendarEvent: parseCalendarEvent(record.google_calendar_event),
|
|
3919
4083
|
content: stringValue(record.content),
|
|
3920
4084
|
createdAt: stringValue(record.created_at),
|
|
3921
4085
|
id: stringValue(record.id),
|
|
3922
4086
|
lastViewedPanel: parseLastViewedPanel(record.last_viewed_panel),
|
|
3923
4087
|
notes: parseProseMirrorDoc(record.notes),
|
|
3924
4088
|
notesPlain: stringValue(record.notes_plain),
|
|
3925
|
-
tags: stringArray$
|
|
4089
|
+
tags: stringArray$2(record.tags),
|
|
3926
4090
|
title: stringValue(record.title),
|
|
3927
4091
|
updatedAt: stringValue(record.updated_at)
|
|
3928
4092
|
};
|
|
@@ -3973,6 +4137,7 @@ function parsePublicNote(value) {
|
|
|
3973
4137
|
const summaryMarkdown = stringValue(record.summary_markdown);
|
|
3974
4138
|
const summaryText = stringValue(record.summary_text);
|
|
3975
4139
|
return {
|
|
4140
|
+
calendarEvent: parseCalendarEvent(record.google_calendar_event),
|
|
3976
4141
|
content: summaryMarkdown || summaryText,
|
|
3977
4142
|
createdAt: stringValue(record.created_at),
|
|
3978
4143
|
folderMemberships: Array.isArray(record.folder_membership) ? record.folder_membership.map(parseFolderMembership).filter((membership) => Boolean(membership)) : [],
|
|
@@ -4890,6 +5055,10 @@ function cloneMeetingSummary(meeting) {
|
|
|
4890
5055
|
function resolveActionFilePath(filePath, cwd) {
|
|
4891
5056
|
return cwd ? resolve(cwd, filePath) : resolve(filePath);
|
|
4892
5057
|
}
|
|
5058
|
+
async function readOptionalActionFile(filePath, cwd) {
|
|
5059
|
+
if (!filePath) return;
|
|
5060
|
+
return await readFile(resolveActionFilePath(filePath, cwd), "utf8");
|
|
5061
|
+
}
|
|
4893
5062
|
function combinePromptSections(...values) {
|
|
4894
5063
|
const sections = values.map((value) => value?.trim()).filter((value) => Boolean(value));
|
|
4895
5064
|
return sections.length > 0 ? sections.join("\n\n") : void 0;
|
|
@@ -5689,18 +5858,25 @@ var GranolaApp = class {
|
|
|
5689
5858
|
}
|
|
5690
5859
|
async runAutomationAgent(match, rule, action) {
|
|
5691
5860
|
const bundle = match.eventKind === "meeting.removed" ? void 0 : await this.maybeReadMeetingBundleById(match.meetingId, { requireCache: false });
|
|
5692
|
-
const
|
|
5693
|
-
|
|
5694
|
-
|
|
5861
|
+
const harness = resolveAgentHarness(this.deps.agentHarnessStore ? await this.deps.agentHarnessStore.readHarnesses() : [], {
|
|
5862
|
+
bundle,
|
|
5863
|
+
match
|
|
5864
|
+
}, action.harnessId);
|
|
5865
|
+
const harnessCwd = harness?.cwd;
|
|
5866
|
+
const promptFile = await readOptionalActionFile(action.promptFile, action.cwd ?? harnessCwd);
|
|
5867
|
+
const harnessPromptFile = await readOptionalActionFile(harness?.promptFile, harnessCwd);
|
|
5868
|
+
const systemPromptFile = await readOptionalActionFile(action.systemPromptFile, action.cwd ?? harnessCwd);
|
|
5869
|
+
const harnessSystemPromptFile = await readOptionalActionFile(harness?.systemPromptFile, harnessCwd);
|
|
5870
|
+
const instructions = combinePromptSections(harnessPromptFile, harness?.prompt, promptFile, action.prompt);
|
|
5695
5871
|
if (!instructions) throw new Error(`automation agent action ${action.id} is missing prompt instructions`);
|
|
5696
5872
|
const request = {
|
|
5697
|
-
cwd: action.cwd,
|
|
5873
|
+
cwd: action.cwd ?? harnessCwd,
|
|
5698
5874
|
dryRun: action.dryRun,
|
|
5699
|
-
model: action.model,
|
|
5875
|
+
model: action.model ?? harness?.model,
|
|
5700
5876
|
prompt: buildAutomationAgentPrompt(match, rule, instructions, bundle),
|
|
5701
|
-
provider: action.provider,
|
|
5877
|
+
provider: action.provider ?? harness?.provider,
|
|
5702
5878
|
retries: action.retries,
|
|
5703
|
-
systemPrompt: combinePromptSections(systemPromptFile, action.systemPrompt),
|
|
5879
|
+
systemPrompt: combinePromptSections(harnessSystemPromptFile, harness?.systemPrompt, systemPromptFile, action.systemPrompt),
|
|
5704
5880
|
timeoutMs: action.timeoutMs
|
|
5705
5881
|
};
|
|
5706
5882
|
return await (this.deps.agentRunner ?? createDefaultAutomationAgentRunner(this.config)).run(request);
|
|
@@ -6147,6 +6323,7 @@ async function createGranolaApp(config, options = {}) {
|
|
|
6147
6323
|
const automationRuns = await automationRunStore.readRuns({ limit: 0 });
|
|
6148
6324
|
const automationRuleStore = createDefaultAutomationRuleStore(config.automation?.rulesFile ?? defaultAutomationRulesFilePath());
|
|
6149
6325
|
const automationRules = await automationRuleStore.readRules();
|
|
6326
|
+
const agentHarnessStore = createDefaultAgentHarnessStore(config.agents?.harnessesFile);
|
|
6150
6327
|
const authController = createDefaultGranolaAuthController(config);
|
|
6151
6328
|
const exportJobStore = createDefaultExportJobStore();
|
|
6152
6329
|
const exportJobs = await exportJobStore.readJobs();
|
|
@@ -6160,6 +6337,7 @@ async function createGranolaApp(config, options = {}) {
|
|
|
6160
6337
|
return new GranolaApp(config, {
|
|
6161
6338
|
auth,
|
|
6162
6339
|
agentRunner: createDefaultAutomationAgentRunner(config),
|
|
6340
|
+
agentHarnessStore,
|
|
6163
6341
|
authController,
|
|
6164
6342
|
automationMatches,
|
|
6165
6343
|
automationMatchStore,
|
|
@@ -6257,6 +6435,7 @@ async function loadConfig(options) {
|
|
|
6257
6435
|
return value === "codex" || value === "openai" || value === "openrouter" ? value : void 0;
|
|
6258
6436
|
})(),
|
|
6259
6437
|
dryRun: envFlag(env.GRANOLA_AGENT_DRY_RUN) ?? pickBoolean(configValues["agent-dry-run"]) ?? pickBoolean(configValues.agentDryRun) ?? false,
|
|
6438
|
+
harnessesFile: pickString(env.GRANOLA_AGENT_HARNESSES_FILE) ?? pickString(configValues["agent-harnesses-file"]) ?? pickString(configValues.agentHarnessesFile) ?? defaultGranolaToolkitPersistenceLayout().agentHarnessesFile,
|
|
6260
6439
|
maxRetries: pickNumber(env.GRANOLA_AGENT_MAX_RETRIES) ?? pickNumber(configValues["agent-max-retries"]) ?? pickNumber(configValues.agentMaxRetries) ?? 2,
|
|
6261
6440
|
openaiBaseUrl: pickString(env.GRANOLA_OPENAI_BASE_URL) ?? pickString(env.OPENAI_BASE_URL) ?? pickString(configValues["openai-base-url"]) ?? pickString(configValues.openaiBaseUrl) ?? "https://api.openai.com/v1",
|
|
6262
6441
|
openrouterBaseUrl: pickString(env.GRANOLA_OPENROUTER_BASE_URL) ?? pickString(env.OPENROUTER_BASE_URL) ?? pickString(configValues["openrouter-base-url"]) ?? pickString(configValues.openrouterBaseUrl) ?? "https://openrouter.ai/api/v1",
|