@wanasapps/deluge-core 1.0.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/DELUGE_GUIDE.md +312 -0
- package/assets/DELUGE_REFERENCE.md +956 -0
- package/assets/DELUGE_SKILL.md +391 -0
- package/assets/deluge-reference.json +2648 -0
- package/package.json +11 -4
- package/scripts/gen-skill.js +137 -0
- package/scripts/scrape-deluge-docs.js +196 -0
- package/src/index.js +4 -0
- package/src/language/index.js +33 -2
package/package.json
CHANGED
|
@@ -1,17 +1,20 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wanasapps/deluge-core",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "Deluge language tooling for every Zoho product
|
|
3
|
+
"version": "1.2.0",
|
|
4
|
+
"description": "Deluge language tooling for every Zoho product: formatter, source transforms, TextMate grammar, snippets, an editor-agnostic linter, and AI-agent skills \u2014 a guide with runtime traps verified on a live org, plus a complete reference of every statement, data type, built-in function and integration task generated from Zoho's Deluge documentation. No I/O, no HTTP, no editor dependency.",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"files": [
|
|
7
7
|
"src",
|
|
8
8
|
"assets",
|
|
9
|
+
"scripts",
|
|
9
10
|
"README.md",
|
|
10
11
|
"LICENSE"
|
|
11
12
|
],
|
|
12
13
|
"scripts": {
|
|
13
14
|
"test": "node test/run.js",
|
|
14
|
-
"prepublishOnly": "node test/run.js"
|
|
15
|
+
"prepublishOnly": "node scripts/gen-skill.js && node test/run.js",
|
|
16
|
+
"gen:skill": "node scripts/gen-skill.js",
|
|
17
|
+
"scrape:docs": "node scripts/scrape-deluge-docs.js"
|
|
15
18
|
},
|
|
16
19
|
"keywords": [
|
|
17
20
|
"deluge",
|
|
@@ -22,7 +25,11 @@
|
|
|
22
25
|
"linter",
|
|
23
26
|
"textmate",
|
|
24
27
|
"grammar",
|
|
25
|
-
"language"
|
|
28
|
+
"language",
|
|
29
|
+
"skill",
|
|
30
|
+
"ai-agent",
|
|
31
|
+
"claude-code",
|
|
32
|
+
"cursor"
|
|
26
33
|
],
|
|
27
34
|
"author": "Wanas Apps FZ-LLC",
|
|
28
35
|
"license": "MIT",
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Render the two agent-facing Deluge documents from two sources:
|
|
4
|
+
*
|
|
5
|
+
* assets/DELUGE_GUIDE.md hand-written: how to think, contracts, verified traps
|
|
6
|
+
* assets/deluge-reference.json generated from Zoho's Deluge help (scripts/scrape-deluge-docs.js):
|
|
7
|
+
* every statement, data type, built-in function, integration task
|
|
8
|
+
*
|
|
9
|
+
* Outputs:
|
|
10
|
+
* assets/DELUGE_SKILL.md the skill an agent loads whenever it writes Deluge:
|
|
11
|
+
* the guide + a complete INDEX of every function and task
|
|
12
|
+
* by name (~6k tokens), pointing at the reference for syntax
|
|
13
|
+
* assets/DELUGE_REFERENCE.md the full reference: statements, data types, operators,
|
|
14
|
+
* system variables, every function and task with Zoho's own
|
|
15
|
+
* syntax, returns and description, documented limits (~25k)
|
|
16
|
+
*
|
|
17
|
+
* Two files because only a skill's description is resident per prompt while
|
|
18
|
+
* its body loads on trigger: the guide is what every Deluge task needs; the
|
|
19
|
+
* exact signature of one function is needed rarely, so it costs nothing until
|
|
20
|
+
* asked for. Edit the sources, never the outputs; `npm run gen:skill` rebuilds
|
|
21
|
+
* them and the tests fail when they are stale.
|
|
22
|
+
*/
|
|
23
|
+
const fs = require('fs');
|
|
24
|
+
const path = require('path');
|
|
25
|
+
|
|
26
|
+
const ASSETS = path.join(__dirname, '..', 'assets');
|
|
27
|
+
const guide = fs.readFileSync(path.join(ASSETS, 'DELUGE_GUIDE.md'), 'utf8');
|
|
28
|
+
const ref = JSON.parse(fs.readFileSync(path.join(ASSETS, 'deluge-reference.json'), 'utf8'));
|
|
29
|
+
|
|
30
|
+
const esc = (s) => String(s || '').replace(/\|/g, '\\|').replace(/\s+/g, ' ').trim();
|
|
31
|
+
const code = (s) => `\`${String(s || '').replace(/`/g, "'").replace(/\s+/g, ' ').trim()}\``;
|
|
32
|
+
/** Trim the TOC/footer text that occasionally trails a section on Zoho's pages. */
|
|
33
|
+
const untoc = (s) => String(s || '').split(/Related Links|Sample Response|Table of Contents|Get Started Now/)[0];
|
|
34
|
+
/** First syntax line only, for tables; the "(OR)" alternates are noise there. */
|
|
35
|
+
const firstSyntax = (s) => untoc(s).split('\n').map((l) => l.trim()).filter((l) => l && !/^\(OR\)$/i.test(l))[0] || '';
|
|
36
|
+
const byName = (a, b) => a.name.localeCompare(b.name, 'en', { sensitivity: 'base' });
|
|
37
|
+
|
|
38
|
+
const CAT_ORDER = ['Text', 'Number', 'List', 'Key-value (Map)', 'Collection', 'Date-time', 'Common / conversion', 'Encryption', 'XML'];
|
|
39
|
+
const LABEL = { crm: 'Zoho CRM', books: 'Zoho Books', desk: 'Zoho Desk', creator: 'Zoho Creator', people: 'Zoho People', projects: 'Zoho Projects', recruit: 'Zoho Recruit', mail: 'Zoho Mail', cliq: 'Zoho Cliq', sheet: 'Zoho Sheet', writer: 'Zoho Writer', workdrive: 'Zoho WorkDrive', analytics: 'Zoho Analytics', invoice: 'Zoho Invoice', inventory: 'Zoho Inventory', subscriptions: 'Zoho Billing (Subscriptions)', bookings: 'Zoho Bookings', calendar: 'Zoho Calendar', salesiq: 'Zoho SalesIQ' };
|
|
40
|
+
|
|
41
|
+
function grouped(items, keyOf) {
|
|
42
|
+
const m = new Map();
|
|
43
|
+
for (const it of items || []) {
|
|
44
|
+
const k = keyOf(it);
|
|
45
|
+
if (!m.has(k)) m.set(k, []);
|
|
46
|
+
m.get(k).push(it);
|
|
47
|
+
}
|
|
48
|
+
return m;
|
|
49
|
+
}
|
|
50
|
+
const fnByCat = grouped(ref.functions, (f) => f.category);
|
|
51
|
+
const taskByProd = grouped(ref.tasks, (t) => (t.product === 'subscription' ? 'subscriptions' : t.product));
|
|
52
|
+
const catOrder = [...CAT_ORDER, ...[...fnByCat.keys()].filter((k) => !CAT_ORDER.includes(k))].filter((k) => fnByCat.has(k));
|
|
53
|
+
const prodOrder = ['crm', ...[...taskByProd.keys()].filter((k) => k !== 'crm').sort()];
|
|
54
|
+
|
|
55
|
+
// ---------------------------------------------------------------------------
|
|
56
|
+
// DELUGE_SKILL.md — guide + complete name index
|
|
57
|
+
// ---------------------------------------------------------------------------
|
|
58
|
+
const skill = [];
|
|
59
|
+
skill.push(guide.trimEnd(), '', '---', '');
|
|
60
|
+
skill.push('# Part 2 — Everything that exists (names only)', '');
|
|
61
|
+
skill.push(`Every documented statement, built-in function and integration task, by name, generated from ${ref.source}. If a name is not here, it does not exist in Deluge — do not invent one. For a function's exact syntax, parameters and return type, load the companion skill \`zoho-deluge-reference\` (or run \`zone llm deluge-reference\`).`, '');
|
|
62
|
+
|
|
63
|
+
skill.push('## Statements', '');
|
|
64
|
+
skill.push((ref.statements || []).map((s) => `\`${s.name}\``).concat(['`invokeurl`', '`info`', '`return`']).join(' · '), '');
|
|
65
|
+
|
|
66
|
+
skill.push('## Data types', '');
|
|
67
|
+
skill.push((ref.datatypes || []).map((d) => `\`${d.name}\``).join(' · '), '');
|
|
68
|
+
|
|
69
|
+
skill.push('## Built-in functions', '');
|
|
70
|
+
for (const cat of catOrder) {
|
|
71
|
+
const names = fnByCat.get(cat).slice().sort(byName).map((f) => `\`${f.name}\``);
|
|
72
|
+
skill.push(`**${cat}** (${names.length}): ${names.join(', ')}`, '');
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
skill.push('## Integration tasks', '');
|
|
76
|
+
for (const prod of prodOrder) {
|
|
77
|
+
const names = taskByProd.get(prod).slice().sort(byName).map((t) => `\`${t.name}\``);
|
|
78
|
+
skill.push(`**${LABEL[prod] || prod}** (${names.length}): ${names.join(', ')}`, '');
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
skill.push('## System variables', '');
|
|
82
|
+
skill.push('`zoho.currentdate`, `zoho.currenttime`, `zoho.loginuser`, `zoho.loginuser.name` (Creator), `zoho.loginuserid`, `zoho.adminuser`, `zoho.adminuserid`, `zoho.appname` (Creator), `zoho.appuri` (Creator), `zoho.ipaddress`, `zoho.device.type` (Creator).', '');
|
|
83
|
+
|
|
84
|
+
// ---------------------------------------------------------------------------
|
|
85
|
+
// DELUGE_REFERENCE.md — the full thing
|
|
86
|
+
// ---------------------------------------------------------------------------
|
|
87
|
+
const out = [];
|
|
88
|
+
out.push('# Zoho Deluge — complete reference', '');
|
|
89
|
+
out.push(`Generated from ${ref.source}. Every entry is one documented statement, data type, built-in function or integration task; the syntax is Zoho's own. The companion \`zoho-deluge\` skill carries the guidance and the traps verified on a live org; where its **(verified)** notes disagree with wording here, they take precedence.`, '');
|
|
90
|
+
|
|
91
|
+
out.push('## Statements', '');
|
|
92
|
+
for (const s of ref.statements || []) {
|
|
93
|
+
out.push(`### ${s.name}`, '');
|
|
94
|
+
if (s.overview) out.push(untoc(s.overview).trim(), '');
|
|
95
|
+
if (s.syntax) out.push('```deluge', untoc(s.syntax).trim(), '```', '');
|
|
96
|
+
}
|
|
97
|
+
if (ref.invokeurl) {
|
|
98
|
+
out.push('### invokeurl', '');
|
|
99
|
+
if (ref.invokeurl.overview) out.push(untoc(ref.invokeurl.overview).trim(), '');
|
|
100
|
+
out.push('```deluge', untoc(ref.invokeurl.syntax).trim(), '```', '');
|
|
101
|
+
if (ref.invokeurl.params) out.push('Parameters, as documented:', '', '```text', untoc(ref.invokeurl.params).trim(), '```', '');
|
|
102
|
+
}
|
|
103
|
+
out.push('### info / return', '', '`info <expression>;` writes to the execution log. `return <expression>;` (or bare `return;` in a `void` function) ends the function with that value.', '');
|
|
104
|
+
|
|
105
|
+
out.push('## Data types', '', '| Type | Summary | Example |', '|---|---|---|');
|
|
106
|
+
for (const d of ref.datatypes || []) out.push(`| **${esc(d.name)}** | ${esc(untoc(d.notes || d.overview)).slice(0, 380)} | ${d.example ? code(d.example.split('\n')[0]) : ''} |`);
|
|
107
|
+
out.push('', 'Reserved keywords (not usable as variable names): ' + (ref.keywords || []).map((k) => `\`${k}\``).join(', '), '');
|
|
108
|
+
|
|
109
|
+
if (ref.operators) out.push('## Operators', '', '```text', ref.operators.trim(), '```', '');
|
|
110
|
+
if (ref.systemVariables) out.push('## System variables', '', '```text', ref.systemVariables.trim(), '```', '');
|
|
111
|
+
|
|
112
|
+
out.push('## Built-in functions', '', "Columns: how it is called (Zoho's syntax, first form), what it returns, what it does. `<variable>` is the result; the receiver is the value before the dot.", '');
|
|
113
|
+
for (const cat of catOrder) {
|
|
114
|
+
const list = fnByCat.get(cat).slice().sort(byName);
|
|
115
|
+
out.push(`### ${cat} (${list.length})`, '', '| Function | Syntax | Returns | Does |', '|---|---|---|---|');
|
|
116
|
+
for (const f of list) out.push(`| \`${f.name}\` | ${code(firstSyntax(f.syntax))} | ${esc(f.returns)} | ${esc(untoc(f.overview))} |`);
|
|
117
|
+
out.push('');
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
out.push('## Integration tasks (`zoho.<product>.*`)', '', 'Each task is one API call against a daily limit of 2,000 per user; inside a loop it counts once per iteration. Product-specific arguments (connection names, module names, ids) follow the REST API behind each task.', '');
|
|
121
|
+
for (const prod of prodOrder) {
|
|
122
|
+
const list = taskByProd.get(prod).slice().sort(byName);
|
|
123
|
+
out.push(`### ${LABEL[prod] || prod} (${list.length})`, '', '| Task | Syntax | Does |', '|---|---|---|');
|
|
124
|
+
for (const t of list) out.push(`| \`${esc(t.name)}\` | ${code(firstSyntax(t.syntax))} | ${esc(untoc(t.overview))} |`);
|
|
125
|
+
out.push('');
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (ref.limits) out.push('## Documented limits', '', '```text', ref.limits.trim(), '```', '');
|
|
129
|
+
|
|
130
|
+
const tidy = (arr) => arr.join('\n').replace(/\n{3,}/g, '\n\n') + '\n';
|
|
131
|
+
const skillText = tidy(skill);
|
|
132
|
+
const refText = tidy(out);
|
|
133
|
+
fs.writeFileSync(path.join(ASSETS, 'DELUGE_SKILL.md'), skillText);
|
|
134
|
+
fs.writeFileSync(path.join(ASSETS, 'DELUGE_REFERENCE.md'), refText);
|
|
135
|
+
const tok = (s) => Math.round(s.length / 4).toLocaleString('en-US');
|
|
136
|
+
console.log(`DELUGE_SKILL.md ${skillText.length.toLocaleString('en-US')} chars (~${tok(skillText)} tokens)`);
|
|
137
|
+
console.log(`DELUGE_REFERENCE.md ${refText.length.toLocaleString('en-US')} chars (~${tok(refText)} tokens) — ${(ref.functions || []).length} functions, ${(ref.tasks || []).length} tasks, ${(ref.statements || []).length} statements, ${(ref.datatypes || []).length} data types`);
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Rebuild assets/deluge-reference.json from Zoho's Deluge documentation.
|
|
4
|
+
*
|
|
5
|
+
* npm run scrape:docs # then: npm run gen:skill
|
|
6
|
+
*
|
|
7
|
+
* Why a scraper and not a hand-written list: the reference has 209 functions
|
|
8
|
+
* and 139 integration tasks. Typing them from memory is how an agent ends up
|
|
9
|
+
* with `getJSONType()` — a function that does not exist. Every entry here is
|
|
10
|
+
* read from the page Zoho publishes for it, and the page URL is kept so a
|
|
11
|
+
* disputed signature can be checked at the source.
|
|
12
|
+
*
|
|
13
|
+
* How the site is laid out (discovered, not documented — the navigation is
|
|
14
|
+
* rendered client-side, so category and product index pages are fetched
|
|
15
|
+
* directly and their links followed):
|
|
16
|
+
*
|
|
17
|
+
* /deluge/help/functions/<category>.html category index -> /functions/<cat>/<fn>.html
|
|
18
|
+
* /deluge/help/<product>-tasks.html product index -> /<product>/<task>.html
|
|
19
|
+
* /deluge/help/conditional-statements/*.html, /misc-statements/*.html, /list-manipulations/*.html
|
|
20
|
+
* /deluge/help/web-data/invokeurl-task.html, /datatypes/*.html, operators.html, system-variables.html, limitations.html
|
|
21
|
+
*
|
|
22
|
+
* Each page has a client-side TOC that repeats the heading names, so sections
|
|
23
|
+
* are located by their <h2>/<h3> tag (not by the first occurrence of the word).
|
|
24
|
+
* Network only; no dependency beyond Node's fetch.
|
|
25
|
+
*/
|
|
26
|
+
const fs = require('fs');
|
|
27
|
+
const path = require('path');
|
|
28
|
+
|
|
29
|
+
const BASE = 'https://www.zoho.com';
|
|
30
|
+
const HELP = `${BASE}/deluge/help/`;
|
|
31
|
+
const OUT = path.join(__dirname, '..', 'assets', 'deluge-reference.json');
|
|
32
|
+
const UA = 'Mozilla/5.0 (deluge-core reference scraper; +https://www.npmjs.com/package/@wanasapps/deluge-core)';
|
|
33
|
+
|
|
34
|
+
const FUNCTION_CATEGORIES = ['text', 'number', 'list', 'key-value', 'date-time', 'time', 'logical', 'collection', 'conversion', 'type-check', 'utilities', 'xml'];
|
|
35
|
+
const FUNCTION_EXTRA_INDEXES = ['functions-returning-boolean', 'encryption-tasks'];
|
|
36
|
+
const PRODUCT_INDEXES = ['crm-integration-tasks-V8', 'crm-tasks', 'books-tasks', 'creator-tasks', 'desk-tasks', 'people-tasks', 'mail-tasks', 'cliq-tasks',
|
|
37
|
+
'projects-tasks', 'recruit-tasks', 'sheet-tasks', 'writer-tasks', 'workdrive-tasks', 'analytics-tasks', 'invoice-tasks', 'inventory-tasks',
|
|
38
|
+
'subscriptions-tasks', 'bookings-tasks', 'calendar-tasks', 'salesiq-tasks'];
|
|
39
|
+
const STATEMENTS = [
|
|
40
|
+
['conditional-statements/condition', 'if / else if / else / conditional if / ifNull'],
|
|
41
|
+
['list-manipulations/for-each-element', 'for each'],
|
|
42
|
+
['misc-statements/break', 'break'], ['misc-statements/continue', 'continue'],
|
|
43
|
+
['misc-statements/try-catch', 'try / catch'], ['misc-statements/throw', 'throw'],
|
|
44
|
+
['misc-statements/send-mail', 'sendmail']
|
|
45
|
+
];
|
|
46
|
+
const DATATYPES = ['datatypes/text', 'datatypes/number', 'datatypes/decimal', 'datatypes/boolean', 'datatypes/date-time', 'datatypes/time', 'datatypes/list', 'datatypes/key-value', 'datatypes/collection', 'create-list-datatype'];
|
|
47
|
+
const CATEGORY_LABEL = { string: 'Text', text: 'Text', number: 'Number', list: 'List', map: 'Key-value (Map)', datetime: 'Date-time', collection: 'Collection', common: 'Common / conversion', encryption: 'Encryption', xml: 'XML' };
|
|
48
|
+
|
|
49
|
+
/** Pages whose indexed URL 404s; Zoho's own search snippets describe them. */
|
|
50
|
+
const MOVED_FUNCTIONS = [
|
|
51
|
+
['getPrefixIgnoreCase', 'Text', '<variable> = <text>.getPrefixIgnoreCase(<search_text>);', 'Gets the prefix of the specified search text in the input text, performing a case-insensitive search.'],
|
|
52
|
+
['getSuffixIgnoreCase', 'Text', '<variable> = <text>.getSuffixIgnoreCase(<search_text>);', 'Gets the suffix of the specified search text in the input text, performing a case-insensitive search.'],
|
|
53
|
+
['replaceAllIgnoreCase', 'Text', '<variable> = <text>.replaceAllIgnoreCase(<search_text>, <new_text>);', 'Replaces all occurrences of the search text with the new text, performing a case-insensitive search.'],
|
|
54
|
+
['replaceFirstIgnoreCase', 'Text', '<variable> = <text>.replaceFirstIgnoreCase(<search_text>, <new_text>);', 'Replaces the first occurrence of the search text with the new text, performing a case-insensitive search.'],
|
|
55
|
+
['isAscii', 'Text', '<variable> = <text>.isAscii();', 'Returns true if every character of the text is ASCII.'],
|
|
56
|
+
['previousWeekDay', 'Date-time', '<variable> = <date>.previousWeekDay();', 'Returns the previous weekday (Monday to Friday) before the given date.']
|
|
57
|
+
];
|
|
58
|
+
|
|
59
|
+
// ---- html helpers --------------------------------------------------------------
|
|
60
|
+
|
|
61
|
+
const ZWSP = //g;
|
|
62
|
+
function clean(html) {
|
|
63
|
+
let t = html.replace(/<script[\s\S]*?<\/script>|<style[\s\S]*?<\/style>/gi, '');
|
|
64
|
+
t = t.replace(/<br\s*\/?>/gi, '\n').replace(/<\/(p|div|li|tr|h[1-6]|pre|td|th)>/gi, '\n');
|
|
65
|
+
t = t.replace(/<[^>]+>/g, '');
|
|
66
|
+
t = t.replace(/ /g, ' ').replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"').replace(/'/g, "'");
|
|
67
|
+
t = t.replace(ZWSP, '').replace(/ /g, ' ').replace(/�/g, ' ');
|
|
68
|
+
return t.replace(/[ \t]+/g, ' ').replace(/\n\s*\n+/g, '\n\n');
|
|
69
|
+
}
|
|
70
|
+
function section(html, name) {
|
|
71
|
+
const re = new RegExp(`<h[23][^>]*>(?:<a[^>]*></a>)?\\s*${name}\\s*</h[23]>([\\s\\S]*?)(?=<h[23]|$)`, 'i');
|
|
72
|
+
const m = re.exec(html);
|
|
73
|
+
return m ? clean(m[1]).trim() : '';
|
|
74
|
+
}
|
|
75
|
+
const first = (s) => (s.trim() ? s.trim().split(/(?<=[.!?])\s/)[0] : '');
|
|
76
|
+
const untoc = (s) => String(s || '').split(/Related Links|Sample Response|Table of Contents|Get Started Now/)[0];
|
|
77
|
+
|
|
78
|
+
async function get(url) {
|
|
79
|
+
const r = await fetch(url, { headers: { 'User-Agent': UA } });
|
|
80
|
+
if (!r.ok) return null;
|
|
81
|
+
return (await r.text()).replace(ZWSP, '');
|
|
82
|
+
}
|
|
83
|
+
const helpLinks = (html) => [...new Set([...html.matchAll(/href="([^"#?]*\/deluge\/help\/[^"#?]*\.html)"/g)].map((m) => m[1].replace(/^https?:\/\/www\.zoho\.com/, '')))];
|
|
84
|
+
|
|
85
|
+
async function pageRecord(rel) {
|
|
86
|
+
const html = await get(BASE + rel);
|
|
87
|
+
if (!html) return null;
|
|
88
|
+
const title = clean((/<h1[^>]*>([\s\S]*?)<\/h1>/.exec(html) || [])[1] || '').trim();
|
|
89
|
+
const syn = section(html, 'Syntax');
|
|
90
|
+
const cut = /\n(?:where[:,]?|Param|Parameter|Params)\b/.exec(syn);
|
|
91
|
+
const syntax = untoc(cut ? syn.slice(0, cut.index) : syn).trim();
|
|
92
|
+
const params = cut ? untoc(syn.slice(cut.index)).trim() : '';
|
|
93
|
+
let overview = section(html, 'Overview').split('\n').filter((l) => l.trim() && !/^Note/.test(l.trim())).join('\n').trim();
|
|
94
|
+
if (!overview) overview = (/<meta name="description" content="([^"]*)"/.exec(html) || [])[1] || '';
|
|
95
|
+
const returns = section(html, 'Return Type').split('\n')[0].trim();
|
|
96
|
+
return { url: BASE + rel, title, syntax, params, overview: first(untoc(overview)), returns };
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async function mapLimit(items, limit, fn) {
|
|
100
|
+
const out = new Array(items.length);
|
|
101
|
+
let i = 0;
|
|
102
|
+
await Promise.all(Array.from({ length: limit }, async () => {
|
|
103
|
+
for (;;) { const n = i++; if (n >= items.length) return; out[n] = await fn(items[n], n); }
|
|
104
|
+
}));
|
|
105
|
+
return out;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// ---- main ----------------------------------------------------------------------
|
|
109
|
+
|
|
110
|
+
(async () => {
|
|
111
|
+
const log = (m) => process.stderr.write(m + '\n');
|
|
112
|
+
|
|
113
|
+
// 1. function pages from every category index
|
|
114
|
+
const fnLinks = new Set();
|
|
115
|
+
for (const cat of FUNCTION_CATEGORIES) {
|
|
116
|
+
const html = await get(`${HELP}functions/${cat}.html`);
|
|
117
|
+
if (!html) { log(` (no index for functions/${cat})`); continue; }
|
|
118
|
+
for (const l of helpLinks(html)) if (/\/functions\/[^/]+\/[^/]+\.html$/.test(l)) fnLinks.add(l);
|
|
119
|
+
}
|
|
120
|
+
for (const idx of FUNCTION_EXTRA_INDEXES) {
|
|
121
|
+
const html = await get(`${HELP}${idx}.html`);
|
|
122
|
+
if (html) for (const l of helpLinks(html)) if (/\/functions\//.test(l)) fnLinks.add(l);
|
|
123
|
+
}
|
|
124
|
+
log(`function pages: ${fnLinks.size}`);
|
|
125
|
+
|
|
126
|
+
// 2. task pages from every product index
|
|
127
|
+
const taskLinks = new Set();
|
|
128
|
+
for (const idx of PRODUCT_INDEXES) {
|
|
129
|
+
const html = await get(`${HELP}${idx}.html`);
|
|
130
|
+
if (!html) { log(` (no index for ${idx})`); continue; }
|
|
131
|
+
for (const l of helpLinks(html)) if (!/\/functions\/|-tasks\.html$|integration-tasks|built-in-functions/.test(l)) taskLinks.add(l);
|
|
132
|
+
}
|
|
133
|
+
log(`task pages: ${taskLinks.size}`);
|
|
134
|
+
|
|
135
|
+
// 3. fetch + parse
|
|
136
|
+
const functions = [];
|
|
137
|
+
for (const rec of await mapLimit([...fnLinks], 8, pageRecord)) {
|
|
138
|
+
if (!rec || !rec.syntax) continue;
|
|
139
|
+
const cat = (/\/functions\/([^/]+)\//.exec(rec.url) || [])[1];
|
|
140
|
+
const name = (/\.\s*([A-Za-z_][A-Za-z0-9_]*)\s*\(/.exec(rec.syntax) || /\b([A-Za-z_][A-Za-z0-9_]*)\s*\(/.exec(rec.syntax) || [])[1] || path.basename(rec.url, '.html');
|
|
141
|
+
functions.push({ category: CATEGORY_LABEL[cat] || cat, name, syntax: rec.syntax.slice(0, 800), returns: rec.returns.slice(0, 60), overview: rec.overview.slice(0, 260), params: rec.params.slice(0, 700), url: rec.url });
|
|
142
|
+
}
|
|
143
|
+
for (const [name, category, syntax, overview] of MOVED_FUNCTIONS) {
|
|
144
|
+
if (!functions.some((f) => f.name.toLowerCase() === name.toLowerCase())) functions.push({ category, name, syntax, returns: '', overview, params: '', url: null });
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const tasks = [];
|
|
148
|
+
for (const rec of await mapLimit([...taskLinks], 8, pageRecord)) {
|
|
149
|
+
if (!rec || !rec.syntax) continue;
|
|
150
|
+
const m = /\/deluge\/help\/([^/]+)\/([^/]+)\.html$/.exec(rec.url);
|
|
151
|
+
if (!m) continue;
|
|
152
|
+
const product = m[1] === 'subscription' ? 'subscriptions' : m[1];
|
|
153
|
+
const slugWords = m[2].replace(/-v8$/i, '').split('-');
|
|
154
|
+
const camel = slugWords[0] + slugWords.slice(1).map((w) => w[0].toUpperCase() + w.slice(1)).join('');
|
|
155
|
+
const cands = [...rec.syntax.matchAll(/(zoho\.[a-zA-Z0-9_.]+)\s*\(/g)].map((x) => x[1]);
|
|
156
|
+
let name = cands.find((c) => c.split('.').pop().toLowerCase() === camel.toLowerCase()) || cands[0] || null;
|
|
157
|
+
if (!name || (cands.length && name.split('.').pop().toLowerCase() !== camel.toLowerCase() && /invokeconnector/i.test(name) && !/invoke-connector/.test(m[2]))) {
|
|
158
|
+
name = `zoho.${product}.${camel} (via invokeConnector)`;
|
|
159
|
+
}
|
|
160
|
+
tasks.push({ product, name, syntax: rec.syntax.slice(0, 400), overview: rec.overview.slice(0, 220), url: rec.url });
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// 4. statements, invokeurl, data types, limits, operators, system variables
|
|
164
|
+
const statements = [];
|
|
165
|
+
for (const [rel, name] of STATEMENTS) {
|
|
166
|
+
const rec = await pageRecord(`/deluge/help/${rel}.html`);
|
|
167
|
+
if (rec) statements.push({ name, syntax: rec.syntax.slice(0, 900), overview: rec.overview.slice(0, 300), url: rec.url });
|
|
168
|
+
}
|
|
169
|
+
const iu = await pageRecord('/deluge/help/web-data/invokeurl-task.html');
|
|
170
|
+
const invokeurl = iu ? { syntax: iu.syntax.slice(0, 900), params: iu.params.slice(0, 4000), overview: iu.overview.slice(0, 300), url: iu.url } : null;
|
|
171
|
+
|
|
172
|
+
const datatypes = [];
|
|
173
|
+
for (const rel of DATATYPES) {
|
|
174
|
+
const html = await get(`${HELP}${rel}.html`);
|
|
175
|
+
if (!html) continue;
|
|
176
|
+
const t = clean(html);
|
|
177
|
+
const i = t.indexOf('Overview');
|
|
178
|
+
const body = i >= 0 ? t.slice(i + 8, i + 1400) : '';
|
|
179
|
+
const ex = /Example\n([\s\S]*?)(?:\nNote|\nSupported|\nGet Started|$)/.exec(body);
|
|
180
|
+
datatypes.push({ name: rel.replace('datatypes/', '').replace('create-list-datatype', 'typed list'), overview: first(body.split('\n')[0] || '').slice(0, 260), notes: untoc(body.slice(0, 700)).replace(/\s+/g, ' '), example: (ex ? ex[1].trim() : '').slice(0, 300) });
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const slice = (t, from, to, max) => { const i = t.indexOf(from); if (i < 0) return ''; const j = to ? t.indexOf(to, i) : -1; return t.slice(i, j > i ? j : i + max).slice(0, max); };
|
|
184
|
+
const limitsHtml = await get(`${HELP}limitations.html`);
|
|
185
|
+
const limits = limitsHtml ? slice(clean(limitsHtml), 'Statement Limitation\nThe', 'Time Zone Limitations', 3200) : '';
|
|
186
|
+
const sysHtml = await get(`${HELP}system-variables.html`);
|
|
187
|
+
const systemVariables = sysHtml ? slice(clean(sysHtml), 'Date Variables', null, 2600) : '';
|
|
188
|
+
const opsHtml = await get(`${HELP}operators.html`);
|
|
189
|
+
const operators = opsHtml ? slice(clean(opsHtml), 'Types of Operators', null, 2200) : '';
|
|
190
|
+
const keywords = 'bool collection date false for-each else else-if float from if ifnull in int is list map null permissions portal reload return string thisapp true void zoho'.split(' ');
|
|
191
|
+
|
|
192
|
+
const out = { source: `${HELP} (scraped ${new Date().toISOString().slice(0, 10)})`, functions, tasks, statements, invokeurl, datatypes, limits, systemVariables, operators, keywords };
|
|
193
|
+
fs.writeFileSync(OUT, JSON.stringify(out, null, 1) + '\n');
|
|
194
|
+
log(`wrote ${path.relative(process.cwd(), OUT)}: ${functions.length} functions, ${tasks.length} tasks, ${statements.length} statements, ${datatypes.length} data types`);
|
|
195
|
+
log('now run: npm run gen:skill');
|
|
196
|
+
})().catch((e) => { console.error(e); process.exit(1); });
|
package/src/index.js
CHANGED
|
@@ -33,3 +33,7 @@ module.exports = {
|
|
|
33
33
|
// editor-agnostic analysis engines
|
|
34
34
|
analyze
|
|
35
35
|
};
|
|
36
|
+
|
|
37
|
+
/** The agent-facing Deluge guide (also `language.skill()`). */
|
|
38
|
+
module.exports.delugeSkill = module.exports.language.skill;
|
|
39
|
+
module.exports.delugeReference = module.exports.language.reference;
|
package/src/language/index.js
CHANGED
|
@@ -29,11 +29,42 @@ function snippets() {
|
|
|
29
29
|
return read('snippets.json');
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
+
/**
|
|
33
|
+
* The Deluge skill: a guide written for AI coding agents — syntax, control
|
|
34
|
+
* flow, the standard library, calling Zoho, HTTP, and the runtime traps that
|
|
35
|
+
* were verified against a live org. Tools install it where an agent reads
|
|
36
|
+
* (`zone skill deluge`), so the agent writes Deluge instead of JavaScript with
|
|
37
|
+
* Deluge punctuation. Plain Markdown, no frontmatter; the installer adds
|
|
38
|
+
* whatever header its target needs.
|
|
39
|
+
*/
|
|
40
|
+
function skill() {
|
|
41
|
+
return fs.readFileSync(path.join(ASSETS, 'DELUGE_SKILL.md'), 'utf8');
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* The complete reference — every statement, data type, built-in function and
|
|
46
|
+
* integration task with Zoho's own syntax — rendered from deluge-reference.json.
|
|
47
|
+
* Separate from skill() because it is ~22k tokens: an agent needs the guide on
|
|
48
|
+
* every Deluge task, but the exact signature of one function only occasionally.
|
|
49
|
+
*/
|
|
50
|
+
function reference() {
|
|
51
|
+
return fs.readFileSync(path.join(ASSETS, 'DELUGE_REFERENCE.md'), 'utf8');
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** The structured data behind reference(): functions, tasks, statements, data types, limits. */
|
|
55
|
+
function referenceData() {
|
|
56
|
+
return JSON.parse(fs.readFileSync(path.join(ASSETS, 'deluge-reference.json'), 'utf8'));
|
|
57
|
+
}
|
|
58
|
+
|
|
32
59
|
/** Absolute paths, for tools that must point a consumer at the raw files. */
|
|
33
60
|
const assetPaths = {
|
|
34
61
|
grammar: path.join(ASSETS, 'deluge.tmLanguage.json'),
|
|
35
62
|
configuration: path.join(ASSETS, 'language-configuration.json'),
|
|
36
|
-
snippets: path.join(ASSETS, 'snippets.json')
|
|
63
|
+
snippets: path.join(ASSETS, 'snippets.json'),
|
|
64
|
+
skill: path.join(ASSETS, 'DELUGE_SKILL.md'),
|
|
65
|
+
reference: path.join(ASSETS, 'DELUGE_REFERENCE.md'),
|
|
66
|
+
referenceData: path.join(ASSETS, 'deluge-reference.json'),
|
|
67
|
+
guide: path.join(ASSETS, 'DELUGE_GUIDE.md')
|
|
37
68
|
};
|
|
38
69
|
|
|
39
|
-
module.exports = { EXTENSIONS, grammar, configuration, snippets, assetPaths };
|
|
70
|
+
module.exports = { EXTENSIONS, grammar, configuration, snippets, skill, reference, referenceData, assetPaths };
|