dsh-gitnexus-plus 0.0.0-stage → 0.1.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.
Files changed (78) hide show
  1. package/LICENSE +38 -0
  2. package/README.md +483 -2
  3. package/cordis.patch.yml +18 -0
  4. package/lib/bin/gitnexus-coverage.d.ts +14 -0
  5. package/lib/bin/gitnexus-coverage.d.ts.map +1 -0
  6. package/lib/bin/gitnexus-coverage.js +171 -0
  7. package/lib/bin/gitnexus-coverage.js.map +1 -0
  8. package/lib/client.js +1642 -0
  9. package/lib/gitnexus/analyze.d.ts +148 -0
  10. package/lib/gitnexus/analyze.d.ts.map +1 -0
  11. package/lib/gitnexus/analyze.js +283 -0
  12. package/lib/gitnexus/analyze.js.map +1 -0
  13. package/lib/gitnexus/commands.d.ts +96 -0
  14. package/lib/gitnexus/commands.d.ts.map +1 -0
  15. package/lib/gitnexus/commands.js +132 -0
  16. package/lib/gitnexus/commands.js.map +1 -0
  17. package/lib/gitnexus/coverage.d.ts +340 -0
  18. package/lib/gitnexus/coverage.d.ts.map +1 -0
  19. package/lib/gitnexus/coverage.js +552 -0
  20. package/lib/gitnexus/coverage.js.map +1 -0
  21. package/lib/gitnexus/format.d.ts +16 -0
  22. package/lib/gitnexus/format.d.ts.map +1 -0
  23. package/lib/gitnexus/format.js +178 -0
  24. package/lib/gitnexus/format.js.map +1 -0
  25. package/lib/gitnexus/git-activity.d.ts +136 -0
  26. package/lib/gitnexus/git-activity.d.ts.map +1 -0
  27. package/lib/gitnexus/git-activity.js +345 -0
  28. package/lib/gitnexus/git-activity.js.map +1 -0
  29. package/lib/gitnexus/git.d.ts +70 -0
  30. package/lib/gitnexus/git.d.ts.map +1 -0
  31. package/lib/gitnexus/git.js +121 -0
  32. package/lib/gitnexus/git.js.map +1 -0
  33. package/lib/gitnexus/index-meta.d.ts +150 -0
  34. package/lib/gitnexus/index-meta.d.ts.map +1 -0
  35. package/lib/gitnexus/index-meta.js +156 -0
  36. package/lib/gitnexus/index-meta.js.map +1 -0
  37. package/lib/gitnexus/install.d.ts +97 -0
  38. package/lib/gitnexus/install.d.ts.map +1 -0
  39. package/lib/gitnexus/install.js +228 -0
  40. package/lib/gitnexus/install.js.map +1 -0
  41. package/lib/gitnexus/languages.d.ts +73 -0
  42. package/lib/gitnexus/languages.d.ts.map +1 -0
  43. package/lib/gitnexus/languages.js +217 -0
  44. package/lib/gitnexus/languages.js.map +1 -0
  45. package/lib/gitnexus/root.d.ts +68 -0
  46. package/lib/gitnexus/root.d.ts.map +1 -0
  47. package/lib/gitnexus/root.js +162 -0
  48. package/lib/gitnexus/root.js.map +1 -0
  49. package/lib/gitnexus/scan.d.ts +67 -0
  50. package/lib/gitnexus/scan.d.ts.map +1 -0
  51. package/lib/gitnexus/scan.js +247 -0
  52. package/lib/gitnexus/scan.js.map +1 -0
  53. package/lib/gitnexus/toolchain.d.ts +136 -0
  54. package/lib/gitnexus/toolchain.d.ts.map +1 -0
  55. package/lib/gitnexus/toolchain.js +251 -0
  56. package/lib/gitnexus/toolchain.js.map +1 -0
  57. package/lib/index.d.ts +79 -0
  58. package/lib/index.d.ts.map +1 -0
  59. package/lib/index.js +211 -0
  60. package/lib/index.js.map +1 -0
  61. package/lib/routes/analyze.d.ts +35 -0
  62. package/lib/routes/analyze.d.ts.map +1 -0
  63. package/lib/routes/analyze.js +71 -0
  64. package/lib/routes/analyze.js.map +1 -0
  65. package/lib/routes/coverage.d.ts +44 -0
  66. package/lib/routes/coverage.d.ts.map +1 -0
  67. package/lib/routes/coverage.js +68 -0
  68. package/lib/routes/coverage.js.map +1 -0
  69. package/lib/routes/guard.d.ts +92 -0
  70. package/lib/routes/guard.d.ts.map +1 -0
  71. package/lib/routes/guard.js +187 -0
  72. package/lib/routes/guard.js.map +1 -0
  73. package/lib/routes/install.d.ts +27 -0
  74. package/lib/routes/install.d.ts.map +1 -0
  75. package/lib/routes/install.js +46 -0
  76. package/lib/routes/install.js.map +1 -0
  77. package/package.json +87 -4
  78. package/scripts/link-runtime.mjs +146 -0
package/lib/index.d.ts ADDED
@@ -0,0 +1,79 @@
1
+ /**
2
+ * dsh-gitnexus-plus 鈥?host half.
3
+ *
4
+ * Registers one read-only tool and one loopback HTTP route over the coverage
5
+ * engine in `./gitnexus/`. Both answer the same question 鈥?"what did this index
6
+ * leave out?" 鈥?for the two callers that need it: the model, and a browser half
7
+ * that will render it.
8
+ *
9
+ * Deliberately read-only. Nothing here triggers `analyze`, writes to `.gitnexus/`,
10
+ * or takes the index lock; driving updates is a separate concern with its own
11
+ * safety story, and mixing the two would mean a status check could start a
12
+ * 30-second rebuild.
13
+ */
14
+ import type { Context } from '@deepseek-ai/cordis';
15
+ import { type MaintenanceCommand } from './gitnexus/commands.js';
16
+ export declare const name = "gitnexus-plus";
17
+ /**
18
+ * Only `tools` is required. `webServer` is reached through `ctx.inject` because a
19
+ * headless profile has no HTTP carrier at all 鈥?naming it here would leave the
20
+ * plugin permanently pending there, and the tool would never register.
21
+ */
22
+ export declare const inject: readonly ["tools"];
23
+ /** The shape `Schema.object` infers below, named for use in signatures. */
24
+ export interface PluginConfig {
25
+ scanDefaultCapKb: number;
26
+ minifiedSampleLimit: number;
27
+ serveRoutes: boolean;
28
+ /**
29
+ * Shapes the maintenance commands the panel hands to the operator. The defaults
30
+ * are the values measured to work inside a confined DSH process; a deployment
31
+ * that is not sandboxed can turn them off to get keyword search and a
32
+ * self-managed heap.
33
+ */
34
+ skipFts: boolean;
35
+ memoryOff: boolean;
36
+ analyzeHeapMb: number;
37
+ /**
38
+ * Wall-clock ceiling for a run this plugin starts.
39
+ *
40
+ * Ten minutes, because the escape hatch this panel offers is a full rebuild and
41
+ * those are measured in minutes on a large repository. Too short a ceiling turns
42
+ * the feature into "it always times out on the repos that need it most"; the run
43
+ * is cancellable, so the cost of it being generous is bounded by the operator.
44
+ */
45
+ analyzeTimeoutMs: number;
46
+ /**
47
+ * Where the plugin installs GitNexus when asked. Empty means the default,
48
+ * `$DSH_HOME/gitnexus-plus/gitnexus`.
49
+ */
50
+ gitnexusInstallDir: string;
51
+ /** Wall-clock ceiling for that install. Compiling native modules is slow. */
52
+ installTimeoutMs: number;
53
+ gitnexusHome: string;
54
+ }
55
+ export declare const Config: import("@deepseek-ai/schemastery").SchemaLike<{
56
+ scanDefaultCapKb: number;
57
+ minifiedSampleLimit: number;
58
+ serveRoutes: boolean;
59
+ skipFts: boolean;
60
+ memoryOff: boolean;
61
+ analyzeHeapMb: number;
62
+ analyzeTimeoutMs: number;
63
+ gitnexusInstallDir: string;
64
+ installTimeoutMs: number;
65
+ gitnexusHome: string;
66
+ }>;
67
+ /** The maintenance payload the panel renders alongside a report. */
68
+ export interface MaintenanceInfo {
69
+ commands: MaintenanceCommand[];
70
+ rationale: string[];
71
+ }
72
+ export declare function apply(ctx: Context, config: PluginConfig): void;
73
+ /**
74
+ * Human-readable rendering lives in `./gitnexus/format.js` 鈥?free of DSH imports
75
+ * so it can be tested without a host. Re-exported here for callers that already
76
+ * hold the plugin module.
77
+ */
78
+ export { formatReport } from './gitnexus/format.js';
79
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AAKnD,OAAO,EAAyC,KAAK,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAqBxG,eAAO,MAAM,IAAI,kBAAkB,CAAC;AAEpC;;;;GAIG;AACH,eAAO,MAAM,MAAM,oBAAqB,CAAC;AAEzC,2EAA2E;AAC3E,MAAM,WAAW,YAAY;IAC3B,gBAAgB,EAAE,MAAM,CAAC;IACzB,mBAAmB,EAAE,MAAM,CAAC;IAC5B,WAAW,EAAE,OAAO,CAAC;IACrB;;;;;OAKG;IACH,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,EAAE,OAAO,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB;;;;;;;OAOG;IACH,gBAAgB,EAAE,MAAM,CAAC;IACzB;;;OAGG;IACH,kBAAkB,EAAE,MAAM,CAAC;IAC3B,6EAA6E;IAC7E,gBAAgB,EAAE,MAAM,CAAC;IACzB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,eAAO,MAAM,MAAM;;;;;;;;;;;EAWjB,CAAC;AAqBH,oEAAoE;AACpE,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,kBAAkB,EAAE,CAAC;IAC/B,SAAS,EAAE,MAAM,EAAE,CAAC;CACrB;AAiBD,wBAAgB,KAAK,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,YAAY,GAAG,IAAI,CAuJ9D;AAED;;;;GAIG;AACH,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC"}
package/lib/index.js ADDED
@@ -0,0 +1,211 @@
1
+ /**
2
+ * dsh-gitnexus-plus 鈥?host half.
3
+ *
4
+ * Registers one read-only tool and one loopback HTTP route over the coverage
5
+ * engine in `./gitnexus/`. Both answer the same question 鈥?"what did this index
6
+ * leave out?" 鈥?for the two callers that need it: the model, and a browser half
7
+ * that will render it.
8
+ *
9
+ * Deliberately read-only. Nothing here triggers `analyze`, writes to `.gitnexus/`,
10
+ * or takes the index lock; driving updates is a separate concern with its own
11
+ * safety story, and mixing the two would mean a status check could start a
12
+ * 30-second rebuild.
13
+ */
14
+ import Schema from '@deepseek-ai/schemastery';
15
+ import { defineTool } from '@deepseek-ai/dsh-tools';
16
+ import { analyzeCoverage } from './gitnexus/coverage.js';
17
+ import { formatReport } from './gitnexus/format.js';
18
+ import { maintenanceCommands, commandRationale } from './gitnexus/commands.js';
19
+ import { mountCoverageRoute } from './routes/coverage.js';
20
+ import { mountAnalyzeRoute } from './routes/analyze.js';
21
+ import { mountInstallRoute } from './routes/install.js';
22
+ import { createAnalyzeRunner } from './gitnexus/analyze.js';
23
+ import { createInstallRunner } from './gitnexus/install.js';
24
+ /**
25
+ * Whether a service resolved from the context has the shape the runner needs.
26
+ *
27
+ * `ctx.get` is untyped here 鈥?the DSH packages ship no declarations, so the service
28
+ * arrives as `unknown`. Checking the two methods the runner actually calls is the
29
+ * narrowest claim that can be made: asserting the full `ctx.shell` contract from this
30
+ * side would be a guess, and a wrong guess fails at the first click rather than here.
31
+ */
32
+ function isShellLike(value) {
33
+ if (value === null || typeof value !== 'object')
34
+ return false;
35
+ const candidate = value;
36
+ return typeof candidate.resolve === 'function' && typeof candidate.execute === 'function';
37
+ }
38
+ export const name = 'gitnexus-plus';
39
+ /**
40
+ * Only `tools` is required. `webServer` is reached through `ctx.inject` because a
41
+ * headless profile has no HTTP carrier at all 鈥?naming it here would leave the
42
+ * plugin permanently pending there, and the tool would never register.
43
+ */
44
+ export const inject = ['tools'];
45
+ export const Config = Schema.object({
46
+ scanDefaultCapKb: Schema.natural().default(512),
47
+ minifiedSampleLimit: Schema.natural().default(40),
48
+ serveRoutes: Schema.boolean().default(true),
49
+ skipFts: Schema.boolean().default(true),
50
+ memoryOff: Schema.boolean().default(true),
51
+ analyzeHeapMb: Schema.natural().default(8192),
52
+ analyzeTimeoutMs: Schema.natural().default(600000),
53
+ gitnexusInstallDir: Schema.string().default(''),
54
+ installTimeoutMs: Schema.natural().default(1200000),
55
+ gitnexusHome: Schema.string().default(''),
56
+ });
57
+ const configShapeMatches = true;
58
+ void configShapeMatches;
59
+ export function apply(ctx, config) {
60
+ const cache = new Map();
61
+ const CACHE_TTL_MS = 15_000;
62
+ /**
63
+ * Resolve a coverage report, from cache when it is recent enough.
64
+ *
65
+ * Scan results are pure functions of the working tree, so a short TTL removes the
66
+ * repeated cost of a 28k-file walk without ever showing a stale verdict to a caller
67
+ * who just changed something.
68
+ *
69
+ * `fresh` bypasses the TTL. A manual refresh is an explicit instruction from a
70
+ * person who believes the numbers may have moved, and answering it with a cached
71
+ * copy makes the control look broken 鈥?which is exactly how it was reported. A
72
+ * background poll is the case the TTL exists for; a click is not.
73
+ */
74
+ const coverageFor = async (repoPath, options = {}) => {
75
+ const key = String(repoPath);
76
+ if (options.fresh !== true) {
77
+ const hit = cache.get(key);
78
+ if (hit !== undefined && Date.now() - hit.at < CACHE_TTL_MS)
79
+ return hit.report;
80
+ }
81
+ const report = await analyzeCoverage(key, {
82
+ minifiedSampleLimit: config.minifiedSampleLimit,
83
+ });
84
+ cache.set(key, { at: Date.now(), report });
85
+ return report;
86
+ };
87
+ /**
88
+ * The maintenance commands, resolved at the boundary rather than inside
89
+ * `analyzeCoverage`: they depend on this plugin's config, and the report is a
90
+ * pure function of the repository.
91
+ */
92
+ const maintenanceFor = (repoPath) => {
93
+ const options = {
94
+ skipFts: config.skipFts,
95
+ memoryOff: config.memoryOff,
96
+ gitnexusHome: config.gitnexusHome === '' ? null : config.gitnexusHome,
97
+ };
98
+ return {
99
+ commands: maintenanceCommands(repoPath, options),
100
+ rationale: commandRationale(options),
101
+ };
102
+ };
103
+ ctx.tools.register(defineTool({
104
+ name: 'gitnexus_coverage',
105
+ description: 'Report what a GitNexus index omitted for a repository: files skipped for exceeding the ' +
106
+ 'size cap, oversized generated/minified output that inflates a language share, and files ' +
107
+ 'a fresh scan would select but the index does not cover. Read-only; never triggers a ' +
108
+ 're-index. Use it to judge how much of a repository the index actually represents before ' +
109
+ 'trusting graph queries.',
110
+ parameters: {
111
+ repo: {
112
+ type: 'string',
113
+ required: true,
114
+ description: 'Absolute path to the repository root (the directory containing .gitnexus).',
115
+ },
116
+ includeLanguages: {
117
+ type: 'boolean',
118
+ description: 'Include the per-language byte breakdown in the result. Defaults to true.',
119
+ },
120
+ },
121
+ output: {
122
+ schema: {
123
+ type: 'object',
124
+ additionalProperties: true,
125
+ properties: {
126
+ // `required: true` per property, never `required: [...]`. The DSL's
127
+ // compiler rejects the array form outright 鈥?it collects these marks
128
+ // into the JSON Schema `required` list itself.
129
+ text: { type: 'string', required: true },
130
+ indexed: { type: 'boolean', required: true },
131
+ largeFileCount: { type: 'integer', required: true },
132
+ largeBytes: { type: 'integer', required: true },
133
+ warnings: { type: 'array' },
134
+ },
135
+ },
136
+ render: (_args, value) => [{ type: 'text', text: value.text }],
137
+ },
138
+ async execute(args) {
139
+ const report = await coverageFor(args.repo);
140
+ return {
141
+ text: formatReport(report, args.includeLanguages !== false),
142
+ indexed: report.repo.indexed,
143
+ largeFileCount: report.totals.large,
144
+ largeBytes: report.totals.largeBytes,
145
+ warnings: report.warnings.map((w) => ({ level: w.level, code: w.code, message: w.message })),
146
+ };
147
+ },
148
+ presentCall: (args) => ({
149
+ card: 'generic',
150
+ title: `GitNexus coverage 路 ${String(args.repo).split(/[\\/]/).pop()}`,
151
+ kind: 'read',
152
+ }),
153
+ }));
154
+ if (config.serveRoutes) {
155
+ // Optional carrier: absent on headless profiles, so the tool above still works.
156
+ ctx.inject(['webServer'], (svcCtx) => {
157
+ // Read through `ctx.get` rather than injecting, because `shell` is optional here
158
+ // and `inject` would make the whole route block wait for a service a headless
159
+ // deployment never provides. `null` is handed to the runner, which reports
160
+ // `unavailable` and tells the operator to run the command themselves 鈥?the
161
+ // read-only half of the panel keeps working either way.
162
+ const shellRaw = svcCtx.get('shell');
163
+ const analyze = createAnalyzeRunner({
164
+ shell: isShellLike(shellRaw) ? shellRaw : null,
165
+ timeoutMs: config.analyzeTimeoutMs,
166
+ });
167
+ // GitNexus is what produces the index this plugin reads, so a machine without it
168
+ // has nothing to display and no way to change that from here. This runner is the
169
+ // way out: it installs a plugin-owned copy so the operator does not have to work
170
+ // out a package manager, a registry, or a global prefix themselves.
171
+ const install = createInstallRunner({
172
+ shell: isShellLike(shellRaw) ? shellRaw : null,
173
+ prefix: config.gitnexusInstallDir === '' ? undefined : config.gitnexusInstallDir,
174
+ timeoutMs: config.installTimeoutMs,
175
+ });
176
+ const disposers = [
177
+ mountCoverageRoute(svcCtx.webServer, {
178
+ get: (repoPath, options) => coverageFor(repoPath, options),
179
+ maintenance: (repoPath) => maintenanceFor(repoPath),
180
+ }),
181
+ mountAnalyzeRoute(svcCtx.webServer, {
182
+ start: (repoPath) => analyze.start(repoPath),
183
+ status: (repoPath) => analyze.status(repoPath),
184
+ cancel: (repoPath) => analyze.cancel(repoPath),
185
+ }),
186
+ mountInstallRoute(svcCtx.webServer, {
187
+ start: () => install.start(),
188
+ status: () => install.status(),
189
+ cancel: () => install.cancel(),
190
+ cliPath: () => install.cliPath(),
191
+ }),
192
+ ];
193
+ ctx.effect(() => () => {
194
+ for (const dispose of disposers)
195
+ dispose();
196
+ // Abort before clearing, so a run in flight is stopped rather than orphaned
197
+ // holding a handle onto a server that no longer exists.
198
+ analyze.dispose();
199
+ install.dispose();
200
+ cache.clear();
201
+ }, 'gitnexus-plus: HTTP routes');
202
+ });
203
+ }
204
+ }
205
+ /**
206
+ * Human-readable rendering lives in `./gitnexus/format.js` 鈥?free of DSH imports
207
+ * so it can be tested without a host. Re-exported here for callers that already
208
+ * hold the plugin module.
209
+ */
210
+ export { formatReport } from './gitnexus/format.js';
211
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,35 @@
1
+ /**
2
+ * The one endpoint that changes something.
3
+ *
4
+ * Everything else this plugin serves is a reading of the working tree; this route
5
+ * starts `gitnexus analyze --index-only` through the host's shell executor. It is a
6
+ * separate module from `coverage.ts` rather than another branch inside it, so the
7
+ * read-only half keeps a guard set that cannot mutate and this half can be reviewed
8
+ * on its own.
9
+ *
10
+ * THREE METHODS, THREE MEANINGS
11
+ * -----------------------------
12
+ * `POST` starts a run, `GET` reports where it got to, `DELETE` asks it to stop. They
13
+ * are one endpoint because the run is one resource and the panel polls the same URL it
14
+ * started, which removes the id bookkeeping a separate status route would need.
15
+ *
16
+ * WHAT THIS ROUTE DOES NOT DO
17
+ * ---------------------------
18
+ * It does not escalate. The runner never sets `sandboxPolicy`, so the process is
19
+ * confined exactly as the deployment confines every other command. A refusal comes
20
+ * back as `denied` and is not retried wider — see `gitnexus/analyze.ts`, which is
21
+ * where that decision lives and is tested.
22
+ */
23
+ import type { WebServerService } from '@deepseek-ai/cordis';
24
+ import type { AnalyzeStatus } from '../gitnexus/analyze.js';
25
+ export interface AnalyzeRouteDeps {
26
+ /** Begin a run, or return the one already in flight for this repository. */
27
+ start: (repoPath: string) => AnalyzeStatus;
28
+ status: (repoPath: string) => AnalyzeStatus | null;
29
+ cancel: (repoPath: string) => boolean;
30
+ }
31
+ /**
32
+ * @returns disposer that unregisters the route
33
+ */
34
+ export declare function mountAnalyzeRoute(webServer: WebServerService, deps: AnalyzeRouteDeps): () => void;
35
+ //# sourceMappingURL=analyze.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"analyze.d.ts","sourceRoot":"","sources":["../../src/routes/analyze.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAIH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAQ5D,MAAM,WAAW,gBAAgB;IAC/B,4EAA4E;IAC5E,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,aAAa,CAAC;IAC3C,MAAM,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,aAAa,GAAG,IAAI,CAAC;IACnD,MAAM,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC;CACvC;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,gBAAgB,EAAE,IAAI,EAAE,gBAAgB,GAAG,MAAM,IAAI,CAuCjG"}
@@ -0,0 +1,71 @@
1
+ /**
2
+ * The one endpoint that changes something.
3
+ *
4
+ * Everything else this plugin serves is a reading of the working tree; this route
5
+ * starts `gitnexus analyze --index-only` through the host's shell executor. It is a
6
+ * separate module from `coverage.ts` rather than another branch inside it, so the
7
+ * read-only half keeps a guard set that cannot mutate and this half can be reviewed
8
+ * on its own.
9
+ *
10
+ * THREE METHODS, THREE MEANINGS
11
+ * -----------------------------
12
+ * `POST` starts a run, `GET` reports where it got to, `DELETE` asks it to stop. They
13
+ * are one endpoint because the run is one resource and the panel polls the same URL it
14
+ * started, which removes the id bookkeeping a separate status route would need.
15
+ *
16
+ * WHAT THIS ROUTE DOES NOT DO
17
+ * ---------------------------
18
+ * It does not escalate. The runner never sets `sandboxPolicy`, so the process is
19
+ * confined exactly as the deployment confines every other command. A refusal comes
20
+ * back as `denied` and is not retried wider — see `gitnexus/analyze.ts`, which is
21
+ * where that decision lives and is tested.
22
+ */
23
+ import path from 'node:path';
24
+ import { guardHandler, passGuard, passWriteGuard, answerHead, sendJson } from './guard.js';
25
+ /**
26
+ * The methods this route answers. A wider set than the coverage route's on purpose:
27
+ * the difference between the two routes *is* their method policy.
28
+ */
29
+ const ANALYZE_METHODS = new Set(['GET', 'HEAD', 'POST', 'DELETE']);
30
+ /**
31
+ * @returns disposer that unregisters the route
32
+ */
33
+ export function mountAnalyzeRoute(webServer, deps) {
34
+ const handler = guardHandler(async (req, res) => {
35
+ if (!passGuard(req, res, ANALYZE_METHODS))
36
+ return;
37
+ const url = new URL(req.url ?? '/', 'http://127.0.0.1');
38
+ const repo = url.searchParams.get('repo');
39
+ if (repo === null || repo.trim() === '') {
40
+ sendJson(res, 400, { ok: false, code: 'missing-repo', message: 'repo query parameter is required' });
41
+ return;
42
+ }
43
+ const absRepo = path.resolve(repo);
44
+ if (answerHead(req, res))
45
+ return;
46
+ if (req.method === 'POST') {
47
+ // The only mutating verb here, so the only one that takes the write guard. A
48
+ // cross-origin page can reach loopback; `Origin` is what separates it from the
49
+ // desktop shell's own fetch.
50
+ if (!passWriteGuard(req, res))
51
+ return;
52
+ sendJson(res, 200, { ok: true, run: deps.start(absRepo) });
53
+ return;
54
+ }
55
+ if (req.method === 'DELETE') {
56
+ if (!passWriteGuard(req, res))
57
+ return;
58
+ const cancelled = deps.cancel(absRepo);
59
+ // Cancelling nothing is not an error worth a 404: the caller's intent — "this
60
+ // repository should not be analysing" — already holds.
61
+ sendJson(res, 200, { ok: true, cancelled, run: deps.status(absRepo) });
62
+ return;
63
+ }
64
+ const run = deps.status(absRepo);
65
+ // `null` means nothing has run here in this host's lifetime, which is a state the
66
+ // panel renders ("尚未运行"), not a failure.
67
+ sendJson(res, 200, { ok: true, run });
68
+ });
69
+ return webServer.register({ kind: 'prefix', path: '/api/gitnexus/analyze', handler });
70
+ }
71
+ //# sourceMappingURL=analyze.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"analyze.js","sourceRoot":"","sources":["../../src/routes/analyze.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,cAAc,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAI3F;;;GAGG;AACH,MAAM,eAAe,GAAwB,IAAI,GAAG,CAAC,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC;AASxF;;GAEG;AACH,MAAM,UAAU,iBAAiB,CAAC,SAA2B,EAAE,IAAsB;IACnF,MAAM,OAAO,GAAG,YAAY,CAAC,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,EAAE;QAC9C,IAAI,CAAC,SAAS,CAAC,GAAG,EAAE,GAAG,EAAE,eAAe,CAAC;YAAE,OAAO;QAElD,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,GAAG,IAAI,GAAG,EAAE,kBAAkB,CAAC,CAAC;QACxD,MAAM,IAAI,GAAG,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QAC1C,IAAI,IAAI,KAAK,IAAI,IAAI,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;YACxC,QAAQ,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,cAAc,EAAE,OAAO,EAAE,kCAAkC,EAAE,CAAC,CAAC;YACrG,OAAO;QACT,CAAC;QACD,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QAEnC,IAAI,UAAU,CAAC,GAAG,EAAE,GAAG,CAAC;YAAE,OAAO;QAEjC,IAAI,GAAG,CAAC,MAAM,KAAK,MAAM,EAAE,CAAC;YAC1B,6EAA6E;YAC7E,+EAA+E;YAC/E,6BAA6B;YAC7B,IAAI,CAAC,cAAc,CAAC,GAAG,EAAE,GAAG,CAAC;gBAAE,OAAO;YACtC,QAAQ,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;YAC3D,OAAO;QACT,CAAC;QAED,IAAI,GAAG,CAAC,MAAM,KAAK,QAAQ,EAAE,CAAC;YAC5B,IAAI,CAAC,cAAc,CAAC,GAAG,EAAE,GAAG,CAAC;gBAAE,OAAO;YACtC,MAAM,SAAS,GAAG,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;YACvC,8EAA8E;YAC9E,uDAAuD;YACvD,QAAQ,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,GAAG,EAAE,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;YACvE,OAAO;QACT,CAAC;QAED,MAAM,GAAG,GAAG,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;QACjC,kFAAkF;QAClF,yCAAyC;QACzC,QAAQ,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC,CAAC;IACxC,CAAC,CAAC,CAAC;IAEH,OAAO,SAAS,CAAC,QAAQ,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,uBAAuB,EAAE,OAAO,EAAE,CAAC,CAAC;AACxF,CAAC"}
@@ -0,0 +1,44 @@
1
+ /**
2
+ * `GET /api/gitnexus/coverage?repo=<project>` — the panel's data source.
3
+ *
4
+ * Route shape follows the community convention (`/api/<plugin-slug>/…`) and the
5
+ * carrier contract in `@deepseek-ai/dsh-host-webserver`: bare `node:http`, no
6
+ * Express, exact-then-longest-prefix matching, and a disposer for teardown.
7
+ *
8
+ * THE BOUND IS "IS A GITNEXUS INDEX", NOT "IS IN THE REGISTRY"
9
+ * -----------------------------------------------------------
10
+ * An earlier revision accepted only paths present in `~/.gitnexus/registry.json`.
11
+ * That was wrong for the panel's actual job: it shows the project the user is
12
+ * *in*, and a working directory the global registry has never heard of — a fresh
13
+ * clone, an index built with `GITNEXUS_HOME` redirected — is exactly the case the
14
+ * panel exists to report on. Refusing it would have been a correctness bug wearing
15
+ * a security costume.
16
+ *
17
+ * The route still refuses to describe arbitrary directories: the path must already
18
+ * contain `.gitnexus/gitnexus.json` or `.gitnexus/meta.json`, so a caller cannot
19
+ * use it to enumerate a filesystem. Loopback and method guards live in `./guard.js`.
20
+ */
21
+ import type { WebServerService } from '@deepseek-ai/cordis';
22
+ import type { CoverageReport } from '../gitnexus/coverage.js';
23
+ import type { MaintenanceInfo } from '../index.js';
24
+ /**
25
+ * What the route needs from the host half.
26
+ *
27
+ * Injected rather than imported so the route stays testable without a GitNexus
28
+ * install: the engine arrives as two functions, and a test supplies stubs.
29
+ */
30
+ export interface CoverageRouteDeps {
31
+ /**
32
+ * `fresh` asks the engine to skip its short-lived cache. A manual refresh carries
33
+ * it; a background poll does not, which is the case the cache exists for.
34
+ */
35
+ get: (repoPath: string, options?: {
36
+ fresh?: boolean;
37
+ }) => Promise<CoverageReport>;
38
+ maintenance?: (repoPath: string) => MaintenanceInfo;
39
+ }
40
+ /**
41
+ * @returns disposer that unregisters the route
42
+ */
43
+ export declare function mountCoverageRoute(webServer: WebServerService, deps: CoverageRouteDeps): () => void;
44
+ //# sourceMappingURL=coverage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"coverage.d.ts","sourceRoot":"","sources":["../../src/routes/coverage.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAKH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAC9D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAEnD;;;;;GAKG;AACH,MAAM,WAAW,iBAAiB;IAChC;;;OAGG;IACH,GAAG,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,KAAK,OAAO,CAAC,cAAc,CAAC,CAAC;IAClF,WAAW,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,eAAe,CAAC;CACrD;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,gBAAgB,EAAE,IAAI,EAAE,iBAAiB,GAAG,MAAM,IAAI,CA2CnG"}
@@ -0,0 +1,68 @@
1
+ /**
2
+ * `GET /api/gitnexus/coverage?repo=<project>` — the panel's data source.
3
+ *
4
+ * Route shape follows the community convention (`/api/<plugin-slug>/…`) and the
5
+ * carrier contract in `@deepseek-ai/dsh-host-webserver`: bare `node:http`, no
6
+ * Express, exact-then-longest-prefix matching, and a disposer for teardown.
7
+ *
8
+ * THE BOUND IS "IS A GITNEXUS INDEX", NOT "IS IN THE REGISTRY"
9
+ * -----------------------------------------------------------
10
+ * An earlier revision accepted only paths present in `~/.gitnexus/registry.json`.
11
+ * That was wrong for the panel's actual job: it shows the project the user is
12
+ * *in*, and a working directory the global registry has never heard of — a fresh
13
+ * clone, an index built with `GITNEXUS_HOME` redirected — is exactly the case the
14
+ * panel exists to report on. Refusing it would have been a correctness bug wearing
15
+ * a security costume.
16
+ *
17
+ * The route still refuses to describe arbitrary directories: the path must already
18
+ * contain `.gitnexus/gitnexus.json` or `.gitnexus/meta.json`, so a caller cannot
19
+ * use it to enumerate a filesystem. Loopback and method guards live in `./guard.js`.
20
+ */
21
+ import path from 'node:path';
22
+ import { hasIndex } from '../gitnexus/index-meta.js';
23
+ import { guardHandler, passGuard, answerHead, sendJson } from './guard.js';
24
+ /**
25
+ * @returns disposer that unregisters the route
26
+ */
27
+ export function mountCoverageRoute(webServer, deps) {
28
+ const handler = guardHandler(async (req, res) => {
29
+ if (!passGuard(req, res))
30
+ return;
31
+ const url = new URL(req.url ?? '/', 'http://127.0.0.1');
32
+ const repo = url.searchParams.get('repo');
33
+ if (repo === null || repo.trim() === '') {
34
+ sendJson(res, 400, {
35
+ ok: false,
36
+ code: 'missing-repo',
37
+ message: 'repo query parameter is required',
38
+ });
39
+ return;
40
+ }
41
+ const absRepo = path.resolve(repo);
42
+ if (!(await hasIndex(absRepo))) {
43
+ // Not an error the caller should retry: this directory simply has no index
44
+ // yet, which is the state the panel's initialize action exists for.
45
+ sendJson(res, 404, {
46
+ ok: false,
47
+ code: 'not-indexed',
48
+ message: 'directory has no GitNexus index (.gitnexus/gitnexus.json or meta.json)',
49
+ });
50
+ return;
51
+ }
52
+ if (answerHead(req, res))
53
+ return;
54
+ // `refresh=1` comes from the panel's manual refresh control. A background poll
55
+ // omits it and gets the cached copy, which is what the TTL is for.
56
+ const fresh = url.searchParams.get('refresh') === '1';
57
+ const report = await deps.get(absRepo, { fresh });
58
+ // The maintenance commands ride along with the report because they are
59
+ // config-derived and only meaningful next to the index they would rebuild.
60
+ const maintenance = typeof deps.maintenance === 'function' ? deps.maintenance(absRepo) : null;
61
+ // `readAt` is what makes a refresh visible: without it two reads of an unchanged
62
+ // repository are byte-identical on screen, so the control looks inert even when
63
+ // it did exactly what it was asked.
64
+ sendJson(res, 200, { ok: true, report, maintenance, readAt: new Date().toISOString() });
65
+ });
66
+ return webServer.register({ kind: 'prefix', path: '/api/gitnexus/coverage', handler });
67
+ }
68
+ //# sourceMappingURL=coverage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"coverage.js","sourceRoot":"","sources":["../../src/routes/coverage.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,QAAQ,EAAE,MAAM,2BAA2B,CAAC;AACrD,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAoB3E;;GAEG;AACH,MAAM,UAAU,kBAAkB,CAAC,SAA2B,EAAE,IAAuB;IACrF,MAAM,OAAO,GAAG,YAAY,CAAC,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,EAAE;QAC9C,IAAI,CAAC,SAAS,CAAC,GAAG,EAAE,GAAG,CAAC;YAAE,OAAO;QAEjC,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,GAAG,IAAI,GAAG,EAAE,kBAAkB,CAAC,CAAC;QACxD,MAAM,IAAI,GAAG,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QAC1C,IAAI,IAAI,KAAK,IAAI,IAAI,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;YACxC,QAAQ,CAAC,GAAG,EAAE,GAAG,EAAE;gBACjB,EAAE,EAAE,KAAK;gBACT,IAAI,EAAE,cAAc;gBACpB,OAAO,EAAE,kCAAkC;aAC5C,CAAC,CAAC;YACH,OAAO;QACT,CAAC;QAED,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QACnC,IAAI,CAAC,CAAC,MAAM,QAAQ,CAAC,OAAO,CAAC,CAAC,EAAE,CAAC;YAC/B,2EAA2E;YAC3E,oEAAoE;YACpE,QAAQ,CAAC,GAAG,EAAE,GAAG,EAAE;gBACjB,EAAE,EAAE,KAAK;gBACT,IAAI,EAAE,aAAa;gBACnB,OAAO,EAAE,wEAAwE;aAClF,CAAC,CAAC;YACH,OAAO;QACT,CAAC;QAED,IAAI,UAAU,CAAC,GAAG,EAAE,GAAG,CAAC;YAAE,OAAO;QAEjC,+EAA+E;QAC/E,mEAAmE;QACnE,MAAM,KAAK,GAAG,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,SAAS,CAAC,KAAK,GAAG,CAAC;QACtD,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;QAClD,uEAAuE;QACvE,2EAA2E;QAC3E,MAAM,WAAW,GAAG,OAAO,IAAI,CAAC,WAAW,KAAK,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;QAC9F,iFAAiF;QACjF,gFAAgF;QAChF,oCAAoC;QACpC,QAAQ,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC;IAC1F,CAAC,CAAC,CAAC;IAEH,OAAO,SAAS,CAAC,QAAQ,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,wBAAwB,EAAE,OAAO,EAAE,CAAC,CAAC;AACzF,CAAC"}
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Shared guards for this plugin's HTTP routes.
3
+ *
4
+ * Extracted because two routes now need identical treatment, and a guard that is
5
+ * copy-pasted stops being a guard the first time one copy is edited.
6
+ *
7
+ * WHY THE GUARD EXISTS AT ALL
8
+ * ---------------------------
9
+ * On a bare `dsh web` profile a third-party named route is unauthenticated: the
10
+ * carrier owns no TLS, authentication, or Origin policy, and the Host/Origin
11
+ * checks that do exist belong to `client/connection` and cover only the routes it
12
+ * registers itself. The Desktop carrier adds its own gate (an
13
+ * `x-dsh-desktop-renderer` token; unauthenticated requests get 403), but a route
14
+ * that is safe only on one carrier is not safe.
15
+ */
16
+ import type { IncomingMessage, ServerResponse } from 'node:http';
17
+ import type { RequestHandler } from '@deepseek-ai/cordis';
18
+ /** This plugin only ever answers reads. */
19
+ export declare const ALLOWED_METHODS: Set<string>;
20
+ /**
21
+ * @param {import('node:http').IncomingMessage} req
22
+ * @returns {boolean} whether the peer is this machine
23
+ */
24
+ export declare function isLoopback(req: IncomingMessage): boolean;
25
+ /**
26
+ * @param {import('node:http').ServerResponse} res
27
+ * @param {number} status
28
+ * @param {unknown} body
29
+ * @param {Record<string, string>} [headers]
30
+ */
31
+ export declare function sendJson(res: ServerResponse, status: number, body: unknown, headers?: Record<string, string>): void;
32
+ /**
33
+ * Run the shared pre-checks, answering the request when one fails.
34
+ *
35
+ * `methods` exists because the default set is the read-only one: the coverage route
36
+ * answers GET and HEAD and nothing else, and giving it POST would widen it by accident.
37
+ * A route that legitimately mutates passes its own set, so the method policy is stated
38
+ * at the route rather than inferred from what the guard happens to allow.
39
+ *
40
+ * @param {import('node:http').IncomingMessage} req
41
+ * @param {import('node:http').ServerResponse} res
42
+ * @param methods the methods this route accepts
43
+ * @returns `true` when the caller may continue handling the request
44
+ */
45
+ export declare function passGuard(req: IncomingMessage, res: ServerResponse, methods?: ReadonlySet<string>): boolean;
46
+ /**
47
+ * Answer a HEAD request with headers only, or report that the caller should
48
+ * continue and send the body.
49
+ *
50
+ * @returns `true` when the request was fully answered
51
+ */
52
+ export declare function answerHead(req: IncomingMessage, res: ServerResponse): boolean;
53
+ /**
54
+ * Wrap a handler so no failure escapes as an unhandled rejection on the carrier.
55
+ *
56
+ * `Promise.resolve` around the call is not decoration: `RequestHandler` may return
57
+ * `void`, and calling `.catch` on `void` is a runtime `TypeError`. The shape the
58
+ * carrier actually uses is "sync throw or async rejection", and only the resolved
59
+ * form covers both.
60
+ */
61
+ export declare function guardHandler(handler: RequestHandler): RequestHandler;
62
+ /**
63
+ * Extra guard for routes that *change* something.
64
+ *
65
+ * The loopback check alone is not enough for a mutation: any page the operator
66
+ * visits can issue a request to `127.0.0.1`, and a cross-origin `POST` needs no
67
+ * preflight when it uses a simple content type. `Origin` is the discriminator —
68
+ * a malicious page always sends its own origin, and it will not match this host.
69
+ *
70
+ * A request with NO `Origin` is allowed through, and that is deliberate rather
71
+ * than lazy: a same-origin `fetch` from the desktop shell, and any local tool
72
+ * (including this repository's own tests), legitimately send none. The remaining
73
+ * gates — loopback peer, and the Desktop carrier's renderer token — still apply,
74
+ * so an absent header is not by itself a way in.
75
+ *
76
+ * @param {import('node:http').IncomingMessage} req
77
+ * @param {import('node:http').ServerResponse} res
78
+ * @returns {boolean} `true` when the caller may proceed
79
+ */
80
+ export declare function passWriteGuard(req: IncomingMessage, res: ServerResponse): boolean;
81
+ /**
82
+ * Read a JSON body with a hard byte cap.
83
+ *
84
+ * Bounded because the cap is the only thing standing between a malformed client
85
+ * and unbounded host memory — and this plugin's bodies are at most a few fields.
86
+ *
87
+ * @param {import('node:http').IncomingMessage} req
88
+ * @param {number} [maxBytes]
89
+ * @returns {Promise<Record<string, unknown>>}
90
+ */
91
+ export declare function readJsonBody(req: IncomingMessage, maxBytes?: number): Promise<unknown>;
92
+ //# sourceMappingURL=guard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"guard.d.ts","sourceRoot":"","sources":["../../src/routes/guard.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AACjE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAE1D,2CAA2C;AAC3C,eAAO,MAAM,eAAe,aAA2B,CAAC;AAExD;;;GAGG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE,eAAe,GAAG,OAAO,CASxD;AAED;;;;;GAKG;AACH,wBAAgB,QAAQ,CAAC,GAAG,EAAE,cAAc,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAM,QAShH;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,SAAS,CACvB,GAAG,EAAE,eAAe,EACpB,GAAG,EAAE,cAAc,EACnB,OAAO,GAAE,WAAW,CAAC,MAAM,CAAmB,GAC7C,OAAO,CAUT;AAED;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE,eAAe,EAAE,GAAG,EAAE,cAAc,GAAG,OAAO,CAM7E;AAED;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,cAAc,GAAG,cAAc,CAcpE;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,eAAe,EAAE,GAAG,EAAE,cAAc,GAAG,OAAO,CAmBjF;AAED;;;;;;;;;GASG;AACH,wBAAsB,YAAY,CAAC,GAAG,EAAE,eAAe,EAAE,QAAQ,SAAW,GAAG,OAAO,CAAC,OAAO,CAAC,CA4B9F"}