@wanasapps/deluge-core 1.0.0 → 1.3.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/README.md +17 -0
- package/assets/DELUGE_GUIDE.md +312 -0
- package/assets/DELUGE_REFERENCE.md +956 -0
- package/assets/DELUGE_SKILL.md +339 -0
- package/assets/deluge-reference.json +2648 -0
- package/package.json +11 -4
- package/scripts/check-cores-published.js +63 -0
- package/scripts/gen-skill.js +139 -0
- package/scripts/scrape-deluge-docs.js +196 -0
- package/src/format/signature.js +70 -70
- 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.3.0",
|
|
4
|
+
"description": "Deluge language tooling for every Zoho product: formatter, source transforms, TextMate grammar, snippets, an editor-agnostic linter, and AI-agent skills — 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,63 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Refuse to publish before the @wanasapps packages this one depends on.
|
|
4
|
+
*
|
|
5
|
+
* These five packages are independent repos, not a workspace. Publishing a
|
|
6
|
+
* consumer before its cores yields a package npm cannot install — it has
|
|
7
|
+
* happened before (zone 0.4.3). CI runs this before `npm ci`, so the failure
|
|
8
|
+
* names the missing package instead of surfacing as a bare ETARGET.
|
|
9
|
+
*/
|
|
10
|
+
'use strict';
|
|
11
|
+
const cp = require('child_process');
|
|
12
|
+
const pj = require('../package.json');
|
|
13
|
+
|
|
14
|
+
const deps = { ...(pj.dependencies || {}), ...(pj.peerDependencies || {}) };
|
|
15
|
+
const wanas = Object.entries(deps).filter(([n]) => n.startsWith('@wanasapps'));
|
|
16
|
+
|
|
17
|
+
if (!wanas.length) {
|
|
18
|
+
console.log('no @wanasapps dependencies — nothing to check');
|
|
19
|
+
process.exit(0);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// A lockfile that records a local link resolves on the dev machine and
|
|
23
|
+
// nowhere else: `npm ci` creates the link, exits 0, and the module is
|
|
24
|
+
// missing at runtime. Caught zoho-api 1.1.1 in CI.
|
|
25
|
+
try {
|
|
26
|
+
const lock = require('../package-lock.json');
|
|
27
|
+
const linked = Object.entries(lock.packages || {})
|
|
28
|
+
.filter(([, v]) => v && v.link)
|
|
29
|
+
.map(([k]) => k.replace('node_modules/', ''));
|
|
30
|
+
if (linked.length) {
|
|
31
|
+
console.error(` LINKED ${linked.join(', ')} recorded as a local link in package-lock.json`);
|
|
32
|
+
console.error(' Regenerate it against the registry: npm install --package-lock-only');
|
|
33
|
+
process.exit(1);
|
|
34
|
+
}
|
|
35
|
+
} catch { /* no lockfile is fine */ }
|
|
36
|
+
|
|
37
|
+
let missing = 0;
|
|
38
|
+
for (const [name, range] of wanas) {
|
|
39
|
+
let found = '';
|
|
40
|
+
try {
|
|
41
|
+
// --json so a range matching several versions returns an array, rather
|
|
42
|
+
// than npm's "<name>@<ver> '<ver>'" lines, which are awkward to parse.
|
|
43
|
+
const raw = cp.execSync(`npm view ${name}@"${range}" version --json`, {
|
|
44
|
+
encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore']
|
|
45
|
+
}).trim();
|
|
46
|
+
const parsed = raw ? JSON.parse(raw) : null;
|
|
47
|
+
found = Array.isArray(parsed) ? parsed[parsed.length - 1] : (parsed || '');
|
|
48
|
+
} catch { /* not published, or the range matches nothing */ }
|
|
49
|
+
|
|
50
|
+
if (found) {
|
|
51
|
+
console.log(` ok ${name}@${range} -> ${found}`);
|
|
52
|
+
} else {
|
|
53
|
+
console.error(` MISSING ${name}@${range} does not resolve on npm`);
|
|
54
|
+
missing++;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (missing) {
|
|
59
|
+
console.error('\nPublish bottom-up:');
|
|
60
|
+
console.error(' deluge-core -> zoho-auth -> zoho-api -> zcrm-core -> wanas-zone-cli');
|
|
61
|
+
process.exit(1);
|
|
62
|
+
}
|
|
63
|
+
console.log('all @wanasapps dependencies resolve on npm');
|
|
@@ -0,0 +1,139 @@
|
|
|
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 fnCount = [...fnByCat.values()].reduce((n, v) => n + v.length, 0);
|
|
59
|
+
const taskCount = [...taskByProd.values()].reduce((n, v) => n + v.length, 0);
|
|
60
|
+
|
|
61
|
+
const skill = [];
|
|
62
|
+
skill.push(guide.trimEnd(), '', '---', '');
|
|
63
|
+
skill.push('# Part 2 — Everything that exists (names only)', '');
|
|
64
|
+
skill.push(`What exists in Deluge, generated from ${ref.source}. Statements and data types are listed in full below because they are few. The ${fnCount} built-in functions and ${taskCount} integration tasks are summarised by category — **every one of them is named, with its exact syntax, parameters and return type, in the companion reference**: read \`references/REFERENCE.md\` in this skill folder, or run \`zone llm deluge-reference\`. If a name is not there, it does not exist in Deluge — do not invent one.`, '');
|
|
65
|
+
|
|
66
|
+
skill.push('## Statements', '');
|
|
67
|
+
skill.push((ref.statements || []).map((s) => `\`${s.name}\``).concat(['`invokeurl`', '`info`', '`return`']).join(' · '), '');
|
|
68
|
+
|
|
69
|
+
skill.push('## Data types', '');
|
|
70
|
+
skill.push((ref.datatypes || []).map((d) => `\`${d.name}\``).join(' · '), '');
|
|
71
|
+
|
|
72
|
+
// Categories and counts, not 337 names. Every one of those names already
|
|
73
|
+
// appears in DELUGE_REFERENCE.md WITH its syntax, so listing them here bought
|
|
74
|
+
// nothing and cost ~1,800 tokens on every trigger — pushing the skill body over
|
|
75
|
+
// the Agent Skills spec's 5,000-token recommendation. The agent is told where
|
|
76
|
+
// to look instead, which preserves the "do not invent a name" guarantee.
|
|
77
|
+
skill.push('## Built-in functions — ' + fnCount + ' across ' + catOrder.length + ' categories', '');
|
|
78
|
+
skill.push(catOrder.map((cat) => `**${cat}** (${fnByCat.get(cat).length})`).join(' · '), '');
|
|
79
|
+
|
|
80
|
+
skill.push('## Integration tasks — ' + taskCount + ' across ' + prodOrder.length + ' products', '');
|
|
81
|
+
skill.push(prodOrder.map((prod) => `**${LABEL[prod] || prod}** (${taskByProd.get(prod).length})`).join(' · '), '');
|
|
82
|
+
|
|
83
|
+
skill.push('## System variables', '');
|
|
84
|
+
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).', '');
|
|
85
|
+
|
|
86
|
+
// ---------------------------------------------------------------------------
|
|
87
|
+
// DELUGE_REFERENCE.md — the full thing
|
|
88
|
+
// ---------------------------------------------------------------------------
|
|
89
|
+
const out = [];
|
|
90
|
+
out.push('# Zoho Deluge — complete reference', '');
|
|
91
|
+
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.`, '');
|
|
92
|
+
|
|
93
|
+
out.push('## Statements', '');
|
|
94
|
+
for (const s of ref.statements || []) {
|
|
95
|
+
out.push(`### ${s.name}`, '');
|
|
96
|
+
if (s.overview) out.push(untoc(s.overview).trim(), '');
|
|
97
|
+
if (s.syntax) out.push('```deluge', untoc(s.syntax).trim(), '```', '');
|
|
98
|
+
}
|
|
99
|
+
if (ref.invokeurl) {
|
|
100
|
+
out.push('### invokeurl', '');
|
|
101
|
+
if (ref.invokeurl.overview) out.push(untoc(ref.invokeurl.overview).trim(), '');
|
|
102
|
+
out.push('```deluge', untoc(ref.invokeurl.syntax).trim(), '```', '');
|
|
103
|
+
if (ref.invokeurl.params) out.push('Parameters, as documented:', '', '```text', untoc(ref.invokeurl.params).trim(), '```', '');
|
|
104
|
+
}
|
|
105
|
+
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.', '');
|
|
106
|
+
|
|
107
|
+
out.push('## Data types', '', '| Type | Summary | Example |', '|---|---|---|');
|
|
108
|
+
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]) : ''} |`);
|
|
109
|
+
out.push('', 'Reserved keywords (not usable as variable names): ' + (ref.keywords || []).map((k) => `\`${k}\``).join(', '), '');
|
|
110
|
+
|
|
111
|
+
if (ref.operators) out.push('## Operators', '', '```text', ref.operators.trim(), '```', '');
|
|
112
|
+
if (ref.systemVariables) out.push('## System variables', '', '```text', ref.systemVariables.trim(), '```', '');
|
|
113
|
+
|
|
114
|
+
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.", '');
|
|
115
|
+
for (const cat of catOrder) {
|
|
116
|
+
const list = fnByCat.get(cat).slice().sort(byName);
|
|
117
|
+
out.push(`### ${cat} (${list.length})`, '', '| Function | Syntax | Returns | Does |', '|---|---|---|---|');
|
|
118
|
+
for (const f of list) out.push(`| \`${f.name}\` | ${code(firstSyntax(f.syntax))} | ${esc(f.returns)} | ${esc(untoc(f.overview))} |`);
|
|
119
|
+
out.push('');
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
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.', '');
|
|
123
|
+
for (const prod of prodOrder) {
|
|
124
|
+
const list = taskByProd.get(prod).slice().sort(byName);
|
|
125
|
+
out.push(`### ${LABEL[prod] || prod} (${list.length})`, '', '| Task | Syntax | Does |', '|---|---|---|');
|
|
126
|
+
for (const t of list) out.push(`| \`${esc(t.name)}\` | ${code(firstSyntax(t.syntax))} | ${esc(untoc(t.overview))} |`);
|
|
127
|
+
out.push('');
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (ref.limits) out.push('## Documented limits', '', '```text', ref.limits.trim(), '```', '');
|
|
131
|
+
|
|
132
|
+
const tidy = (arr) => arr.join('\n').replace(/\n{3,}/g, '\n\n') + '\n';
|
|
133
|
+
const skillText = tidy(skill);
|
|
134
|
+
const refText = tidy(out);
|
|
135
|
+
fs.writeFileSync(path.join(ASSETS, 'DELUGE_SKILL.md'), skillText);
|
|
136
|
+
fs.writeFileSync(path.join(ASSETS, 'DELUGE_REFERENCE.md'), refText);
|
|
137
|
+
const tok = (s) => Math.round(s.length / 4).toLocaleString('en-US');
|
|
138
|
+
console.log(`DELUGE_SKILL.md ${skillText.length.toLocaleString('en-US')} chars (~${tok(skillText)} tokens)`);
|
|
139
|
+
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/format/signature.js
CHANGED
|
@@ -1,70 +1,70 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Minimal parser for the *signature line* of a Deluge function script.
|
|
3
|
-
*
|
|
4
|
-
* Zoho stores a function's code with its signature on the first meaningful
|
|
5
|
-
* line, e.g.:
|
|
6
|
-
*
|
|
7
|
-
* string standalone.Get_Assistants()
|
|
8
|
-
* void automation.k_test(String Data)
|
|
9
|
-
*
|
|
10
|
-
* We only need the signature to learn the function's namespace + api name (to
|
|
11
|
-
* build the test URL and enforce the standalone-only rule) and its declared
|
|
12
|
-
* arguments (to prompt for values). The full script is still submitted verbatim
|
|
13
|
-
* to Zoho — this parser never rewrites the code.
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Parse the signature of a Deluge script.
|
|
18
|
-
* @param {string} scriptText - The full Deluge source.
|
|
19
|
-
* @returns {{returnType: string|null, namespace: string|null, apiName: string|null, args: Array<{type:string,name:string}>}}
|
|
20
|
-
*/
|
|
21
|
-
function parseSignature(scriptText) {
|
|
22
|
-
const result = { returnType: null, namespace: null, apiName: null, args: [] };
|
|
23
|
-
if (!scriptText || typeof scriptText !== 'string') return result;
|
|
24
|
-
|
|
25
|
-
// Find the first non-comment, non-blank line — that's the signature.
|
|
26
|
-
let sigLine = null;
|
|
27
|
-
let inBlockComment = false;
|
|
28
|
-
for (const raw of scriptText.split('\n')) {
|
|
29
|
-
let line = raw.trim();
|
|
30
|
-
if (!line) continue;
|
|
31
|
-
|
|
32
|
-
if (inBlockComment) {
|
|
33
|
-
const end = line.indexOf('*/');
|
|
34
|
-
if (end === -1) continue;
|
|
35
|
-
line = line.slice(end + 2).trim();
|
|
36
|
-
inBlockComment = false;
|
|
37
|
-
if (!line) continue;
|
|
38
|
-
}
|
|
39
|
-
if (line.startsWith('//')) continue;
|
|
40
|
-
if (line.startsWith('/*')) {
|
|
41
|
-
const end = line.indexOf('*/');
|
|
42
|
-
if (end === -1) { inBlockComment = true; continue; }
|
|
43
|
-
line = line.slice(end + 2).trim();
|
|
44
|
-
if (!line) continue;
|
|
45
|
-
}
|
|
46
|
-
sigLine = line;
|
|
47
|
-
break;
|
|
48
|
-
}
|
|
49
|
-
if (!sigLine) return result;
|
|
50
|
-
|
|
51
|
-
// <returnType> <namespace>.<apiName>(<args>)
|
|
52
|
-
const m = sigLine.match(/^(\w+)\s+([A-Za-z0-9_]+)\.([A-Za-z0-9_]+)\s*\(([^)]*)\)/);
|
|
53
|
-
if (!m) return result;
|
|
54
|
-
|
|
55
|
-
result.returnType = m[1];
|
|
56
|
-
result.namespace = m[2];
|
|
57
|
-
result.apiName = m[3];
|
|
58
|
-
|
|
59
|
-
const argsRaw = m[4].trim();
|
|
60
|
-
if (argsRaw) {
|
|
61
|
-
for (const part of argsRaw.split(',')) {
|
|
62
|
-
// each param is "<type> <name>" (e.g. "String Data", "int count")
|
|
63
|
-
const am = part.trim().match(/^(\S+)\s+([A-Za-z0-9_]+)$/);
|
|
64
|
-
if (am) result.args.push({ type: am[1], name: am[2] });
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
return result;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
module.exports = { parseSignature };
|
|
1
|
+
/**
|
|
2
|
+
* Minimal parser for the *signature line* of a Deluge function script.
|
|
3
|
+
*
|
|
4
|
+
* Zoho stores a function's code with its signature on the first meaningful
|
|
5
|
+
* line, e.g.:
|
|
6
|
+
*
|
|
7
|
+
* string standalone.Get_Assistants()
|
|
8
|
+
* void automation.k_test(String Data)
|
|
9
|
+
*
|
|
10
|
+
* We only need the signature to learn the function's namespace + api name (to
|
|
11
|
+
* build the test URL and enforce the standalone-only rule) and its declared
|
|
12
|
+
* arguments (to prompt for values). The full script is still submitted verbatim
|
|
13
|
+
* to Zoho — this parser never rewrites the code.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Parse the signature of a Deluge script.
|
|
18
|
+
* @param {string} scriptText - The full Deluge source.
|
|
19
|
+
* @returns {{returnType: string|null, namespace: string|null, apiName: string|null, args: Array<{type:string,name:string}>}}
|
|
20
|
+
*/
|
|
21
|
+
function parseSignature(scriptText) {
|
|
22
|
+
const result = { returnType: null, namespace: null, apiName: null, args: [] };
|
|
23
|
+
if (!scriptText || typeof scriptText !== 'string') return result;
|
|
24
|
+
|
|
25
|
+
// Find the first non-comment, non-blank line — that's the signature.
|
|
26
|
+
let sigLine = null;
|
|
27
|
+
let inBlockComment = false;
|
|
28
|
+
for (const raw of scriptText.split('\n')) {
|
|
29
|
+
let line = raw.trim();
|
|
30
|
+
if (!line) continue;
|
|
31
|
+
|
|
32
|
+
if (inBlockComment) {
|
|
33
|
+
const end = line.indexOf('*/');
|
|
34
|
+
if (end === -1) continue;
|
|
35
|
+
line = line.slice(end + 2).trim();
|
|
36
|
+
inBlockComment = false;
|
|
37
|
+
if (!line) continue;
|
|
38
|
+
}
|
|
39
|
+
if (line.startsWith('//')) continue;
|
|
40
|
+
if (line.startsWith('/*')) {
|
|
41
|
+
const end = line.indexOf('*/');
|
|
42
|
+
if (end === -1) { inBlockComment = true; continue; }
|
|
43
|
+
line = line.slice(end + 2).trim();
|
|
44
|
+
if (!line) continue;
|
|
45
|
+
}
|
|
46
|
+
sigLine = line;
|
|
47
|
+
break;
|
|
48
|
+
}
|
|
49
|
+
if (!sigLine) return result;
|
|
50
|
+
|
|
51
|
+
// <returnType> <namespace>.<apiName>(<args>)
|
|
52
|
+
const m = sigLine.match(/^(\w+)\s+([A-Za-z0-9_]+)\.([A-Za-z0-9_]+)\s*\(([^)]*)\)/);
|
|
53
|
+
if (!m) return result;
|
|
54
|
+
|
|
55
|
+
result.returnType = m[1];
|
|
56
|
+
result.namespace = m[2];
|
|
57
|
+
result.apiName = m[3];
|
|
58
|
+
|
|
59
|
+
const argsRaw = m[4].trim();
|
|
60
|
+
if (argsRaw) {
|
|
61
|
+
for (const part of argsRaw.split(',')) {
|
|
62
|
+
// each param is "<type> <name>" (e.g. "String Data", "int count")
|
|
63
|
+
const am = part.trim().match(/^(\S+)\s+([A-Za-z0-9_]+)$/);
|
|
64
|
+
if (am) result.args.push({ type: am[1], name: am[2] });
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return result;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
module.exports = { parseSignature };
|
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 };
|