@duckcodeailabs/dql-cli 1.3.6 → 1.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/apps-api.d.ts +16 -0
- package/dist/apps-api.d.ts.map +1 -0
- package/dist/apps-api.js +249 -0
- package/dist/apps-api.js.map +1 -0
- package/dist/args.d.ts +2 -0
- package/dist/args.d.ts.map +1 -1
- package/dist/args.js +4 -0
- package/dist/args.js.map +1 -1
- package/dist/assets/dql-notebook/assets/index-BJ7MV8Gv.js +847 -0
- package/dist/assets/dql-notebook/index.html +1 -1
- package/dist/commands/agent.d.ts +19 -0
- package/dist/commands/agent.d.ts.map +1 -0
- package/dist/commands/agent.js +117 -0
- package/dist/commands/agent.js.map +1 -0
- package/dist/commands/app.d.ts +29 -0
- package/dist/commands/app.d.ts.map +1 -1
- package/dist/commands/app.js +236 -120
- package/dist/commands/app.js.map +1 -1
- package/dist/commands/notebook.d.ts.map +1 -1
- package/dist/commands/notebook.js +13 -2
- package/dist/commands/notebook.js.map +1 -1
- package/dist/commands/slack.d.ts +13 -0
- package/dist/commands/slack.d.ts.map +1 -0
- package/dist/commands/slack.js +53 -0
- package/dist/commands/slack.js.map +1 -0
- package/dist/commands/verify.d.ts +11 -0
- package/dist/commands/verify.d.ts.map +1 -0
- package/dist/commands/verify.js +74 -0
- package/dist/commands/verify.js.map +1 -0
- package/dist/index.js +19 -1
- package/dist/index.js.map +1 -1
- package/dist/local-runtime.d.ts +6 -0
- package/dist/local-runtime.d.ts.map +1 -1
- package/dist/local-runtime.js +19 -2
- package/dist/local-runtime.js.map +1 -1
- package/package.json +10 -8
- package/dist/assets/dql-notebook/assets/index-jwFfZgBm.js +0 -847
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
8
8
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
9
9
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;500;600;700&family=Kalam:wght@400;700&family=Caveat:wght@400;600;700&display=swap" rel="stylesheet" />
|
|
10
|
-
<script type="module" crossorigin src="/assets/index-
|
|
10
|
+
<script type="module" crossorigin src="/assets/index-BJ7MV8Gv.js"></script>
|
|
11
11
|
<link rel="modulepreload" crossorigin href="/assets/react-CRB3T2We.js">
|
|
12
12
|
<link rel="modulepreload" crossorigin href="/assets/codemirror-DJYUkPr1.js">
|
|
13
13
|
<link rel="stylesheet" crossorigin href="/assets/index-DrhoZmtv.css">
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `dql agent` — block-first answer loop on the command line.
|
|
3
|
+
*
|
|
4
|
+
* dql agent ask "what was revenue last week?"
|
|
5
|
+
* [--provider claude|openai|gemini|ollama]
|
|
6
|
+
* [--user alice@acme.com] (filters Skills + records feedback as this user)
|
|
7
|
+
* [--domain growth] (scopes KG search)
|
|
8
|
+
* [--format json] (emits structured JSON instead of prose)
|
|
9
|
+
*
|
|
10
|
+
* dql agent reindex
|
|
11
|
+
* Rebuilds .dql/cache/agent-kg.sqlite from the project's manifest +
|
|
12
|
+
* Skills folder. Equivalent to `dql app reindex`.
|
|
13
|
+
*
|
|
14
|
+
* dql agent feedback <up|down> --block <id> --question "..."
|
|
15
|
+
* Records feedback into the KG. Used by clients without MCP access.
|
|
16
|
+
*/
|
|
17
|
+
import type { CLIFlags } from '../args.js';
|
|
18
|
+
export declare function runAgent(sub: string | null, rest: string[], flags: CLIFlags): Promise<void>;
|
|
19
|
+
//# sourceMappingURL=agent.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent.d.ts","sourceRoot":"","sources":["../../src/commands/agent.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAYH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAG3C,wBAAsB,QAAQ,CAC5B,GAAG,EAAE,MAAM,GAAG,IAAI,EAClB,IAAI,EAAE,MAAM,EAAE,EACd,KAAK,EAAE,QAAQ,GACd,OAAO,CAAC,IAAI,CAAC,CAgBf"}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `dql agent` — block-first answer loop on the command line.
|
|
3
|
+
*
|
|
4
|
+
* dql agent ask "what was revenue last week?"
|
|
5
|
+
* [--provider claude|openai|gemini|ollama]
|
|
6
|
+
* [--user alice@acme.com] (filters Skills + records feedback as this user)
|
|
7
|
+
* [--domain growth] (scopes KG search)
|
|
8
|
+
* [--format json] (emits structured JSON instead of prose)
|
|
9
|
+
*
|
|
10
|
+
* dql agent reindex
|
|
11
|
+
* Rebuilds .dql/cache/agent-kg.sqlite from the project's manifest +
|
|
12
|
+
* Skills folder. Equivalent to `dql app reindex`.
|
|
13
|
+
*
|
|
14
|
+
* dql agent feedback <up|down> --block <id> --question "..."
|
|
15
|
+
* Records feedback into the KG. Used by clients without MCP access.
|
|
16
|
+
*/
|
|
17
|
+
import { existsSync } from 'node:fs';
|
|
18
|
+
import { KGStore, defaultKgPath, reindexProject, loadSkills, pickProvider, answer, } from '@duckcodeailabs/dql-agent';
|
|
19
|
+
import { findProjectRoot } from '../local-runtime.js';
|
|
20
|
+
export async function runAgent(sub, rest, flags) {
|
|
21
|
+
switch (sub) {
|
|
22
|
+
case 'ask':
|
|
23
|
+
return runAsk(rest, flags);
|
|
24
|
+
case 'reindex':
|
|
25
|
+
return runReindex(flags);
|
|
26
|
+
case 'feedback':
|
|
27
|
+
return runFeedback(rest, flags);
|
|
28
|
+
default:
|
|
29
|
+
throw new Error('Usage: dql agent <ask|reindex|feedback> [args]\n' +
|
|
30
|
+
' dql agent ask "<question>" [--provider claude|openai|gemini|ollama] [--user <id>] [--domain <d>]\n' +
|
|
31
|
+
' dql agent reindex\n' +
|
|
32
|
+
' dql agent feedback up|down --block <id> --question "..."');
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
async function runAsk(rest, flags) {
|
|
36
|
+
const question = rest.join(' ').trim();
|
|
37
|
+
if (!question)
|
|
38
|
+
throw new Error('Usage: dql agent ask "<question>"');
|
|
39
|
+
const projectRoot = findProjectRoot(process.cwd());
|
|
40
|
+
const kgPath = defaultKgPath(projectRoot);
|
|
41
|
+
if (!existsSync(kgPath)) {
|
|
42
|
+
throw new Error('KG not built. Run `dql agent reindex` (or `dql app reindex`) before asking.');
|
|
43
|
+
}
|
|
44
|
+
const providerName = flags.provider;
|
|
45
|
+
const userId = flags.user;
|
|
46
|
+
const domain = flags.domain;
|
|
47
|
+
const format = flags.format;
|
|
48
|
+
const provider = await pickProvider(providerName);
|
|
49
|
+
const kg = new KGStore(kgPath);
|
|
50
|
+
const { skills } = loadSkills(projectRoot);
|
|
51
|
+
try {
|
|
52
|
+
const result = await answer({ question, provider, kg, skills, userId, domain });
|
|
53
|
+
if (format === 'json') {
|
|
54
|
+
console.log(JSON.stringify(result, null, 2));
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
const badge = result.kind === 'certified'
|
|
58
|
+
? '✓ Certified'
|
|
59
|
+
: result.kind === 'uncertified'
|
|
60
|
+
? '! AI-generated · uncertified'
|
|
61
|
+
: '? No answer';
|
|
62
|
+
const cite = result.citations.length > 0
|
|
63
|
+
? '\n\nCitations:\n' + result.citations.map((c) => ` - ${c.kind} \`${c.name}\`${c.gitSha ? ` · ${c.gitSha.slice(0, 8)}` : ''}`).join('\n')
|
|
64
|
+
: '';
|
|
65
|
+
console.log(`${badge}\n\n${result.text}${cite}`);
|
|
66
|
+
if (result.proposedSql) {
|
|
67
|
+
console.log(`\n--- Proposed SQL (review before saving as a block) ---\n${result.proposedSql}`);
|
|
68
|
+
if (result.suggestedViz)
|
|
69
|
+
console.log(`Viz: ${result.suggestedViz}`);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
finally {
|
|
73
|
+
kg.close();
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
async function runReindex(flags) {
|
|
77
|
+
const projectRoot = findProjectRoot(process.cwd());
|
|
78
|
+
const stats = await reindexProject(projectRoot);
|
|
79
|
+
if (flags.format === 'json') {
|
|
80
|
+
console.log(JSON.stringify({ ok: true, ...stats }, null, 2));
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
console.log(` ✓ KG rebuilt — ${stats.nodes} nodes, ${stats.edges} edges, ${stats.skills} skill(s).`);
|
|
84
|
+
}
|
|
85
|
+
async function runFeedback(rest, flags) {
|
|
86
|
+
const rating = rest[0];
|
|
87
|
+
if (rating !== 'up' && rating !== 'down') {
|
|
88
|
+
throw new Error('Usage: dql agent feedback up|down --block <id> --question "..."');
|
|
89
|
+
}
|
|
90
|
+
const blockId = flags.block;
|
|
91
|
+
const question = flags.question;
|
|
92
|
+
const user = flags.user ?? `${process.env.USER ?? 'owner'}@local`;
|
|
93
|
+
if (!question)
|
|
94
|
+
throw new Error('--question is required');
|
|
95
|
+
const projectRoot = findProjectRoot(process.cwd());
|
|
96
|
+
const kgPath = defaultKgPath(projectRoot);
|
|
97
|
+
if (!existsSync(kgPath))
|
|
98
|
+
throw new Error('KG not built. Run `dql agent reindex`.');
|
|
99
|
+
const kg = new KGStore(kgPath);
|
|
100
|
+
try {
|
|
101
|
+
kg.recordFeedback({
|
|
102
|
+
id: `fb_${Date.now().toString(36)}`,
|
|
103
|
+
ts: new Date().toISOString(),
|
|
104
|
+
user,
|
|
105
|
+
question,
|
|
106
|
+
answerKind: blockId?.startsWith('block:') ? 'certified' : 'uncertified',
|
|
107
|
+
blockId,
|
|
108
|
+
rating,
|
|
109
|
+
comment: flags.comment,
|
|
110
|
+
});
|
|
111
|
+
console.log(` ✓ Recorded ${rating} from ${user}.`);
|
|
112
|
+
}
|
|
113
|
+
finally {
|
|
114
|
+
kg.close();
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
//# sourceMappingURL=agent.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent.js","sourceRoot":"","sources":["../../src/commands/agent.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EACL,OAAO,EACP,aAAa,EACb,cAAc,EACd,UAAU,EACV,YAAY,EACZ,MAAM,GAEP,MAAM,2BAA2B,CAAC;AAEnC,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAEtD,MAAM,CAAC,KAAK,UAAU,QAAQ,CAC5B,GAAkB,EAClB,IAAc,EACd,KAAe;IAEf,QAAQ,GAAG,EAAE,CAAC;QACZ,KAAK,KAAK;YACR,OAAO,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;QAC7B,KAAK,SAAS;YACZ,OAAO,UAAU,CAAC,KAAK,CAAC,CAAC;QAC3B,KAAK,UAAU;YACb,OAAO,WAAW,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;QAClC;YACE,MAAM,IAAI,KAAK,CACb,kDAAkD;gBAChD,sGAAsG;gBACtG,uBAAuB;gBACvB,4DAA4D,CAC/D,CAAC;IACN,CAAC;AACH,CAAC;AAED,KAAK,UAAU,MAAM,CAAC,IAAc,EAAE,KAAe;IACnD,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;IACvC,IAAI,CAAC,QAAQ;QAAE,MAAM,IAAI,KAAK,CAAC,mCAAmC,CAAC,CAAC;IAEpE,MAAM,WAAW,GAAG,eAAe,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC;IACnD,MAAM,MAAM,GAAG,aAAa,CAAC,WAAW,CAAC,CAAC;IAC1C,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;QACxB,MAAM,IAAI,KAAK,CACb,6EAA6E,CAC9E,CAAC;IACJ,CAAC;IAED,MAAM,YAAY,GAAI,KAA+B,CAAC,QAAoC,CAAC;IAC3F,MAAM,MAAM,GAAI,KAA2B,CAAC,IAAI,CAAC;IACjD,MAAM,MAAM,GAAI,KAA6B,CAAC,MAAM,CAAC;IACrD,MAAM,MAAM,GAAI,KAA6B,CAAC,MAAM,CAAC;IAErD,MAAM,QAAQ,GAAG,MAAM,YAAY,CAAC,YAAY,CAAC,CAAC;IAClD,MAAM,EAAE,GAAG,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;IAC/B,MAAM,EAAE,MAAM,EAAE,GAAG,UAAU,CAAC,WAAW,CAAC,CAAC;IAE3C,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,EAAE,QAAQ,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;QAEhF,IAAI,MAAM,KAAK,MAAM,EAAE,CAAC;YACtB,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;YAC7C,OAAO;QACT,CAAC;QAED,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,KAAK,WAAW;YACvC,CAAC,CAAC,aAAa;YACf,CAAC,CAAC,MAAM,CAAC,IAAI,KAAK,aAAa;gBAC7B,CAAC,CAAC,8BAA8B;gBAChC,CAAC,CAAC,aAAa,CAAC;QACpB,MAAM,IAAI,GAAG,MAAM,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC;YACtC,CAAC,CAAC,kBAAkB,GAAG,MAAM,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,CAAC,IAAI,MAAM,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC;YAC3I,CAAC,CAAC,EAAE,CAAC;QACP,OAAO,CAAC,GAAG,CAAC,GAAG,KAAK,OAAO,MAAM,CAAC,IAAI,GAAG,IAAI,EAAE,CAAC,CAAC;QACjD,IAAI,MAAM,CAAC,WAAW,EAAE,CAAC;YACvB,OAAO,CAAC,GAAG,CAAC,6DAA6D,MAAM,CAAC,WAAW,EAAE,CAAC,CAAC;YAC/F,IAAI,MAAM,CAAC,YAAY;gBAAE,OAAO,CAAC,GAAG,CAAC,QAAQ,MAAM,CAAC,YAAY,EAAE,CAAC,CAAC;QACtE,CAAC;IACH,CAAC;YAAS,CAAC;QACT,EAAE,CAAC,KAAK,EAAE,CAAC;IACb,CAAC;AACH,CAAC;AAED,KAAK,UAAU,UAAU,CAAC,KAAe;IACvC,MAAM,WAAW,GAAG,eAAe,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC;IACnD,MAAM,KAAK,GAAG,MAAM,cAAc,CAAC,WAAW,CAAC,CAAC;IAChD,IAAK,KAA6B,CAAC,MAAM,KAAK,MAAM,EAAE,CAAC;QACrD,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,GAAG,KAAK,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;QAC7D,OAAO;IACT,CAAC;IACD,OAAO,CAAC,GAAG,CAAC,oBAAoB,KAAK,CAAC,KAAK,WAAW,KAAK,CAAC,KAAK,WAAW,KAAK,CAAC,MAAM,YAAY,CAAC,CAAC;AACxG,CAAC;AAED,KAAK,UAAU,WAAW,CAAC,IAAc,EAAE,KAAe;IACxD,MAAM,MAAM,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;IACvB,IAAI,MAAM,KAAK,IAAI,IAAI,MAAM,KAAK,MAAM,EAAE,CAAC;QACzC,MAAM,IAAI,KAAK,CAAC,iEAAiE,CAAC,CAAC;IACrF,CAAC;IACD,MAAM,OAAO,GAAI,KAA4B,CAAC,KAAK,CAAC;IACpD,MAAM,QAAQ,GAAI,KAA+B,CAAC,QAAQ,CAAC;IAC3D,MAAM,IAAI,GAAI,KAA2B,CAAC,IAAI,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,IAAI,OAAO,QAAQ,CAAC;IACzF,IAAI,CAAC,QAAQ;QAAE,MAAM,IAAI,KAAK,CAAC,wBAAwB,CAAC,CAAC;IAEzD,MAAM,WAAW,GAAG,eAAe,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC;IACnD,MAAM,MAAM,GAAG,aAAa,CAAC,WAAW,CAAC,CAAC;IAC1C,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,wCAAwC,CAAC,CAAC;IACnF,MAAM,EAAE,GAAG,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;IAC/B,IAAI,CAAC;QACH,EAAE,CAAC,cAAc,CAAC;YAChB,EAAE,EAAE,MAAM,IAAI,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,EAAE;YACnC,EAAE,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;YAC5B,IAAI;YACJ,QAAQ;YACR,UAAU,EAAE,OAAO,EAAE,UAAU,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,aAAa;YACvE,OAAO;YACP,MAAM;YACN,OAAO,EAAG,KAA8B,CAAC,OAAO;SACjD,CAAC,CAAC;QACH,OAAO,CAAC,GAAG,CAAC,gBAAgB,MAAM,SAAS,IAAI,GAAG,CAAC,CAAC;IACtD,CAAC;YAAS,CAAC;QACT,EAAE,CAAC,KAAK,EAAE,CAAC;IACb,CAAC;AACH,CAAC"}
|
package/dist/commands/app.d.ts
CHANGED
|
@@ -1,3 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `dql app` — manage Apps (consumption-layer artifacts).
|
|
3
|
+
*
|
|
4
|
+
* Apps live at `apps/<id>/dql.app.json` and bundle dashboards/notebooks plus
|
|
5
|
+
* declarative members, roles, policies, RLS bindings, and schedules. They're
|
|
6
|
+
* compiled into the `apps[]` and `dashboards[]` records of `dql-manifest.json`
|
|
7
|
+
* and read by both the desktop UI and the CLI.
|
|
8
|
+
*
|
|
9
|
+
* Subcommands:
|
|
10
|
+
* dql app new <id> --domain <domain> [--owner <user>]
|
|
11
|
+
* dql app ls
|
|
12
|
+
* dql app show <id>
|
|
13
|
+
* dql app build [--app <id>]
|
|
14
|
+
* dql app reindex [--app <id>] (no-op until the agent KG package lands; aliased)
|
|
15
|
+
*/
|
|
16
|
+
import { type ManifestApp } from '@duckcodeailabs/dql-core';
|
|
1
17
|
import type { CLIFlags } from '../args.js';
|
|
2
18
|
export declare function runApp(sub: string | null, rest: string[], flags: CLIFlags): Promise<void>;
|
|
19
|
+
interface ResolvedApp extends Omit<ManifestApp, 'dashboards'> {
|
|
20
|
+
dashboards: Array<{
|
|
21
|
+
id: string;
|
|
22
|
+
title: string;
|
|
23
|
+
}>;
|
|
24
|
+
}
|
|
25
|
+
declare function collectApps(projectRoot: string): ResolvedApp[];
|
|
26
|
+
declare function humanise(id: string): string;
|
|
27
|
+
export declare const __test__: {
|
|
28
|
+
collectApps: typeof collectApps;
|
|
29
|
+
humanise: typeof humanise;
|
|
30
|
+
};
|
|
31
|
+
export {};
|
|
3
32
|
//# sourceMappingURL=app.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../../src/commands/app.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../../src/commands/app.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAKH,OAAO,EASL,KAAK,WAAW,EACjB,MAAM,0BAA0B,CAAC;AAClC,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAK3C,wBAAsB,MAAM,CAC1B,GAAG,EAAE,MAAM,GAAG,IAAI,EAClB,IAAI,EAAE,MAAM,EAAE,EACd,KAAK,EAAE,QAAQ,GACd,OAAO,CAAC,IAAI,CAAC,CAuBf;AAkOD,UAAU,WAAY,SAAQ,IAAI,CAAC,WAAW,EAAE,YAAY,CAAC;IAC3D,UAAU,EAAE,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAClD;AAED,iBAAS,WAAW,CAAC,WAAW,EAAE,MAAM,GAAG,WAAW,EAAE,CAkDvD;AAOD,iBAAS,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAMpC;AAGD,eAAO,MAAM,QAAQ;;;CAGpB,CAAC"}
|
package/dist/commands/app.js
CHANGED
|
@@ -1,191 +1,307 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `dql app` — manage Apps (consumption-layer artifacts).
|
|
3
|
+
*
|
|
4
|
+
* Apps live at `apps/<id>/dql.app.json` and bundle dashboards/notebooks plus
|
|
5
|
+
* declarative members, roles, policies, RLS bindings, and schedules. They're
|
|
6
|
+
* compiled into the `apps[]` and `dashboards[]` records of `dql-manifest.json`
|
|
7
|
+
* and read by both the desktop UI and the CLI.
|
|
8
|
+
*
|
|
9
|
+
* Subcommands:
|
|
10
|
+
* dql app new <id> --domain <domain> [--owner <user>]
|
|
11
|
+
* dql app ls
|
|
12
|
+
* dql app show <id>
|
|
13
|
+
* dql app build [--app <id>]
|
|
14
|
+
* dql app reindex [--app <id>] (no-op until the agent KG package lands; aliased)
|
|
15
|
+
*/
|
|
1
16
|
import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
-
import { join,
|
|
3
|
-
import {
|
|
17
|
+
import { join, relative } from 'node:path';
|
|
18
|
+
import { resolveDbtManifestPath } from '@duckcodeailabs/dql-core';
|
|
19
|
+
import { buildManifest, loadAppDocument, findAppDocuments, loadDashboardDocument, findDashboardsForApp, suggestAppId, } from '@duckcodeailabs/dql-core';
|
|
4
20
|
import { findProjectRoot } from '../local-runtime.js';
|
|
5
|
-
const APP_DIR_SUFFIX = '.dql-app';
|
|
6
21
|
const APPS_ROOT = 'apps';
|
|
7
22
|
export async function runApp(sub, rest, flags) {
|
|
8
23
|
switch (sub) {
|
|
9
24
|
case 'new':
|
|
10
25
|
return runAppNew(rest, flags);
|
|
11
26
|
case 'ls':
|
|
27
|
+
case 'list':
|
|
12
28
|
return runAppList(flags);
|
|
13
29
|
case 'show':
|
|
14
30
|
return runAppShow(rest, flags);
|
|
31
|
+
case 'build':
|
|
32
|
+
return runAppBuild(flags);
|
|
33
|
+
case 'reindex':
|
|
34
|
+
return runAppReindex(flags);
|
|
15
35
|
default:
|
|
16
|
-
throw new Error('Usage: dql app <new|ls|show> [args]'
|
|
36
|
+
throw new Error('Usage: dql app <new|ls|show|build|reindex> [args]\n' +
|
|
37
|
+
' dql app new <id> --domain <domain> [--owner <user>]\n' +
|
|
38
|
+
' dql app ls\n' +
|
|
39
|
+
' dql app show <id>\n' +
|
|
40
|
+
' dql app build\n' +
|
|
41
|
+
' dql app reindex');
|
|
17
42
|
}
|
|
18
43
|
}
|
|
44
|
+
// ---- new ----
|
|
19
45
|
async function runAppNew(rest, flags) {
|
|
20
|
-
const
|
|
21
|
-
if (!
|
|
22
|
-
throw new Error('Usage: dql app new <
|
|
46
|
+
const rawId = rest[0];
|
|
47
|
+
if (!rawId) {
|
|
48
|
+
throw new Error('Usage: dql app new <id> --domain <domain> [--owner <user>]');
|
|
49
|
+
}
|
|
23
50
|
const domain = flags.domain?.trim();
|
|
24
51
|
if (!domain)
|
|
25
52
|
throw new Error('--domain is required for "dql app new"');
|
|
53
|
+
const id = suggestAppId(rawId);
|
|
26
54
|
const projectRoot = findProjectRoot(process.cwd());
|
|
27
|
-
const
|
|
28
|
-
const appDir = join(projectRoot, APPS_ROOT, `${slug}${APP_DIR_SUFFIX}`);
|
|
55
|
+
const appDir = join(projectRoot, APPS_ROOT, id);
|
|
29
56
|
if (existsSync(appDir)) {
|
|
30
57
|
throw new Error(`App already exists at ${relFromRoot(projectRoot, appDir)}`);
|
|
31
58
|
}
|
|
32
|
-
const owner = flags.owner?.trim() || process.env.USER
|
|
33
|
-
const
|
|
34
|
-
|
|
59
|
+
const owner = flags.owner?.trim() || `${process.env.USER ?? 'owner'}@local`;
|
|
60
|
+
const displayName = humanise(id);
|
|
61
|
+
const appJson = {
|
|
62
|
+
version: 1,
|
|
63
|
+
id,
|
|
64
|
+
name: displayName,
|
|
65
|
+
description: `${displayName} — consumption surface for ${domain}`,
|
|
35
66
|
domain,
|
|
36
|
-
owner,
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
67
|
+
owners: [owner],
|
|
68
|
+
tags: [],
|
|
69
|
+
members: [
|
|
70
|
+
{ userId: owner, displayName: owner, roles: ['owner'] },
|
|
71
|
+
],
|
|
72
|
+
roles: [
|
|
73
|
+
{ id: 'owner', displayName: 'Owner', description: 'Full access to all dashboards and configuration.' },
|
|
74
|
+
{ id: 'analyst', displayName: 'Analyst', description: 'Can view and run dashboards, propose new blocks.' },
|
|
75
|
+
{ id: 'viewer', displayName: 'Viewer', description: 'Read-only access to certified dashboards.' },
|
|
76
|
+
],
|
|
77
|
+
policies: [
|
|
78
|
+
{
|
|
79
|
+
id: 'viewers-read',
|
|
80
|
+
domain,
|
|
81
|
+
minClassification: 'internal',
|
|
82
|
+
allowedRoles: ['viewer', 'analyst', 'owner'],
|
|
83
|
+
accessLevel: 'read',
|
|
84
|
+
enabled: true,
|
|
85
|
+
},
|
|
49
86
|
{
|
|
50
|
-
id: '
|
|
51
|
-
|
|
52
|
-
|
|
87
|
+
id: 'analyst-execute',
|
|
88
|
+
domain,
|
|
89
|
+
minClassification: 'confidential',
|
|
90
|
+
allowedRoles: ['analyst', 'owner'],
|
|
91
|
+
accessLevel: 'execute',
|
|
92
|
+
enabled: true,
|
|
53
93
|
},
|
|
54
94
|
],
|
|
55
|
-
|
|
95
|
+
rlsBindings: [],
|
|
96
|
+
schedules: [],
|
|
97
|
+
homepage: { type: 'dashboard', id: 'overview' },
|
|
98
|
+
};
|
|
99
|
+
const overview = {
|
|
100
|
+
version: 1,
|
|
101
|
+
id: 'overview',
|
|
102
|
+
metadata: {
|
|
103
|
+
title: `${displayName} — Overview`,
|
|
104
|
+
description: 'Default dashboard scaffolded by `dql app new`. Replace with your own blocks.',
|
|
105
|
+
domain,
|
|
106
|
+
},
|
|
107
|
+
layout: {
|
|
108
|
+
kind: 'grid',
|
|
109
|
+
cols: 12,
|
|
110
|
+
rowHeight: 80,
|
|
111
|
+
items: [],
|
|
112
|
+
},
|
|
113
|
+
};
|
|
114
|
+
mkdirSync(join(appDir, 'dashboards'), { recursive: true });
|
|
115
|
+
mkdirSync(join(appDir, 'notebooks'), { recursive: true });
|
|
116
|
+
writeFileSync(join(appDir, 'dql.app.json'), JSON.stringify(appJson, null, 2) + '\n', 'utf-8');
|
|
117
|
+
writeFileSync(join(appDir, 'dashboards', 'overview.dqld'), JSON.stringify(overview, null, 2) + '\n', 'utf-8');
|
|
56
118
|
const rel = relFromRoot(projectRoot, appDir);
|
|
57
119
|
if (flags.format === 'json') {
|
|
58
|
-
console.log(JSON.stringify({ created: true,
|
|
120
|
+
console.log(JSON.stringify({ created: true, id, path: rel }, null, 2));
|
|
59
121
|
return;
|
|
60
122
|
}
|
|
61
|
-
console.log(`\n ✓ Created app: ${
|
|
123
|
+
console.log(`\n ✓ Created app: ${id}`);
|
|
62
124
|
console.log(` Path: ${rel}`);
|
|
63
125
|
console.log(` Domain: ${domain} Owner: ${owner}`);
|
|
64
126
|
console.log('');
|
|
65
127
|
console.log(' Next steps:');
|
|
66
|
-
console.log(` 1.
|
|
67
|
-
console.log(` 2.
|
|
128
|
+
console.log(` 1. Add blocks to your project under blocks/`);
|
|
129
|
+
console.log(` 2. Edit ${rel}/dashboards/overview.dqld to reference them`);
|
|
130
|
+
console.log(` 3. dql app build # writes apps[] and dashboards[] into dql-manifest.json`);
|
|
131
|
+
console.log(` 4. dql notebook # open the App in the desktop UI`);
|
|
68
132
|
console.log('');
|
|
69
133
|
}
|
|
134
|
+
// ---- ls ----
|
|
70
135
|
async function runAppList(flags) {
|
|
71
136
|
const projectRoot = findProjectRoot(process.cwd());
|
|
72
|
-
const apps =
|
|
137
|
+
const apps = collectApps(projectRoot);
|
|
73
138
|
if (flags.format === 'json') {
|
|
74
139
|
console.log(JSON.stringify({ apps }, null, 2));
|
|
75
140
|
return;
|
|
76
141
|
}
|
|
77
142
|
if (apps.length === 0) {
|
|
78
|
-
console.log('No apps found. Create one with: dql app new <
|
|
143
|
+
console.log('No apps found. Create one with: dql app new <id> --domain <domain>');
|
|
79
144
|
return;
|
|
80
145
|
}
|
|
81
|
-
for (const
|
|
82
|
-
|
|
146
|
+
for (const a of apps) {
|
|
147
|
+
const member = `${a.members.length} member${a.members.length === 1 ? '' : 's'}`;
|
|
148
|
+
const dash = `${a.dashboards.length} dashboard${a.dashboards.length === 1 ? '' : 's'}`;
|
|
149
|
+
console.log(`${a.id.padEnd(28)} domain=${a.domain.padEnd(12)} ${member.padEnd(12)} ${dash}`);
|
|
83
150
|
}
|
|
84
151
|
}
|
|
152
|
+
// ---- show ----
|
|
85
153
|
async function runAppShow(rest, flags) {
|
|
86
|
-
const
|
|
87
|
-
if (!
|
|
88
|
-
throw new Error('Usage: dql app show <
|
|
154
|
+
const id = rest[0];
|
|
155
|
+
if (!id)
|
|
156
|
+
throw new Error('Usage: dql app show <id>');
|
|
89
157
|
const projectRoot = findProjectRoot(process.cwd());
|
|
90
|
-
const
|
|
158
|
+
const apps = collectApps(projectRoot);
|
|
159
|
+
const app = apps.find((a) => a.id === id);
|
|
91
160
|
if (!app)
|
|
92
|
-
throw new Error(`No app named "${
|
|
161
|
+
throw new Error(`No app named "${id}" under ${APPS_ROOT}/`);
|
|
93
162
|
if (flags.format === 'json') {
|
|
94
163
|
console.log(JSON.stringify(app, null, 2));
|
|
95
164
|
return;
|
|
96
165
|
}
|
|
97
|
-
console.log(`App: ${app.
|
|
98
|
-
console.log(` domain: ${app.
|
|
99
|
-
console.log(`
|
|
100
|
-
console.log(` description: ${app.
|
|
101
|
-
console.log(`
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
console.log(` notebooks: ${app.notebooks.length}`);
|
|
105
|
-
console.log(` dashboards: ${app.dashboards.length}`);
|
|
106
|
-
}
|
|
107
|
-
function discoverApps(projectRoot) {
|
|
108
|
-
const appsRoot = join(projectRoot, APPS_ROOT);
|
|
109
|
-
let entries;
|
|
110
|
-
try {
|
|
111
|
-
entries = readdirSync(appsRoot, { withFileTypes: true });
|
|
166
|
+
console.log(`App: ${app.name} (${app.id})`);
|
|
167
|
+
console.log(` domain: ${app.domain}`);
|
|
168
|
+
console.log(` owners: ${app.owners.join(', ')}`);
|
|
169
|
+
console.log(` description: ${app.description ?? '-'}`);
|
|
170
|
+
console.log(` members: ${app.members.length}`);
|
|
171
|
+
for (const m of app.members) {
|
|
172
|
+
console.log(` - ${m.userId} [${m.roles.join(', ')}]`);
|
|
112
173
|
}
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
}
|
|
116
|
-
const
|
|
117
|
-
|
|
118
|
-
if (!entry.isDirectory() || !entry.name.endsWith(APP_DIR_SUFFIX))
|
|
119
|
-
continue;
|
|
120
|
-
const app = readApp(projectRoot, join(appsRoot, entry.name));
|
|
121
|
-
if (app)
|
|
122
|
-
results.push(app);
|
|
174
|
+
console.log(` policies: ${app.policies.length}`);
|
|
175
|
+
console.log(` schedules: ${app.schedules.length}`);
|
|
176
|
+
console.log(` dashboards: ${app.dashboards.length}`);
|
|
177
|
+
for (const d of app.dashboards) {
|
|
178
|
+
console.log(` - ${d.id} (${d.title})`);
|
|
123
179
|
}
|
|
124
|
-
return results.sort((a, b) => a.manifest.name.localeCompare(b.manifest.name));
|
|
125
|
-
}
|
|
126
|
-
function loadAppByName(projectRoot, name) {
|
|
127
|
-
const slug = toSlug(name);
|
|
128
|
-
return readApp(projectRoot, join(projectRoot, APPS_ROOT, `${slug}${APP_DIR_SUFFIX}`));
|
|
129
180
|
}
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
181
|
+
// ---- build ----
|
|
182
|
+
async function runAppBuild(flags) {
|
|
183
|
+
const projectRoot = findProjectRoot(process.cwd());
|
|
184
|
+
// Build the manifest with dbt import resolved the same way `dql compile`
|
|
185
|
+
// does, so `dql app build` produces an identical on-disk artifact.
|
|
186
|
+
const dbtManifestPath = resolveDbtManifestPath(projectRoot) ?? undefined;
|
|
187
|
+
const manifest = buildManifest({ projectRoot, dbtManifestPath });
|
|
188
|
+
// Persist the manifest to dql-manifest.json — without this the App + the
|
|
189
|
+
// dashboards never land in the on-disk artifact, so downstream consumers
|
|
190
|
+
// (KG reindex, lineage CLI, the desktop UI) keep reading the previous build.
|
|
191
|
+
const manifestPath = join(projectRoot, 'dql-manifest.json');
|
|
192
|
+
writeFileSync(manifestPath, JSON.stringify(manifest, null, 2) + '\n', 'utf-8');
|
|
193
|
+
const json = flags.format === 'json';
|
|
194
|
+
const apps = manifest.apps ?? {};
|
|
195
|
+
const dashboards = manifest.dashboards ?? {};
|
|
196
|
+
const diagnostics = (manifest.diagnostics ?? []).filter((d) => d.filePath?.startsWith('apps/') ?? false);
|
|
197
|
+
if (json) {
|
|
198
|
+
console.log(JSON.stringify({
|
|
199
|
+
apps: Object.values(apps).map((a) => ({ id: a.id, name: a.name, dashboards: a.dashboards })),
|
|
200
|
+
dashboards: Object.values(dashboards).map((d) => ({
|
|
201
|
+
id: d.id,
|
|
202
|
+
appId: d.appId,
|
|
203
|
+
blockIds: d.blockIds,
|
|
204
|
+
unresolvedRefs: d.unresolvedRefs,
|
|
205
|
+
})),
|
|
206
|
+
manifestPath: relative(projectRoot, manifestPath),
|
|
207
|
+
diagnostics,
|
|
208
|
+
}, null, 2));
|
|
209
|
+
return;
|
|
137
210
|
}
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
return null;
|
|
142
|
-
return {
|
|
143
|
-
path: relFromRoot(projectRoot, appDir),
|
|
144
|
-
manifest,
|
|
145
|
-
notebooks: listTree(join(appDir, 'notebooks'), '.dqlnb'),
|
|
146
|
-
dashboards: listTree(join(appDir, 'dashboards'), '.dql'),
|
|
147
|
-
};
|
|
211
|
+
console.log(`\n ✓ Built ${Object.keys(apps).length} app(s), ${Object.keys(dashboards).length} dashboard(s).`);
|
|
212
|
+
for (const a of Object.values(apps)) {
|
|
213
|
+
console.log(` - ${a.id}: ${a.dashboards.length} dashboard(s)`);
|
|
148
214
|
}
|
|
149
|
-
|
|
150
|
-
|
|
215
|
+
console.log(`\n Manifest written to: ${relative(projectRoot, manifestPath)}`);
|
|
216
|
+
if (diagnostics.length > 0) {
|
|
217
|
+
console.log('\n Diagnostics:');
|
|
218
|
+
for (const d of diagnostics) {
|
|
219
|
+
console.log(` [${d.severity}] ${d.filePath ?? ''}: ${d.message}`);
|
|
220
|
+
}
|
|
151
221
|
}
|
|
222
|
+
console.log('');
|
|
152
223
|
}
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
224
|
+
// ---- reindex (alias hook for the agent KG package) ----
|
|
225
|
+
async function runAppReindex(flags) {
|
|
226
|
+
const { reindexProject } = await import('@duckcodeailabs/dql-agent');
|
|
227
|
+
const projectRoot = findProjectRoot(process.cwd());
|
|
228
|
+
const stats = await reindexProject(projectRoot);
|
|
229
|
+
if (flags.format === 'json') {
|
|
230
|
+
console.log(JSON.stringify({ ok: true, ...stats }, null, 2));
|
|
231
|
+
return;
|
|
160
232
|
}
|
|
233
|
+
console.log(` ✓ Knowledge graph reindexed — ${stats.nodes} nodes, ${stats.edges} edges, ${stats.skills} skill(s).`);
|
|
234
|
+
}
|
|
235
|
+
function collectApps(projectRoot) {
|
|
161
236
|
const out = [];
|
|
162
|
-
for (const
|
|
163
|
-
const
|
|
164
|
-
if (
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
237
|
+
for (const appJsonPath of findAppDocuments(projectRoot)) {
|
|
238
|
+
const { document } = loadAppDocument(appJsonPath);
|
|
239
|
+
if (!document)
|
|
240
|
+
continue;
|
|
241
|
+
const appDir = appJsonPath.slice(0, -'/dql.app.json'.length);
|
|
242
|
+
const dashboardSummaries = [];
|
|
243
|
+
for (const dqldPath of findDashboardsForApp(appDir)) {
|
|
244
|
+
const { document: d } = loadDashboardDocument(dqldPath);
|
|
245
|
+
if (d)
|
|
246
|
+
dashboardSummaries.push({ id: d.id, title: d.metadata.title });
|
|
169
247
|
}
|
|
248
|
+
out.push({
|
|
249
|
+
id: document.id,
|
|
250
|
+
name: document.name,
|
|
251
|
+
description: document.description,
|
|
252
|
+
domain: document.domain,
|
|
253
|
+
owners: document.owners,
|
|
254
|
+
tags: document.tags ?? [],
|
|
255
|
+
filePath: relative(projectRoot, appDir),
|
|
256
|
+
members: document.members.map((m) => ({
|
|
257
|
+
userId: m.userId,
|
|
258
|
+
displayName: m.displayName,
|
|
259
|
+
roles: m.roles,
|
|
260
|
+
attributes: m.attributes,
|
|
261
|
+
})),
|
|
262
|
+
roles: document.roles,
|
|
263
|
+
policies: document.policies.map((p) => ({
|
|
264
|
+
id: p.id,
|
|
265
|
+
description: p.description,
|
|
266
|
+
domain: p.domain,
|
|
267
|
+
minClassification: p.minClassification,
|
|
268
|
+
allowedRoles: p.allowedRoles,
|
|
269
|
+
allowedUsers: p.allowedUsers,
|
|
270
|
+
accessLevel: p.accessLevel,
|
|
271
|
+
enabled: p.enabled === undefined ? true : Boolean(p.enabled),
|
|
272
|
+
})),
|
|
273
|
+
rlsBindings: document.rlsBindings ?? [],
|
|
274
|
+
schedules: (document.schedules ?? []).map((s) => ({
|
|
275
|
+
id: s.id,
|
|
276
|
+
cron: s.cron,
|
|
277
|
+
dashboard: s.dashboard,
|
|
278
|
+
deliver: s.deliver,
|
|
279
|
+
description: s.description,
|
|
280
|
+
enabled: s.enabled === undefined ? true : Boolean(s.enabled),
|
|
281
|
+
})),
|
|
282
|
+
dashboards: dashboardSummaries,
|
|
283
|
+
homepage: document.homepage,
|
|
284
|
+
});
|
|
170
285
|
}
|
|
171
|
-
return out;
|
|
286
|
+
return out.sort((a, b) => a.name.localeCompare(b.name));
|
|
172
287
|
}
|
|
173
|
-
function relFromRoot(projectRoot,
|
|
288
|
+
function relFromRoot(projectRoot, p) {
|
|
174
289
|
const prefix = projectRoot.endsWith('/') ? projectRoot : `${projectRoot}/`;
|
|
175
|
-
return
|
|
176
|
-
}
|
|
177
|
-
function toSlug(value) {
|
|
178
|
-
return (value
|
|
179
|
-
.trim()
|
|
180
|
-
.toLowerCase()
|
|
181
|
-
.replace(/[^a-z0-9]+/g, '_')
|
|
182
|
-
.replace(/^_+|_+$/g, '') || 'app');
|
|
290
|
+
return p.startsWith(prefix) ? p.slice(prefix.length) : p;
|
|
183
291
|
}
|
|
184
|
-
function
|
|
185
|
-
return
|
|
186
|
-
.split(
|
|
292
|
+
function humanise(id) {
|
|
293
|
+
return id
|
|
294
|
+
.split(/[-_]/)
|
|
187
295
|
.filter(Boolean)
|
|
188
|
-
.map((w) => `${w[0]?.toUpperCase()
|
|
189
|
-
.join(' ');
|
|
296
|
+
.map((w) => `${w[0]?.toUpperCase() ?? ''}${w.slice(1)}`)
|
|
297
|
+
.join(' ') || id;
|
|
190
298
|
}
|
|
299
|
+
// Export internal helpers for tests.
|
|
300
|
+
export const __test__ = {
|
|
301
|
+
collectApps,
|
|
302
|
+
humanise,
|
|
303
|
+
};
|
|
304
|
+
// reference unused readdirSync/readFileSync to keep imports stable for future use
|
|
305
|
+
void readdirSync;
|
|
306
|
+
void readFileSync;
|
|
191
307
|
//# sourceMappingURL=app.js.map
|