Note: general recommendations only, not professional advice. You are responsible for checking before use. Before you record a meeting, tell the participants, and only use AI tools your organization approves for this information. The full text is at the end.
Who this is for
Anyone who sits in a lot of meetings and walks out with "so who's doing what?": CEOs and CFOs, investment committees, nonprofit boards, portfolio teams at funds, weekly team meetings. If tasks get lost between the meeting and the summary email, or that email never gets written, this kit is for you.
You don't need to code. You paste one script into a sheet, once, and from then on everything runs from a menu.
What you'll have at the end of the hour
- A Google Sheet with a "People" tab (name, role, email, aliases) and a "Tasks" tab.
- A main prompt that turns a transcript into a summary, decisions, tasks and open questions, in a format the sheet can read.
- A "Meetings" menu in the sheet: paste the AI's answer and the tasks are filed on their own, each with an owner.
- Gmail drafts: a personal email to each owner with their tasks, and a summary email to all participants. Nothing is sent until you read it and click "Send".
- A daily reminder at 8 in the morning for open tasks that are due.
What you need
- A Google account (Sheets and Gmail). A work account is better than a personal one.
- A meeting transcript. If you don't have one, Step 1 shows ways to get it.
- An AI tool your organization approves: ChatGPT, Claude, Gemini or your internal tool. The script sends nothing to any AI. You choose where the transcript goes.
One rule you don't bend: the AI doesn't invent owners or dates. A task without a clear owner is filed as "Unassigned", and a due date nobody said stays empty. A visible gap is better than an email to the wrong person.
The kit, step by step (60 minutes)
Step 1 (10 minutes): Record and transcribe, with permission
What to do: Get the transcript of one real meeting, so you build everything on something real.
How: Three ways, from easiest to most private:
- Your meeting tool's transcript. Zoom, Google Meet and Teams include transcription in some plans. Turn it on at the start and download the file at the end.
- Record on your phone and upload to an AI tool. Fast, but the recording leaves your computer for the provider's servers. Not for sensitive meetings.
- Transcribe on your own computer. The recording never leaves it. Full instructions in the kit Free Hebrew transcription on your own computer.
At the start of the meeting, say one sentence: "I'm recording so the summary and tasks come out accurate. Does anyone mind?" If someone minds, don't record, and take notes by hand.
Common mistake: starting the recording halfway through. The first minutes are usually where someone says "the goal today is...", and that's exactly what the AI needs to write a good summary.
Step 2 (10 minutes): A sheet and a People tab
What to do: Create a new sheet called "Meetings and tasks". The script will create the tabs in Step 3; for now, prepare the list of people.
How: List everyone who might get a task: full name, role, email and aliases. An alias is any way people refer to someone in a meeting: first name, last name, "the CEO", "our auditor". Separate aliases with commas.
Name Role Email Aliases
[Full name] [Role] [Email] [First name, nickname]
[Full name] [Role] [Email] [Last name]
Why this way: the script looks for an owner in this order: full name, alias, role, then first name. If two people share a first name, it doesn't guess and the task stays "Unassigned". So give everyone a unique alias, such as their last name.
Step 3 (10 minutes): Paste the script and run setup
What to do: Add the script from the appendix to the sheet and give it permissions once.
How:
- In the sheet: Extensions > Apps Script. A
Code.gsfile opens. - Delete what's there, paste the whole script from the appendix at the end of this kit, and save.
- At the top, choose the
setupfunction and click Run. Google asks for permission to the sheet and to Gmail. Allow it. If you see "Google hasn't verified this app", click Advanced and then Go to the project. It's your own code in your own account. - In Project Settings > Script properties, add
LANGwith the valueenfor English tabs, menu and emails. Then runsetupagain. - Go back to the sheet and refresh. You'll see two tabs, "People" and "Tasks", and a new menu called Meetings.
- Paste your people list from Step 2 into cell A2 of the "People" tab.
Why this way: the script only creates Gmail drafts. It sends no email to anyone except the daily reminder, which goes to you (and to owners only if you choose that in Step 6).
Step 4 (10 minutes): From transcript to an answer the sheet can read
What to do: Open a new chat in your AI tool, paste the main prompt, and replace the brackets with your people list and the transcript.
You are a precise meeting secretary. Read the transcript and the people list, and return a single JSON object on one line, with no text before or after it.
People list (name | role | aliases):
[Paste the "People" tab]
Meeting title: [for example: Monthly investment committee]
Meeting date: [yyyy-mm-dd]
Transcript:
[Paste the transcript]
Binding rules:
1. A task is only something someone committed to or was asked to do. An idea, an opinion or "we should..." with no one doing it is not a task.
2. owner: the full name from the list, exactly as written there. If it's unclear who owns it, or the person isn't on the list, write what was said in the transcript ("someone from finance") and don't guess.
3. due: only a date that was said, or that can be worked out for certain from the meeting date ("by Thursday"). Format yyyy-mm-dd. If no date was said, leave "".
4. quote: a short sentence from the transcript where the task was agreed, word for word.
5. Leave out amounts, account numbers, ID numbers and personal details that aren't needed to do the task.
6. participants: only full names from the list of people who spoke or were mentioned as present.
The structure:
{"meeting":{"title":"","date":"","participants":[]},"summary":["up to 5 points"],"decisions":[],"tasks":[{"task":"verb + exactly what","owner":"","due":"","quote":""}],"open_questions":[]}
Common mistake: asking the AI to "summarize and pull out tasks" without the people list. Then it writes "Dan" when there are three Dans in the organization, and invents last names. The list is what turns the answer into something you can send.
Step 5 (10 minutes): Import, check and send
What to do: Move the answer into the sheet, review what was filed, and send the drafts.
How:
- Copy the JSON the AI returned.
- In the sheet: Meetings > Import tasks from a meeting, paste it into the box and click OK.
- The "Tasks" tab gets one row per task. A short message says how many tasks were added, how many drafts were created, and how many tasks have no owner.
- A task marked "Unassigned": assign it by hand (type the name, role and email) and add it to that person's draft.
- Open Drafts in Gmail. Read each one, fix what needs fixing, and send.
Why this way: every task has a "Quote from the meeting" column. When someone replies "I never said I'd do that", there's a factual answer and no argument.
Step 6 (5 minutes): A daily reminder
What to do: Turn on a reminder that goes out every morning for open tasks that are due or overdue.
How:
- In the sheet: Meetings > Turn on a daily reminder at 8:00. Allow permissions if asked.
- From now on, when a task is done, write Done in the Status column. Anything else counts as open.
- By default the reminder goes only to you, with the full list. If you want each owner to get a reminder about their own tasks too: in Apps Script, Project Settings > Script properties, add
REMIND_OWNERSwith the valuetrue.
Run it for a few weeks with the reminder coming only to you, and only then open it to owners.
Step 7 (5 minutes): The next meeting starts from the open tasks
What to do: Before the next meeting, build a short agenda from the sheet: what's closed, what's stuck, and what needs a decision.
How: Filter the "Tasks" tab for anything not marked "Done", copy the rows and run this prompt.
Here are the open tasks from the sheet (task | owner | due | quote): [paste].
Today is [date]. Write an agenda for a 15-minute follow-up meeting:
a. Overdue tasks, by owner. Next to each one: one question for the owner.
b. Tasks that depend on each other, if any.
c. Decisions needed for a task to move forward.
Don't invent a status. If it isn't in the table, write "to check".
Security and privacy, briefly
- The transcript is the sensitive part, not the sheet. In finance, investment or client meetings, use only an AI tool your organization approved, or remove client names, amounts and identifying details from the transcript before you paste it.
- The script sends the transcript nowhere. It only receives the processed answer and stores it in your sheet.
- Share the sheet only with those who need it. Anyone with edit access can see every task.
- Deletion. Set a rule, for example: recordings are deleted once the summary is sent, and transcripts after a month.
When something doesn't work
| What you see | What to do |
|---|---|
| "The JSON is not valid" | The AI added text or broke a line. Ask it: "Return only the JSON, on one line", and paste again. |
| Every task is "Unassigned" | The names in the answer don't match the list. Check that you pasted the people list into the prompt, and add aliases. |
| No drafts were created | People have no email in the "People" tab, or you didn't allow Gmail access. Run setup again. |
| No "Meetings" menu | Refresh the sheet. If it's still missing, run onOpen from Apps Script. |
| The reminder didn't arrive | In Apps Script, open Triggers (the clock) and check there's one trigger for remindOverdue. |
Full example: North One Capital investment committee (a fictional business)
Everything in this example is fictional: the fund, the people, the companies and the amounts.
People tab:
| Name | Role | Aliases | |
|---|---|---|---|
| Michelle Barak | Managing partner | michelle@example.com | Michelle |
| Amit Segal | CFO | amit@example.com | Segal, CFO |
| Noam Dahan | Analyst | noam@example.com | Dahan |
| Noam Peretz | Legal counsel | noamp@example.com | Peretz, legal |
Michelle: On the logistics company, we're not deciding today. Dahan, by Sunday please put together a multiples comparison against three similar deals.
Amit: I'll send the partners the quarterly capital memo by Thursday.
Michelle: And Noam, check the anti-dilution clause before we go back to them.
Peretz: I'm on it.
{"meeting":{"title":"Weekly investment committee","date":"2026-10-20","participants":["Michelle Barak","Amit Segal","Noam Dahan","Noam Peretz"]},"summary":["Decision on the logistics company postponed to the next meeting"],"decisions":["No decision today on the logistics company"],"tasks":[{"task":"Prepare a multiples comparison against three similar deals","owner":"Noam Dahan","due":"2026-10-25","quote":"Dahan, by Sunday please put together a multiples comparison"},{"task":"Send the partners the quarterly capital memo","owner":"Amit Segal","due":"2026-10-22","quote":"I'll send the partners the quarterly capital memo by Thursday"},{"task":"Check the anti-dilution clause","owner":"Noam","due":"","quote":"And Noam, check the anti-dilution clause"}],"open_questions":[]}
What happened in the sheet: three tasks were added. The first two went to Dahan and Segal. For the third the AI wrote only "Noam", and there are two Noams, so it was filed as "Unassigned: Noam". The transcript makes it clear Peretz answered "I'm on it", so you assign it to Noam Peretz by hand. Two personal drafts and one summary draft to all four participants were created.
Notice two things: the third task got no invented due date, because none was said. And the script didn't guess which Noam, even when it looked obvious. That's exactly why you check before sending.
Checklist before every send
- The participants knew the meeting was recorded
- The transcript only went through an AI tool your organization approves
- No task is left "Unassigned"
- Every due date in the sheet was said in the meeting, or left empty
- You read every Gmail draft before sending
- The drafts contain no amounts or details that not every recipient should see
- The recording was deleted, or you've set when it will be
Appendix: the full script (Code.gs)
Copy everything, from the first line to the last, into Code.gs. You don't need to change a line. For an English menu, tabs and emails, add the script property LANG with the value en.
/**
* Meeting -> tasks -> email drafts (free kit, gindiinnovation.com)
*
* Bound to a Google Sheet with two tabs (created by setup):
* People: name | role | email | aliases (comma separated)
* Tasks: id | meeting date | meeting | task | owner | role | email | due | status | quote | created
*
* Script properties (Project Settings > Script properties), all optional:
* LANG he or en, default he (menu, tab names and email texts)
* REMIND_OWNERS true = the daily reminder emails each owner; otherwise it emails only you
*
* No AI key here: you run the prompt in the AI tool your organization allows,
* and paste its JSON. The script only checks it, files the tasks and writes drafts.
*/
var TEXT = {
he: {
people: 'אנשים',
tasks: 'משימות',
peopleHeaders: ['שם', 'תפקיד', 'אימייל', 'כינויים'],
taskHeaders: ['מזהה', 'תאריך הפגישה', 'פגישה', 'משימה', 'אחראי', 'תפקיד', 'אימייל', 'יעד', 'סטטוס', 'ציטוט מהפגישה', 'נוצר'],
menu: 'פגישות',
menuImport: 'ייבוא משימות מפגישה',
menuRemind: 'תזכורת עכשיו: משימות שהגיע זמנן',
menuTrigger: 'הפעלת תזכורת יומית ב-8:00',
paste: 'הדביקו כאן את ה-JSON שהחזיר כלי ה-AI (שורה אחת)',
badJson: 'ה-JSON לא תקין: ',
open: 'פתוח',
done: 'בוצע',
unassigned: 'לא שויך',
noDue: 'לא נקבע',
subjectOwner: 'המשימות שלך מהפגישה: ',
subjectAll: 'סיכום פגישה: ',
subjectRemind: 'תזכורת: משימות פתוחות שהגיע זמנן',
hello: 'שלום ',
yourTasks: 'סוכמו בפגישה המשימות האלה באחריותך:',
due: 'יעד: ',
summary: 'סיכום',
decisions: 'החלטות',
allTasks: 'משימות',
questions: 'שאלות פתוחות',
reply: 'אם משהו לא מדויק, ענו למייל הזה ונתקן.',
remindIntro: 'המשימות האלה פתוחות והיעד שלהן הגיע או עבר:',
result: function (t, d, u) { return 'נוספו ' + t + ' משימות ונוצרו ' + d + ' טיוטות ב-Gmail. ' + (u ? u + ' משימות לא שויכו לאף אחד: שייכו אותן בגיליון.' : 'כל המשימות שויכו.'); },
triggerOk: 'מעכשיו תצא תזכורת כל בוקר בסביבות 8:00.',
setupOk: 'הלשוניות מוכנות. מלאו את לשונית האנשים והתחילו.',
nothing: 'אין משימות פתוחות שהגיע זמנן.',
},
en: {
people: 'People',
tasks: 'Tasks',
peopleHeaders: ['Name', 'Role', 'Email', 'Aliases'],
taskHeaders: ['ID', 'Meeting date', 'Meeting', 'Task', 'Owner', 'Role', 'Email', 'Due', 'Status', 'Quote from the meeting', 'Created'],
menu: 'Meetings',
menuImport: 'Import tasks from a meeting',
menuRemind: 'Remind now: tasks that are due',
menuTrigger: 'Turn on a daily reminder at 8:00',
paste: 'Paste the JSON your AI tool returned (one line)',
badJson: 'The JSON is not valid: ',
open: 'Open',
done: 'Done',
unassigned: 'Unassigned',
noDue: 'Not set',
subjectOwner: 'Your tasks from the meeting: ',
subjectAll: 'Meeting summary: ',
subjectRemind: 'Reminder: open tasks that are due',
hello: 'Hi ',
yourTasks: 'These tasks were agreed in the meeting and are yours:',
due: 'Due: ',
summary: 'Summary',
decisions: 'Decisions',
allTasks: 'Tasks',
questions: 'Open questions',
reply: 'If anything is off, reply to this email and we will fix it.',
remindIntro: 'These tasks are open and due today or earlier:',
result: function (t, d, u) { return t + ' tasks added and ' + d + ' Gmail drafts created. ' + (u ? u + ' tasks have no owner: assign them in the sheet.' : 'Every task has an owner.'); },
triggerOk: 'A reminder will now go out every morning around 8:00.',
setupOk: 'The tabs are ready. Fill in the People tab and start.',
nothing: 'No open tasks are due.',
},
};
function lang_() {
var v = String(PropertiesService.getScriptProperties().getProperty('LANG') || 'he').toLowerCase();
return v === 'en' ? 'en' : 'he';
}
function t_() { return TEXT[lang_()]; }
function onOpen() {
var t = t_();
SpreadsheetApp.getUi().createMenu(t.menu)
.addItem(t.menuImport, 'importMeeting')
.addItem(t.menuRemind, 'remindOverdue')
.addItem(t.menuTrigger, 'setupDailyReminder')
.addToUi();
}
/** Creates the two tabs with their headers, if they are missing. Run once. */
function setup() {
var t = t_();
var ss = SpreadsheetApp.getActive();
[[t.people, t.peopleHeaders], [t.tasks, t.taskHeaders]].forEach(function (s) {
var sh = ss.getSheetByName(s[0]) || ss.insertSheet(s[0]);
if (sh.getLastRow() === 0) {
sh.getRange(1, 1, 1, s[1].length).setValues([s[1]]).setFontWeight('bold');
sh.setFrozenRows(1);
}
});
onOpen();
SpreadsheetApp.getUi().alert(t.setupOk);
}
// ---------- Pure helpers (no Google services, testable anywhere) ----------
/** Lowercase, no quotes or geresh, single spaces */
function norm(s) {
return String(s || '').toLowerCase().replace(/["'`׳״]/g, '').replace(/\s+/g, ' ').trim();
}
/** Accepts the model's answer even with a ```json fence or text around it */
function parseMeetingJson(text) {
var s = String(text || '').trim();
var start = s.indexOf('{');
var end = s.lastIndexOf('}');
if (start < 0 || end <= start) throw new Error('no { ... }');
var data = JSON.parse(s.slice(start, end + 1));
if (!data || !Array.isArray(data.tasks)) throw new Error('missing "tasks"');
data.meeting = data.meeting || {};
data.summary = Array.isArray(data.summary) ? data.summary : [];
data.decisions = Array.isArray(data.decisions) ? data.decisions : [];
data.open_questions = Array.isArray(data.open_questions) ? data.open_questions : [];
data.tasks = data.tasks
.filter(function (x) { return x && String(x.task || '').trim(); })
.map(function (x) {
return { task: String(x.task).trim(), owner: String(x.owner || '').trim(), due: normalizeDue(x.due), quote: String(x.quote || '').trim() };
});
return data;
}
/** "2026-10-03" stays; anything that is not a real yyyy-mm-dd date becomes "" (no invented dates) */
function normalizeDue(v) {
var m = String(v || '').trim().match(/^(\d{4})-(\d{2})-(\d{2})$/);
if (!m) return '';
var d = new Date(Date.UTC(+m[1], +m[2] - 1, +m[3]));
return d.getUTCMonth() === +m[2] - 1 && d.getUTCDate() === +m[3] ? m[0] : '';
}
/**
* Finds the person a task belongs to: full name, then an alias, then the role,
* then a first name that only one person has. No match = null (the task stays unassigned).
*/
function matchPerson(owner, people) {
var o = norm(owner);
if (!o) return null;
var by = function (fn) { var hits = people.filter(fn); return hits.length === 1 ? hits[0] : null; };
return (
by(function (p) { return norm(p.name) === o; }) ||
by(function (p) { return p.aliases.some(function (a) { return norm(a) === o; }); }) ||
by(function (p) { return norm(p.role) === o; }) ||
by(function (p) { return norm(p.name).split(' ')[0] === o; })
);
}
/** Groups the matched tasks by email: { email: { person, tasks[] } } */
function groupByOwner(tasks, people) {
var out = {};
tasks.forEach(function (task) {
var p = matchPerson(task.owner, people);
if (!p || !p.email) return;
(out[p.email] = out[p.email] || { person: p, tasks: [] }).tasks.push(task);
});
return out;
}
function ownerEmailBody(t, person, tasks, meetingTitle) {
var lines = [t.hello + person.name + ',', '', t.yourTasks, ''];
tasks.forEach(function (x, i) { lines.push((i + 1) + '. ' + x.task + ' (' + t.due + (x.due || t.noDue) + ')'); });
lines.push('', meetingTitle, '', t.reply);
return lines.join('\n');
}
function summaryEmailBody(t, data, people) {
var section = function (title, items) { return items.length ? ['', title + ':'].concat(items.map(function (s) { return '- ' + s; })) : []; };
var taskLines = data.tasks.map(function (x) {
var p = matchPerson(x.owner, people);
return x.task + ' | ' + (p ? p.name : t.unassigned) + ' | ' + t.due + (x.due || t.noDue);
});
return [data.meeting.title || '']
.concat(section(t.summary, data.summary), section(t.decisions, data.decisions), section(t.allTasks, taskLines), section(t.questions, data.open_questions), ['', t.reply])
.join('\n');
}
// ---------- Google Sheets and Gmail ----------
function readPeople_() {
var sh = SpreadsheetApp.getActive().getSheetByName(t_().people);
if (!sh || sh.getLastRow() < 2) return [];
return sh.getRange(2, 1, sh.getLastRow() - 1, 4).getValues()
.filter(function (r) { return String(r[0]).trim(); })
.map(function (r) {
return { name: String(r[0]).trim(), role: String(r[1]).trim(), email: String(r[2]).trim(), aliases: String(r[3]).split(',').map(function (a) { return a.trim(); }).filter(String) };
});
}
/** Menu: paste the JSON, file the tasks, write Gmail drafts (nothing is sent) */
function importMeeting() {
var t = t_();
var ui = SpreadsheetApp.getUi();
var res = ui.prompt(t.menuImport, t.paste, ui.ButtonSet.OK_CANCEL);
if (res.getSelectedButton() !== ui.Button.OK) return;
var data;
try { data = parseMeetingJson(res.getResponseText()); } catch (e) { ui.alert(t.badJson + e.message); return; }
var ss = SpreadsheetApp.getActive();
var tz = ss.getSpreadsheetTimeZone();
var sheet = ss.getSheetByName(t.tasks);
var people = readPeople_();
var now = new Date();
var date = normalizeDue(data.meeting.date) || Utilities.formatDate(now, tz, 'yyyy-MM-dd');
var title = String(data.meeting.title || '').trim() || date;
var start = Math.max(sheet.getLastRow(), 1);
var unassigned = 0;
var rows = data.tasks.map(function (x, i) {
var p = matchPerson(x.owner, people);
if (!p) unassigned += 1;
var id = date.replace(/-/g, '').slice(2) + '-' + (start + i);
var owner = p ? p.name : x.owner ? t.unassigned + ': ' + x.owner : t.unassigned;
return [id, date, title, x.task, owner, p ? p.role : '', p ? p.email : '', x.due, t.open, x.quote, now];
});
if (rows.length) sheet.getRange(sheet.getLastRow() + 1, 1, rows.length, rows[0].length).setValues(rows);
var drafts = 0;
var groups = groupByOwner(data.tasks, people);
Object.keys(groups).forEach(function (email) {
GmailApp.createDraft(email, t.subjectOwner + title, ownerEmailBody(t, groups[email].person, groups[email].tasks, title));
drafts += 1;
});
var participants = (data.meeting.participants || [])
.map(function (n) { var p = matchPerson(n, people); return p && p.email; })
.filter(function (e, i, all) { return e && all.indexOf(e) === i; });
if (participants.length) {
GmailApp.createDraft(participants.join(','), t.subjectAll + title, summaryEmailBody(t, data, people));
drafts += 1;
}
ui.alert(t.result(rows.length, drafts, unassigned));
}
/** Open tasks due today or earlier: one email to you, or one per owner if REMIND_OWNERS is true */
function remindOverdue() {
var t = t_();
var ss = SpreadsheetApp.getActive();
var tz = ss.getSpreadsheetTimeZone();
var today = Utilities.formatDate(new Date(), tz, 'yyyy-MM-dd');
var sh = ss.getSheetByName(t.tasks);
var rows = sh.getLastRow() < 2 ? [] : sh.getRange(2, 1, sh.getLastRow() - 1, 11).getValues();
var due = rows.filter(function (r) {
var d = r[7] instanceof Date ? Utilities.formatDate(r[7], tz, 'yyyy-MM-dd') : normalizeDue(r[7]);
return d && d <= today && String(r[8]).trim() !== t.done;
});
var interactive = false;
try { SpreadsheetApp.getUi(); interactive = true; } catch (e) { interactive = false; }
if (!due.length) { if (interactive) SpreadsheetApp.getUi().alert(t.nothing); return; }
var line = function (r) { return '- ' + r[3] + ' | ' + r[4] + ' | ' + t.due + (r[7] instanceof Date ? Utilities.formatDate(r[7], tz, 'yyyy-MM-dd') : r[7]) + ' | ' + r[2]; };
var toOwners = String(PropertiesService.getScriptProperties().getProperty('REMIND_OWNERS')).toLowerCase() === 'true';
if (toOwners) {
var byEmail = {};
due.forEach(function (r) { if (r[6]) (byEmail[r[6]] = byEmail[r[6]] || []).push(r); });
Object.keys(byEmail).forEach(function (email) {
MailApp.sendEmail(email, t.subjectRemind, [t.remindIntro, ''].concat(byEmail[email].map(line)).join('\n'));
});
}
// You always get the full list, including tasks with no owner
MailApp.sendEmail(Session.getEffectiveUser().getEmail(), t.subjectRemind, [t.remindIntro, ''].concat(due.map(line)).join('\n'));
}
/** Menu: one daily trigger for remindOverdue, around 8:00 in the sheet's time zone */
function setupDailyReminder() {
ScriptApp.getProjectTriggers()
.filter(function (tr) { return tr.getHandlerFunction() === 'remindOverdue'; })
.forEach(function (tr) { ScriptApp.deleteTrigger(tr); });
ScriptApp.newTrigger('remindOverdue').timeBased().everyDays(1).atHour(8).create();
SpreadsheetApp.getUi().alert(t_().triggerOk);
}
// For local tests only (Node). Apps Script has no `module`, so this line does nothing there.
if (typeof module !== 'undefined') {
module.exports = { TEXT: TEXT, norm: norm, parseMeetingJson: parseMeetingJson, normalizeDue: normalizeDue, matchPerson: matchPerson, groupByOwner: groupByOwner, ownerEmailBody: ownerEmailBody, summaryEmailBody: summaryEmailBody };
}
Disclaimer
This content is general recommendations only, not professional, legal, accounting or tax advice. Every business and organization is different. You are responsible for checking that it fits your situation, and for checking the data, prompts and code before you use them. Outside tools, prices and terms of service change, so check them at the source. Before you record a meeting or a call, tell the participants, and check with a lawyer what applies to you, especially when it involves information about clients, employees or investors.
The content, prompts, templates and code are provided as is, with no warranty that they fit a particular purpose or are free of errors. Gindi Innovation and Aviad Gindi are not liable for any damage, direct or indirect, arising from using them or relying on them, including output from AI tools. Use is subject to the site terms.
What's next
This kit works with one paste: you take the transcript to the AI and the answer to the sheet. The same process can be connected so the transcript arrives on its own from your meeting tool, the tasks land in the system you already use, and the drafts wait for your approval on your phone. What goes out stays your decision.
Want this running automatically in your business? aviad@gindiinnovation.com · gindiinnovation.com