granola-toolkit 0.53.0 → 0.54.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.
Files changed (2) hide show
  1. package/dist/cli.js +128 -3
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -2767,6 +2767,7 @@ function defaultGranolaToolkitPersistenceLayout(options = {}) {
2767
2767
  dataDirectory,
2768
2768
  exportJobsFile: join(dataDirectory, "export-jobs.json"),
2769
2769
  meetingIndexFile: join(dataDirectory, "meeting-index.json"),
2770
+ pkmTargetsFile: join(dataDirectory, "pkm-targets.json"),
2770
2771
  searchIndexFile: join(dataDirectory, "search-index.json"),
2771
2772
  sessionFile: join(dataDirectory, "session.json"),
2772
2773
  sessionStoreKind: targetPlatform === "darwin" ? "keychain" : "file",
@@ -3079,7 +3080,7 @@ function normaliseArtefact(value) {
3079
3080
  updatedAt
3080
3081
  };
3081
3082
  }
3082
- function normaliseFile(parsed) {
3083
+ function normaliseFile$1(parsed) {
3083
3084
  const record = asRecord(parsed);
3084
3085
  if (!record || record.version !== AUTOMATION_ARTEFACTS_VERSION || !Array.isArray(record.artefacts)) return {
3085
3086
  artefacts: [],
@@ -3102,7 +3103,7 @@ var FileAutomationArtefactStore = class {
3102
3103
  }
3103
3104
  async readArtefacts(options = {}) {
3104
3105
  try {
3105
- const filtered = sortArtefacts(normaliseFile(parseJsonString(await readFile(this.filePath, "utf8"))).artefacts).filter((artefact) => {
3106
+ const filtered = sortArtefacts(normaliseFile$1(parseJsonString(await readFile(this.filePath, "utf8"))).artefacts).filter((artefact) => {
3106
3107
  if (options.kind && artefact.kind !== options.kind) return false;
3107
3108
  if (options.meetingId && artefact.meetingId !== options.meetingId) return false;
3108
3109
  if (options.status && artefact.status !== options.status) return false;
@@ -3757,6 +3758,7 @@ function cloneAction$1(action) {
3757
3758
  };
3758
3759
  case "export-notes":
3759
3760
  case "export-transcript": return { ...action };
3761
+ case "pkm-sync": return { ...action };
3760
3762
  case "slack-message": return { ...action };
3761
3763
  case "webhook": return {
3762
3764
  ...action,
@@ -3771,6 +3773,7 @@ function automationActionName(action) {
3771
3773
  function automationActionTrigger(action) {
3772
3774
  switch (action.kind) {
3773
3775
  case "command":
3776
+ case "pkm-sync":
3774
3777
  case "slack-message":
3775
3778
  case "webhook":
3776
3779
  case "write-file": return action.trigger ?? "match";
@@ -3788,6 +3791,7 @@ function enabledAutomationActions(rule, options = {}) {
3788
3791
  if (options.trigger !== "approval") return true;
3789
3792
  switch (action.kind) {
3790
3793
  case "command":
3794
+ case "pkm-sync":
3791
3795
  case "slack-message":
3792
3796
  case "webhook":
3793
3797
  case "write-file": return !options.sourceActionId || action.sourceActionId === options.sourceActionId;
@@ -3809,7 +3813,7 @@ function baseRun(match, rule, action, startedAt, context, options = {}) {
3809
3813
  matchedAt: match.matchedAt,
3810
3814
  meetingId: match.meetingId,
3811
3815
  meta: {
3812
- sourceActionId: action.kind === "command" || action.kind === "slack-message" || action.kind === "webhook" || action.kind === "write-file" ? action.sourceActionId : void 0,
3816
+ sourceActionId: action.kind === "command" || action.kind === "pkm-sync" || action.kind === "slack-message" || action.kind === "webhook" || action.kind === "write-file" ? action.sourceActionId : void 0,
3813
3817
  trigger: context.trigger
3814
3818
  },
3815
3819
  ruleId: rule.id,
@@ -3934,6 +3938,19 @@ async function executeAutomationAction(match, rule, action, handlers, options =
3934
3938
  } catch (error) {
3935
3939
  return failedRun(run, handlers.nowIso(), error);
3936
3940
  }
3941
+ case "pkm-sync": try {
3942
+ const result = await handlers.runPkmSync(match, rule, action, context);
3943
+ return completedRun(run, handlers.nowIso(), {
3944
+ meta: {
3945
+ ...run.meta ? structuredClone(run.meta) : {},
3946
+ filePath: result.filePath,
3947
+ targetId: result.targetId
3948
+ },
3949
+ result: `Synced PKM target ${result.targetId} to ${result.filePath}`
3950
+ });
3951
+ } catch (error) {
3952
+ return failedRun(run, handlers.nowIso(), error);
3953
+ }
3937
3954
  case "webhook": try {
3938
3955
  const result = await handlers.runWebhook(match, rule, action, context);
3939
3956
  return completedRun(run, handlers.nowIso(), {
@@ -4196,6 +4213,7 @@ function cloneAction(action) {
4196
4213
  };
4197
4214
  case "export-notes":
4198
4215
  case "export-transcript": return { ...action };
4216
+ case "pkm-sync": return { ...action };
4199
4217
  case "slack-message": return { ...action };
4200
4218
  case "webhook": return {
4201
4219
  ...action,
@@ -4316,6 +4334,19 @@ function parseAction(value, index) {
4316
4334
  outputDir: typeof record.outputDir === "string" && record.outputDir.trim() ? record.outputDir.trim() : void 0,
4317
4335
  scopedOutput: typeof record.scopedOutput === "boolean" ? record.scopedOutput : void 0
4318
4336
  };
4337
+ case "pkm-sync": {
4338
+ const targetId = typeof record.targetId === "string" && record.targetId.trim() ? record.targetId.trim() : void 0;
4339
+ if (!id || !targetId) return;
4340
+ return {
4341
+ enabled,
4342
+ id,
4343
+ kind,
4344
+ name,
4345
+ sourceActionId: typeof record.sourceActionId === "string" && record.sourceActionId.trim() ? record.sourceActionId.trim() : void 0,
4346
+ targetId,
4347
+ trigger: parseTrigger(record.trigger)
4348
+ };
4349
+ }
4319
4350
  case "slack-message":
4320
4351
  if (!id) return;
4321
4352
  return {
@@ -5303,6 +5334,55 @@ function createDefaultMeetingIndexStore() {
5303
5334
  return new FileMeetingIndexStore();
5304
5335
  }
5305
5336
  //#endregion
5337
+ //#region src/pkm-targets.ts
5338
+ function cloneTarget(target) {
5339
+ return { ...target };
5340
+ }
5341
+ function normaliseTarget(value) {
5342
+ const record = asRecord(value);
5343
+ if (!record) return;
5344
+ const id = stringValue(record.id).trim();
5345
+ const outputDir = stringValue(record.outputDir).trim();
5346
+ const kind = stringValue(record.kind).trim();
5347
+ if (!id || !outputDir || kind !== "docs-folder" && kind !== "obsidian") return;
5348
+ return {
5349
+ filenameTemplate: stringValue(record.filenameTemplate).trim() || void 0,
5350
+ folderSubdirectories: typeof record.folderSubdirectories === "boolean" ? record.folderSubdirectories : void 0,
5351
+ frontmatter: typeof record.frontmatter === "boolean" ? record.frontmatter : void 0,
5352
+ id,
5353
+ kind,
5354
+ name: stringValue(record.name).trim() || void 0,
5355
+ outputDir
5356
+ };
5357
+ }
5358
+ function normaliseFile(parsed) {
5359
+ const record = asRecord(parsed);
5360
+ if (!record || !Array.isArray(record.targets)) return { targets: [] };
5361
+ return { targets: record.targets.map((target) => normaliseTarget(target)).filter((target) => Boolean(target)) };
5362
+ }
5363
+ var FilePkmTargetStore = class {
5364
+ constructor(filePath = defaultPkmTargetsFilePath()) {
5365
+ this.filePath = filePath;
5366
+ }
5367
+ async readTargets() {
5368
+ try {
5369
+ return normaliseFile(parseJsonString(await readFile(this.filePath, "utf8"))).targets.map((target) => cloneTarget(target));
5370
+ } catch {
5371
+ return [];
5372
+ }
5373
+ }
5374
+ async writeTargets(targets) {
5375
+ await mkdir(dirname(this.filePath), { recursive: true });
5376
+ await writeFile(this.filePath, `${JSON.stringify({ targets }, null, 2)}\n`, "utf8");
5377
+ }
5378
+ };
5379
+ function defaultPkmTargetsFilePath() {
5380
+ return defaultGranolaToolkitPersistenceLayout().pkmTargetsFile;
5381
+ }
5382
+ function createDefaultPkmTargetStore(filePath) {
5383
+ return new FilePkmTargetStore(filePath);
5384
+ }
5385
+ //#endregion
5306
5386
  //#region src/sync-state.ts
5307
5387
  const SYNC_STATE_VERSION = 1;
5308
5388
  const MAX_STORED_CHANGES = 50;
@@ -6264,6 +6344,7 @@ var GranolaApp = class {
6264
6344
  };
6265
6345
  case "export-notes":
6266
6346
  case "export-transcript": return { ...action };
6347
+ case "pkm-sync": return { ...action };
6267
6348
  case "slack-message": return { ...action };
6268
6349
  case "webhook": return {
6269
6350
  ...action,
@@ -6401,6 +6482,7 @@ var GranolaApp = class {
6401
6482
  runCommand: async (nextMatch, nextRule, nextAction, context) => await this.runAutomationCommand(nextMatch, nextRule, nextAction, context),
6402
6483
  runSlackMessage: async (nextMatch, nextRule, nextAction, context) => await this.runAutomationSlackMessage(nextMatch, nextRule, nextAction, context),
6403
6484
  runWebhook: async (nextMatch, nextRule, nextAction, context) => await this.runAutomationWebhook(nextMatch, nextRule, nextAction, context),
6485
+ runPkmSync: async (nextMatch, nextRule, nextAction, context) => await this.runAutomationPkmSync(nextMatch, nextRule, nextAction, context),
6404
6486
  writeFile: async (nextMatch, nextRule, nextAction, context) => await this.runAutomationWriteFile(nextMatch, nextRule, nextAction, context)
6405
6487
  };
6406
6488
  }
@@ -7170,6 +7252,46 @@ var GranolaApp = class {
7170
7252
  format: action.format ?? "markdown"
7171
7253
  };
7172
7254
  }
7255
+ async readPkmTargets() {
7256
+ if (!this.deps.pkmTargetStore) return [];
7257
+ return (await this.deps.pkmTargetStore.readTargets()).map((target) => ({ ...target }));
7258
+ }
7259
+ pkmFrontmatterEnabled(target) {
7260
+ return target.frontmatter ?? target.kind === "obsidian";
7261
+ }
7262
+ buildPkmFrontmatter(target, artefact, match) {
7263
+ if (!this.pkmFrontmatterEnabled(target)) return "";
7264
+ return [
7265
+ "---",
7266
+ `title: ${quoteYamlString(artefact.structured.title)}`,
7267
+ `meetingId: ${quoteYamlString(match.meetingId)}`,
7268
+ `artefactId: ${quoteYamlString(artefact.id)}`,
7269
+ `artefactKind: ${quoteYamlString(artefact.kind)}`,
7270
+ `ruleId: ${quoteYamlString(artefact.ruleId)}`,
7271
+ `sourceActionId: ${quoteYamlString(artefact.actionId)}`,
7272
+ `provider: ${quoteYamlString(artefact.provider)}`,
7273
+ `model: ${quoteYamlString(artefact.model)}`,
7274
+ "tags:",
7275
+ ...match.tags.map((tag) => ` - ${quoteYamlString(tag)}`),
7276
+ "folders:",
7277
+ ...match.folders.map((folder) => ` - ${quoteYamlString(folder.name)}`),
7278
+ "---",
7279
+ ""
7280
+ ].join("\n");
7281
+ }
7282
+ async runAutomationPkmSync(match, rule, action, context) {
7283
+ if (!context.artefact) throw new Error(`automation PKM sync action ${action.id} requires an artefact`);
7284
+ const target = (await this.readPkmTargets()).find((candidate) => candidate.id === action.targetId);
7285
+ if (!target) throw new Error(`automation PKM target not found: ${action.targetId}`);
7286
+ const meetingTitle = match.title || context.artefact.structured.title;
7287
+ const folderName = match.folders[0]?.name;
7288
+ const filePath = join(target.folderSubdirectories && folderName ? join(target.outputDir, sanitiseFilename(folderName, "folder")) : target.outputDir, sanitiseFilename(target.filenameTemplate?.trim() ? target.filenameTemplate.replaceAll("{{meeting.title}}", meetingTitle).replaceAll("{{artefact.kind}}", context.artefact.kind).replaceAll("{{artefact.title}}", context.artefact.structured.title) : `${sanitiseFilename(`${meetingTitle}-${context.artefact.kind}`)}.md`, "meeting.md"));
7289
+ await writeTextFile(filePath, `${this.buildPkmFrontmatter(target, context.artefact, match)}${context.artefact.structured.markdown.trim()}\n`);
7290
+ return {
7291
+ filePath,
7292
+ targetId: target.id
7293
+ };
7294
+ }
7173
7295
  async buildAutomationAgentAttempt(match, rule, action, bundle, harness) {
7174
7296
  const harnessCwd = harness?.cwd;
7175
7297
  const promptFile = await readOptionalActionFile(action.promptFile, action.cwd ?? harnessCwd);
@@ -7734,6 +7856,7 @@ async function createGranolaApp(config, options = {}) {
7734
7856
  const exportJobs = await exportJobStore.readJobs();
7735
7857
  const meetingIndexStore = createDefaultMeetingIndexStore();
7736
7858
  const meetingIndex = await meetingIndexStore.readIndex();
7859
+ const pkmTargetStore = createDefaultPkmTargetStore(config.automation?.pkmTargetsFile);
7737
7860
  const searchIndexStore = createDefaultSearchIndexStore();
7738
7861
  const searchIndex = await searchIndexStore.readIndex();
7739
7862
  const syncEventStore = createDefaultSyncEventStore();
@@ -7759,6 +7882,7 @@ async function createGranolaApp(config, options = {}) {
7759
7882
  meetingIndex,
7760
7883
  meetingIndexStore,
7761
7884
  now: options.now,
7885
+ pkmTargetStore,
7762
7886
  searchIndex,
7763
7887
  searchIndexStore,
7764
7888
  syncEventStore,
@@ -7835,6 +7959,7 @@ async function loadConfig(options) {
7835
7959
  return {
7836
7960
  automation: {
7837
7961
  artefactsFile: pickString(env.GRANOLA_AUTOMATION_ARTEFACTS_FILE) ?? pickString(configValues["automation-artefacts-file"]) ?? pickString(configValues.automationArtefactsFile) ?? defaultGranolaToolkitPersistenceLayout().automationArtefactsFile,
7962
+ pkmTargetsFile: pickString(env.GRANOLA_PKM_TARGETS_FILE) ?? pickString(configValues["pkm-targets-file"]) ?? pickString(configValues.pkmTargetsFile) ?? defaultGranolaToolkitPersistenceLayout().pkmTargetsFile,
7838
7963
  rulesFile: pickString(options.globalFlags.rules) ?? pickString(env.GRANOLA_AUTOMATION_RULES_FILE) ?? pickString(configValues["automation-rules-file"]) ?? pickString(configValues.automationRulesFile) ?? defaultGranolaToolkitPersistenceLayout().automationRulesFile
7839
7964
  },
7840
7965
  agents: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "granola-toolkit",
3
- "version": "0.53.0",
3
+ "version": "0.54.0",
4
4
  "description": "Toolkit for exporting and working with Granola meetings, notes, and transcripts",
5
5
  "keywords": [
6
6
  "cli",