@marlin-notes/scheduler-cli 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/dist/index.js CHANGED
@@ -29,6 +29,7 @@ var import_commander = require("commander");
29
29
  // src/summarizer.ts
30
30
  var import_fs = __toESM(require("fs"));
31
31
  var import_path = __toESM(require("path"));
32
+ var import_child_process = require("child_process");
32
33
  var import_gray_matter = __toESM(require("gray-matter"));
33
34
  var import_date_fns = require("date-fns");
34
35
 
@@ -70,7 +71,9 @@ async function summarize({
70
71
  frequency,
71
72
  tags,
72
73
  apiUrl,
73
- userId
74
+ userId,
75
+ baseDir,
76
+ push
74
77
  }) {
75
78
  const now = /* @__PURE__ */ new Date();
76
79
  let startDate;
@@ -84,8 +87,14 @@ async function summarize({
84
87
  console.log(`[Scheduler] Running '${taskName}' (${frequency})`);
85
88
  console.log(`[Scheduler] Filtering from: ${startDate.toISOString()}`);
86
89
  console.log(`[Scheduler] Tags: ${tags.join(", ") || "(none)"}`);
87
- const notesDir = process.cwd();
88
- const files = import_fs.default.readdirSync(notesDir).filter((f) => f.endsWith(".md"));
90
+ const notesDir = baseDir ? import_path.default.resolve(process.cwd(), baseDir) : process.cwd();
91
+ console.log(`[Scheduler] Searching in: ${notesDir}`);
92
+ if (!import_fs.default.existsSync(notesDir)) {
93
+ throw new Error(`Directory not found: ${notesDir}`);
94
+ }
95
+ const files = import_fs.default.readdirSync(notesDir, { recursive: true }).map((f) => String(f)).filter(
96
+ (f) => f.endsWith(".md") && !f.includes("node_modules") && !f.includes(".git")
97
+ );
89
98
  const matchedNotes = [];
90
99
  for (const file of files) {
91
100
  const filePath = import_path.default.join(notesDir, file);
@@ -168,28 +177,60 @@ async function summarize({
168
177
  const summaryFilename = `summary-${frequency}-${now.toISOString().split("T")[0]}.md`;
169
178
  const summaryContent = `---
170
179
  date: ${now.getTime()}
171
- tags: ["#summary/${frequency}", "#auto"]
180
+ tags: ["summary", "${taskName}", "auto"]
172
181
  title: ${frequency} Summary - ${taskName}
182
+ frequency: ${frequency}
173
183
  ---
174
184
 
175
185
  ${result.summary}
176
186
  `;
177
187
  import_fs.default.writeFileSync(import_path.default.join(notesDir, summaryFilename), summaryContent);
178
188
  console.log(`[Scheduler] Summary saved to ${summaryFilename}`);
189
+ if (push) {
190
+ try {
191
+ console.log("[Scheduler] Committing and pushing changes...");
192
+ try {
193
+ (0, import_child_process.execSync)("git config user.name", { stdio: "ignore" });
194
+ } catch {
195
+ console.log("[Scheduler] Configuring git user...");
196
+ (0, import_child_process.execSync)('git config user.name "Marlin Scheduler"');
197
+ (0, import_child_process.execSync)('git config user.email "scheduler@marlin.app"');
198
+ }
199
+ const relativePath = import_path.default.relative(
200
+ process.cwd(),
201
+ import_path.default.join(notesDir, summaryFilename)
202
+ );
203
+ (0, import_child_process.execSync)(`git add "${relativePath}"`);
204
+ (0, import_child_process.execSync)(
205
+ `git commit -m "chore(scheduler): add ${frequency} summary for ${taskName}"`
206
+ );
207
+ (0, import_child_process.execSync)("git push");
208
+ console.log("[Scheduler] Successfully pushed to remote.");
209
+ } catch (error) {
210
+ console.error("[Scheduler] Failed to push changes:", error);
211
+ }
212
+ }
179
213
  }
180
214
 
181
215
  // src/index.ts
182
216
  var program = new import_commander.Command();
183
- program.name("marlin-scheduler").description("CLI for Marlin scheduled tasks").version("0.0.1");
184
- program.command("summarize").description("Run summarization task").option("--task-name <name>", "Name of the task").option("--frequency <freq>", "Frequency (weekly/monthly)").option("--tags <json>", "JSON string of tags").option("--api-url <url>", "Marlin API URL").option("--user-id <id>", "GitHub User ID").action(async (options) => {
217
+ program.name("marlin-scheduler").description("CLI for Marlin scheduled tasks").version("0.0.5");
218
+ program.command("summarize").description("Run summarization task").option("--task-name <name>", "Name of the task").option("--frequency <freq>", "Frequency (weekly/monthly)").option("--tags <json>", "JSON string of tags").option("--api-url <url>", "Marlin API URL").option("--user-id <id>", "GitHub User ID").option(
219
+ "--dir <path>",
220
+ "Directory to search for notes (default: current dir)"
221
+ ).option("--push", "Commit and push the summary to the git repository").action(async (options) => {
185
222
  try {
186
223
  const taskName = options.taskName || process.env.INPUT_TASK_NAME || "Manual Task";
187
224
  const frequency = options.frequency || process.env.INPUT_FREQUENCY || "weekly";
188
225
  const tagsJson = options.tags || process.env.INPUT_TAGS || "[]";
189
226
  const apiUrl = options.apiUrl || process.env.INPUT_API_URL || "https://marlinnotes.com/api/ai/summarize";
190
227
  const userId = options.userId || process.env.INPUT_USER_ID || process.env.GITHUB_ACTOR_ID;
228
+ const baseDir = options.dir || process.env.INPUT_DIR;
229
+ const push = options.push || process.env.INPUT_PUSH === "true";
191
230
  if (!userId) {
192
- console.error("Error: User ID is required. Pass via --user-id, set INPUT_USER_ID, or run in GitHub Actions (GITHUB_ACTOR_ID).");
231
+ console.error(
232
+ "Error: User ID is required. Pass via --user-id, set INPUT_USER_ID, or run in GitHub Actions (GITHUB_ACTOR_ID)."
233
+ );
193
234
  process.exit(1);
194
235
  }
195
236
  let tags = [];
@@ -203,7 +244,9 @@ program.command("summarize").description("Run summarization task").option("--tas
203
244
  frequency,
204
245
  tags,
205
246
  apiUrl,
206
- userId
247
+ userId,
248
+ baseDir,
249
+ push
207
250
  });
208
251
  } catch (error) {
209
252
  console.error("Failed:", error);
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/summarizer.ts","../src/oidc.ts"],"sourcesContent":["import { Command } from \"commander\";\nimport { summarize } from \"./summarizer\";\n\nconst program = new Command();\n\nprogram\n .name(\"marlin-scheduler\")\n .description(\"CLI for Marlin scheduled tasks\")\n .version(\"0.0.1\");\n\nprogram\n .command(\"summarize\")\n .description(\"Run summarization task\")\n .option(\"--task-name <name>\", \"Name of the task\")\n .option(\"--frequency <freq>\", \"Frequency (weekly/monthly)\")\n .option(\"--tags <json>\", \"JSON string of tags\")\n .option(\"--api-url <url>\", \"Marlin API URL\")\n .option(\"--user-id <id>\", \"GitHub User ID\")\n .action(async (options) => {\n try {\n // Prioritize flags, then Env Vars (standard Action inputs often use INPUT_ prefix)\n const taskName = options.taskName || process.env.INPUT_TASK_NAME || \"Manual Task\";\n const frequency = options.frequency || process.env.INPUT_FREQUENCY || \"weekly\";\n const tagsJson = options.tags || process.env.INPUT_TAGS || \"[]\";\n // Default to production API if not specified\n const apiUrl = options.apiUrl || process.env.INPUT_API_URL || \"https://marlinnotes.com/api/ai/summarize\";\n \n // Resolve User ID: Flag -> Input -> GitHub Actor ID\n const userId = options.userId || process.env.INPUT_USER_ID || process.env.GITHUB_ACTOR_ID;\n\n if (!userId) {\n console.error(\"Error: User ID is required. Pass via --user-id, set INPUT_USER_ID, or run in GitHub Actions (GITHUB_ACTOR_ID).\");\n process.exit(1);\n }\n\n let tags: string[] = [];\n try {\n tags = JSON.parse(tagsJson);\n } catch (e) {\n console.warn(\"Failed to parse tags JSON, assuming empty array:\", e);\n }\n\n await summarize({\n taskName,\n frequency: frequency as \"weekly\" | \"monthly\",\n tags,\n apiUrl,\n userId,\n });\n } catch (error) {\n console.error(\"Failed:\", error);\n process.exit(1);\n }\n });\n\nprogram.parse();\n","import fs from \"fs\";\nimport path from \"path\";\nimport matter from \"gray-matter\";\nimport { subDays, subMonths, isBefore, isAfter } from \"date-fns\";\nimport { getGithubOidcToken } from \"./oidc\";\n\ninterface SummarizeOptions {\n taskName: string;\n frequency: \"weekly\" | \"monthly\";\n tags: string[];\n apiUrl: string;\n userId: string;\n}\n\nexport async function summarize({\n taskName,\n frequency,\n tags,\n apiUrl,\n userId,\n}: SummarizeOptions) {\n // ... (existing date logic unchanged) ...\n const now = new Date();\n let startDate: Date;\n\n if (frequency === \"weekly\") {\n startDate = subDays(now, 7);\n } else if (frequency === \"monthly\") {\n startDate = subMonths(now, 1);\n } else {\n throw new Error(`Unknown frequency: ${frequency}`);\n }\n\n console.log(`[Scheduler] Running '${taskName}' (${frequency})`);\n console.log(`[Scheduler] Filtering from: ${startDate.toISOString()}`);\n console.log(`[Scheduler] Tags: ${tags.join(\", \") || \"(none)\"}`);\n\n const notesDir = process.cwd(); // Run in current dir\n const files = fs.readdirSync(notesDir).filter((f) => f.endsWith(\".md\"));\n const matchedNotes = [];\n\n for (const file of files) {\n const filePath = path.join(notesDir, file);\n const content = fs.readFileSync(filePath, \"utf-8\");\n const { data, content: body } = matter(content);\n\n // 1. Date Check\n let noteDate: Date | null = null;\n\n // Check fields in priority: date -> updatedAt -> createdAt\n const dateFields = [data.date, data.updatedAt, data.createdAt];\n\n for (const field of dateFields) {\n if (!field) continue;\n\n if (field instanceof Date) {\n noteDate = field;\n } else {\n const d = new Date(field);\n if (!isNaN(d.getTime())) {\n noteDate = d;\n }\n }\n\n if (noteDate) break;\n }\n\n // Fallback to filename timestamp if no valid date found in frontmatter\n if (!noteDate) {\n const filenameTs = parseInt(file.replace(\".md\", \"\"));\n if (!isNaN(filenameTs) && filenameTs > 1000000000000) {\n noteDate = new Date(filenameTs);\n }\n }\n\n if (!noteDate || isNaN(noteDate.getTime())) continue;\n\n if (isBefore(noteDate, startDate) || isAfter(noteDate, now)) continue;\n\n // 2. Tag Check\n const noteTags: string[] = Array.isArray(data.tags) ? data.tags : [];\n const normalizedNoteTags = noteTags.map((t) =>\n String(t).replace(/^#/, \"\").toLowerCase()\n );\n const normalizedSearchTags = tags.map((t) =>\n t.replace(/^#/, \"\").toLowerCase()\n );\n\n const hasAllTags = normalizedSearchTags.every((t) =>\n normalizedNoteTags.includes(t)\n );\n\n if (hasAllTags || tags.length === 0) {\n matchedNotes.push({\n title: data.title || file.replace(\".md\", \"\"),\n content: body,\n date: noteDate.toISOString(),\n tags: normalizedNoteTags,\n });\n }\n }\n\n console.log(`[Scheduler] Found ${matchedNotes.length} matching notes.`);\n\n if (matchedNotes.length === 0) {\n console.log(\"No notes to summarize.\");\n return;\n }\n\n // 3. API Call\n console.log(`[Scheduler] Sending to API: ${apiUrl}`);\n\n // Try to get OIDC token\n const oidcToken = await getGithubOidcToken(\"marlin-api\");\n const headers: Record<string, string> = {\n \"Content-Type\": \"application/json\",\n \"X-Marlin-User-Id\": userId,\n };\n\n if (oidcToken) {\n console.log(\"[Scheduler] Authenticating with GitHub OIDC\");\n headers[\"Authorization\"] = `Bearer ${oidcToken}`;\n } else {\n // Fallback or Error\n // We allow running without OIDC if strictly testing, but warn loudly.\n // In production, the API will likely reject it.\n console.warn(\n \"[Scheduler] WARNING: No OIDC token found. Ensure 'id-token: write' permission is set in the workflow.\"\n );\n }\n\n const response = await fetch(apiUrl, {\n method: \"POST\",\n headers,\n body: JSON.stringify({\n notes: matchedNotes,\n taskName,\n period: frequency,\n }),\n });\n\n if (!response.ok) {\n const errText = await response.text();\n throw new Error(`API Error ${response.status}: ${errText}`);\n }\n\n const result = (await response.json()) as { summary: string };\n\n // 4. Save Summary\n const summaryFilename = `summary-${frequency}-${now.toISOString().split(\"T\")[0]}.md`;\n\n const summaryContent = `---\ndate: ${now.getTime()}\ntags: [\"#summary/${frequency}\", \"#auto\"]\ntitle: ${frequency} Summary - ${taskName}\n---\n\n${result.summary}\n`;\n\n fs.writeFileSync(path.join(notesDir, summaryFilename), summaryContent);\n console.log(`[Scheduler] Summary saved to ${summaryFilename}`);\n}\n","export async function getGithubOidcToken(audience?: string): Promise<string | null> {\n const requestUrl = process.env.ACTIONS_ID_TOKEN_REQUEST_URL;\n const requestToken = process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN;\n\n if (!requestUrl || !requestToken) {\n // Not running in GitHub Actions or permissions not set\n return null;\n }\n\n try {\n const url = new URL(requestUrl);\n if (audience) {\n url.searchParams.append(\"audience\", audience);\n }\n\n const response = await fetch(url.toString(), {\n headers: {\n Authorization: `Bearer ${requestToken}`,\n Accept: \"application/json; api-version=2.0\",\n \"Content-Type\": \"application/json\",\n },\n });\n\n if (!response.ok) {\n const text = await response.text();\n console.warn(`[OIDC] Failed to fetch token: ${response.status} ${text}`);\n return null;\n }\n\n const data = (await response.json()) as { value: string };\n return data.value;\n } catch (error) {\n console.warn(\"[OIDC] Error fetching token:\", error);\n return null;\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,uBAAwB;;;ACAxB,gBAAe;AACf,kBAAiB;AACjB,yBAAmB;AACnB,sBAAsD;;;ACHtD,eAAsB,mBAAmB,UAA2C;AAClF,QAAM,aAAa,QAAQ,IAAI;AAC/B,QAAM,eAAe,QAAQ,IAAI;AAEjC,MAAI,CAAC,cAAc,CAAC,cAAc;AAEhC,WAAO;AAAA,EACT;AAEA,MAAI;AACF,UAAM,MAAM,IAAI,IAAI,UAAU;AAC9B,QAAI,UAAU;AACZ,UAAI,aAAa,OAAO,YAAY,QAAQ;AAAA,IAC9C;AAEA,UAAM,WAAW,MAAM,MAAM,IAAI,SAAS,GAAG;AAAA,MAC3C,SAAS;AAAA,QACP,eAAe,UAAU,YAAY;AAAA,QACrC,QAAQ;AAAA,QACR,gBAAgB;AAAA,MAClB;AAAA,IACF,CAAC;AAED,QAAI,CAAC,SAAS,IAAI;AAChB,YAAM,OAAO,MAAM,SAAS,KAAK;AACjC,cAAQ,KAAK,iCAAiC,SAAS,MAAM,IAAI,IAAI,EAAE;AACvE,aAAO;AAAA,IACT;AAEA,UAAM,OAAQ,MAAM,SAAS,KAAK;AAClC,WAAO,KAAK;AAAA,EACd,SAAS,OAAO;AACd,YAAQ,KAAK,gCAAgC,KAAK;AAClD,WAAO;AAAA,EACT;AACF;;;ADrBA,eAAsB,UAAU;AAAA,EAC9B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAAqB;AAEnB,QAAM,MAAM,oBAAI,KAAK;AACrB,MAAI;AAEJ,MAAI,cAAc,UAAU;AAC1B,oBAAY,yBAAQ,KAAK,CAAC;AAAA,EAC5B,WAAW,cAAc,WAAW;AAClC,oBAAY,2BAAU,KAAK,CAAC;AAAA,EAC9B,OAAO;AACL,UAAM,IAAI,MAAM,sBAAsB,SAAS,EAAE;AAAA,EACnD;AAEA,UAAQ,IAAI,wBAAwB,QAAQ,MAAM,SAAS,GAAG;AAC9D,UAAQ,IAAI,+BAA+B,UAAU,YAAY,CAAC,EAAE;AACpE,UAAQ,IAAI,qBAAqB,KAAK,KAAK,IAAI,KAAK,QAAQ,EAAE;AAE9D,QAAM,WAAW,QAAQ,IAAI;AAC7B,QAAM,QAAQ,UAAAA,QAAG,YAAY,QAAQ,EAAE,OAAO,CAAC,MAAM,EAAE,SAAS,KAAK,CAAC;AACtE,QAAM,eAAe,CAAC;AAEtB,aAAW,QAAQ,OAAO;AACxB,UAAM,WAAW,YAAAC,QAAK,KAAK,UAAU,IAAI;AACzC,UAAM,UAAU,UAAAD,QAAG,aAAa,UAAU,OAAO;AACjD,UAAM,EAAE,MAAM,SAAS,KAAK,QAAI,mBAAAE,SAAO,OAAO;AAG9C,QAAI,WAAwB;AAG5B,UAAM,aAAa,CAAC,KAAK,MAAM,KAAK,WAAW,KAAK,SAAS;AAE7D,eAAW,SAAS,YAAY;AAC9B,UAAI,CAAC,MAAO;AAEZ,UAAI,iBAAiB,MAAM;AACzB,mBAAW;AAAA,MACb,OAAO;AACL,cAAM,IAAI,IAAI,KAAK,KAAK;AACxB,YAAI,CAAC,MAAM,EAAE,QAAQ,CAAC,GAAG;AACvB,qBAAW;AAAA,QACb;AAAA,MACF;AAEA,UAAI,SAAU;AAAA,IAChB;AAGA,QAAI,CAAC,UAAU;AACb,YAAM,aAAa,SAAS,KAAK,QAAQ,OAAO,EAAE,CAAC;AACnD,UAAI,CAAC,MAAM,UAAU,KAAK,aAAa,MAAe;AACpD,mBAAW,IAAI,KAAK,UAAU;AAAA,MAChC;AAAA,IACF;AAEA,QAAI,CAAC,YAAY,MAAM,SAAS,QAAQ,CAAC,EAAG;AAE5C,YAAI,0BAAS,UAAU,SAAS,SAAK,yBAAQ,UAAU,GAAG,EAAG;AAG7D,UAAM,WAAqB,MAAM,QAAQ,KAAK,IAAI,IAAI,KAAK,OAAO,CAAC;AACnE,UAAM,qBAAqB,SAAS;AAAA,MAAI,CAAC,MACvC,OAAO,CAAC,EAAE,QAAQ,MAAM,EAAE,EAAE,YAAY;AAAA,IAC1C;AACA,UAAM,uBAAuB,KAAK;AAAA,MAAI,CAAC,MACrC,EAAE,QAAQ,MAAM,EAAE,EAAE,YAAY;AAAA,IAClC;AAEA,UAAM,aAAa,qBAAqB;AAAA,MAAM,CAAC,MAC7C,mBAAmB,SAAS,CAAC;AAAA,IAC/B;AAEA,QAAI,cAAc,KAAK,WAAW,GAAG;AACnC,mBAAa,KAAK;AAAA,QAChB,OAAO,KAAK,SAAS,KAAK,QAAQ,OAAO,EAAE;AAAA,QAC3C,SAAS;AAAA,QACT,MAAM,SAAS,YAAY;AAAA,QAC3B,MAAM;AAAA,MACR,CAAC;AAAA,IACH;AAAA,EACF;AAEA,UAAQ,IAAI,qBAAqB,aAAa,MAAM,kBAAkB;AAEtE,MAAI,aAAa,WAAW,GAAG;AAC7B,YAAQ,IAAI,wBAAwB;AACpC;AAAA,EACF;AAGA,UAAQ,IAAI,+BAA+B,MAAM,EAAE;AAGnD,QAAM,YAAY,MAAM,mBAAmB,YAAY;AACvD,QAAM,UAAkC;AAAA,IACtC,gBAAgB;AAAA,IAChB,oBAAoB;AAAA,EACtB;AAEA,MAAI,WAAW;AACb,YAAQ,IAAI,6CAA6C;AACzD,YAAQ,eAAe,IAAI,UAAU,SAAS;AAAA,EAChD,OAAO;AAIL,YAAQ;AAAA,MACN;AAAA,IACF;AAAA,EACF;AAEA,QAAM,WAAW,MAAM,MAAM,QAAQ;AAAA,IACnC,QAAQ;AAAA,IACR;AAAA,IACA,MAAM,KAAK,UAAU;AAAA,MACnB,OAAO;AAAA,MACP;AAAA,MACA,QAAQ;AAAA,IACV,CAAC;AAAA,EACH,CAAC;AAED,MAAI,CAAC,SAAS,IAAI;AAChB,UAAM,UAAU,MAAM,SAAS,KAAK;AACpC,UAAM,IAAI,MAAM,aAAa,SAAS,MAAM,KAAK,OAAO,EAAE;AAAA,EAC5D;AAEA,QAAM,SAAU,MAAM,SAAS,KAAK;AAGpC,QAAM,kBAAkB,WAAW,SAAS,IAAI,IAAI,YAAY,EAAE,MAAM,GAAG,EAAE,CAAC,CAAC;AAE/E,QAAM,iBAAiB;AAAA,QACjB,IAAI,QAAQ,CAAC;AAAA,mBACF,SAAS;AAAA,SACnB,SAAS,cAAc,QAAQ;AAAA;AAAA;AAAA,EAGtC,OAAO,OAAO;AAAA;AAGd,YAAAF,QAAG,cAAc,YAAAC,QAAK,KAAK,UAAU,eAAe,GAAG,cAAc;AACrE,UAAQ,IAAI,gCAAgC,eAAe,EAAE;AAC/D;;;AD/JA,IAAM,UAAU,IAAI,yBAAQ;AAE5B,QACG,KAAK,kBAAkB,EACvB,YAAY,gCAAgC,EAC5C,QAAQ,OAAO;AAElB,QACG,QAAQ,WAAW,EACnB,YAAY,wBAAwB,EACpC,OAAO,sBAAsB,kBAAkB,EAC/C,OAAO,sBAAsB,4BAA4B,EACzD,OAAO,iBAAiB,qBAAqB,EAC7C,OAAO,mBAAmB,gBAAgB,EAC1C,OAAO,kBAAkB,gBAAgB,EACzC,OAAO,OAAO,YAAY;AACzB,MAAI;AAEF,UAAM,WAAW,QAAQ,YAAY,QAAQ,IAAI,mBAAmB;AACpE,UAAM,YAAY,QAAQ,aAAa,QAAQ,IAAI,mBAAmB;AACtE,UAAM,WAAW,QAAQ,QAAQ,QAAQ,IAAI,cAAc;AAE3D,UAAM,SAAS,QAAQ,UAAU,QAAQ,IAAI,iBAAiB;AAG9D,UAAM,SAAS,QAAQ,UAAU,QAAQ,IAAI,iBAAiB,QAAQ,IAAI;AAE1E,QAAI,CAAC,QAAQ;AACX,cAAQ,MAAM,gHAAgH;AAC9H,cAAQ,KAAK,CAAC;AAAA,IAChB;AAEA,QAAI,OAAiB,CAAC;AACtB,QAAI;AACF,aAAO,KAAK,MAAM,QAAQ;AAAA,IAC5B,SAAS,GAAG;AACV,cAAQ,KAAK,oDAAoD,CAAC;AAAA,IACpE;AAEA,UAAM,UAAU;AAAA,MACd;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF,CAAC;AAAA,EACH,SAAS,OAAO;AACd,YAAQ,MAAM,WAAW,KAAK;AAC9B,YAAQ,KAAK,CAAC;AAAA,EAChB;AACF,CAAC;AAEH,QAAQ,MAAM;","names":["fs","path","matter"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/summarizer.ts","../src/oidc.ts"],"sourcesContent":["import { Command } from \"commander\";\nimport { summarize } from \"./summarizer\";\n\nconst program = new Command();\n\nprogram\n .name(\"marlin-scheduler\")\n .description(\"CLI for Marlin scheduled tasks\")\n .version(\"0.0.5\");\n\nprogram\n .command(\"summarize\")\n .description(\"Run summarization task\")\n .option(\"--task-name <name>\", \"Name of the task\")\n .option(\"--frequency <freq>\", \"Frequency (weekly/monthly)\")\n .option(\"--tags <json>\", \"JSON string of tags\")\n .option(\"--api-url <url>\", \"Marlin API URL\")\n .option(\"--user-id <id>\", \"GitHub User ID\")\n .option(\n \"--dir <path>\",\n \"Directory to search for notes (default: current dir)\"\n )\n .option(\"--push\", \"Commit and push the summary to the git repository\")\n .action(async (options) => {\n try {\n // Prioritize flags, then Env Vars (standard Action inputs often use INPUT_ prefix)\n const taskName =\n options.taskName || process.env.INPUT_TASK_NAME || \"Manual Task\";\n const frequency =\n options.frequency || process.env.INPUT_FREQUENCY || \"weekly\";\n const tagsJson = options.tags || process.env.INPUT_TAGS || \"[]\";\n // Default to production API if not specified\n const apiUrl =\n options.apiUrl ||\n process.env.INPUT_API_URL ||\n \"https://marlinnotes.com/api/ai/summarize\";\n\n // Resolve User ID: Flag -> Input -> GitHub Actor ID\n const userId =\n options.userId ||\n process.env.INPUT_USER_ID ||\n process.env.GITHUB_ACTOR_ID;\n const baseDir = options.dir || process.env.INPUT_DIR;\n const push = options.push || process.env.INPUT_PUSH === \"true\";\n\n if (!userId) {\n console.error(\n \"Error: User ID is required. Pass via --user-id, set INPUT_USER_ID, or run in GitHub Actions (GITHUB_ACTOR_ID).\"\n );\n process.exit(1);\n }\n\n let tags: string[] = [];\n try {\n tags = JSON.parse(tagsJson);\n } catch (e) {\n console.warn(\"Failed to parse tags JSON, assuming empty array:\", e);\n }\n\n await summarize({\n taskName,\n frequency: frequency as \"weekly\" | \"monthly\",\n tags,\n apiUrl,\n userId,\n baseDir,\n push,\n });\n } catch (error) {\n console.error(\"Failed:\", error);\n process.exit(1);\n }\n });\n\nprogram.parse();\n","import fs from \"fs\";\nimport path from \"path\";\nimport { execSync } from \"child_process\";\nimport matter from \"gray-matter\";\nimport { subDays, subMonths, isBefore, isAfter } from \"date-fns\";\nimport { getGithubOidcToken } from \"./oidc\";\n\ninterface SummarizeOptions {\n taskName: string;\n frequency: \"weekly\" | \"monthly\";\n tags: string[];\n apiUrl: string;\n userId: string;\n baseDir?: string;\n push?: boolean;\n}\n\nexport async function summarize({\n taskName,\n frequency,\n tags,\n apiUrl,\n userId,\n baseDir,\n push,\n}: SummarizeOptions) {\n // ... (existing date logic unchanged) ...\n const now = new Date();\n let startDate: Date;\n\n if (frequency === \"weekly\") {\n startDate = subDays(now, 7);\n } else if (frequency === \"monthly\") {\n startDate = subMonths(now, 1);\n } else {\n throw new Error(`Unknown frequency: ${frequency}`);\n }\n\n console.log(`[Scheduler] Running '${taskName}' (${frequency})`);\n console.log(`[Scheduler] Filtering from: ${startDate.toISOString()}`);\n console.log(`[Scheduler] Tags: ${tags.join(\", \") || \"(none)\"}`);\n\n const notesDir = baseDir\n ? path.resolve(process.cwd(), baseDir)\n : process.cwd();\n console.log(`[Scheduler] Searching in: ${notesDir}`);\n\n if (!fs.existsSync(notesDir)) {\n throw new Error(`Directory not found: ${notesDir}`);\n }\n\n // Use recursive search if available (Node 20+), otherwise shallow\n // We filter out node_modules and .git to avoid clutter\n const files = fs\n .readdirSync(notesDir, { recursive: true } as any)\n .map((f) => String(f)) // Ensure string\n .filter(\n (f) =>\n f.endsWith(\".md\") && !f.includes(\"node_modules\") && !f.includes(\".git\")\n );\n\n const matchedNotes = [];\n\n for (const file of files) {\n const filePath = path.join(notesDir, file);\n const content = fs.readFileSync(filePath, \"utf-8\");\n const { data, content: body } = matter(content);\n\n // 1. Date Check\n let noteDate: Date | null = null;\n\n // Check fields in priority: date -> updatedAt -> createdAt\n const dateFields = [data.date, data.updatedAt, data.createdAt];\n\n for (const field of dateFields) {\n if (!field) continue;\n\n if (field instanceof Date) {\n noteDate = field;\n } else {\n const d = new Date(field);\n if (!isNaN(d.getTime())) {\n noteDate = d;\n }\n }\n\n if (noteDate) break;\n }\n\n // Fallback to filename timestamp if no valid date found in frontmatter\n if (!noteDate) {\n const filenameTs = parseInt(file.replace(\".md\", \"\"));\n if (!isNaN(filenameTs) && filenameTs > 1000000000000) {\n noteDate = new Date(filenameTs);\n }\n }\n\n if (!noteDate || isNaN(noteDate.getTime())) continue;\n\n if (isBefore(noteDate, startDate) || isAfter(noteDate, now)) continue;\n\n // 2. Tag Check\n const noteTags: string[] = Array.isArray(data.tags) ? data.tags : [];\n const normalizedNoteTags = noteTags.map((t) =>\n String(t).replace(/^#/, \"\").toLowerCase()\n );\n const normalizedSearchTags = tags.map((t) =>\n t.replace(/^#/, \"\").toLowerCase()\n );\n\n const hasAllTags = normalizedSearchTags.every((t) =>\n normalizedNoteTags.includes(t)\n );\n\n if (hasAllTags || tags.length === 0) {\n matchedNotes.push({\n title: data.title || file.replace(\".md\", \"\"),\n content: body,\n date: noteDate.toISOString(),\n tags: normalizedNoteTags,\n });\n }\n }\n\n console.log(`[Scheduler] Found ${matchedNotes.length} matching notes.`);\n\n if (matchedNotes.length === 0) {\n console.log(\"No notes to summarize.\");\n return;\n }\n\n // 3. API Call\n console.log(`[Scheduler] Sending to API: ${apiUrl}`);\n\n // Try to get OIDC token\n const oidcToken = await getGithubOidcToken(\"marlin-api\");\n const headers: Record<string, string> = {\n \"Content-Type\": \"application/json\",\n \"X-Marlin-User-Id\": userId,\n };\n\n if (oidcToken) {\n console.log(\"[Scheduler] Authenticating with GitHub OIDC\");\n headers[\"Authorization\"] = `Bearer ${oidcToken}`;\n } else {\n // Fallback or Error\n // We allow running without OIDC if strictly testing, but warn loudly.\n // In production, the API will likely reject it.\n console.warn(\n \"[Scheduler] WARNING: No OIDC token found. Ensure 'id-token: write' permission is set in the workflow.\"\n );\n }\n\n const response = await fetch(apiUrl, {\n method: \"POST\",\n headers,\n body: JSON.stringify({\n notes: matchedNotes,\n taskName,\n period: frequency,\n }),\n });\n\n if (!response.ok) {\n const errText = await response.text();\n throw new Error(`API Error ${response.status}: ${errText}`);\n }\n\n const result = (await response.json()) as { summary: string };\n\n // 4. Save Summary\n const summaryFilename = `summary-${frequency}-${now.toISOString().split(\"T\")[0]}.md`;\n\n const summaryContent = `---\ndate: ${now.getTime()}\ntags: [\"summary\", \"${taskName}\", \"auto\"]\ntitle: ${frequency} Summary - ${taskName}\nfrequency: ${frequency}\n---\n\n${result.summary}\n`;\n\n fs.writeFileSync(path.join(notesDir, summaryFilename), summaryContent);\n console.log(`[Scheduler] Summary saved to ${summaryFilename}`);\n\n if (push) {\n try {\n console.log(\"[Scheduler] Committing and pushing changes...\");\n try {\n execSync(\"git config user.name\", { stdio: \"ignore\" });\n } catch {\n console.log(\"[Scheduler] Configuring git user...\");\n execSync('git config user.name \"Marlin Scheduler\"');\n execSync('git config user.email \"scheduler@marlin.app\"');\n }\n\n const relativePath = path.relative(\n process.cwd(),\n path.join(notesDir, summaryFilename)\n );\n execSync(`git add \"${relativePath}\"`);\n execSync(\n `git commit -m \"chore(scheduler): add ${frequency} summary for ${taskName}\"`\n );\n execSync(\"git push\");\n console.log(\"[Scheduler] Successfully pushed to remote.\");\n } catch (error) {\n console.error(\"[Scheduler] Failed to push changes:\", error);\n }\n }\n}\n","export async function getGithubOidcToken(audience?: string): Promise<string | null> {\n const requestUrl = process.env.ACTIONS_ID_TOKEN_REQUEST_URL;\n const requestToken = process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN;\n\n if (!requestUrl || !requestToken) {\n // Not running in GitHub Actions or permissions not set\n return null;\n }\n\n try {\n const url = new URL(requestUrl);\n if (audience) {\n url.searchParams.append(\"audience\", audience);\n }\n\n const response = await fetch(url.toString(), {\n headers: {\n Authorization: `Bearer ${requestToken}`,\n Accept: \"application/json; api-version=2.0\",\n \"Content-Type\": \"application/json\",\n },\n });\n\n if (!response.ok) {\n const text = await response.text();\n console.warn(`[OIDC] Failed to fetch token: ${response.status} ${text}`);\n return null;\n }\n\n const data = (await response.json()) as { value: string };\n return data.value;\n } catch (error) {\n console.warn(\"[OIDC] Error fetching token:\", error);\n return null;\n }\n}\n"],"mappings":"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","names":["path","fs","matter"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@marlin-notes/scheduler-cli",
3
- "version": "0.0.3",
3
+ "version": "0.0.5",
4
4
  "description": "CLI tool for Marlin scheduled tasks",
5
5
  "bin": {
6
6
  "marlin-scheduler": "./dist/index.js"
package/src/index.ts CHANGED
@@ -6,7 +6,7 @@ const program = new Command();
6
6
  program
7
7
  .name("marlin-scheduler")
8
8
  .description("CLI for Marlin scheduled tasks")
9
- .version("0.0.1");
9
+ .version("0.0.5");
10
10
 
11
11
  program
12
12
  .command("summarize")
@@ -16,20 +16,37 @@ program
16
16
  .option("--tags <json>", "JSON string of tags")
17
17
  .option("--api-url <url>", "Marlin API URL")
18
18
  .option("--user-id <id>", "GitHub User ID")
19
+ .option(
20
+ "--dir <path>",
21
+ "Directory to search for notes (default: current dir)"
22
+ )
23
+ .option("--push", "Commit and push the summary to the git repository")
19
24
  .action(async (options) => {
20
25
  try {
21
26
  // Prioritize flags, then Env Vars (standard Action inputs often use INPUT_ prefix)
22
- const taskName = options.taskName || process.env.INPUT_TASK_NAME || "Manual Task";
23
- const frequency = options.frequency || process.env.INPUT_FREQUENCY || "weekly";
27
+ const taskName =
28
+ options.taskName || process.env.INPUT_TASK_NAME || "Manual Task";
29
+ const frequency =
30
+ options.frequency || process.env.INPUT_FREQUENCY || "weekly";
24
31
  const tagsJson = options.tags || process.env.INPUT_TAGS || "[]";
25
32
  // Default to production API if not specified
26
- const apiUrl = options.apiUrl || process.env.INPUT_API_URL || "https://marlinnotes.com/api/ai/summarize";
27
-
33
+ const apiUrl =
34
+ options.apiUrl ||
35
+ process.env.INPUT_API_URL ||
36
+ "https://marlinnotes.com/api/ai/summarize";
37
+
28
38
  // Resolve User ID: Flag -> Input -> GitHub Actor ID
29
- const userId = options.userId || process.env.INPUT_USER_ID || process.env.GITHUB_ACTOR_ID;
39
+ const userId =
40
+ options.userId ||
41
+ process.env.INPUT_USER_ID ||
42
+ process.env.GITHUB_ACTOR_ID;
43
+ const baseDir = options.dir || process.env.INPUT_DIR;
44
+ const push = options.push || process.env.INPUT_PUSH === "true";
30
45
 
31
46
  if (!userId) {
32
- console.error("Error: User ID is required. Pass via --user-id, set INPUT_USER_ID, or run in GitHub Actions (GITHUB_ACTOR_ID).");
47
+ console.error(
48
+ "Error: User ID is required. Pass via --user-id, set INPUT_USER_ID, or run in GitHub Actions (GITHUB_ACTOR_ID)."
49
+ );
33
50
  process.exit(1);
34
51
  }
35
52
 
@@ -46,6 +63,8 @@ program
46
63
  tags,
47
64
  apiUrl,
48
65
  userId,
66
+ baseDir,
67
+ push,
49
68
  });
50
69
  } catch (error) {
51
70
  console.error("Failed:", error);
package/src/summarizer.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import fs from "fs";
2
2
  import path from "path";
3
+ import { execSync } from "child_process";
3
4
  import matter from "gray-matter";
4
5
  import { subDays, subMonths, isBefore, isAfter } from "date-fns";
5
6
  import { getGithubOidcToken } from "./oidc";
@@ -10,6 +11,8 @@ interface SummarizeOptions {
10
11
  tags: string[];
11
12
  apiUrl: string;
12
13
  userId: string;
14
+ baseDir?: string;
15
+ push?: boolean;
13
16
  }
14
17
 
15
18
  export async function summarize({
@@ -18,6 +21,8 @@ export async function summarize({
18
21
  tags,
19
22
  apiUrl,
20
23
  userId,
24
+ baseDir,
25
+ push,
21
26
  }: SummarizeOptions) {
22
27
  // ... (existing date logic unchanged) ...
23
28
  const now = new Date();
@@ -35,8 +40,25 @@ export async function summarize({
35
40
  console.log(`[Scheduler] Filtering from: ${startDate.toISOString()}`);
36
41
  console.log(`[Scheduler] Tags: ${tags.join(", ") || "(none)"}`);
37
42
 
38
- const notesDir = process.cwd(); // Run in current dir
39
- const files = fs.readdirSync(notesDir).filter((f) => f.endsWith(".md"));
43
+ const notesDir = baseDir
44
+ ? path.resolve(process.cwd(), baseDir)
45
+ : process.cwd();
46
+ console.log(`[Scheduler] Searching in: ${notesDir}`);
47
+
48
+ if (!fs.existsSync(notesDir)) {
49
+ throw new Error(`Directory not found: ${notesDir}`);
50
+ }
51
+
52
+ // Use recursive search if available (Node 20+), otherwise shallow
53
+ // We filter out node_modules and .git to avoid clutter
54
+ const files = fs
55
+ .readdirSync(notesDir, { recursive: true } as any)
56
+ .map((f) => String(f)) // Ensure string
57
+ .filter(
58
+ (f) =>
59
+ f.endsWith(".md") && !f.includes("node_modules") && !f.includes(".git")
60
+ );
61
+
40
62
  const matchedNotes = [];
41
63
 
42
64
  for (const file of files) {
@@ -151,8 +173,9 @@ export async function summarize({
151
173
 
152
174
  const summaryContent = `---
153
175
  date: ${now.getTime()}
154
- tags: ["#summary/${frequency}", "#auto"]
176
+ tags: ["summary", "${taskName}", "auto"]
155
177
  title: ${frequency} Summary - ${taskName}
178
+ frequency: ${frequency}
156
179
  ---
157
180
 
158
181
  ${result.summary}
@@ -160,4 +183,30 @@ ${result.summary}
160
183
 
161
184
  fs.writeFileSync(path.join(notesDir, summaryFilename), summaryContent);
162
185
  console.log(`[Scheduler] Summary saved to ${summaryFilename}`);
186
+
187
+ if (push) {
188
+ try {
189
+ console.log("[Scheduler] Committing and pushing changes...");
190
+ try {
191
+ execSync("git config user.name", { stdio: "ignore" });
192
+ } catch {
193
+ console.log("[Scheduler] Configuring git user...");
194
+ execSync('git config user.name "Marlin Scheduler"');
195
+ execSync('git config user.email "scheduler@marlin.app"');
196
+ }
197
+
198
+ const relativePath = path.relative(
199
+ process.cwd(),
200
+ path.join(notesDir, summaryFilename)
201
+ );
202
+ execSync(`git add "${relativePath}"`);
203
+ execSync(
204
+ `git commit -m "chore(scheduler): add ${frequency} summary for ${taskName}"`
205
+ );
206
+ execSync("git push");
207
+ console.log("[Scheduler] Successfully pushed to remote.");
208
+ } catch (error) {
209
+ console.error("[Scheduler] Failed to push changes:", error);
210
+ }
211
+ }
163
212
  }