@marlin-notes/scheduler-cli 0.0.1 → 0.0.3

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
@@ -92,15 +92,20 @@ async function summarize({
92
92
  const content = import_fs.default.readFileSync(filePath, "utf-8");
93
93
  const { data, content: body } = (0, import_gray_matter.default)(content);
94
94
  let noteDate = null;
95
- if (data.date) {
96
- if (typeof data.date === "number") {
97
- noteDate = new Date(data.date);
98
- } else if (data.date instanceof Date) {
99
- noteDate = data.date;
95
+ const dateFields = [data.date, data.updatedAt, data.createdAt];
96
+ for (const field of dateFields) {
97
+ if (!field) continue;
98
+ if (field instanceof Date) {
99
+ noteDate = field;
100
100
  } else {
101
- noteDate = new Date(data.date);
101
+ const d = new Date(field);
102
+ if (!isNaN(d.getTime())) {
103
+ noteDate = d;
104
+ }
102
105
  }
103
- } else {
106
+ if (noteDate) break;
107
+ }
108
+ if (!noteDate) {
104
109
  const filenameTs = parseInt(file.replace(".md", ""));
105
110
  if (!isNaN(filenameTs) && filenameTs > 1e12) {
106
111
  noteDate = new Date(filenameTs);
@@ -109,8 +114,15 @@ async function summarize({
109
114
  if (!noteDate || isNaN(noteDate.getTime())) continue;
110
115
  if ((0, import_date_fns.isBefore)(noteDate, startDate) || (0, import_date_fns.isAfter)(noteDate, now)) continue;
111
116
  const noteTags = Array.isArray(data.tags) ? data.tags : [];
112
- const normalizedNoteTags = noteTags.map(String);
113
- const hasAllTags = tags.every((t) => normalizedNoteTags.includes(t));
117
+ const normalizedNoteTags = noteTags.map(
118
+ (t) => String(t).replace(/^#/, "").toLowerCase()
119
+ );
120
+ const normalizedSearchTags = tags.map(
121
+ (t) => t.replace(/^#/, "").toLowerCase()
122
+ );
123
+ const hasAllTags = normalizedSearchTags.every(
124
+ (t) => normalizedNoteTags.includes(t)
125
+ );
114
126
  if (hasAllTags || tags.length === 0) {
115
127
  matchedNotes.push({
116
128
  title: data.title || file.replace(".md", ""),
@@ -134,6 +146,10 @@ async function summarize({
134
146
  if (oidcToken) {
135
147
  console.log("[Scheduler] Authenticating with GitHub OIDC");
136
148
  headers["Authorization"] = `Bearer ${oidcToken}`;
149
+ } else {
150
+ console.warn(
151
+ "[Scheduler] WARNING: No OIDC token found. Ensure 'id-token: write' permission is set in the workflow."
152
+ );
137
153
  }
138
154
  const response = await fetch(apiUrl, {
139
155
  method: "POST",
@@ -170,10 +186,10 @@ program.command("summarize").description("Run summarization task").option("--tas
170
186
  const taskName = options.taskName || process.env.INPUT_TASK_NAME || "Manual Task";
171
187
  const frequency = options.frequency || process.env.INPUT_FREQUENCY || "weekly";
172
188
  const tagsJson = options.tags || process.env.INPUT_TAGS || "[]";
173
- const apiUrl = options.apiUrl || process.env.INPUT_API_URL || "https://marlin.app/api/ai/summarize";
174
- const userId = options.userId || process.env.INPUT_USER_ID;
189
+ const apiUrl = options.apiUrl || process.env.INPUT_API_URL || "https://marlinnotes.com/api/ai/summarize";
190
+ const userId = options.userId || process.env.INPUT_USER_ID || process.env.GITHUB_ACTOR_ID;
175
191
  if (!userId) {
176
- console.error("Error: User ID is required via --user-id or INPUT_USER_ID");
192
+ console.error("Error: User ID is required. Pass via --user-id, set INPUT_USER_ID, or run in GitHub Actions (GITHUB_ACTOR_ID).");
177
193
  process.exit(1);
178
194
  }
179
195
  let tags = [];
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://marlin.app/api/ai/summarize\";\n const userId = options.userId || process.env.INPUT_USER_ID;\n\n if (!userId) {\n console.error(\"Error: User ID is required via --user-id or INPUT_USER_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 if (data.date) {\n if (typeof data.date === \"number\") {\n noteDate = new Date(data.date);\n } else if (data.date instanceof Date) {\n noteDate = data.date;\n } else {\n noteDate = new Date(data.date);\n }\n } else {\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(String);\n\n const hasAllTags = tags.every((t) => normalizedNoteTags.includes(t));\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 }\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":"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","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.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":"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","names":["fs","path","matter"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@marlin-notes/scheduler-cli",
3
- "version": "0.0.1",
3
+ "version": "0.0.3",
4
4
  "description": "CLI tool for Marlin scheduled tasks",
5
5
  "bin": {
6
6
  "marlin-scheduler": "./dist/index.js"
package/src/index.ts CHANGED
@@ -23,11 +23,13 @@ program
23
23
  const frequency = options.frequency || process.env.INPUT_FREQUENCY || "weekly";
24
24
  const tagsJson = options.tags || process.env.INPUT_TAGS || "[]";
25
25
  // Default to production API if not specified
26
- const apiUrl = options.apiUrl || process.env.INPUT_API_URL || "https://marlin.app/api/ai/summarize";
27
- const userId = options.userId || process.env.INPUT_USER_ID;
26
+ const apiUrl = options.apiUrl || process.env.INPUT_API_URL || "https://marlinnotes.com/api/ai/summarize";
27
+
28
+ // Resolve User ID: Flag -> Input -> GitHub Actor ID
29
+ const userId = options.userId || process.env.INPUT_USER_ID || process.env.GITHUB_ACTOR_ID;
28
30
 
29
31
  if (!userId) {
30
- console.error("Error: User ID is required via --user-id or INPUT_USER_ID");
32
+ console.error("Error: User ID is required. Pass via --user-id, set INPUT_USER_ID, or run in GitHub Actions (GITHUB_ACTOR_ID).");
31
33
  process.exit(1);
32
34
  }
33
35
 
package/src/summarizer.ts CHANGED
@@ -47,15 +47,26 @@ export async function summarize({
47
47
  // 1. Date Check
48
48
  let noteDate: Date | null = null;
49
49
 
50
- if (data.date) {
51
- if (typeof data.date === "number") {
52
- noteDate = new Date(data.date);
53
- } else if (data.date instanceof Date) {
54
- noteDate = data.date;
50
+ // Check fields in priority: date -> updatedAt -> createdAt
51
+ const dateFields = [data.date, data.updatedAt, data.createdAt];
52
+
53
+ for (const field of dateFields) {
54
+ if (!field) continue;
55
+
56
+ if (field instanceof Date) {
57
+ noteDate = field;
55
58
  } else {
56
- noteDate = new Date(data.date);
59
+ const d = new Date(field);
60
+ if (!isNaN(d.getTime())) {
61
+ noteDate = d;
62
+ }
57
63
  }
58
- } else {
64
+
65
+ if (noteDate) break;
66
+ }
67
+
68
+ // Fallback to filename timestamp if no valid date found in frontmatter
69
+ if (!noteDate) {
59
70
  const filenameTs = parseInt(file.replace(".md", ""));
60
71
  if (!isNaN(filenameTs) && filenameTs > 1000000000000) {
61
72
  noteDate = new Date(filenameTs);
@@ -68,9 +79,16 @@ export async function summarize({
68
79
 
69
80
  // 2. Tag Check
70
81
  const noteTags: string[] = Array.isArray(data.tags) ? data.tags : [];
71
- const normalizedNoteTags = noteTags.map(String);
82
+ const normalizedNoteTags = noteTags.map((t) =>
83
+ String(t).replace(/^#/, "").toLowerCase()
84
+ );
85
+ const normalizedSearchTags = tags.map((t) =>
86
+ t.replace(/^#/, "").toLowerCase()
87
+ );
72
88
 
73
- const hasAllTags = tags.every((t) => normalizedNoteTags.includes(t));
89
+ const hasAllTags = normalizedSearchTags.every((t) =>
90
+ normalizedNoteTags.includes(t)
91
+ );
74
92
 
75
93
  if (hasAllTags || tags.length === 0) {
76
94
  matchedNotes.push({
@@ -91,7 +109,7 @@ export async function summarize({
91
109
 
92
110
  // 3. API Call
93
111
  console.log(`[Scheduler] Sending to API: ${apiUrl}`);
94
-
112
+
95
113
  // Try to get OIDC token
96
114
  const oidcToken = await getGithubOidcToken("marlin-api");
97
115
  const headers: Record<string, string> = {
@@ -102,6 +120,13 @@ export async function summarize({
102
120
  if (oidcToken) {
103
121
  console.log("[Scheduler] Authenticating with GitHub OIDC");
104
122
  headers["Authorization"] = `Bearer ${oidcToken}`;
123
+ } else {
124
+ // Fallback or Error
125
+ // We allow running without OIDC if strictly testing, but warn loudly.
126
+ // In production, the API will likely reject it.
127
+ console.warn(
128
+ "[Scheduler] WARNING: No OIDC token found. Ensure 'id-token: write' permission is set in the workflow."
129
+ );
105
130
  }
106
131
 
107
132
  const response = await fetch(apiUrl, {