@karmaniverous/jeeves-server 3.0.0-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/.env.local +13 -0
- package/.env.local.template +13 -0
- package/.tsbuildinfo +1 -0
- package/CHANGELOG.md +450 -0
- package/about.md +82 -0
- package/client/README.md +73 -0
- package/client/eslint.config.js +23 -0
- package/client/index.html +14 -0
- package/client/package-lock.json +5181 -0
- package/client/package.json +60 -0
- package/client/public/vite.svg +1 -0
- package/client/src/App.tsx +22 -0
- package/client/src/components/AccountMenu.tsx +167 -0
- package/client/src/components/ActionDropdown.tsx +120 -0
- package/client/src/components/CodeEditor.tsx +143 -0
- package/client/src/components/CodeViewer.tsx +113 -0
- package/client/src/components/ConfirmDialog.tsx +32 -0
- package/client/src/components/DirectoryRow.tsx +62 -0
- package/client/src/components/DirectoryTable.tsx +42 -0
- package/client/src/components/DownloadDropdown.tsx +116 -0
- package/client/src/components/DriveList.tsx +54 -0
- package/client/src/components/EmbeddedDiagramPanzoom.ts +28 -0
- package/client/src/components/FileContentView.tsx +155 -0
- package/client/src/components/InlineSvgPanzoom.ts +60 -0
- package/client/src/components/LazyDiagram.ts +93 -0
- package/client/src/components/LinkDropdown.tsx +134 -0
- package/client/src/components/MarkdownView.tsx +115 -0
- package/client/src/components/MermaidViewer.tsx +21 -0
- package/client/src/components/PlantUmlViewer.tsx +21 -0
- package/client/src/components/SearchModal.tsx +424 -0
- package/client/src/components/SvgViewer.tsx +107 -0
- package/client/src/components/TabBar.tsx +96 -0
- package/client/src/components/layout/Header.tsx +270 -0
- package/client/src/components/panzoom.ts +203 -0
- package/client/src/components/renderableUtils.ts +15 -0
- package/client/src/components/runner/JobTable.tsx +153 -0
- package/client/src/components/runner/RunHistory.tsx +140 -0
- package/client/src/components/runner/StatsBar.tsx +43 -0
- package/client/src/components/runner/StatusPill.tsx +27 -0
- package/client/src/components/runner/jobTableUtils.ts +65 -0
- package/client/src/components/scrollUtils.ts +39 -0
- package/client/src/components/ui/alert-dialog.tsx +107 -0
- package/client/src/components/ui/button.tsx +40 -0
- package/client/src/components/ui/dropdown-menu.tsx +79 -0
- package/client/src/components/ui/input.tsx +26 -0
- package/client/src/components/useActionState.ts +43 -0
- package/client/src/hooks/useFileBrowser.ts +102 -0
- package/client/src/hooks/useFileData.ts +78 -0
- package/client/src/hooks/useScrollAnchor.ts +70 -0
- package/client/src/hooks/useShareSettings.ts +22 -0
- package/client/src/hooks/useTopBar.ts +27 -0
- package/client/src/index.css +281 -0
- package/client/src/lib/AuthContext.ts +27 -0
- package/client/src/lib/api.ts +239 -0
- package/client/src/lib/auth.tsx +50 -0
- package/client/src/lib/codeBlockCm6.ts +129 -0
- package/client/src/lib/codeBlockCopy.ts +43 -0
- package/client/src/lib/codemirror.ts +77 -0
- package/client/src/lib/runner-api.ts +172 -0
- package/client/src/lib/svg.ts +50 -0
- package/client/src/lib/theme.ts +34 -0
- package/client/src/lib/utils.ts +6 -0
- package/client/src/main.tsx +11 -0
- package/client/src/pages/FileBrowser.tsx +135 -0
- package/client/src/pages/Home.tsx +46 -0
- package/client/src/pages/Runner.tsx +151 -0
- package/client/src/pages/RunnerJob.tsx +170 -0
- package/client/tsconfig.app.json +32 -0
- package/client/tsconfig.json +7 -0
- package/client/tsconfig.node.json +26 -0
- package/client/vite.config.ts +35 -0
- package/content/privacy.md +61 -0
- package/content/terms.md +41 -0
- package/dist/client/assets/CodeEditor-0XHVI8Nu.js +1 -0
- package/dist/client/assets/CodeViewer-CykMVsfX.js +1 -0
- package/dist/client/assets/index--MBieNJA.js +1 -0
- package/dist/client/assets/index-BENeXQI_.js +1 -0
- package/dist/client/assets/index-BbBpoOxz.js +1 -0
- package/dist/client/assets/index-BdV9g5AM.js +6 -0
- package/dist/client/assets/index-BjAilRri.js +2 -0
- package/dist/client/assets/index-BqbhWo2I.js +3 -0
- package/dist/client/assets/index-CVbycZ0H.js +1 -0
- package/dist/client/assets/index-Cs5oz2oJ.js +5 -0
- package/dist/client/assets/index-D8KZVveX.js +1 -0
- package/dist/client/assets/index-DC4HMHxY.js +13 -0
- package/dist/client/assets/index-DbMebkkd.css +1 -0
- package/dist/client/assets/index-DcY2RXqX.js +1 -0
- package/dist/client/assets/index-Duy-tZYV.js +1 -0
- package/dist/client/assets/index-Dw7rDFmE.js +7 -0
- package/dist/client/assets/index-FlCUvrjv.js +2 -0
- package/dist/client/assets/index-K6OVmfhg.js +1 -0
- package/dist/client/assets/index-LjwgzZ7F.js +62 -0
- package/dist/client/assets/index-MLwyFRN0.js +1 -0
- package/dist/client/assets/index-OpqBpSjn.js +1 -0
- package/dist/client/assets/index-SsHei0HE.js +1 -0
- package/dist/client/assets/index-uQa2yckk.js +1 -0
- package/dist/client/assets/index-udkXoIER.js +1 -0
- package/dist/client/index.html +15 -0
- package/dist/client/vite.svg +1 -0
- package/dist/src/auth/google.js +57 -0
- package/dist/src/auth/keys.js +185 -0
- package/dist/src/auth/resolve.js +102 -0
- package/dist/src/auth/session.js +57 -0
- package/dist/src/cli/commands/config.js +100 -0
- package/dist/src/cli/commands/config.test.js +84 -0
- package/dist/src/cli/commands/service.js +93 -0
- package/dist/src/cli/commands/start.js +24 -0
- package/dist/src/cli/index.js +20 -0
- package/dist/src/config/index.js +90 -0
- package/dist/src/config/loadConfig.test.js +127 -0
- package/dist/src/config/resolve.js +134 -0
- package/dist/src/config/resolve.test.js +148 -0
- package/dist/src/config/schema.js +159 -0
- package/dist/src/config/substituteEnvVars.js +45 -0
- package/dist/src/config/substituteEnvVars.test.js +51 -0
- package/dist/src/config/types.js +5 -0
- package/dist/src/routes/api/auth-status.js +56 -0
- package/dist/src/routes/api/diagrams.js +35 -0
- package/dist/src/routes/api/directory.js +93 -0
- package/dist/src/routes/api/drives.js +15 -0
- package/dist/src/routes/api/export.js +218 -0
- package/dist/src/routes/api/fileContent.js +286 -0
- package/dist/src/routes/api/index.js +33 -0
- package/dist/src/routes/api/linkInfo.js +71 -0
- package/dist/src/routes/api/linkInfo.test.js +104 -0
- package/dist/src/routes/api/middleware.js +117 -0
- package/dist/src/routes/api/raw.js +38 -0
- package/dist/src/routes/api/runner.js +59 -0
- package/dist/src/routes/api/search.js +236 -0
- package/dist/src/routes/api/sharing.js +203 -0
- package/dist/src/routes/api/status.js +68 -0
- package/dist/src/routes/api/status.test.js +62 -0
- package/dist/src/routes/auth.js +99 -0
- package/dist/src/routes/event.js +77 -0
- package/dist/src/routes/event.test.js +206 -0
- package/dist/src/routes/health.js +10 -0
- package/dist/src/routes/keys.js +129 -0
- package/dist/src/routes/path/index.js +17 -0
- package/dist/src/routes/static.js +30 -0
- package/dist/src/server.js +90 -0
- package/dist/src/services/deepShareLinks.js +163 -0
- package/dist/src/services/diagramCache.js +104 -0
- package/dist/src/services/embeddedDiagrams.js +136 -0
- package/dist/src/services/eventLog.js +55 -0
- package/dist/src/services/eventLog.test.js +113 -0
- package/dist/src/services/eventQueue.js +154 -0
- package/dist/src/services/eventQueue.test.js +104 -0
- package/dist/src/services/export.js +220 -0
- package/dist/src/services/exportCache.js +196 -0
- package/dist/src/services/markdown.js +147 -0
- package/dist/src/services/mermaid.js +97 -0
- package/dist/src/services/plantuml.js +145 -0
- package/dist/src/services/puppeteer.js +156 -0
- package/dist/src/util/breadcrumbs.js +22 -0
- package/dist/src/util/crypto.js +56 -0
- package/dist/src/util/crypto.test.js +99 -0
- package/dist/src/util/fileDetection.js +66 -0
- package/dist/src/util/fileDetection.test.js +89 -0
- package/dist/src/util/formatters.js +43 -0
- package/dist/src/util/formatters.test.js +83 -0
- package/dist/src/util/packageVersion.js +25 -0
- package/dist/src/util/platform.js +148 -0
- package/dist/src/util/state.js +46 -0
- package/dist/vitest.config.js +12 -0
- package/favicon.svg +3 -0
- package/guides/access-decision-flow.mmd +24 -0
- package/guides/access-decision-flow.svg +1 -0
- package/guides/api-integration.md +236 -0
- package/guides/deployment.md +287 -0
- package/guides/event-gateway.md +204 -0
- package/guides/event-gateway.mmd +17 -0
- package/guides/event-gateway.svg +1 -0
- package/guides/exports.md +239 -0
- package/guides/setup.md +313 -0
- package/guides/sharing.md +204 -0
- package/jeeves-server.config.template.json +25 -0
- package/package.json +124 -0
- package/scripts/download-plantuml.js +70 -0
- package/src/auth/google.ts +93 -0
- package/src/auth/keys.ts +252 -0
- package/src/auth/resolve.ts +157 -0
- package/src/auth/session.ts +77 -0
- package/src/cli/commands/config.test.ts +107 -0
- package/src/cli/commands/config.ts +113 -0
- package/src/cli/commands/service.ts +129 -0
- package/src/cli/commands/start.ts +27 -0
- package/src/cli/index.ts +25 -0
- package/src/config/index.ts +113 -0
- package/src/config/loadConfig.test.ts +155 -0
- package/src/config/resolve.test.ts +192 -0
- package/src/config/resolve.ts +173 -0
- package/src/config/schema.ts +179 -0
- package/src/config/substituteEnvVars.test.ts +64 -0
- package/src/config/substituteEnvVars.ts +52 -0
- package/src/config/types.ts +129 -0
- package/src/routes/api/auth-status.ts +85 -0
- package/src/routes/api/diagrams.ts +53 -0
- package/src/routes/api/directory.ts +123 -0
- package/src/routes/api/drives.ts +23 -0
- package/src/routes/api/export.ts +314 -0
- package/src/routes/api/fileContent.ts +414 -0
- package/src/routes/api/index.ts +37 -0
- package/src/routes/api/linkInfo.test.ts +132 -0
- package/src/routes/api/linkInfo.ts +83 -0
- package/src/routes/api/middleware.ts +156 -0
- package/src/routes/api/raw.ts +54 -0
- package/src/routes/api/runner.ts +107 -0
- package/src/routes/api/search.ts +321 -0
- package/src/routes/api/sharing.ts +259 -0
- package/src/routes/api/status.test.ts +72 -0
- package/src/routes/api/status.ts +82 -0
- package/src/routes/auth.ts +143 -0
- package/src/routes/event.test.ts +248 -0
- package/src/routes/event.ts +109 -0
- package/src/routes/health.ts +13 -0
- package/src/routes/keys.ts +192 -0
- package/src/routes/path/index.ts +24 -0
- package/src/routes/static.ts +54 -0
- package/src/server.ts +104 -0
- package/src/services/deepShareLinks.ts +203 -0
- package/src/services/diagramCache.ts +128 -0
- package/src/services/embeddedDiagrams.ts +168 -0
- package/src/services/eventLog.test.ts +144 -0
- package/src/services/eventLog.ts +68 -0
- package/src/services/eventQueue.test.ts +127 -0
- package/src/services/eventQueue.ts +196 -0
- package/src/services/export.ts +267 -0
- package/src/services/exportCache.ts +216 -0
- package/src/services/markdown.ts +189 -0
- package/src/services/mermaid.ts +113 -0
- package/src/services/plantuml.ts +172 -0
- package/src/services/puppeteer.ts +188 -0
- package/src/types/fastify.d.ts +13 -0
- package/src/types/jsonmap.d.ts +10 -0
- package/src/types/plantuml-encoder.d.ts +4 -0
- package/src/util/breadcrumbs.ts +33 -0
- package/src/util/crypto.test.ts +132 -0
- package/src/util/crypto.ts +79 -0
- package/src/util/fileDetection.test.ts +115 -0
- package/src/util/fileDetection.ts +70 -0
- package/src/util/formatters.test.ts +105 -0
- package/src/util/formatters.ts +44 -0
- package/src/util/packageVersion.ts +30 -0
- package/src/util/platform.ts +178 -0
- package/src/util/state.ts +55 -0
- package/test-docs/diagram-retry-test.md +18 -0
- package/test-docs/embedded-diagrams.md +52 -0
- package/test-docs/lazy-diagrams-test.md +333 -0
- package/test-docs/page-a.md +7 -0
- package/test-docs/page-b.md +7 -0
- package/test-docs/page-c.md +7 -0
- package/test-docs/sub/page-d.md +7 -0
- package/test-docs/test-diagram.puml +13 -0
- package/test-docs/validate-deep-share.js +318 -0
- package/tsconfig.json +37 -0
- package/tsdoc.json +13 -0
- package/vendor/.plantuml-version +1 -0
- package/vendor/plantuml.jar +0 -0
- package/vitest.config.js +12 -0
- package/vitest.config.ts +13 -0
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for formatting utilities
|
|
3
|
+
*/
|
|
4
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
5
|
+
import { formatRelativeTime, formatSize, nowIso } from './formatters.js';
|
|
6
|
+
describe('formatters', () => {
|
|
7
|
+
describe('formatSize', () => {
|
|
8
|
+
it('should format 0 bytes', () => {
|
|
9
|
+
expect(formatSize(0)).toBe('0 B');
|
|
10
|
+
});
|
|
11
|
+
it('should format bytes', () => {
|
|
12
|
+
expect(formatSize(500)).toBe('500 B');
|
|
13
|
+
expect(formatSize(1023)).toBe('1023 B');
|
|
14
|
+
});
|
|
15
|
+
it('should format kilobytes', () => {
|
|
16
|
+
expect(formatSize(1024)).toBe('1.0 KB');
|
|
17
|
+
expect(formatSize(1536)).toBe('1.5 KB');
|
|
18
|
+
expect(formatSize(10240)).toBe('10.0 KB');
|
|
19
|
+
});
|
|
20
|
+
it('should format megabytes', () => {
|
|
21
|
+
expect(formatSize(1048576)).toBe('1.0 MB');
|
|
22
|
+
expect(formatSize(1572864)).toBe('1.5 MB');
|
|
23
|
+
});
|
|
24
|
+
it('should format gigabytes', () => {
|
|
25
|
+
expect(formatSize(1073741824)).toBe('1.0 GB');
|
|
26
|
+
expect(formatSize(2147483648)).toBe('2.0 GB');
|
|
27
|
+
});
|
|
28
|
+
it('should format terabytes', () => {
|
|
29
|
+
expect(formatSize(1099511627776)).toBe('1.0 TB');
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
describe('formatRelativeTime', () => {
|
|
33
|
+
beforeEach(() => {
|
|
34
|
+
vi.useFakeTimers();
|
|
35
|
+
vi.setSystemTime(new Date('2026-02-15T12:00:00Z'));
|
|
36
|
+
});
|
|
37
|
+
it('should return null for null input', () => {
|
|
38
|
+
expect(formatRelativeTime(null)).toBeNull();
|
|
39
|
+
});
|
|
40
|
+
it('should return null for future timestamps', () => {
|
|
41
|
+
const future = new Date('2026-02-15T13:00:00Z').toISOString();
|
|
42
|
+
expect(formatRelativeTime(future)).toBeNull();
|
|
43
|
+
});
|
|
44
|
+
it('should return "just now" for very recent timestamps', () => {
|
|
45
|
+
const recent = new Date('2026-02-15T11:59:30Z').toISOString();
|
|
46
|
+
expect(formatRelativeTime(recent)).toBe('just now');
|
|
47
|
+
});
|
|
48
|
+
it('should return minutes ago', () => {
|
|
49
|
+
const mins5 = new Date('2026-02-15T11:55:00Z').toISOString();
|
|
50
|
+
expect(formatRelativeTime(mins5)).toBe('5m ago');
|
|
51
|
+
const mins45 = new Date('2026-02-15T11:15:00Z').toISOString();
|
|
52
|
+
expect(formatRelativeTime(mins45)).toBe('45m ago');
|
|
53
|
+
});
|
|
54
|
+
it('should return hours ago', () => {
|
|
55
|
+
const hours2 = new Date('2026-02-15T10:00:00Z').toISOString();
|
|
56
|
+
expect(formatRelativeTime(hours2)).toBe('2h ago');
|
|
57
|
+
const hours12 = new Date('2026-02-15T00:00:00Z').toISOString();
|
|
58
|
+
expect(formatRelativeTime(hours12)).toBe('12h ago');
|
|
59
|
+
});
|
|
60
|
+
it('should return days ago', () => {
|
|
61
|
+
const days1 = new Date('2026-02-14T12:00:00Z').toISOString();
|
|
62
|
+
expect(formatRelativeTime(days1)).toBe('1d ago');
|
|
63
|
+
const days7 = new Date('2026-02-08T12:00:00Z').toISOString();
|
|
64
|
+
expect(formatRelativeTime(days7)).toBe('7d ago');
|
|
65
|
+
});
|
|
66
|
+
it('should prioritize days over hours', () => {
|
|
67
|
+
const days1Hours5 = new Date('2026-02-14T07:00:00Z').toISOString();
|
|
68
|
+
expect(formatRelativeTime(days1Hours5)).toBe('1d ago');
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
describe('nowIso', () => {
|
|
72
|
+
beforeEach(() => {
|
|
73
|
+
vi.useFakeTimers();
|
|
74
|
+
vi.setSystemTime(new Date('2026-02-15T12:34:56.789Z'));
|
|
75
|
+
});
|
|
76
|
+
it('should return current time in ISO format', () => {
|
|
77
|
+
expect(nowIso()).toBe('2026-02-15T12:34:56.789Z');
|
|
78
|
+
});
|
|
79
|
+
it('should match ISO 8601 format', () => {
|
|
80
|
+
expect(nowIso()).toMatch(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/);
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve the service package version by walking up from the caller's directory.
|
|
3
|
+
*/
|
|
4
|
+
import fs from 'node:fs';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import { fileURLToPath } from 'node:url';
|
|
7
|
+
function findPackageJson(startDir) {
|
|
8
|
+
let dir = startDir;
|
|
9
|
+
while (dir !== path.dirname(dir)) {
|
|
10
|
+
const candidate = path.join(dir, 'package.json');
|
|
11
|
+
if (fs.existsSync(candidate)) {
|
|
12
|
+
const pkg = JSON.parse(fs.readFileSync(candidate, 'utf8'));
|
|
13
|
+
// Find our package specifically, not the monorepo root
|
|
14
|
+
if (pkg.name === '@karmaniverous/jeeves-server')
|
|
15
|
+
return candidate;
|
|
16
|
+
}
|
|
17
|
+
dir = path.dirname(dir);
|
|
18
|
+
}
|
|
19
|
+
throw new Error('Could not find @karmaniverous/jeeves-server package.json');
|
|
20
|
+
}
|
|
21
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
22
|
+
const pkgPath = findPackageJson(__dirname);
|
|
23
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
|
24
|
+
/** The package version of the jeeves-server service package. */
|
|
25
|
+
export const packageVersion = pkg.version;
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Platform abstraction for filesystem operations.
|
|
3
|
+
*
|
|
4
|
+
* Handles the differences between Windows (drive letters, backslashes)
|
|
5
|
+
* and Linux (mount points, forward slashes) for URL path ↔ filesystem path conversion.
|
|
6
|
+
*/
|
|
7
|
+
import fs from 'node:fs';
|
|
8
|
+
import path from 'node:path';
|
|
9
|
+
const IS_WINDOWS = process.platform === 'win32';
|
|
10
|
+
/**
|
|
11
|
+
* Discover available filesystem roots.
|
|
12
|
+
* Windows: enumerate accessible drive letters.
|
|
13
|
+
* Linux: return configured roots or default to '/'.
|
|
14
|
+
*/
|
|
15
|
+
export function getRoots(configuredRoots) {
|
|
16
|
+
if (IS_WINDOWS) {
|
|
17
|
+
const roots = [];
|
|
18
|
+
for (let code = 65; code <= 90; code++) {
|
|
19
|
+
const letter = String.fromCharCode(code);
|
|
20
|
+
const drivePath = `${letter}:\\`;
|
|
21
|
+
try {
|
|
22
|
+
fs.accessSync(drivePath, fs.constants.R_OK);
|
|
23
|
+
roots.push({
|
|
24
|
+
id: letter.toLowerCase(),
|
|
25
|
+
label: `${letter}:`,
|
|
26
|
+
fsPath: drivePath,
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
// Drive not accessible
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return roots;
|
|
34
|
+
}
|
|
35
|
+
// Linux: use configured roots or default to filesystem root
|
|
36
|
+
if (configuredRoots && Object.keys(configuredRoots).length > 0) {
|
|
37
|
+
return Object.entries(configuredRoots).map(([id, fsPath]) => ({
|
|
38
|
+
id,
|
|
39
|
+
label: fsPath,
|
|
40
|
+
fsPath: fsPath.endsWith('/') ? fsPath : fsPath + '/',
|
|
41
|
+
}));
|
|
42
|
+
}
|
|
43
|
+
return [{ id: 'root', label: '/', fsPath: '/' }];
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Convert a URL path to a filesystem path.
|
|
47
|
+
*
|
|
48
|
+
* URL paths use forward slashes and start with the root id:
|
|
49
|
+
* Windows: /e/jeeves-server/README.md → E:\\jeeves-server\\README.md
|
|
50
|
+
* Linux: /home/user/docs/README.md → /home/user/docs/README.md
|
|
51
|
+
* /root/docs/README.md → /docs/README.md (if root id is "root" mapping to "/")
|
|
52
|
+
*/
|
|
53
|
+
export function urlPathToFs(urlPath, roots) {
|
|
54
|
+
const normalized = urlPath.replace(/^\/+/, '');
|
|
55
|
+
if (!normalized)
|
|
56
|
+
return null;
|
|
57
|
+
if (IS_WINDOWS) {
|
|
58
|
+
// First segment is the drive letter
|
|
59
|
+
const slashIdx = normalized.indexOf('/');
|
|
60
|
+
const driveLetter = slashIdx >= 0 ? normalized.substring(0, slashIdx) : normalized;
|
|
61
|
+
const rest = slashIdx >= 0 ? normalized.substring(slashIdx + 1) : '';
|
|
62
|
+
if (driveLetter.length !== 1)
|
|
63
|
+
return null;
|
|
64
|
+
const fsPath = `${driveLetter.toUpperCase()}:\\${rest.replace(/\//g, '\\')}`;
|
|
65
|
+
return fsPath;
|
|
66
|
+
}
|
|
67
|
+
// Linux: match against configured roots
|
|
68
|
+
for (const root of roots) {
|
|
69
|
+
if (normalized.startsWith(root.id + '/') || normalized === root.id) {
|
|
70
|
+
const rest = normalized.substring(root.id.length);
|
|
71
|
+
// rest starts with '/' or is empty
|
|
72
|
+
const fsPath = root.fsPath.replace(/\/+$/, '') + (rest || '/');
|
|
73
|
+
return fsPath;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Convert a filesystem path to a URL path.
|
|
80
|
+
*
|
|
81
|
+
* Windows: E:\\jeeves-server\\README.md → /e/jeeves-server/README.md
|
|
82
|
+
* Linux: /home/user/docs/README.md → /home/user/docs/README.md (root="root" → "/")
|
|
83
|
+
*/
|
|
84
|
+
export function fsPathToUrl(fsPath, roots) {
|
|
85
|
+
if (IS_WINDOWS) {
|
|
86
|
+
return ('/' +
|
|
87
|
+
fsPath
|
|
88
|
+
.replace(/\\/g, '/')
|
|
89
|
+
.replace(/^([A-Za-z]):/, (_, d) => d.toLowerCase()));
|
|
90
|
+
}
|
|
91
|
+
// Linux: find the matching root and prepend the root id
|
|
92
|
+
for (const root of roots) {
|
|
93
|
+
const rootFs = root.fsPath.replace(/\/+$/, '');
|
|
94
|
+
if (fsPath === rootFs || fsPath.startsWith(rootFs + '/')) {
|
|
95
|
+
const rest = fsPath.substring(rootFs.length); // starts with '/' or is empty
|
|
96
|
+
if (root.id === 'root' && root.fsPath === '/') {
|
|
97
|
+
// Default root — URL path is just the fs path
|
|
98
|
+
return rest || '/';
|
|
99
|
+
}
|
|
100
|
+
return '/' + root.id + rest;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
// Fallback: return as-is with forward slashes
|
|
104
|
+
return fsPath.replace(/\\/g, '/');
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Split a filesystem path into breadcrumb parts.
|
|
108
|
+
* Returns [\{label, urlPath\}] from root to leaf.
|
|
109
|
+
*/
|
|
110
|
+
export function breadcrumbParts(fsPath, roots) {
|
|
111
|
+
const urlPath = fsPathToUrl(fsPath, roots);
|
|
112
|
+
const parts = urlPath
|
|
113
|
+
.replace(/^\/+/, '')
|
|
114
|
+
.split('/')
|
|
115
|
+
.filter((p) => p);
|
|
116
|
+
return parts.map((_part, i) => {
|
|
117
|
+
const accumulated = parts.slice(0, i + 1).join('/');
|
|
118
|
+
return { label: parts[i], path: accumulated };
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Recursively calculate total size of a directory in bytes.
|
|
123
|
+
*/
|
|
124
|
+
export function getDirSize(dirPath) {
|
|
125
|
+
let totalSize = 0;
|
|
126
|
+
try {
|
|
127
|
+
const entries = fs.readdirSync(dirPath, { withFileTypes: true });
|
|
128
|
+
for (const entry of entries) {
|
|
129
|
+
const entryPath = path.join(dirPath, entry.name);
|
|
130
|
+
try {
|
|
131
|
+
if (entry.isDirectory()) {
|
|
132
|
+
totalSize += getDirSize(entryPath);
|
|
133
|
+
}
|
|
134
|
+
else {
|
|
135
|
+
const s = fs.statSync(entryPath);
|
|
136
|
+
totalSize += s.size;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
catch {
|
|
140
|
+
/* skip inaccessible */
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
catch {
|
|
145
|
+
/* skip inaccessible */
|
|
146
|
+
}
|
|
147
|
+
return totalSize;
|
|
148
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server state management (key rotation tracking, etc.)
|
|
3
|
+
*/
|
|
4
|
+
import fs from 'node:fs';
|
|
5
|
+
import { getConfig } from '../config/index.js';
|
|
6
|
+
/**
|
|
7
|
+
* Load state from file
|
|
8
|
+
*/
|
|
9
|
+
export function loadState() {
|
|
10
|
+
const { stateFile } = getConfig();
|
|
11
|
+
try {
|
|
12
|
+
if (fs.existsSync(stateFile)) {
|
|
13
|
+
const content = fs.readFileSync(stateFile, 'utf8');
|
|
14
|
+
return JSON.parse(content);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
// Ignore errors, return empty state
|
|
19
|
+
}
|
|
20
|
+
return {};
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Save state to file
|
|
24
|
+
*/
|
|
25
|
+
export function saveState(state) {
|
|
26
|
+
const { stateFile } = getConfig();
|
|
27
|
+
fs.writeFileSync(stateFile, JSON.stringify(state, null, 2), 'utf8');
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Set key rotation timestamp
|
|
31
|
+
*/
|
|
32
|
+
export function setKeyRotationTimestamp(timestamp) {
|
|
33
|
+
const state = loadState();
|
|
34
|
+
state.keyRotatedAt = timestamp;
|
|
35
|
+
saveState(state);
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Set an insider's auto-generated key in state
|
|
39
|
+
*/
|
|
40
|
+
export function setInsiderKey(email, seed, createdAt) {
|
|
41
|
+
const state = loadState();
|
|
42
|
+
if (!state.insiderKeys)
|
|
43
|
+
state.insiderKeys = {};
|
|
44
|
+
state.insiderKeys[email.toLowerCase()] = { seed, createdAt };
|
|
45
|
+
saveState(state);
|
|
46
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { defineConfig } from 'vitest/config';
|
|
2
|
+
export default defineConfig({
|
|
3
|
+
test: {
|
|
4
|
+
globals: true,
|
|
5
|
+
environment: 'happy-dom',
|
|
6
|
+
exclude: ['dist/**', '**/node_modules/**'],
|
|
7
|
+
coverage: {
|
|
8
|
+
provider: 'v8',
|
|
9
|
+
reporter: ['text', 'lcov'],
|
|
10
|
+
},
|
|
11
|
+
},
|
|
12
|
+
});
|
package/favicon.svg
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
flowchart TD
|
|
2
|
+
A["Request arrives"] --> B{"Has session cookie?\n(Google OAuth)"}
|
|
3
|
+
B -- Yes --> C{"Email in\ninsiders map?"}
|
|
4
|
+
C -- Yes --> D["✅ INSIDER access\n(within scopes)"]
|
|
5
|
+
C -- No --> E["❌ DENIED"]
|
|
6
|
+
|
|
7
|
+
B -- No --> F{"Has ?key=\nparameter?"}
|
|
8
|
+
F -- Yes --> G{"Matches machine\ninsider key?"}
|
|
9
|
+
G -- Yes --> H["✅ INSIDER access\n(within scopes)"]
|
|
10
|
+
G -- No --> I{"Matches outsider key?\n(machine or insider seed)"}
|
|
11
|
+
I -- Yes --> J{"Path matches key\npath or ancestor?"}
|
|
12
|
+
J -- Yes --> K["✅ OUTSIDER access"]
|
|
13
|
+
J -- No --> L["❌ DENIED"]
|
|
14
|
+
I -- No --> M["❌ DENIED"]
|
|
15
|
+
|
|
16
|
+
F -- No --> N["❌ DENIED\n(or redirect to Google login)"]
|
|
17
|
+
|
|
18
|
+
style D fill:#22c55e,color:#fff,stroke:#16a34a
|
|
19
|
+
style H fill:#22c55e,color:#fff,stroke:#16a34a
|
|
20
|
+
style K fill:#22c55e,color:#fff,stroke:#16a34a
|
|
21
|
+
style E fill:#ef4444,color:#fff,stroke:#dc2626
|
|
22
|
+
style L fill:#ef4444,color:#fff,stroke:#dc2626
|
|
23
|
+
style M fill:#ef4444,color:#fff,stroke:#dc2626
|
|
24
|
+
style N fill:#f59e0b,color:#fff,stroke:#d97706
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg id="my-svg" width="100%" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" class="flowchart" style="max-width: 1102.22px; background-color: white;" viewBox="0 0 1102.21875 1878.078125" role="graphics-document document" aria-roledescription="flowchart-v2"><style>#my-svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#my-svg .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#my-svg .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#my-svg .error-icon{fill:#552222;}#my-svg .error-text{fill:#552222;stroke:#552222;}#my-svg .edge-thickness-normal{stroke-width:1px;}#my-svg .edge-thickness-thick{stroke-width:3.5px;}#my-svg .edge-pattern-solid{stroke-dasharray:0;}#my-svg .edge-thickness-invisible{stroke-width:0;fill:none;}#my-svg .edge-pattern-dashed{stroke-dasharray:3;}#my-svg .edge-pattern-dotted{stroke-dasharray:2;}#my-svg .marker{fill:#333333;stroke:#333333;}#my-svg .marker.cross{stroke:#333333;}#my-svg svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#my-svg p{margin:0;}#my-svg .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#my-svg .cluster-label text{fill:#333;}#my-svg .cluster-label span{color:#333;}#my-svg .cluster-label span p{background-color:transparent;}#my-svg .label text,#my-svg span{fill:#333;color:#333;}#my-svg .node rect,#my-svg .node circle,#my-svg .node ellipse,#my-svg .node polygon,#my-svg .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#my-svg .rough-node .label text,#my-svg .node .label text,#my-svg .image-shape .label,#my-svg .icon-shape .label{text-anchor:middle;}#my-svg .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#my-svg .rough-node .label,#my-svg .node .label,#my-svg .image-shape .label,#my-svg .icon-shape .label{text-align:center;}#my-svg .node.clickable{cursor:pointer;}#my-svg .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#my-svg .arrowheadPath{fill:#333333;}#my-svg .edgePath .path{stroke:#333333;stroke-width:2.0px;}#my-svg .flowchart-link{stroke:#333333;fill:none;}#my-svg .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#my-svg .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#my-svg .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#my-svg .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#my-svg .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#my-svg .cluster text{fill:#333;}#my-svg .cluster span{color:#333;}#my-svg div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#my-svg .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#my-svg rect.text{fill:none;stroke-width:0;}#my-svg .icon-shape,#my-svg .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#my-svg .icon-shape p,#my-svg .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#my-svg .icon-shape rect,#my-svg .image-shape rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#my-svg .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#my-svg .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#my-svg :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}</style><g><marker id="my-svg_flowchart-v2-pointEnd" class="marker flowchart-v2" viewBox="0 0 10 10" refX="5" refY="5" markerUnits="userSpaceOnUse" markerWidth="8" markerHeight="8" orient="auto"><path d="M 0 0 L 10 5 L 0 10 z" class="arrowMarkerPath" style="stroke-width: 1; stroke-dasharray: 1, 0;"/></marker><marker id="my-svg_flowchart-v2-pointStart" class="marker flowchart-v2" viewBox="0 0 10 10" refX="4.5" refY="5" markerUnits="userSpaceOnUse" markerWidth="8" markerHeight="8" orient="auto"><path d="M 0 5 L 10 10 L 10 0 z" class="arrowMarkerPath" style="stroke-width: 1; stroke-dasharray: 1, 0;"/></marker><marker id="my-svg_flowchart-v2-circleEnd" class="marker flowchart-v2" viewBox="0 0 10 10" refX="11" refY="5" markerUnits="userSpaceOnUse" markerWidth="11" markerHeight="11" orient="auto"><circle cx="5" cy="5" r="5" class="arrowMarkerPath" style="stroke-width: 1; stroke-dasharray: 1, 0;"/></marker><marker id="my-svg_flowchart-v2-circleStart" class="marker flowchart-v2" viewBox="0 0 10 10" refX="-1" refY="5" markerUnits="userSpaceOnUse" markerWidth="11" markerHeight="11" orient="auto"><circle cx="5" cy="5" r="5" class="arrowMarkerPath" style="stroke-width: 1; stroke-dasharray: 1, 0;"/></marker><marker id="my-svg_flowchart-v2-crossEnd" class="marker cross flowchart-v2" viewBox="0 0 11 11" refX="12" refY="5.2" markerUnits="userSpaceOnUse" markerWidth="11" markerHeight="11" orient="auto"><path d="M 1,1 l 9,9 M 10,1 l -9,9" class="arrowMarkerPath" style="stroke-width: 2; stroke-dasharray: 1, 0;"/></marker><marker id="my-svg_flowchart-v2-crossStart" class="marker cross flowchart-v2" viewBox="0 0 11 11" refX="-1" refY="5.2" markerUnits="userSpaceOnUse" markerWidth="11" markerHeight="11" orient="auto"><path d="M 1,1 l 9,9 M 10,1 l -9,9" class="arrowMarkerPath" style="stroke-width: 2; stroke-dasharray: 1, 0;"/></marker><g class="root"><g class="clusters"/><g class="edgePaths"><path d="M551.109,62L551.109,66.167C551.109,70.333,551.109,78.667,551.109,86.333C551.109,94,551.109,101,551.109,104.5L551.109,108" id="L_A_B_0" class="edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link" style=";" data-edge="true" data-et="edge" data-id="L_A_B_0" data-points="W3sieCI6NTUxLjEwOTM3NSwieSI6NjJ9LHsieCI6NTUxLjEwOTM3NSwieSI6ODd9LHsieCI6NTUxLjEwOTM3NSwieSI6MTEyfV0=" marker-end="url(#my-svg_flowchart-v2-pointEnd)"/><path d="M464.771,303.661L431.068,324.218C397.365,344.774,329.96,385.887,296.257,411.944C262.555,438,262.555,449,262.555,454.5L262.555,460" id="L_B_C_0" class="edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link" style=";" data-edge="true" data-et="edge" data-id="L_B_C_0" data-points="W3sieCI6NDY0Ljc3MDU1OTI2NTg0NiwieSI6MzAzLjY2MTE4NDI2NTg0Nn0seyJ4IjoyNjIuNTU0Njg3NSwieSI6NDI3fSx7IngiOjI2Mi41NTQ2ODc1LCJ5Ijo0NjR9XQ==" marker-end="url(#my-svg_flowchart-v2-pointEnd)"/><path d="M211.818,635.342L199.515,649.964C187.212,664.587,162.606,693.833,150.303,730.622C138,767.411,138,811.745,138,833.911L138,856.078" id="L_C_D_0" class="edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link" style=";" data-edge="true" data-et="edge" data-id="L_C_D_0" data-points="W3sieCI6MjExLjgxODI1MDEwOTI2NTczLCJ5Ijo2MzUuMzQxNjg3NjA5MjY1N30seyJ4IjoxMzgsInkiOjcyMy4wNzgxMjV9LHsieCI6MTM4LCJ5Ijo4NjAuMDc4MTI1fV0=" marker-end="url(#my-svg_flowchart-v2-pointEnd)"/><path d="M313.291,635.342L325.594,649.964C337.897,664.587,362.503,693.833,374.806,732.622C387.109,771.411,387.109,819.745,387.109,843.911L387.109,868.078" id="L_C_E_0" class="edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link" style=";" data-edge="true" data-et="edge" data-id="L_C_E_0" data-points="W3sieCI6MzEzLjI5MTEyNDg5MDczNDI0LCJ5Ijo2MzUuMzQxNjg3NjA5MjY1N30seyJ4IjozODcuMTA5Mzc1LCJ5Ijo3MjMuMDc4MTI1fSx7IngiOjM4Ny4xMDkzNzUsInkiOjg3Mi4wNzgxMjV9XQ==" marker-end="url(#my-svg_flowchart-v2-pointEnd)"/><path d="M633.165,307.945L661.757,327.787C690.349,347.63,747.534,387.315,776.126,412.941C804.719,438.568,804.719,450.135,804.719,455.919L804.719,461.703" id="L_B_F_0" class="edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link" style=";" data-edge="true" data-et="edge" data-id="L_B_F_0" data-points="W3sieCI6NjMzLjE2NDYyMTQwODQzNzksInkiOjMwNy45NDQ3NTM1OTE1NjIxfSx7IngiOjgwNC43MTg3NSwieSI6NDI3fSx7IngiOjgwNC43MTg3NSwieSI6NDY1LjcwMzEyNX1d" marker-end="url(#my-svg_flowchart-v2-pointEnd)"/><path d="M748.013,627.67L730.881,643.571C713.749,659.473,679.484,691.275,662.351,712.677C645.219,734.078,645.219,745.078,645.219,750.578L645.219,756.078" id="L_F_G_0" class="edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link" style=";" data-edge="true" data-et="edge" data-id="L_F_G_0" data-points="W3sieCI6NzQ4LjAxMzQ5MTUyMTY1NjMsInkiOjYyNy42Njk3NDE1MjE2NTYzfSx7IngiOjY0NS4yMTg3NSwieSI6NzIzLjA3ODEyNX0seyJ4Ijo2NDUuMjE4NzUsInkiOjc2MC4wNzgxMjV9XQ==" marker-end="url(#my-svg_flowchart-v2-pointEnd)"/><path d="M579.137,971.996L563.567,989.176C547.997,1006.357,516.858,1040.717,501.288,1080.064C485.719,1119.411,485.719,1163.745,485.719,1185.911L485.719,1208.078" id="L_G_H_0" class="edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link" style=";" data-edge="true" data-et="edge" data-id="L_G_H_0" data-points="W3sieCI6NTc5LjEzNjc4Mjc4Njg4NTIsInkiOjk3MS45OTYxNTc3ODY4ODUyfSx7IngiOjQ4NS43MTg3NSwieSI6MTA3NS4wNzgxMjV9LHsieCI6NDg1LjcxODc1LCJ5IjoxMjEyLjA3ODEyNX1d" marker-end="url(#my-svg_flowchart-v2-pointEnd)"/><path d="M711.301,971.996L726.87,989.176C742.44,1006.357,773.579,1040.717,789.149,1063.398C804.719,1086.078,804.719,1097.078,804.719,1102.578L804.719,1108.078" id="L_G_I_0" class="edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link" style=";" data-edge="true" data-et="edge" data-id="L_G_I_0" data-points="W3sieCI6NzExLjMwMDcxNzIxMzExNDgsInkiOjk3MS45OTYxNTc3ODY4ODUyfSx7IngiOjgwNC43MTg3NSwieSI6MTA3NS4wNzgxMjV9LHsieCI6ODA0LjcxODc1LCJ5IjoxMTEyLjA3ODEyNX1d" marker-end="url(#my-svg_flowchart-v2-pointEnd)"/><path d="M745.914,1331.274L734.206,1347.241C722.497,1363.208,699.081,1395.143,687.372,1416.611C675.664,1438.078,675.664,1449.078,675.664,1454.578L675.664,1460.078" id="L_I_J_0" class="edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link" style=";" data-edge="true" data-et="edge" data-id="L_I_J_0" data-points="W3sieCI6NzQ1LjkxNDIwNjc1NzI0MTIsInkiOjEzMzEuMjczNTgxNzU3MjQxM30seyJ4Ijo2NzUuNjY0MDYyNSwieSI6MTQyNy4wNzgxMjV9LHsieCI6Njc1LjY2NDA2MjUsInkiOjE0NjQuMDc4MTI1fV0=" marker-end="url(#my-svg_flowchart-v2-pointEnd)"/><path d="M621.753,1688.167L612.153,1703.319C602.553,1718.471,583.352,1748.774,573.752,1769.426C564.152,1790.078,564.152,1801.078,564.152,1806.578L564.152,1812.078" id="L_J_K_0" class="edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link" style=";" data-edge="true" data-et="edge" data-id="L_J_K_0" data-points="W3sieCI6NjIxLjc1Mjc2ODEyMzQxMjEsInkiOjE2ODguMTY2ODMwNjIzNDEyMX0seyJ4Ijo1NjQuMTUyMzQzNzUsInkiOjE3NzkuMDc4MTI1fSx7IngiOjU2NC4xNTIzNDM3NSwieSI6MTgxNi4wNzgxMjV9XQ==" marker-end="url(#my-svg_flowchart-v2-pointEnd)"/><path d="M729.575,1688.167L739.175,1703.319C748.775,1718.471,767.976,1748.774,777.576,1769.426C787.176,1790.078,787.176,1801.078,787.176,1806.578L787.176,1812.078" id="L_J_L_0" class="edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link" style=";" data-edge="true" data-et="edge" data-id="L_J_L_0" data-points="W3sieCI6NzI5LjU3NTM1Njg3NjU4NzksInkiOjE2ODguMTY2ODMwNjIzNDEyMX0seyJ4Ijo3ODcuMTc1NzgxMjUsInkiOjE3NzkuMDc4MTI1fSx7IngiOjc4Ny4xNzU3ODEyNSwieSI6MTgxNi4wNzgxMjV9XQ==" marker-end="url(#my-svg_flowchart-v2-pointEnd)"/><path d="M863.523,1331.274L875.232,1347.241C886.94,1363.208,910.357,1395.143,922.065,1435.277C933.773,1475.411,933.773,1523.745,933.773,1547.911L933.773,1572.078" id="L_I_M_0" class="edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link" style=";" data-edge="true" data-et="edge" data-id="L_I_M_0" data-points="W3sieCI6ODYzLjUyMzI5MzI0Mjc1ODgsInkiOjEzMzEuMjczNTgxNzU3MjQxM30seyJ4Ijo5MzMuNzczNDM3NSwieSI6MTQyNy4wNzgxMjV9LHsieCI6OTMzLjc3MzQzNzUsInkiOjE1NzYuMDc4MTI1fV0=" marker-end="url(#my-svg_flowchart-v2-pointEnd)"/><path d="M861.424,627.67L878.556,643.571C895.689,659.473,929.954,691.275,947.086,729.343C964.219,767.411,964.219,811.745,964.219,833.911L964.219,856.078" id="L_F_N_0" class="edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link" style=";" data-edge="true" data-et="edge" data-id="L_F_N_0" data-points="W3sieCI6ODYxLjQyNDAwODQ3ODM0MzcsInkiOjYyNy42Njk3NDE1MjE2NTYzfSx7IngiOjk2NC4yMTg3NSwieSI6NzIzLjA3ODEyNX0seyJ4Ijo5NjQuMjE4NzUsInkiOjg2MC4wNzgxMjV9XQ==" marker-end="url(#my-svg_flowchart-v2-pointEnd)"/></g><g class="edgeLabels"><g class="edgeLabel"><g class="label" data-id="L_A_B_0" transform="translate(0, 0)"><foreignObject width="0" height="0"><div xmlns="http://www.w3.org/1999/xhtml" class="labelBkg" style="display: table-cell; white-space: nowrap; line-height: 1.5; max-width: 200px; text-align: center;"><span class="edgeLabel"></span></div></foreignObject></g></g><g class="edgeLabel" transform="translate(262.5546875, 427)"><g class="label" data-id="L_B_C_0" transform="translate(-11.328125, -12)"><foreignObject width="22.65625" height="24"><div xmlns="http://www.w3.org/1999/xhtml" class="labelBkg" style="display: table-cell; white-space: nowrap; line-height: 1.5; max-width: 200px; text-align: center;"><span class="edgeLabel"><p>Yes</p></span></div></foreignObject></g></g><g class="edgeLabel" transform="translate(138, 723.078125)"><g class="label" data-id="L_C_D_0" transform="translate(-11.328125, -12)"><foreignObject width="22.65625" height="24"><div xmlns="http://www.w3.org/1999/xhtml" class="labelBkg" style="display: table-cell; white-space: nowrap; line-height: 1.5; max-width: 200px; text-align: center;"><span class="edgeLabel"><p>Yes</p></span></div></foreignObject></g></g><g class="edgeLabel" transform="translate(387.109375, 723.078125)"><g class="label" data-id="L_C_E_0" transform="translate(-9.3984375, -12)"><foreignObject width="18.796875" height="24"><div xmlns="http://www.w3.org/1999/xhtml" class="labelBkg" style="display: table-cell; white-space: nowrap; line-height: 1.5; max-width: 200px; text-align: center;"><span class="edgeLabel"><p>No</p></span></div></foreignObject></g></g><g class="edgeLabel" transform="translate(804.71875, 427)"><g class="label" data-id="L_B_F_0" transform="translate(-9.3984375, -12)"><foreignObject width="18.796875" height="24"><div xmlns="http://www.w3.org/1999/xhtml" class="labelBkg" style="display: table-cell; white-space: nowrap; line-height: 1.5; max-width: 200px; text-align: center;"><span class="edgeLabel"><p>No</p></span></div></foreignObject></g></g><g class="edgeLabel" transform="translate(645.21875, 723.078125)"><g class="label" data-id="L_F_G_0" transform="translate(-11.328125, -12)"><foreignObject width="22.65625" height="24"><div xmlns="http://www.w3.org/1999/xhtml" class="labelBkg" style="display: table-cell; white-space: nowrap; line-height: 1.5; max-width: 200px; text-align: center;"><span class="edgeLabel"><p>Yes</p></span></div></foreignObject></g></g><g class="edgeLabel" transform="translate(485.71875, 1075.078125)"><g class="label" data-id="L_G_H_0" transform="translate(-11.328125, -12)"><foreignObject width="22.65625" height="24"><div xmlns="http://www.w3.org/1999/xhtml" class="labelBkg" style="display: table-cell; white-space: nowrap; line-height: 1.5; max-width: 200px; text-align: center;"><span class="edgeLabel"><p>Yes</p></span></div></foreignObject></g></g><g class="edgeLabel" transform="translate(804.71875, 1075.078125)"><g class="label" data-id="L_G_I_0" transform="translate(-9.3984375, -12)"><foreignObject width="18.796875" height="24"><div xmlns="http://www.w3.org/1999/xhtml" class="labelBkg" style="display: table-cell; white-space: nowrap; line-height: 1.5; max-width: 200px; text-align: center;"><span class="edgeLabel"><p>No</p></span></div></foreignObject></g></g><g class="edgeLabel" transform="translate(675.6640625, 1427.078125)"><g class="label" data-id="L_I_J_0" transform="translate(-11.328125, -12)"><foreignObject width="22.65625" height="24"><div xmlns="http://www.w3.org/1999/xhtml" class="labelBkg" style="display: table-cell; white-space: nowrap; line-height: 1.5; max-width: 200px; text-align: center;"><span class="edgeLabel"><p>Yes</p></span></div></foreignObject></g></g><g class="edgeLabel" transform="translate(564.15234375, 1779.078125)"><g class="label" data-id="L_J_K_0" transform="translate(-11.328125, -12)"><foreignObject width="22.65625" height="24"><div xmlns="http://www.w3.org/1999/xhtml" class="labelBkg" style="display: table-cell; white-space: nowrap; line-height: 1.5; max-width: 200px; text-align: center;"><span class="edgeLabel"><p>Yes</p></span></div></foreignObject></g></g><g class="edgeLabel" transform="translate(787.17578125, 1779.078125)"><g class="label" data-id="L_J_L_0" transform="translate(-9.3984375, -12)"><foreignObject width="18.796875" height="24"><div xmlns="http://www.w3.org/1999/xhtml" class="labelBkg" style="display: table-cell; white-space: nowrap; line-height: 1.5; max-width: 200px; text-align: center;"><span class="edgeLabel"><p>No</p></span></div></foreignObject></g></g><g class="edgeLabel" transform="translate(933.7734375, 1427.078125)"><g class="label" data-id="L_I_M_0" transform="translate(-9.3984375, -12)"><foreignObject width="18.796875" height="24"><div xmlns="http://www.w3.org/1999/xhtml" class="labelBkg" style="display: table-cell; white-space: nowrap; line-height: 1.5; max-width: 200px; text-align: center;"><span class="edgeLabel"><p>No</p></span></div></foreignObject></g></g><g class="edgeLabel" transform="translate(964.21875, 723.078125)"><g class="label" data-id="L_F_N_0" transform="translate(-9.3984375, -12)"><foreignObject width="18.796875" height="24"><div xmlns="http://www.w3.org/1999/xhtml" class="labelBkg" style="display: table-cell; white-space: nowrap; line-height: 1.5; max-width: 200px; text-align: center;"><span class="edgeLabel"><p>No</p></span></div></foreignObject></g></g></g><g class="nodes"><g class="node default" id="flowchart-A-0" transform="translate(551.109375, 35)"><rect class="basic label-container" style="" x="-84.9296875" y="-27" width="169.859375" height="54"/><g class="label" style="" transform="translate(-54.9296875, -12)"><rect/><foreignObject width="109.859375" height="24"><div xmlns="http://www.w3.org/1999/xhtml" style="display: table-cell; white-space: nowrap; line-height: 1.5; max-width: 200px; text-align: center;"><span class="nodeLabel"><p>Request arrives</p></span></div></foreignObject></g></g><g class="node default" id="flowchart-B-1" transform="translate(551.109375, 251)"><polygon points="139,0 278,-139 139,-278 0,-139" class="label-container" transform="translate(-138.5, 139)"/><g class="label" style="" transform="translate(-100, -24)"><rect/><foreignObject width="200" height="48"><div xmlns="http://www.w3.org/1999/xhtml" style="display: table; white-space: break-spaces; line-height: 1.5; max-width: 200px; text-align: center; width: 200px;"><span class="nodeLabel"><p>Has session cookie?\n(Google OAuth)</p></span></div></foreignObject></g></g><g class="node default" id="flowchart-C-3" transform="translate(262.5546875, 575.0390625)"><polygon points="111.0390625,0 222.078125,-111.0390625 111.0390625,-222.078125 0,-111.0390625" class="label-container" transform="translate(-110.5390625, 111.0390625)"/><g class="label" style="" transform="translate(-84.0390625, -12)"><rect/><foreignObject width="168.078125" height="24"><div xmlns="http://www.w3.org/1999/xhtml" style="display: table-cell; white-space: nowrap; line-height: 1.5; max-width: 200px; text-align: center;"><span class="nodeLabel"><p>Email in\ninsiders map?</p></span></div></foreignObject></g></g><g class="node default" id="flowchart-D-5" transform="translate(138, 899.078125)"><rect class="basic label-container" style="fill:#22c55e !important;stroke:#16a34a !important" x="-130" y="-39" width="260" height="78"/><g class="label" style="color:#fff !important" transform="translate(-100, -24)"><rect/><foreignObject width="200" height="48"><div style="color: rgb(255, 255, 255) !important; display: table; white-space: break-spaces; line-height: 1.5; max-width: 200px; text-align: center; width: 200px;" xmlns="http://www.w3.org/1999/xhtml"><span style="color:#fff !important" class="nodeLabel"><p>✅ INSIDER access\n(within scopes)</p></span></div></foreignObject></g></g><g class="node default" id="flowchart-E-7" transform="translate(387.109375, 899.078125)"><rect class="basic label-container" style="fill:#ef4444 !important;stroke:#dc2626 !important" x="-69.109375" y="-27" width="138.21875" height="54"/><g class="label" style="color:#fff !important" transform="translate(-39.109375, -12)"><rect/><foreignObject width="78.21875" height="24"><div style="color: rgb(255, 255, 255) !important; display: table-cell; white-space: nowrap; line-height: 1.5; max-width: 200px; text-align: center;" xmlns="http://www.w3.org/1999/xhtml"><span style="color:#fff !important" class="nodeLabel"><p>❌ DENIED</p></span></div></foreignObject></g></g><g class="node default" id="flowchart-F-9" transform="translate(804.71875, 575.0390625)"><polygon points="109.3359375,0 218.671875,-109.3359375 109.3359375,-218.671875 0,-109.3359375" class="label-container" transform="translate(-108.8359375, 109.3359375)"/><g class="label" style="" transform="translate(-82.3359375, -12)"><rect/><foreignObject width="164.671875" height="24"><div xmlns="http://www.w3.org/1999/xhtml" style="display: table-cell; white-space: nowrap; line-height: 1.5; max-width: 200px; text-align: center;"><span class="nodeLabel"><p>Has ?key=\nparameter?</p></span></div></foreignObject></g></g><g class="node default" id="flowchart-G-11" transform="translate(645.21875, 899.078125)"><polygon points="139,0 278,-139 139,-278 0,-139" class="label-container" transform="translate(-138.5, 139)"/><g class="label" style="" transform="translate(-100, -24)"><rect/><foreignObject width="200" height="48"><div xmlns="http://www.w3.org/1999/xhtml" style="display: table; white-space: break-spaces; line-height: 1.5; max-width: 200px; text-align: center; width: 200px;"><span class="nodeLabel"><p>Matches machine\ninsider key?</p></span></div></foreignObject></g></g><g class="node default" id="flowchart-H-13" transform="translate(485.71875, 1251.078125)"><rect class="basic label-container" style="fill:#22c55e !important;stroke:#16a34a !important" x="-130" y="-39" width="260" height="78"/><g class="label" style="color:#fff !important" transform="translate(-100, -24)"><rect/><foreignObject width="200" height="48"><div style="color: rgb(255, 255, 255) !important; display: table; white-space: break-spaces; line-height: 1.5; max-width: 200px; text-align: center; width: 200px;" xmlns="http://www.w3.org/1999/xhtml"><span style="color:#fff !important" class="nodeLabel"><p>✅ INSIDER access\n(within scopes)</p></span></div></foreignObject></g></g><g class="node default" id="flowchart-I-15" transform="translate(804.71875, 1251.078125)"><polygon points="139,0 278,-139 139,-278 0,-139" class="label-container" transform="translate(-138.5, 139)"/><g class="label" style="" transform="translate(-100, -24)"><rect/><foreignObject width="200" height="48"><div xmlns="http://www.w3.org/1999/xhtml" style="display: table; white-space: break-spaces; line-height: 1.5; max-width: 200px; text-align: center; width: 200px;"><span class="nodeLabel"><p>Matches outsider key?\n(machine or insider seed)</p></span></div></foreignObject></g></g><g class="node default" id="flowchart-J-17" transform="translate(675.6640625, 1603.078125)"><polygon points="139,0 278,-139 139,-278 0,-139" class="label-container" transform="translate(-138.5, 139)"/><g class="label" style="" transform="translate(-100, -24)"><rect/><foreignObject width="200" height="48"><div xmlns="http://www.w3.org/1999/xhtml" style="display: table; white-space: break-spaces; line-height: 1.5; max-width: 200px; text-align: center; width: 200px;"><span class="nodeLabel"><p>Path matches key\npath or ancestor?</p></span></div></foreignObject></g></g><g class="node default" id="flowchart-K-19" transform="translate(564.15234375, 1843.078125)"><rect class="basic label-container" style="fill:#22c55e !important;stroke:#16a34a !important" x="-103.9140625" y="-27" width="207.828125" height="54"/><g class="label" style="color:#fff !important" transform="translate(-73.9140625, -12)"><rect/><foreignObject width="147.828125" height="24"><div style="color: rgb(255, 255, 255) !important; display: table-cell; white-space: nowrap; line-height: 1.5; max-width: 200px; text-align: center;" xmlns="http://www.w3.org/1999/xhtml"><span style="color:#fff !important" class="nodeLabel"><p>✅ OUTSIDER access</p></span></div></foreignObject></g></g><g class="node default" id="flowchart-L-21" transform="translate(787.17578125, 1843.078125)"><rect class="basic label-container" style="fill:#ef4444 !important;stroke:#dc2626 !important" x="-69.109375" y="-27" width="138.21875" height="54"/><g class="label" style="color:#fff !important" transform="translate(-39.109375, -12)"><rect/><foreignObject width="78.21875" height="24"><div style="color: rgb(255, 255, 255) !important; display: table-cell; white-space: nowrap; line-height: 1.5; max-width: 200px; text-align: center;" xmlns="http://www.w3.org/1999/xhtml"><span style="color:#fff !important" class="nodeLabel"><p>❌ DENIED</p></span></div></foreignObject></g></g><g class="node default" id="flowchart-M-23" transform="translate(933.7734375, 1603.078125)"><rect class="basic label-container" style="fill:#ef4444 !important;stroke:#dc2626 !important" x="-69.109375" y="-27" width="138.21875" height="54"/><g class="label" style="color:#fff !important" transform="translate(-39.109375, -12)"><rect/><foreignObject width="78.21875" height="24"><div style="color: rgb(255, 255, 255) !important; display: table-cell; white-space: nowrap; line-height: 1.5; max-width: 200px; text-align: center;" xmlns="http://www.w3.org/1999/xhtml"><span style="color:#fff !important" class="nodeLabel"><p>❌ DENIED</p></span></div></foreignObject></g></g><g class="node default" id="flowchart-N-25" transform="translate(964.21875, 899.078125)"><rect class="basic label-container" style="fill:#f59e0b !important;stroke:#d97706 !important" x="-130" y="-39" width="260" height="78"/><g class="label" style="color:#fff !important" transform="translate(-100, -24)"><rect/><foreignObject width="200" height="48"><div style="color: rgb(255, 255, 255) !important; display: table; white-space: break-spaces; line-height: 1.5; max-width: 200px; text-align: center; width: 200px;" xmlns="http://www.w3.org/1999/xhtml"><span style="color:#fff !important" class="nodeLabel"><p>❌ DENIED\n(or redirect to Google login)</p></span></div></foreignObject></g></g></g></g></g></svg>
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
# API & Integration Guide
|
|
2
|
+
|
|
3
|
+
How to interact with Jeeves Server programmatically — for scripts, bots, AI assistants, and CI/CD pipelines.
|
|
4
|
+
|
|
5
|
+
## Authentication for API Access
|
|
6
|
+
|
|
7
|
+
All API requests authenticate via `?key=<insider-key>` URL parameter or session cookie. For programmatic access, use a key:
|
|
8
|
+
|
|
9
|
+
```typescript
|
|
10
|
+
// In jeeves.config.ts
|
|
11
|
+
keys: {
|
|
12
|
+
'ci-bot': 'random-seed-string',
|
|
13
|
+
// Scoped key for webhooks only:
|
|
14
|
+
'webhook': { key: 'another-seed', scopes: ['/event'] },
|
|
15
|
+
},
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
### Getting the derived key
|
|
19
|
+
|
|
20
|
+
The config contains **seeds**. The actual URL key is derived via HMAC:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
# Get the insider key from a seed
|
|
24
|
+
curl -s "http://localhost:1934/insider-key" -H "X-API-Key: <seed>"
|
|
25
|
+
# Returns: { "key": "a1b2c3d4..." }
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Or compute it yourself:
|
|
29
|
+
|
|
30
|
+
```javascript
|
|
31
|
+
const crypto = require('crypto');
|
|
32
|
+
function insiderKey(seed) {
|
|
33
|
+
return crypto.createHmac('sha256', seed).update('insider').digest('hex').substring(0, 32);
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## API Endpoints
|
|
38
|
+
|
|
39
|
+
### File Access
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
# Get file content (rendered)
|
|
43
|
+
GET /api/file/d/docs/design.md?key=<key>
|
|
44
|
+
# Returns: { type: "markdown", html: "...", headings: [...], content: "...", fileName: "..." }
|
|
45
|
+
|
|
46
|
+
# Get file content (raw text)
|
|
47
|
+
GET /api/file/d/docs/design.md?key=<key>&mode=raw
|
|
48
|
+
# Returns: { type: "text", content: "...", fileName: "..." }
|
|
49
|
+
|
|
50
|
+
# Get raw file bytes
|
|
51
|
+
GET /path/d/docs/design.md?key=<key>&raw=1
|
|
52
|
+
# Returns: file content with appropriate Content-Type
|
|
53
|
+
|
|
54
|
+
# Export as PDF
|
|
55
|
+
GET /path/d/docs/design.md?key=<key>&export=pdf
|
|
56
|
+
# Returns: application/pdf
|
|
57
|
+
|
|
58
|
+
# Export as DOCX
|
|
59
|
+
GET /path/d/docs/design.md?key=<key>&export=docx
|
|
60
|
+
# Returns: application/vnd.openxmlformats-officedocument.wordprocessingml.document
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Directory Listing
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
# List drives
|
|
67
|
+
GET /api/drives?key=<key>
|
|
68
|
+
# Returns: { drives: [{ letter: "C", label: "System", ... }] }
|
|
69
|
+
|
|
70
|
+
# List directory
|
|
71
|
+
GET /api/directory/d/docs?key=<key>
|
|
72
|
+
# Returns: { path: "d/docs", entries: [{ name: "...", type: "file"|"directory", ... }] }
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Authentication
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
# Check auth status
|
|
79
|
+
GET /api/auth/status?key=<key>
|
|
80
|
+
# Returns: { authenticated: true, email: "...", isInsider: true, mode: "key" }
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Sharing
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
# Get insider key (requires X-API-Key header with seed)
|
|
87
|
+
GET /insider-key
|
|
88
|
+
# Headers: X-API-Key: <seed>
|
|
89
|
+
# Returns: { key: "a1b2c3d4..." }
|
|
90
|
+
|
|
91
|
+
# Compute outsider key for a path
|
|
92
|
+
GET /key?path=/d/docs/design.md
|
|
93
|
+
# Headers: X-API-Key: <seed>
|
|
94
|
+
# Returns: { key: "e5f6a7b8..." }
|
|
95
|
+
|
|
96
|
+
# Rotate a key
|
|
97
|
+
POST /rotate-key
|
|
98
|
+
# Body: { key: "<current-insider-key>" }
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### Event Gateway
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
# Send a webhook
|
|
105
|
+
POST /event?key=<webhook-key>
|
|
106
|
+
Content-Type: application/json
|
|
107
|
+
Body: { "type": "page.content_updated", "data": { "page_id": "abc123" } }
|
|
108
|
+
# Returns: { matched: "notion-page-update" } or { matched: null }
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### Health
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
GET /health
|
|
115
|
+
# Returns: 200 OK (no auth required)
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## Converting Windows Paths to URLs
|
|
119
|
+
|
|
120
|
+
Jeeves Server maps Windows filesystem paths to URL paths:
|
|
121
|
+
|
|
122
|
+
```
|
|
123
|
+
D:\docs\design.md → /d/docs/design.md
|
|
124
|
+
E:\projects\foo → /e/projects/foo
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
**Conversion formula:**
|
|
128
|
+
1. Replace backslashes with forward slashes
|
|
129
|
+
2. Replace the drive letter + colon with lowercase letter
|
|
130
|
+
3. Prepend the route prefix (`/path/` for legacy, `/browse/` for SPA, `/api/file/` for API)
|
|
131
|
+
|
|
132
|
+
```javascript
|
|
133
|
+
function winPathToUrl(winPath, prefix = '/path/') {
|
|
134
|
+
const urlPath = winPath
|
|
135
|
+
.replace(/\\/g, '/')
|
|
136
|
+
.replace(/^([A-Z]):/, (_, d) => d.toLowerCase());
|
|
137
|
+
return `${prefix}${urlPath}`;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// D:\docs\design.md → /path/d/docs/design.md
|
|
141
|
+
// D:\docs\design.md → /browse/d/docs/design.md
|
|
142
|
+
// D:\docs\design.md → /api/file/d/docs/design.md
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
```powershell
|
|
146
|
+
# PowerShell equivalent
|
|
147
|
+
function Convert-ToJeevesUrl {
|
|
148
|
+
param([string]$Path, [string]$Prefix = '/path/')
|
|
149
|
+
$urlPath = $Path -replace '\\','/' -replace '^([A-Z]):',{ $_.Groups[1].Value.ToLower() }
|
|
150
|
+
return "${Prefix}${urlPath}"
|
|
151
|
+
}
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## Generating Share Links
|
|
155
|
+
|
|
156
|
+
### Insider links
|
|
157
|
+
|
|
158
|
+
```javascript
|
|
159
|
+
const insiderKey = computeInsiderKey(seed);
|
|
160
|
+
const url = `https://jeeves.example.com/browse/d/docs/design.md?key=${insiderKey}`;
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### Outsider links (path-scoped)
|
|
164
|
+
|
|
165
|
+
```javascript
|
|
166
|
+
const crypto = require('crypto');
|
|
167
|
+
|
|
168
|
+
function outsiderKey(seed, path) {
|
|
169
|
+
const normalized = path.toLowerCase().replace(/^\/+|\/+$/g, '');
|
|
170
|
+
return crypto.createHmac('sha256', seed).update(normalized).digest('hex').substring(0, 32);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function outsiderKeyWithExpiry(seed, path, expiryMs) {
|
|
174
|
+
const normalized = path.toLowerCase().replace(/^\/+|\/+$/g, '');
|
|
175
|
+
const data = `${normalized}|${expiryMs}`;
|
|
176
|
+
return crypto.createHmac('sha256', seed).update(data).digest('hex').substring(0, 32);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Non-expiring outsider link
|
|
180
|
+
const key = outsiderKey(seed, 'd/docs/design.md');
|
|
181
|
+
const url = `https://jeeves.example.com/browse/d/docs/design.md?key=${key}`;
|
|
182
|
+
|
|
183
|
+
// Expiring outsider link (1 week)
|
|
184
|
+
const expiry = Date.now() + 7 * 24 * 60 * 60 * 1000;
|
|
185
|
+
const key = outsiderKeyWithExpiry(seed, 'd/docs/design.md', expiry);
|
|
186
|
+
const url = `https://jeeves.example.com/browse/d/docs/design.md?key=${key}&exp=${expiry}`;
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
### Directory links
|
|
190
|
+
|
|
191
|
+
Outsider keys for directories grant access to all descendants:
|
|
192
|
+
|
|
193
|
+
```javascript
|
|
194
|
+
// Share an entire directory
|
|
195
|
+
const key = outsiderKey(seed, 'd/projects/client-x');
|
|
196
|
+
const url = `https://jeeves.example.com/browse/d/projects/client-x?key=${key}`;
|
|
197
|
+
// Grants access to all files under D:\projects\client-x\
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
## For AI Assistants
|
|
201
|
+
|
|
202
|
+
If you're an AI assistant working with Jeeves Server, here's what you need to know:
|
|
203
|
+
|
|
204
|
+
### Generating links to share with humans
|
|
205
|
+
|
|
206
|
+
When your human asks you to share a document:
|
|
207
|
+
|
|
208
|
+
1. **Convert the Windows path** to a URL path (see above)
|
|
209
|
+
2. **Use the insider key** for team members, or generate an outsider key for external recipients
|
|
210
|
+
3. **Choose the right route**: `/browse/` for browser viewing, `/path/` with `?export=pdf` for direct PDF download
|
|
211
|
+
|
|
212
|
+
### Authoring documents
|
|
213
|
+
|
|
214
|
+
Write Markdown files to the server's filesystem. Jeeves Server will render them beautifully. You can:
|
|
215
|
+
- Embed Mermaid diagrams (rendered inline)
|
|
216
|
+
- Embed SVG files (rendered with pan/zoom)
|
|
217
|
+
- Use code blocks with language hints (syntax highlighted)
|
|
218
|
+
- Reference other local files with relative paths
|
|
219
|
+
|
|
220
|
+
### Checking server status
|
|
221
|
+
|
|
222
|
+
```bash
|
|
223
|
+
curl -s http://localhost:1934/health
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
### Triggering webhooks
|
|
227
|
+
|
|
228
|
+
If you need to trigger an action via the event gateway:
|
|
229
|
+
|
|
230
|
+
```bash
|
|
231
|
+
curl -X POST "http://localhost:1934/event?key=<webhook-key>" \
|
|
232
|
+
-H "Content-Type: application/json" \
|
|
233
|
+
-d '{"action": "rebuild", "target": "docs"}'
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
Match this against a configured event schema to dispatch your handler.
|