nothumanallowed 16.0.17 → 16.0.18
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nothumanallowed",
|
|
3
|
-
"version": "16.0.
|
|
3
|
+
"version": "16.0.18",
|
|
4
4
|
"description": "NotHumanAllowed — 38 AI agents, 80 tools, Studio (visual agentic workflows). Email, calendar, browser automation, screen capture, canvas, cron/heartbeat, Alexandria E2E messaging, GitHub, Notion, Slack, voice chat, free AI (Liara), 28 languages. Zero-dependency CLI.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
package/src/constants.mjs
CHANGED
|
@@ -5,7 +5,7 @@ import { fileURLToPath } from 'url';
|
|
|
5
5
|
const __filename = fileURLToPath(import.meta.url);
|
|
6
6
|
const __dirname = path.dirname(__filename);
|
|
7
7
|
|
|
8
|
-
export const VERSION = '16.0.
|
|
8
|
+
export const VERSION = '16.0.18';
|
|
9
9
|
export const BASE_URL = 'https://nothumanallowed.com/cli';
|
|
10
10
|
export const API_BASE = 'https://nothumanallowed.com/api/v1';
|
|
11
11
|
|
|
@@ -1596,21 +1596,152 @@ class TelegramResponder {
|
|
|
1596
1596
|
}
|
|
1597
1597
|
|
|
1598
1598
|
/**
|
|
1599
|
-
* Detect a generic anaphoric
|
|
1600
|
-
*
|
|
1599
|
+
* Detect a generic anaphoric verb. Covers 15 verbs in IT+EN.
|
|
1600
|
+
* Order matters: more-specific patterns come BEFORE generic delete/edit
|
|
1601
|
+
* (e.g. "spostalo" must NOT be classified as delete because of '...alo').
|
|
1602
|
+
* Returns the verb string or null.
|
|
1601
1603
|
*/
|
|
1602
1604
|
_detectAnaphoricAction(userMessage) {
|
|
1603
1605
|
const t = (userMessage || '').trim();
|
|
1604
1606
|
if (!t) return null;
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
+
|
|
1608
|
+
// YES/CONFIRM — short standalone tokens
|
|
1609
|
+
if (/^\s*(s[ìi]\b|si\s|sì\s|ok\b|okay\b|certo\b|certamente\b|d'?accordo\b|fai\b|fallo|procedi|esegui|conferm[oa]|yes\b|yep\b|confirm\b|do\s*it|go\s*ahead)/i.test(t)) return 'confirm';
|
|
1610
|
+
|
|
1611
|
+
// MOVE/RESCHEDULE — order: BEFORE delete, else "sposta" can be confused
|
|
1612
|
+
if (/\b(spost[ao]l?[oaie]?|sposta|rimand|rinvi[ao]|riprogramm|posticip|sposta?lo|sposta?la|move|reschedul|postpone)\w*/i.test(t)) return 'move';
|
|
1613
|
+
|
|
1614
|
+
// RENAME
|
|
1615
|
+
if (/\b(rinomin|rename|chiamalo|chiamala)\w*/i.test(t)) return 'rename';
|
|
1616
|
+
|
|
1617
|
+
// MARK READ / UNREAD (must come before generic "open/leggi")
|
|
1618
|
+
if (/\b(segna(?:lo|la|li|le)?\s+come\s+(non\s+letto|unread)|mark\s+unread)\b/i.test(t)) return 'mark_unread';
|
|
1619
|
+
if (/\b(segna(?:lo|la|li|le)?\s+come\s+letto|mark\s+(?:as\s+)?read|gi[àa]\s+letto|letto\s+gi[àa])\b/i.test(t)) return 'mark_read';
|
|
1620
|
+
|
|
1621
|
+
// ARCHIVE
|
|
1622
|
+
if (/\b(archivi)\w*/i.test(t)) return 'archive';
|
|
1623
|
+
|
|
1624
|
+
// LABEL / TAG
|
|
1625
|
+
if (/\b(etichett|categoriz|tag\s|aggiungi\s+(?:label|etichetta)|label\b)\w*/i.test(t)) return 'label';
|
|
1626
|
+
|
|
1627
|
+
// FORWARD
|
|
1628
|
+
if (/\b(inoltra|inoltralo|inoltrala|forward|gira(?:lo|la)?)\w*/i.test(t)) return 'forward';
|
|
1629
|
+
|
|
1630
|
+
// SHARE
|
|
1631
|
+
if (/\b(condivid|condividilo|condividila|share|invia\s+link)\w*/i.test(t)) return 'share';
|
|
1632
|
+
|
|
1633
|
+
// PRIORITY change
|
|
1634
|
+
if (/\b(prioriti?z|priority|priorit[àa])\w*/i.test(t)) return 'priority';
|
|
1635
|
+
|
|
1636
|
+
// SNOOZE
|
|
1637
|
+
if (/\b(snooze|rimanda\s+notifica|posticipa\s+(?:notifica|reminder|promemoria))\w*/i.test(t)) return 'snooze';
|
|
1638
|
+
|
|
1639
|
+
// UNDO
|
|
1640
|
+
if (/\b(annulla|undo|disfa|disfa\s+l'?ultima)\w*/i.test(t)) return 'undo';
|
|
1641
|
+
|
|
1642
|
+
// EDIT/MODIFY — generic. Must come AFTER specific edits (rename/label/priority).
|
|
1643
|
+
if (/\b(modifi|aggiorn|cambi(?!a\s+canale)|edit|update)\w*/i.test(t)) return 'edit';
|
|
1644
|
+
|
|
1645
|
+
// DELETE — generic
|
|
1607
1646
|
if (/(cancell|elimin|rimuov|delete|remove)\w*\s*[!.?]?$/i.test(t)) return 'delete';
|
|
1608
|
-
|
|
1647
|
+
|
|
1648
|
+
// COMPLETE / DONE
|
|
1649
|
+
if (/(complet|don[ei]\b|spunt|finit|fatt)\w*\s*[!.?]?$/i.test(t)) return 'complete';
|
|
1650
|
+
|
|
1651
|
+
// REPLY
|
|
1609
1652
|
if (/(rispond|reply|risp\b)\w*\s*[!.?]?$/i.test(t)) return 'reply';
|
|
1610
|
-
|
|
1653
|
+
|
|
1654
|
+
// OPEN / VIEW / READ
|
|
1655
|
+
if (/(apri|open|leggi|read|mostra|view|visualizz)\w*\s*[!.?]?$/i.test(t)) return 'open';
|
|
1656
|
+
|
|
1611
1657
|
return null;
|
|
1612
1658
|
}
|
|
1613
1659
|
|
|
1660
|
+
/**
|
|
1661
|
+
* Extract structured parameters for a given verb against a target item.
|
|
1662
|
+
* Uses cheap regex first, then a tiny LLM NLU call when needed.
|
|
1663
|
+
* Returns {} for verbs that don't need params (delete, complete, archive...).
|
|
1664
|
+
*/
|
|
1665
|
+
async _extractParamsForVerb(verb, kind, userText, config) {
|
|
1666
|
+
const text = String(userText || '');
|
|
1667
|
+
// ── No-param verbs ────────────────────────────────────────────────────
|
|
1668
|
+
if (['delete', 'complete', 'archive', 'mark_read', 'mark_unread', 'share', 'open', 'snooze', 'undo', 'confirm'].includes(verb)) {
|
|
1669
|
+
return {};
|
|
1670
|
+
}
|
|
1671
|
+
|
|
1672
|
+
// ── PRIORITY: regex extract high/medium/low ──────────────────────────
|
|
1673
|
+
if (verb === 'priority') {
|
|
1674
|
+
if (/\b(alta|high|urgent[ei]?|importante)\b/i.test(text)) return { priority: 'high' };
|
|
1675
|
+
if (/\b(media|medium|normale)\b/i.test(text)) return { priority: 'medium' };
|
|
1676
|
+
if (/\b(bassa|low|secondaria|non\s+urgente)\b/i.test(text)) return { priority: 'low' };
|
|
1677
|
+
return {};
|
|
1678
|
+
}
|
|
1679
|
+
|
|
1680
|
+
// ── LABEL: extract quoted or bare label after the keyword ─────────────
|
|
1681
|
+
if (verb === 'label') {
|
|
1682
|
+
const m = text.match(/(?:etichett[ao]|tag(?:ga)?(?:lo|la)?|label)\s+(?:come\s+|with\s+|as\s+)?["']?([\w\-#/]+)["']?/i);
|
|
1683
|
+
if (m) return { label: m[1] };
|
|
1684
|
+
return {};
|
|
1685
|
+
}
|
|
1686
|
+
|
|
1687
|
+
// ── FORWARD: extract recipient email ─────────────────────────────────
|
|
1688
|
+
if (verb === 'forward') {
|
|
1689
|
+
const m = text.match(/[\w.+-]+@[\w-]+\.[\w.-]+/);
|
|
1690
|
+
if (m) return { to: m[0] };
|
|
1691
|
+
return {};
|
|
1692
|
+
}
|
|
1693
|
+
|
|
1694
|
+
// ── RENAME: extract new name (quoted or after "in/a") ─────────────────
|
|
1695
|
+
if (verb === 'rename') {
|
|
1696
|
+
const m1 = text.match(/(?:rinomina(?:lo|la)?|rename(?:\s+it)?|chiamal[oa])\s+(?:in\s+|a\s+|to\s+|as\s+)?["']([^"']+)["']/i);
|
|
1697
|
+
if (m1) return { newName: m1[1] };
|
|
1698
|
+
const m2 = text.match(/(?:rinomina(?:lo|la)?|rename(?:\s+it)?|chiamal[oa])\s+(?:in\s+|a\s+|to\s+|as\s+)?([\w\-./]+\.\w{2,5})/i);
|
|
1699
|
+
if (m2) return { newName: m2[1] };
|
|
1700
|
+
const m3 = text.match(/(?:rinomina(?:lo|la)?|rename(?:\s+it)?|chiamal[oa])\s+(?:in\s+|a\s+|to\s+|as\s+)([^\n!.?]{3,80})/i);
|
|
1701
|
+
if (m3) return { newName: m3[1].trim() };
|
|
1702
|
+
return {};
|
|
1703
|
+
}
|
|
1704
|
+
|
|
1705
|
+
// ── MOVE: needs newStart/newEnd — use existing _nluExtractCalendarMove ──
|
|
1706
|
+
if (verb === 'move' && kind === 'calendar') {
|
|
1707
|
+
try {
|
|
1708
|
+
const parsed = await this._nluExtractCalendarMove(text, config);
|
|
1709
|
+
if (parsed) return { newStart: parsed.newStart, newEnd: parsed.newEnd };
|
|
1710
|
+
} catch {}
|
|
1711
|
+
return {};
|
|
1712
|
+
}
|
|
1713
|
+
|
|
1714
|
+
// ── EDIT: free-form. Tiny LLM extractor returning a partial object ────
|
|
1715
|
+
if (verb === 'edit') {
|
|
1716
|
+
try {
|
|
1717
|
+
const sys =
|
|
1718
|
+
'You are a parameter extractor for an EDIT command. Given the user instruction and ' +
|
|
1719
|
+
`the entity kind (${kind}), return STRICT JSON with the fields to update. ` +
|
|
1720
|
+
'Fields by kind: ' +
|
|
1721
|
+
'calendar={summary?,start?,end?,location?,description?}; ' +
|
|
1722
|
+
'email={subject?,body?}; ' +
|
|
1723
|
+
'task={description?,priority?,due?}; ' +
|
|
1724
|
+
'contact={name?,email?,phone?}; ' +
|
|
1725
|
+
'drive={name?}; note={title?,body?}; ' +
|
|
1726
|
+
'reminder={message?,when?}; gtask={title?,due?}. ' +
|
|
1727
|
+
'ONLY include fields the user explicitly mentioned. No extra prose.';
|
|
1728
|
+
const raw = await callLLM(config, sys, text, { max_tokens: 200, temperature: 0.1 });
|
|
1729
|
+
const m = raw.match(/\{[\s\S]*\}/);
|
|
1730
|
+
if (m) return JSON.parse(m[0]);
|
|
1731
|
+
} catch {}
|
|
1732
|
+
return {};
|
|
1733
|
+
}
|
|
1734
|
+
|
|
1735
|
+
// ── REPLY: body extraction — let downstream handler ask user if missing ──
|
|
1736
|
+
if (verb === 'reply') {
|
|
1737
|
+
const m = text.match(/(?:rispond[ie](?:gli)?|reply)\s+(?:con\s+|with\s+)?["']([^"']+)["']/i);
|
|
1738
|
+
if (m) return { body: m[1] };
|
|
1739
|
+
return {};
|
|
1740
|
+
}
|
|
1741
|
+
|
|
1742
|
+
return {};
|
|
1743
|
+
}
|
|
1744
|
+
|
|
1614
1745
|
/**
|
|
1615
1746
|
* Execute an anaphoric verb (delete/complete/reply/open/confirm) against
|
|
1616
1747
|
* an item resolved by _resolveAnaphoric. The (verb, kind) pair maps to a
|
|
@@ -1624,11 +1755,104 @@ class TelegramResponder {
|
|
|
1624
1755
|
if (chatId) this._recordAudit(chatId, { tool: toolName, success, summary });
|
|
1625
1756
|
return { action: toolName, success, message };
|
|
1626
1757
|
};
|
|
1758
|
+
const safe = async (toolName, args, okMsg, summary) => {
|
|
1759
|
+
try { await executeTool(toolName, args, config); return auditAndReturn(toolName, true, okMsg, summary); }
|
|
1760
|
+
catch (e) { return auditAndReturn(toolName, false, `Errore: ${e.message}`, ''); }
|
|
1761
|
+
};
|
|
1762
|
+
const id = item.eventId || item.messageId || item.fileId || item.taskId || item.id;
|
|
1763
|
+
const label = item.subject || item.summary || item.name || item.title || item.description || item.message || String(id);
|
|
1764
|
+
const params = await this._extractParamsForVerb(verb, kind, userText, config);
|
|
1627
1765
|
|
|
1628
1766
|
// confirm = treat as the pending action (default: delete) when there's
|
|
1629
1767
|
// a single recently-listed item.
|
|
1630
1768
|
const effective = verb === 'confirm' ? 'delete' : verb;
|
|
1631
1769
|
|
|
1770
|
+
// ── MOVE/RESCHEDULE ──────────────────────────────────────────────────
|
|
1771
|
+
if (effective === 'move') {
|
|
1772
|
+
if (kind === 'calendar' && id && params.newStart) {
|
|
1773
|
+
return await safe('calendar_move',
|
|
1774
|
+
{ eventId: id, newStart: params.newStart, newEnd: params.newEnd || this._addMinutesIso(params.newStart, 60) },
|
|
1775
|
+
`Spostato "${label}" a ${this._formatDateIT(params.newStart.slice(0, 10))} alle ${params.newStart.slice(11, 16)}.`, label);
|
|
1776
|
+
}
|
|
1777
|
+
if (kind === 'drive' && id && params.newName) {
|
|
1778
|
+
return await safe('drive_move', { fileId: id, folderId: params.newName }, `Spostato "${label}".`, label);
|
|
1779
|
+
}
|
|
1780
|
+
if (!params.newStart) {
|
|
1781
|
+
return { action: 'move_pending', success: false, message: `A quando vuoi spostare "${label}"? Es. "venerdì 23 maggio alle 15".` };
|
|
1782
|
+
}
|
|
1783
|
+
}
|
|
1784
|
+
|
|
1785
|
+
// ── RENAME ────────────────────────────────────────────────────────────
|
|
1786
|
+
if (effective === 'rename' && params.newName) {
|
|
1787
|
+
if (kind === 'drive' && id) return await safe('drive_rename', { fileId: id, newName: params.newName }, `Rinominato "${label}" → "${params.newName}".`, params.newName);
|
|
1788
|
+
if (kind === 'note' && id) return await safe('note_update', { id, title: params.newName }, `Rinominata la nota "${label}" → "${params.newName}".`, params.newName);
|
|
1789
|
+
if (kind === 'contact' && id) return await safe('contact_update', { id, name: params.newName }, `Rinominato il contatto "${label}" → "${params.newName}".`, params.newName);
|
|
1790
|
+
if (kind === 'task' && id) return await safe('task_edit', { id, description: params.newName }, `Rinominato il task → "${params.newName}".`, params.newName);
|
|
1791
|
+
}
|
|
1792
|
+
if (effective === 'rename' && !params.newName) {
|
|
1793
|
+
return { action: 'rename_pending', success: false, message: `Come vuoi rinominare "${label}"?` };
|
|
1794
|
+
}
|
|
1795
|
+
|
|
1796
|
+
// ── EDIT (free-form fields) ──────────────────────────────────────────
|
|
1797
|
+
if (effective === 'edit' && Object.keys(params).length > 0 && id) {
|
|
1798
|
+
if (kind === 'calendar') return await safe('calendar_update', { eventId: id, ...params }, `Aggiornato "${label}".`, label);
|
|
1799
|
+
if (kind === 'email') return await safe('gmail_draft_update', { messageId: id, ...params }, `Aggiornata la bozza email.`, label);
|
|
1800
|
+
if (kind === 'task') return await safe('task_edit', { id, ...params }, `Aggiornato il task "${label}".`, label);
|
|
1801
|
+
if (kind === 'contact') return await safe('contact_update', { id, ...params }, `Aggiornato il contatto "${label}".`, label);
|
|
1802
|
+
if (kind === 'note') return await safe('note_update', { id, ...params }, `Aggiornata la nota "${label}".`, label);
|
|
1803
|
+
if (kind === 'reminder') return await safe('reminder_update', { id, ...params }, `Aggiornato il promemoria.`, label);
|
|
1804
|
+
if (kind === 'gtask') return await safe('gtask_edit', { id, ...params }, `Aggiornato il task Google.`, label);
|
|
1805
|
+
if (kind === 'drive' && params.name) return await safe('drive_rename', { fileId: id, newName: params.name }, `Rinominato il file → "${params.name}".`, params.name);
|
|
1806
|
+
}
|
|
1807
|
+
if (effective === 'edit' && Object.keys(params).length === 0) {
|
|
1808
|
+
return { action: 'edit_pending', success: false, message: `Cosa vuoi modificare di "${label}"? (es. titolo, orario, descrizione, priorità)` };
|
|
1809
|
+
}
|
|
1810
|
+
|
|
1811
|
+
// ── MARK READ/UNREAD (email) ─────────────────────────────────────────
|
|
1812
|
+
if (effective === 'mark_read' && kind === 'email' && id) {
|
|
1813
|
+
return await safe('gmail_mark_read', { messageId: id }, `Segnata come letta: "${label}".`, label);
|
|
1814
|
+
}
|
|
1815
|
+
if (effective === 'mark_unread' && kind === 'email' && id) {
|
|
1816
|
+
return await safe('gmail_mark_unread', { messageId: id }, `Segnata come NON letta: "${label}".`, label);
|
|
1817
|
+
}
|
|
1818
|
+
|
|
1819
|
+
// ── ARCHIVE ──────────────────────────────────────────────────────────
|
|
1820
|
+
if (effective === 'archive') {
|
|
1821
|
+
if (kind === 'email' && id) return await safe('gmail_archive', { messageId: id }, `Archiviata email "${label}".`, label);
|
|
1822
|
+
if (kind === 'task' && id) return await safe('task_archive', { id }, `Archiviato task "${label}".`, label);
|
|
1823
|
+
}
|
|
1824
|
+
|
|
1825
|
+
// ── LABEL/TAG ────────────────────────────────────────────────────────
|
|
1826
|
+
if (effective === 'label' && params.label) {
|
|
1827
|
+
if (kind === 'email' && id) return await safe('gmail_label', { messageId: id, label: params.label }, `Aggiunta etichetta "${params.label}" a "${label}".`, params.label);
|
|
1828
|
+
if (kind === 'task' && id) return await safe('task_tag', { id, tag: params.label }, `Aggiunto tag "${params.label}" al task.`, params.label);
|
|
1829
|
+
}
|
|
1830
|
+
if (effective === 'label' && !params.label) {
|
|
1831
|
+
return { action: 'label_pending', success: false, message: `Quale etichetta vuoi applicare a "${label}"?` };
|
|
1832
|
+
}
|
|
1833
|
+
|
|
1834
|
+
// ── FORWARD (email) ──────────────────────────────────────────────────
|
|
1835
|
+
if (effective === 'forward' && kind === 'email' && id) {
|
|
1836
|
+
if (!params.to) return { action: 'forward_pending', success: false, message: `A chi vuoi inoltrare "${label}"?` };
|
|
1837
|
+
return await safe('gmail_forward', { messageId: id, to: params.to }, `Inoltrata "${label}" a ${params.to}.`, params.to);
|
|
1838
|
+
}
|
|
1839
|
+
|
|
1840
|
+
// ── SHARE (drive, calendar) ──────────────────────────────────────────
|
|
1841
|
+
if (effective === 'share') {
|
|
1842
|
+
if (kind === 'drive' && id) return await safe('drive_share', { fileId: id }, `Condiviso "${label}" (link copiato).`, label);
|
|
1843
|
+
}
|
|
1844
|
+
|
|
1845
|
+
// ── PRIORITY change (task) ───────────────────────────────────────────
|
|
1846
|
+
if (effective === 'priority' && params.priority) {
|
|
1847
|
+
if (kind === 'task' && id) return await safe('task_edit', { id, priority: params.priority }, `Priorità di "${label}" → ${params.priority}.`, params.priority);
|
|
1848
|
+
if (kind === 'gtask' && id) return await safe('gtask_edit', { id, priority: params.priority }, `Priorità task Google → ${params.priority}.`, params.priority);
|
|
1849
|
+
}
|
|
1850
|
+
|
|
1851
|
+
// ── SNOOZE (reminder) ───────────────────────────────────────────────
|
|
1852
|
+
if (effective === 'snooze' && kind === 'reminder' && id) {
|
|
1853
|
+
return await safe('reminder_snooze', { id, minutes: 30 }, `Posticipato di 30 minuti il promemoria "${label}".`, label);
|
|
1854
|
+
}
|
|
1855
|
+
|
|
1632
1856
|
// ── DELETE family ─────────────────────────────────────────────────────
|
|
1633
1857
|
if (effective === 'delete') {
|
|
1634
1858
|
if (kind === 'calendar' && item.eventId) {
|
|
@@ -3191,6 +3191,119 @@ export async function executeTool(action, params, config) {
|
|
|
3191
3191
|
return `File ${params.fileId} moved to trash.`;
|
|
3192
3192
|
}
|
|
3193
3193
|
|
|
3194
|
+
case 'drive_rename': {
|
|
3195
|
+
if (!params.fileId || !params.newName) return 'Error: fileId and newName required.';
|
|
3196
|
+
const token = await (await import('./token-store.mjs')).getAccessToken(config);
|
|
3197
|
+
const res = await fetch(`https://www.googleapis.com/drive/v3/files/${params.fileId}?fields=id,name,webViewLink`, {
|
|
3198
|
+
method: 'PATCH',
|
|
3199
|
+
headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' },
|
|
3200
|
+
body: JSON.stringify({ name: params.newName }),
|
|
3201
|
+
});
|
|
3202
|
+
if (!res.ok) {
|
|
3203
|
+
const err = await res.text();
|
|
3204
|
+
throw new Error(`Drive rename ${res.status}: ${err.slice(0, 200)}`);
|
|
3205
|
+
}
|
|
3206
|
+
const data = await res.json();
|
|
3207
|
+
return `Renamed: ${data.name} — ${data.webViewLink || data.id}`;
|
|
3208
|
+
}
|
|
3209
|
+
|
|
3210
|
+
case 'drive_move': {
|
|
3211
|
+
if (!params.fileId || !params.folderId) return 'Error: fileId and folderId required.';
|
|
3212
|
+
const token = await (await import('./token-store.mjs')).getAccessToken(config);
|
|
3213
|
+
// Need to first get current parents to remove them
|
|
3214
|
+
const cur = await fetch(`https://www.googleapis.com/drive/v3/files/${params.fileId}?fields=parents`, { headers: { 'Authorization': `Bearer ${token}` } });
|
|
3215
|
+
const curData = await cur.json();
|
|
3216
|
+
const oldParents = (curData.parents || []).join(',');
|
|
3217
|
+
const res = await fetch(`https://www.googleapis.com/drive/v3/files/${params.fileId}?addParents=${params.folderId}&removeParents=${oldParents}&fields=id,name,parents`, {
|
|
3218
|
+
method: 'PATCH',
|
|
3219
|
+
headers: { 'Authorization': `Bearer ${token}` },
|
|
3220
|
+
});
|
|
3221
|
+
if (!res.ok) throw new Error(`Drive move ${res.status}: ${(await res.text()).slice(0, 200)}`);
|
|
3222
|
+
return `Moved: ${(await res.json()).name}`;
|
|
3223
|
+
}
|
|
3224
|
+
|
|
3225
|
+
case 'drive_share': {
|
|
3226
|
+
if (!params.fileId) return 'Error: fileId required.';
|
|
3227
|
+
const token = await (await import('./token-store.mjs')).getAccessToken(config);
|
|
3228
|
+
const role = params.role || 'reader';
|
|
3229
|
+
const res = await fetch(`https://www.googleapis.com/drive/v3/files/${params.fileId}/permissions`, {
|
|
3230
|
+
method: 'POST',
|
|
3231
|
+
headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' },
|
|
3232
|
+
body: JSON.stringify({ type: 'anyone', role }),
|
|
3233
|
+
});
|
|
3234
|
+
if (!res.ok) throw new Error(`Drive share ${res.status}: ${(await res.text()).slice(0, 200)}`);
|
|
3235
|
+
const meta = await fetch(`https://www.googleapis.com/drive/v3/files/${params.fileId}?fields=webViewLink,name`, { headers: { 'Authorization': `Bearer ${token}` } });
|
|
3236
|
+
const m = await meta.json();
|
|
3237
|
+
return `Shared "${m.name}" (${role}): ${m.webViewLink}`;
|
|
3238
|
+
}
|
|
3239
|
+
|
|
3240
|
+
case 'gmail_label': {
|
|
3241
|
+
if (!params.messageId || !params.label) return 'Error: messageId and label required.';
|
|
3242
|
+
const token = await (await import('./token-store.mjs')).getAccessToken(config);
|
|
3243
|
+
// Find or create the label by name
|
|
3244
|
+
const labelsRes = await fetch('https://gmail.googleapis.com/gmail/v1/users/me/labels', { headers: { 'Authorization': `Bearer ${token}` } });
|
|
3245
|
+
const labelsData = await labelsRes.json();
|
|
3246
|
+
let labelId = (labelsData.labels || []).find(l => l.name.toLowerCase() === params.label.toLowerCase())?.id;
|
|
3247
|
+
if (!labelId) {
|
|
3248
|
+
const createRes = await fetch('https://gmail.googleapis.com/gmail/v1/users/me/labels', {
|
|
3249
|
+
method: 'POST',
|
|
3250
|
+
headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' },
|
|
3251
|
+
body: JSON.stringify({ name: params.label, labelListVisibility: 'labelShow', messageListVisibility: 'show' }),
|
|
3252
|
+
});
|
|
3253
|
+
if (!createRes.ok) throw new Error(`Label create ${createRes.status}`);
|
|
3254
|
+
labelId = (await createRes.json()).id;
|
|
3255
|
+
}
|
|
3256
|
+
const res = await fetch(`https://gmail.googleapis.com/gmail/v1/users/me/messages/${params.messageId}/modify`, {
|
|
3257
|
+
method: 'POST',
|
|
3258
|
+
headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' },
|
|
3259
|
+
body: JSON.stringify({ addLabelIds: [labelId] }),
|
|
3260
|
+
});
|
|
3261
|
+
if (!res.ok) throw new Error(`Gmail label ${res.status}: ${(await res.text()).slice(0, 200)}`);
|
|
3262
|
+
return `Applied label "${params.label}" to message.`;
|
|
3263
|
+
}
|
|
3264
|
+
|
|
3265
|
+
case 'gmail_forward': {
|
|
3266
|
+
if (!params.messageId || !params.to) return 'Error: messageId and to required.';
|
|
3267
|
+
const { getMessage, sendEmail } = await import('./google-gmail.mjs');
|
|
3268
|
+
const orig = await getMessage(config, params.messageId);
|
|
3269
|
+
const body = `\n\n---------- Messaggio inoltrato ----------\nDa: ${orig.from}\nData: ${orig.date}\nOggetto: ${orig.subject}\n\n${orig.body}`;
|
|
3270
|
+
await sendEmail(config, {
|
|
3271
|
+
to: params.to,
|
|
3272
|
+
subject: `Fwd: ${orig.subject}`,
|
|
3273
|
+
body: (params.note || '') + body,
|
|
3274
|
+
});
|
|
3275
|
+
return `Forwarded "${orig.subject}" to ${params.to}.`;
|
|
3276
|
+
}
|
|
3277
|
+
|
|
3278
|
+
case 'gtask_update': {
|
|
3279
|
+
if (!params.id) return 'Error: id required.';
|
|
3280
|
+
const token = await (await import('./token-store.mjs')).getAccessToken(config);
|
|
3281
|
+
const listId = params.listId || '@default';
|
|
3282
|
+
const patch = {};
|
|
3283
|
+
if (params.title) patch.title = params.title;
|
|
3284
|
+
if (params.due) patch.due = new Date(params.due).toISOString();
|
|
3285
|
+
if (params.notes) patch.notes = params.notes;
|
|
3286
|
+
const res = await fetch(`https://tasks.googleapis.com/tasks/v1/lists/${listId}/tasks/${params.id}`, {
|
|
3287
|
+
method: 'PATCH',
|
|
3288
|
+
headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' },
|
|
3289
|
+
body: JSON.stringify(patch),
|
|
3290
|
+
});
|
|
3291
|
+
if (!res.ok) throw new Error(`GTask update ${res.status}: ${(await res.text()).slice(0, 200)}`);
|
|
3292
|
+
return `Updated Google Task.`;
|
|
3293
|
+
}
|
|
3294
|
+
|
|
3295
|
+
case 'gtask_delete': {
|
|
3296
|
+
if (!params.id) return 'Error: id required.';
|
|
3297
|
+
const token = await (await import('./token-store.mjs')).getAccessToken(config);
|
|
3298
|
+
const listId = params.listId || '@default';
|
|
3299
|
+
const res = await fetch(`https://tasks.googleapis.com/tasks/v1/lists/${listId}/tasks/${params.id}`, {
|
|
3300
|
+
method: 'DELETE',
|
|
3301
|
+
headers: { 'Authorization': `Bearer ${token}` },
|
|
3302
|
+
});
|
|
3303
|
+
if (!res.ok && res.status !== 204) throw new Error(`GTask delete ${res.status}`);
|
|
3304
|
+
return `Deleted Google Task.`;
|
|
3305
|
+
}
|
|
3306
|
+
|
|
3194
3307
|
case 'drive_info': {
|
|
3195
3308
|
if (!params.fileId) return 'Error: fileId required.';
|
|
3196
3309
|
const drv = await import('./google-drive.mjs');
|