@nimbus-dev/sdk 1.1.2

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 (101) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +34 -0
  3. package/dist/audit-logger.d.ts +6 -0
  4. package/dist/audit-logger.d.ts.map +1 -0
  5. package/dist/audit-logger.js +18 -0
  6. package/dist/audit-logger.js.map +1 -0
  7. package/dist/contract-tests.d.ts +45 -0
  8. package/dist/contract-tests.d.ts.map +1 -0
  9. package/dist/contract-tests.js +191 -0
  10. package/dist/contract-tests.js.map +1 -0
  11. package/dist/crypto/app-store-connect-jwt.d.ts +19 -0
  12. package/dist/crypto/app-store-connect-jwt.d.ts.map +1 -0
  13. package/dist/crypto/app-store-connect-jwt.js +30 -0
  14. package/dist/crypto/app-store-connect-jwt.js.map +1 -0
  15. package/dist/crypto/canonical-json.d.ts +36 -0
  16. package/dist/crypto/canonical-json.d.ts.map +1 -0
  17. package/dist/crypto/canonical-json.js +75 -0
  18. package/dist/crypto/canonical-json.js.map +1 -0
  19. package/dist/crypto/jwt.d.ts +30 -0
  20. package/dist/crypto/jwt.d.ts.map +1 -0
  21. package/dist/crypto/jwt.js +30 -0
  22. package/dist/crypto/jwt.js.map +1 -0
  23. package/dist/crypto/service-account-token.d.ts +36 -0
  24. package/dist/crypto/service-account-token.d.ts.map +1 -0
  25. package/dist/crypto/service-account-token.js +96 -0
  26. package/dist/crypto/service-account-token.js.map +1 -0
  27. package/dist/crypto/verify-signature.d.ts +57 -0
  28. package/dist/crypto/verify-signature.d.ts.map +1 -0
  29. package/dist/crypto/verify-signature.js +102 -0
  30. package/dist/crypto/verify-signature.js.map +1 -0
  31. package/dist/distribution-channel.d.ts +34 -0
  32. package/dist/distribution-channel.d.ts.map +1 -0
  33. package/dist/distribution-channel.js +73 -0
  34. package/dist/distribution-channel.js.map +1 -0
  35. package/dist/hitl-request.d.ts +7 -0
  36. package/dist/hitl-request.d.ts.map +1 -0
  37. package/dist/hitl-request.js +15 -0
  38. package/dist/hitl-request.js.map +1 -0
  39. package/dist/index.d.ts +23 -0
  40. package/dist/index.d.ts.map +1 -0
  41. package/dist/index.js +19 -0
  42. package/dist/index.js.map +1 -0
  43. package/dist/ipc/index.d.ts +2 -0
  44. package/dist/ipc/index.d.ts.map +1 -0
  45. package/dist/ipc/index.js +2 -0
  46. package/dist/ipc/index.js.map +1 -0
  47. package/dist/ipc/ndjson-line-reader.d.ts +20 -0
  48. package/dist/ipc/ndjson-line-reader.d.ts.map +1 -0
  49. package/dist/ipc/ndjson-line-reader.js +56 -0
  50. package/dist/ipc/ndjson-line-reader.js.map +1 -0
  51. package/dist/server.d.ts +29 -0
  52. package/dist/server.d.ts.map +1 -0
  53. package/dist/server.js +23 -0
  54. package/dist/server.js.map +1 -0
  55. package/dist/testing/index.d.ts +15 -0
  56. package/dist/testing/index.d.ts.map +1 -0
  57. package/dist/testing/index.js +17 -0
  58. package/dist/testing/index.js.map +1 -0
  59. package/dist/testing/sandbox-contract.d.ts +83 -0
  60. package/dist/testing/sandbox-contract.d.ts.map +1 -0
  61. package/dist/testing/sandbox-contract.js +105 -0
  62. package/dist/testing/sandbox-contract.js.map +1 -0
  63. package/dist/testing/sandbox-probe.d.ts +23 -0
  64. package/dist/testing/sandbox-probe.d.ts.map +1 -0
  65. package/dist/testing/sandbox-probe.js +78 -0
  66. package/dist/testing/sandbox-probe.js.map +1 -0
  67. package/dist/types.d.ts +41 -0
  68. package/dist/types.d.ts.map +1 -0
  69. package/dist/types.js +5 -0
  70. package/dist/types.js.map +1 -0
  71. package/package.json +55 -0
  72. package/src/audit-logger.test.ts +33 -0
  73. package/src/audit-logger.ts +23 -0
  74. package/src/contract-tests.test.ts +203 -0
  75. package/src/contract-tests.ts +220 -0
  76. package/src/crypto/app-store-connect-jwt.test.ts +80 -0
  77. package/src/crypto/app-store-connect-jwt.ts +42 -0
  78. package/src/crypto/canonical-json.test.ts +121 -0
  79. package/src/crypto/canonical-json.ts +73 -0
  80. package/src/crypto/jwt.test.ts +62 -0
  81. package/src/crypto/jwt.ts +45 -0
  82. package/src/crypto/service-account-token.test.ts +128 -0
  83. package/src/crypto/service-account-token.ts +116 -0
  84. package/src/crypto/verify-signature.test.ts +118 -0
  85. package/src/crypto/verify-signature.ts +138 -0
  86. package/src/distribution-channel.test.ts +107 -0
  87. package/src/distribution-channel.ts +105 -0
  88. package/src/hitl-request.ts +22 -0
  89. package/src/index.ts +59 -0
  90. package/src/ipc/index.ts +5 -0
  91. package/src/ipc/ndjson-line-reader.test.ts +64 -0
  92. package/src/ipc/ndjson-line-reader.ts +70 -0
  93. package/src/plugin-api-v1.test.ts +50 -0
  94. package/src/sdk.test.ts +23 -0
  95. package/src/server.test.ts +96 -0
  96. package/src/server.ts +39 -0
  97. package/src/testing/index.ts +18 -0
  98. package/src/testing/sandbox-contract.test.ts +146 -0
  99. package/src/testing/sandbox-contract.ts +155 -0
  100. package/src/testing/sandbox-probe.ts +87 -0
  101. package/src/types.ts +42 -0
@@ -0,0 +1,146 @@
1
+ import { describe, expect, it } from "bun:test";
2
+ import { mkdtempSync, writeFileSync } from "node:fs";
3
+ import { tmpdir } from "node:os";
4
+ import { join } from "node:path";
5
+
6
+ import {
7
+ __defaultRunProbe,
8
+ type ProbeResult,
9
+ type ProbeRunner,
10
+ runSandboxContractTests,
11
+ } from "./sandbox-contract.ts";
12
+
13
+ function makeProbeRunner(
14
+ responses: ReadonlyArray<{ probe: string; arg?: string; result: ProbeResult }>,
15
+ ): { runner: ProbeRunner; calls: Array<{ probe: string; arg: string }> } {
16
+ const calls: Array<{ probe: string; arg: string }> = [];
17
+ const runner: ProbeRunner = (probe, arg) => {
18
+ calls.push({ probe, arg });
19
+ const match = responses.find(
20
+ (r) => r.probe === probe && (r.arg === undefined || r.arg === arg),
21
+ );
22
+ if (match === undefined) {
23
+ throw new Error(`unexpected probe call: ${probe} arg=${arg}`);
24
+ }
25
+ return match.result;
26
+ };
27
+ return { runner, calls };
28
+ }
29
+
30
+ function writeManifest(perms: unknown): string {
31
+ const dir = mkdtempSync(join(tmpdir(), "sdk-contract-stub-"));
32
+ const manifestPath = join(dir, "nimbus.extension.json");
33
+ writeFileSync(manifestPath, JSON.stringify({ id: "test", permissions: perms }));
34
+ return manifestPath;
35
+ }
36
+
37
+ describe("runSandboxContractTests", () => {
38
+ it("rejects when the manifest file does not exist", async () => {
39
+ const dir = mkdtempSync(join(tmpdir(), "sdk-contract-missing-"));
40
+ const manifestPath = join(dir, "missing.json");
41
+ await expect(runSandboxContractTests(manifestPath)).rejects.toThrow();
42
+ });
43
+
44
+ it("rejects when the manifest is not valid JSON", async () => {
45
+ const dir = mkdtempSync(join(tmpdir(), "sdk-contract-bad-"));
46
+ const manifestPath = join(dir, "nimbus.extension.json");
47
+ writeFileSync(manifestPath, "{not-json");
48
+ await expect(runSandboxContractTests(manifestPath)).rejects.toThrow();
49
+ });
50
+
51
+ it("handles a manifest with no declared network hosts without crashing", async () => {
52
+ const dir = mkdtempSync(join(tmpdir(), "sdk-contract-empty-"));
53
+ const manifestPath = join(dir, "nimbus.extension.json");
54
+ writeFileSync(manifestPath, JSON.stringify({ id: "test.empty", permissions: {} }));
55
+ let outcome: "pass" | "fail" = "pass";
56
+ try {
57
+ await runSandboxContractTests(manifestPath);
58
+ } catch {
59
+ outcome = "fail";
60
+ }
61
+ if (process.platform === "win32") {
62
+ expect(outcome).toBe("pass");
63
+ } else {
64
+ expect(outcome).toBe("fail");
65
+ }
66
+ }, 30_000);
67
+
68
+ it("runs all three probes when manifest declares hosts (Linux/macOS)", async () => {
69
+ const manifestPath = writeManifest({ network: ["api.github.com"] });
70
+ const { runner, calls } = makeProbeRunner([
71
+ {
72
+ probe: "network-listed",
73
+ arg: "api.github.com",
74
+ result: { status: 0, stderr: "", stdout: "" },
75
+ },
76
+ { probe: "network-unlisted", arg: "", result: { status: 11, stderr: "", stdout: "" } },
77
+ { probe: "fs-denied", arg: "", result: { status: 10, stderr: "", stdout: "" } },
78
+ ]);
79
+ await runSandboxContractTests(manifestPath, { runProbe: runner, platform: "linux" });
80
+ expect(calls).toEqual([
81
+ { probe: "network-listed", arg: "api.github.com" },
82
+ { probe: "network-unlisted", arg: "" },
83
+ { probe: "fs-denied", arg: "" },
84
+ ]);
85
+ });
86
+
87
+ it("skips the network-unlisted probe on Windows", async () => {
88
+ const manifestPath = writeManifest({ network: ["api.github.com"] });
89
+ const { runner, calls } = makeProbeRunner([
90
+ { probe: "network-listed", result: { status: 0, stderr: "", stdout: "" } },
91
+ { probe: "fs-denied", result: { status: 10, stderr: "", stdout: "" } },
92
+ ]);
93
+ await runSandboxContractTests(manifestPath, { runProbe: runner, platform: "win32" });
94
+ expect(calls.map((c) => c.probe)).toEqual(["network-listed", "fs-denied"]);
95
+ });
96
+
97
+ it("throws when the listed-host probe exits non-zero", async () => {
98
+ const manifestPath = writeManifest({ network: ["api.github.com"] });
99
+ const { runner } = makeProbeRunner([
100
+ { probe: "network-listed", result: { status: 7, stderr: "connect refused", stdout: "" } },
101
+ ]);
102
+ await expect(
103
+ runSandboxContractTests(manifestPath, { runProbe: runner, platform: "linux" }),
104
+ ).rejects.toThrow(/network-listed probe failed for api\.github\.com.*exit 7.*connect refused/);
105
+ });
106
+
107
+ it("throws when the unlisted-host probe does NOT return exit 11", async () => {
108
+ const manifestPath = writeManifest({ network: ["api.github.com"] });
109
+ const { runner } = makeProbeRunner([
110
+ { probe: "network-listed", result: { status: 0, stderr: "", stdout: "" } },
111
+ {
112
+ probe: "network-unlisted",
113
+ result: { status: 2, stderr: "unexpected fetch success", stdout: "" },
114
+ },
115
+ ]);
116
+ await expect(
117
+ runSandboxContractTests(manifestPath, { runProbe: runner, platform: "linux" }),
118
+ ).rejects.toThrow(/network-unlisted probe should have failed.*exit 2.*platform-asymmetry/s);
119
+ });
120
+
121
+ it("throws when fs-denied probe does NOT return exit 10", async () => {
122
+ const manifestPath = writeManifest({});
123
+ const { runner } = makeProbeRunner([
124
+ { probe: "fs-denied", result: { status: 2, stderr: "unexpected file read", stdout: "" } },
125
+ ]);
126
+ await expect(
127
+ runSandboxContractTests(manifestPath, { runProbe: runner, platform: "linux" }),
128
+ ).rejects.toThrow(/fs-denied probe should have returned EACCES.*exit 2.*unexpected file read/s);
129
+ });
130
+
131
+ it("tolerates a manifest with `permissions: string[]` (legacy array form)", async () => {
132
+ const manifestPath = writeManifest(["read-files", "trash"]);
133
+ const { runner, calls } = makeProbeRunner([
134
+ { probe: "fs-denied", result: { status: 10, stderr: "", stdout: "" } },
135
+ ]);
136
+ await runSandboxContractTests(manifestPath, { runProbe: runner, platform: "linux" });
137
+ expect(calls).toEqual([{ probe: "fs-denied", arg: "" }]);
138
+ });
139
+
140
+ it("`__defaultRunProbe` returns a well-formed envelope on a probe that exits non-zero", () => {
141
+ const r = __defaultRunProbe("definitely-not-a-probe", "");
142
+ expect(typeof r.status).toBe("number");
143
+ expect(typeof r.stderr).toBe("string");
144
+ expect(typeof r.stdout).toBe("string");
145
+ }, 30_000);
146
+ });
@@ -0,0 +1,155 @@
1
+ /**
2
+ * `runSandboxContractTests` — verifies the declared sandbox permissions in a
3
+ * manifest match the runtime enforcement seen by a forked probe.
4
+ *
5
+ * Phase 5 T2 PR 1.
6
+ *
7
+ * Three probes run sequentially:
8
+ *
9
+ * 1. **network-listed** — for the first declared host in
10
+ * `permissions.network`, a HEAD fetch must succeed (2xx–4xx).
11
+ * Skipped if no hosts are declared.
12
+ *
13
+ * 2. **network-unlisted** — a fetch to `192.0.2.1` (TEST-NET-1, never
14
+ * routable) must fail with ECONNREFUSED / EPERM / EHOSTUNREACH /
15
+ * ENETUNREACH. Skipped on Windows because AppContainer network
16
+ * filtering is host-allow + deny-by-default at a different layer — the
17
+ * probe sees a generic socket failure that is indistinguishable from
18
+ * the unsandboxed case. See `docs/sandbox.md#platform-asymmetry`.
19
+ * Skipped on every platform if no hosts are declared (the
20
+ * sandbox-runner would deny network entirely in that case, and the
21
+ * probe's expectation collapses to "no network at all", which the
22
+ * first probe doesn't exercise).
23
+ *
24
+ * 3. **fs-denied** — a read of a known-protected path
25
+ * (`/etc/passwd` POSIX, `C:\Windows\System32\config\SAM` Windows)
26
+ * must fail with EACCES / EPERM. This probe always runs.
27
+ *
28
+ * Each probe is invoked via `child_process.spawnSync(process.execPath, …)`
29
+ * directly. **The probe is *not* sandbox-wrapped by the SDK harness alone.**
30
+ * For the SDK to assert real enforcement, a wrapping helper (typically
31
+ * `packages/gateway/test/helpers/sandbox-harness.ts`) needs to fork
32
+ * `runSandboxContractTests` inside a process that is itself sandbox-wrapped,
33
+ * or substitute a sandboxed `execPath`.
34
+ *
35
+ * Used by:
36
+ * - First-party connector contract tests via the gateway test harness.
37
+ * - Third-party extension authors invoking
38
+ * `import { runSandboxContractTests } from "@nimbus-dev/sdk/testing"`.
39
+ *
40
+ * When invoked **outside** a sandboxed harness on Linux/macOS, probes 2 and
41
+ * 3 will either succeed (return exit 2) — which the harness reports as a
42
+ * test failure — or fail with codes other than 10/11. Either way the harness
43
+ * throws. That's the third-party UX: "your contract test failed because the
44
+ * sandbox isn't enforcing the manifest", which is the correct signal even
45
+ * though the UX could be friendlier.
46
+ */
47
+
48
+ import { spawnSync } from "node:child_process";
49
+ import { readFile } from "node:fs/promises";
50
+ import { dirname, resolve } from "node:path";
51
+ import { fileURLToPath } from "node:url";
52
+
53
+ const PROBE_PATH = resolve(dirname(fileURLToPath(import.meta.url)), "sandbox-probe.ts");
54
+
55
+ interface ManifestPermissions {
56
+ network?: string[];
57
+ filesystem?: { read?: string[]; write?: string[] };
58
+ }
59
+
60
+ interface Manifest {
61
+ id?: string;
62
+ permissions?: ManifestPermissions | unknown[];
63
+ }
64
+
65
+ export interface ProbeResult {
66
+ status: number;
67
+ stderr: string;
68
+ stdout: string;
69
+ }
70
+
71
+ /** Probe-runner shape — exposed for testability (see `__defaultRunProbe`). */
72
+ export type ProbeRunner = (probe: string, arg: string) => ProbeResult;
73
+
74
+ export interface RunSandboxContractTestsOptions {
75
+ /**
76
+ * Override the probe runner. Default is `__defaultRunProbe` (a
77
+ * `child_process.spawnSync` wrapper around the bundled probe binary).
78
+ * Tests inject a stub here; production callers leave this undefined.
79
+ */
80
+ runProbe?: ProbeRunner;
81
+ /**
82
+ * Override `process.platform` for testability of the Windows skip
83
+ * branch. Default is the live `process.platform`.
84
+ */
85
+ platform?: NodeJS.Platform;
86
+ }
87
+
88
+ /**
89
+ * Read the manifest at `manifestPath`, fork the probe binary for each
90
+ * declared capability + the FS-denied negative case, and throw if the
91
+ * observed enforcement does not match.
92
+ *
93
+ * Throws on first failure with a message that names the probe, the
94
+ * observed exit code, and the probe's stderr.
95
+ */
96
+ export async function runSandboxContractTests(
97
+ manifestPath: string,
98
+ opts: RunSandboxContractTestsOptions = {},
99
+ ): Promise<void> {
100
+ const runProbe = opts.runProbe ?? __defaultRunProbe;
101
+ const platform = opts.platform ?? process.platform;
102
+
103
+ const raw = await readFile(manifestPath, "utf8");
104
+ const manifest = JSON.parse(raw) as Manifest;
105
+
106
+ const perms = manifest.permissions;
107
+ const objectForm = perms && typeof perms === "object" && !Array.isArray(perms) ? perms : null;
108
+ const hosts = objectForm?.network ?? [];
109
+
110
+ const firstHost = hosts[0];
111
+ if (firstHost !== undefined) {
112
+ const r = runProbe("network-listed", firstHost);
113
+ if (r.status !== 0) {
114
+ throw new Error(
115
+ `network-listed probe failed for ${firstHost}: exit ${r.status}; stderr: ${r.stderr.trim()}`,
116
+ );
117
+ }
118
+ }
119
+
120
+ if (platform !== "win32" && hosts.length > 0) {
121
+ const r = runProbe("network-unlisted", "");
122
+ if (r.status !== 11) {
123
+ throw new Error(
124
+ `network-unlisted probe should have failed with ECONNREFUSED/EPERM/EHOSTUNREACH/ENETUNREACH; ` +
125
+ `got exit ${r.status}; stderr: ${r.stderr.trim()}. ` +
126
+ `See docs/sandbox.md#platform-asymmetry.`,
127
+ );
128
+ }
129
+ }
130
+
131
+ const r3 = runProbe("fs-denied", "");
132
+ if (r3.status !== 10) {
133
+ throw new Error(
134
+ `fs-denied probe should have returned EACCES (exit 10); got exit ${r3.status}; ` +
135
+ `stderr: ${r3.stderr.trim()}.`,
136
+ );
137
+ }
138
+ }
139
+
140
+ /**
141
+ * Default probe runner — forks the bundled probe binary via
142
+ * `child_process.spawnSync`. Exported for direct use in test scaffolding
143
+ * that wants to assert the production wiring without re-implementing the
144
+ * `spawnSync` envelope.
145
+ */
146
+ export function __defaultRunProbe(probe: string, arg: string): ProbeResult {
147
+ const result = spawnSync(process.execPath, [PROBE_PATH, `--probe=${probe}`, `--arg=${arg}`], {
148
+ encoding: "utf8",
149
+ });
150
+ return {
151
+ status: result.status ?? -1,
152
+ stderr: result.stderr ?? "",
153
+ stdout: result.stdout ?? "",
154
+ };
155
+ }
@@ -0,0 +1,87 @@
1
+ /**
2
+ * Sandbox probe binary — invoked by `runSandboxContractTests`.
3
+ *
4
+ * The probe is a tiny standalone program that the contract harness forks
5
+ * (typically wrapped by the gateway's sandbox runner) to exercise one
6
+ * specific capability and report the outcome via process exit code.
7
+ *
8
+ * Exit codes:
9
+ * 0 — expected pass (network reach to a listed host succeeded)
10
+ * 2 — unexpected outcome (test fails)
11
+ * 10 — expected EACCES/EPERM on filesystem read (sandbox enforced)
12
+ * 11 — expected ECONNREFUSED/EPERM/EHOSTUNREACH/ENETUNREACH on network
13
+ *
14
+ * Invocation:
15
+ * bun sandbox-probe.ts --probe=<name> --arg=<value>
16
+ *
17
+ * Probes:
18
+ * network-listed — HEAD-fetch https://<arg>/ ; succeed if 2xx-4xx
19
+ * network-unlisted — fetch http://192.0.2.1 (TEST-NET-1); succeed if blocked
20
+ * fs-denied — read a known-protected path; succeed if EACCES
21
+ */
22
+
23
+ const probe = process.argv.find((a) => a.startsWith("--probe="))?.slice(8);
24
+ const arg = process.argv.find((a) => a.startsWith("--arg="))?.slice(6);
25
+
26
+ function errorCode(e: unknown): string | undefined {
27
+ return (
28
+ (e as { code?: string; cause?: { code?: string } }).code ??
29
+ (e as { cause?: { code?: string } }).cause?.code
30
+ );
31
+ }
32
+
33
+ async function probeNetworkListed(): Promise<number> {
34
+ const url = `https://${arg ?? ""}/`;
35
+ try {
36
+ const res = await fetch(url, { method: "HEAD" });
37
+ return res.status >= 200 && res.status < 500 ? 0 : 2;
38
+ } catch {
39
+ return 2;
40
+ }
41
+ }
42
+
43
+ async function probeNetworkUnlisted(): Promise<number> {
44
+ try {
45
+ await fetch("http://192.0.2.1");
46
+ return 2;
47
+ } catch (e: unknown) {
48
+ const code = errorCode(e);
49
+ if (
50
+ code === "ECONNREFUSED" ||
51
+ code === "EPERM" ||
52
+ code === "EHOSTUNREACH" ||
53
+ code === "ENETUNREACH"
54
+ ) {
55
+ return 11;
56
+ }
57
+ return 2;
58
+ }
59
+ }
60
+
61
+ async function probeFsDenied(): Promise<number> {
62
+ const path =
63
+ process.platform === "win32" ? String.raw`C:\Windows\System32\config\SAM` : "/etc/passwd";
64
+ try {
65
+ await Bun.file(path).text();
66
+ return 2;
67
+ } catch (e: unknown) {
68
+ const code = (e as { code?: string }).code;
69
+ if (code === "EACCES" || code === "EPERM" || code === "EBUSY") return 10;
70
+ return 2;
71
+ }
72
+ }
73
+
74
+ async function main(): Promise<void> {
75
+ if (probe === "network-listed") process.exit(await probeNetworkListed());
76
+ if (probe === "network-unlisted") process.exit(await probeNetworkUnlisted());
77
+ if (probe === "fs-denied") process.exit(await probeFsDenied());
78
+ process.exit(2);
79
+ }
80
+
81
+ // This standalone probe has no imports/exports of its own, but the top-level
82
+ // `await main()` below requires the file to be a module. `export {}` is the
83
+ // canonical module marker for that. (Sonar S7787 flags the specifier-less
84
+ // export, but removing it makes the top-level await a compile error — TS1375.)
85
+ export {}; // NOSONAR S7787: specifier-less export is the module marker required for the top-level `await main()` below (removing it is TS1375).
86
+
87
+ await main();
package/src/types.ts ADDED
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Shared types for Nimbus extensions
3
+ */
4
+
5
+ export interface NimbusItem {
6
+ id: string;
7
+ service: string;
8
+ itemType: "file" | "folder" | "email" | "event" | "photo" | "task";
9
+ name: string;
10
+ mimeType?: string;
11
+ sizeBytes?: number;
12
+ createdAt?: number;
13
+ modifiedAt?: number;
14
+ url?: string;
15
+ parentId?: string;
16
+ rawMeta?: Record<string, unknown>;
17
+ }
18
+
19
+ export interface ExtensionManifest {
20
+ $schema?: string;
21
+ id: string;
22
+ displayName: string;
23
+ version: string;
24
+ description: string;
25
+ author: string;
26
+ homepage?: string;
27
+ icon?: string;
28
+ entrypoint: string;
29
+ runtime: "bun" | "node";
30
+ permissions: Array<"read" | "write" | "delete">;
31
+ hitlRequired: Array<"write" | "delete">;
32
+ oauth?: {
33
+ provider: string;
34
+ scopes: string[];
35
+ authUrl: string;
36
+ tokenUrl: string;
37
+ pkce: boolean;
38
+ };
39
+ syncInterval?: number;
40
+ tags?: string[];
41
+ minNimbusVersion: string;
42
+ }