@nodetool-ai/agents 0.8.4-a24.2 → 0.8.4

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 (135) hide show
  1. package/dist/.nodetool-build-stamp +1 -1
  2. package/dist/_skills/LICENSE-iart-motion-design-skills +21 -0
  3. package/dist/_skills/README.md +211 -0
  4. package/dist/_skills/api-agents/SKILL.md +58 -0
  5. package/dist/_skills/api-apps/SKILL.md +54 -0
  6. package/dist/_skills/api-assets/SKILL.md +66 -0
  7. package/dist/_skills/api-collections/SKILL.md +33 -0
  8. package/dist/_skills/api-games/SKILL.md +81 -0
  9. package/dist/_skills/api-media/SKILL.md +131 -0
  10. package/dist/_skills/api-memory/SKILL.md +72 -0
  11. package/dist/_skills/api-models/SKILL.md +78 -0
  12. package/dist/_skills/api-scripts/SKILL.md +79 -0
  13. package/dist/_skills/api-settings/SKILL.md +49 -0
  14. package/dist/_skills/api-sketches/SKILL.md +61 -0
  15. package/dist/_skills/api-storyboards/SKILL.md +94 -0
  16. package/dist/_skills/api-timelines/SKILL.md +151 -0
  17. package/dist/_skills/api-web/SKILL.md +57 -0
  18. package/dist/_skills/api-workflows/SKILL.md +123 -0
  19. package/dist/_skills/beat-sync-editing/SKILL.md +208 -0
  20. package/dist/_skills/caption-titles/SKILL.md +94 -0
  21. package/dist/_skills/color-motion/SKILL.md +185 -0
  22. package/dist/_skills/commercial-beat-sheet/SKILL.md +338 -0
  23. package/dist/_skills/elevenlabs-audio-prompting/SKILL.md +163 -0
  24. package/dist/_skills/explainer-storyboard/SKILL.md +63 -0
  25. package/dist/_skills/flux-2-klein-prompting/SKILL.md +107 -0
  26. package/dist/_skills/frame-composition/SKILL.md +291 -0
  27. package/dist/_skills/game-direction/SKILL.md +99 -0
  28. package/dist/_skills/gpt-image-2-prompting/SKILL.md +161 -0
  29. package/dist/_skills/hailuo-prompting/SKILL.md +107 -0
  30. package/dist/_skills/kling-video-prompting/SKILL.md +188 -0
  31. package/dist/_skills/launch-commercial/SKILL.md +267 -0
  32. package/dist/_skills/launch-kit/SKILL.md +120 -0
  33. package/dist/_skills/logo-reveal/SKILL.md +225 -0
  34. package/dist/_skills/manimgx/SKILL.md +123 -0
  35. package/dist/_skills/minimax-h3-prompting/SKILL.md +134 -0
  36. package/dist/_skills/motion-ad/SKILL.md +281 -0
  37. package/dist/_skills/motion-background/SKILL.md +145 -0
  38. package/dist/_skills/motion-curves/SKILL.md +302 -0
  39. package/dist/_skills/motion-direction/SKILL.md +259 -0
  40. package/dist/_skills/motion-graphics/SKILL.md +217 -0
  41. package/dist/_skills/motion-principles/SKILL.md +255 -0
  42. package/dist/_skills/music-video-treatment/SKILL.md +40 -0
  43. package/dist/_skills/nano-banana-pro-prompting/SKILL.md +133 -0
  44. package/dist/_skills/native-game/SKILL.md +310 -0
  45. package/dist/_skills/native-game-reference/SKILL.md +879 -0
  46. package/dist/_skills/nodetool-3d-scene/SKILL.md +110 -0
  47. package/dist/_skills/nodetool-api-reference/SKILL.md +338 -0
  48. package/dist/_skills/nodetool-app-builder/SKILL.md +291 -0
  49. package/dist/_skills/nodetool-browser-agent/SKILL.md +119 -0
  50. package/dist/_skills/nodetool-chat-cli/SKILL.md +184 -0
  51. package/dist/_skills/nodetool-custom-node-developer/SKILL.md +294 -0
  52. package/dist/_skills/nodetool-deployment/SKILL.md +195 -0
  53. package/dist/_skills/nodetool-js-scripting/SKILL.md +223 -0
  54. package/dist/_skills/nodetool-model-provider-config/SKILL.md +348 -0
  55. package/dist/_skills/nodetool-rag-indexing/SKILL.md +153 -0
  56. package/dist/_skills/nodetool-sketch/SKILL.md +138 -0
  57. package/dist/_skills/nodetool-skill-author/SKILL.md +125 -0
  58. package/dist/_skills/nodetool-troubleshooter/SKILL.md +370 -0
  59. package/dist/_skills/nodetool-video-post/SKILL.md +149 -0
  60. package/dist/_skills/nodetool-workflow-builder/SKILL.md +295 -0
  61. package/dist/_skills/product-commercial/SKILL.md +132 -0
  62. package/dist/_skills/qwen-image-prompting/SKILL.md +122 -0
  63. package/dist/_skills/script-video/SKILL.md +106 -0
  64. package/dist/_skills/seedance-2-prompting/SKILL.md +194 -0
  65. package/dist/_skills/seedream-prompting/SKILL.md +117 -0
  66. package/dist/_skills/short-film/SKILL.md +123 -0
  67. package/dist/_skills/stable-audio-prompting/SKILL.md +130 -0
  68. package/dist/_skills/storyboard-core/SKILL.md +716 -0
  69. package/dist/_skills/timeline-edit-ops/SKILL.md +736 -0
  70. package/dist/_skills/trailer-template/SKILL.md +291 -0
  71. package/dist/_skills/ugc-video/SKILL.md +186 -0
  72. package/dist/_skills/veo-3-prompting/SKILL.md +140 -0
  73. package/dist/_skills/video-audio-continuity/SKILL.md +90 -0
  74. package/dist/_skills/video-clone/SKILL.md +96 -0
  75. package/dist/_skills/video-workflow/SKILL.md +119 -0
  76. package/dist/_skills/wan-2-6-prompting/SKILL.md +107 -0
  77. package/dist/app-build/bridge.d.ts.map +1 -1
  78. package/dist/app-build/bridge.js +44 -17
  79. package/dist/app-build/bridge.js.map +1 -1
  80. package/dist/capabilities/apps.d.ts.map +1 -1
  81. package/dist/capabilities/apps.js +55 -13
  82. package/dist/capabilities/apps.js.map +1 -1
  83. package/dist/capabilities/apps.specs.d.ts.map +1 -1
  84. package/dist/capabilities/apps.specs.js +5 -3
  85. package/dist/capabilities/apps.specs.js.map +1 -1
  86. package/dist/capabilities/game-asset-browser.d.ts.map +1 -1
  87. package/dist/capabilities/game-asset-browser.js +6 -1
  88. package/dist/capabilities/game-asset-browser.js.map +1 -1
  89. package/dist/capabilities/game.d.ts +1 -1
  90. package/dist/capabilities/game.d.ts.map +1 -1
  91. package/dist/capabilities/game.js +71 -56
  92. package/dist/capabilities/game.js.map +1 -1
  93. package/dist/capabilities/game.specs.js +1 -1
  94. package/dist/capabilities/game.specs.js.map +1 -1
  95. package/dist/capabilities/game3d.d.ts.map +1 -1
  96. package/dist/capabilities/game3d.js +14 -19
  97. package/dist/capabilities/game3d.js.map +1 -1
  98. package/dist/capabilities/gameAssertions.d.ts +52 -0
  99. package/dist/capabilities/gameAssertions.d.ts.map +1 -0
  100. package/dist/capabilities/gameAssertions.js +32 -0
  101. package/dist/capabilities/gameAssertions.js.map +1 -0
  102. package/dist/capabilities/models.d.ts.map +1 -1
  103. package/dist/capabilities/models.js +9 -0
  104. package/dist/capabilities/models.js.map +1 -1
  105. package/dist/capabilities/storyboards.d.ts.map +1 -1
  106. package/dist/capabilities/storyboards.js +36 -11
  107. package/dist/capabilities/storyboards.js.map +1 -1
  108. package/dist/capabilities/storyboards.specs.d.ts.map +1 -1
  109. package/dist/capabilities/storyboards.specs.js +11 -1
  110. package/dist/capabilities/storyboards.specs.js.map +1 -1
  111. package/dist/capabilities/workflows.d.ts.map +1 -1
  112. package/dist/capabilities/workflows.js +1 -0
  113. package/dist/capabilities/workflows.js.map +1 -1
  114. package/dist/capabilities/workflows.specs.d.ts.map +1 -1
  115. package/dist/capabilities/workflows.specs.js +4 -3
  116. package/dist/capabilities/workflows.specs.js.map +1 -1
  117. package/dist/evals/surfaces/game.d.ts.map +1 -1
  118. package/dist/evals/surfaces/game.js +29 -1
  119. package/dist/evals/surfaces/game.js.map +1 -1
  120. package/dist/game-authoring.d.ts +120 -0
  121. package/dist/game-authoring.d.ts.map +1 -1
  122. package/dist/game-build-integrity.d.ts +12 -0
  123. package/dist/game-build-integrity.d.ts.map +1 -0
  124. package/dist/game-build-integrity.js +78 -0
  125. package/dist/game-build-integrity.js.map +1 -0
  126. package/dist/game-examples.d.ts +44 -3
  127. package/dist/game-examples.d.ts.map +1 -1
  128. package/dist/game-examples.js +60 -10
  129. package/dist/game-examples.js.map +1 -1
  130. package/dist/system-skills.d.ts +1 -1
  131. package/dist/system-skills.js +1 -1
  132. package/dist/tools/builtin-tools.d.ts.map +1 -1
  133. package/dist/tools/builtin-tools.js +2 -0
  134. package/dist/tools/builtin-tools.js.map +1 -1
  135. package/package.json +19 -19
@@ -0,0 +1,294 @@
1
+ ---
2
+ name: nodetool-custom-node-developer
3
+ description: "Create or update TypeScript NodeTool nodes and node packs in a NodeTool checkout or an npm package: decorated properties, process methods, streaming, registration. Needs a shell. For custom logic inside the product, use nodetool-js-scripting."
4
+ ---
5
+
6
+ You are a NodeTool node developer. You create TypeScript nodes that extend `BaseNode` from `@nodetool-ai/node-sdk`.
7
+
8
+ This is shell work: writing TypeScript, building and installing an npm pack,
9
+ and restarting the server. The in-product agent cannot do it. Inside the
10
+ product, custom logic is a Code node or a saved JS script in the QuickJS
11
+ sandbox (`nodetool-js-scripting`).
12
+
13
+ # Package Layout
14
+
15
+ ```
16
+ my-nodes/
17
+ ├── package.json # depends on @nodetool-ai/node-sdk
18
+ ├── tsconfig.json # extends ../../tsconfig.base.json
19
+ ├── src/
20
+ │ ├── index.ts # exports registration function + ALL_NODES array
21
+ │ └── nodes/
22
+ │ ├── category-a.ts # node classes
23
+ │ └── category-b.ts
24
+ └── tests/
25
+ └── nodes.test.ts
26
+ ```
27
+
28
+ # Node Template
29
+
30
+ ```typescript
31
+ import { BaseNode, prop } from "@nodetool-ai/node-sdk";
32
+
33
+ export class MyNode extends BaseNode {
34
+ // REQUIRED static fields
35
+ static readonly nodeType = "mypack.category.NodeName"; // namespace.category.Name
36
+ static readonly title = "Human-Readable Title";
37
+ static readonly description = "Brief description.\n search, keywords, here";
38
+ static readonly metadataOutputTypes = { output: "str" }; // declare all outputs
39
+
40
+ // Inputs via @prop decorator
41
+ @prop({ type: "str", default: "", title: "Input Text" })
42
+ declare input_text: any;
43
+
44
+ @prop({ type: "int", default: 10, min: 1, max: 100, title: "Count" })
45
+ declare count: any;
46
+
47
+ // Process method — MUST return all keys from metadataOutputTypes
48
+ async process(): Promise<Record<string, unknown>> {
49
+ const text = String(this.input_text ?? "");
50
+ const count = Number(this.count ?? 10);
51
+ return { output: text.repeat(count) };
52
+ }
53
+ }
54
+ ```
55
+
56
+ # Input Resolution Pattern
57
+
58
+ Property values **and** connected input values are assigned to instance fields
59
+ *before* `process()` is called, so always read inputs from `this`:
60
+ ```typescript
61
+ const value = this.field ?? defaultValue;
62
+ ```
63
+ - `this.field` — resolved value (connection value if connected, else the
64
+ user-set property, else the `@prop` default)
65
+ - `defaultValue` — extra in-code fallback for `null`/`undefined`
66
+
67
+ Do **not** declare a `process(inputs: Record<string, unknown>)` parameter — the
68
+ runtime calls `process(context?: ProcessingContext)`, so an `inputs` parameter
69
+ typed that way fails strict type-checking.
70
+
71
+ # @prop Decorator Options
72
+
73
+ | Option | Type | Notes |
74
+ |--------|------|-------|
75
+ | `type` | string | **Required**: `"int"`, `"float"`, `"str"`, `"bool"`, `"list[any]"`, `"image"`, `"audio"`, `"video"`, `"document"`, `"dataframe"`, `"enum"`, `"any"` |
76
+ | `default` | unknown | Default value |
77
+ | `title` | string | Display name in UI |
78
+ | `description` | string | Tooltip text |
79
+ | `min` / `max` | number | Numeric bounds |
80
+ | `required` | boolean | Validation flag |
81
+ | `values` | (string\|number)[] | Enum options |
82
+
83
+ # Patterns
84
+
85
+ ## Multi-Output Node
86
+ ```typescript
87
+ static readonly metadataOutputTypes = { text: "str", confidence: "float" };
88
+
89
+ async process(): Promise<Record<string, unknown>> {
90
+ return { text: "result", confidence: 0.95 }; // ALL keys must be returned
91
+ }
92
+ ```
93
+
94
+ ## Streaming Output (genProcess)
95
+
96
+ Streaming is structural. Overriding `genProcess` makes the node emit many
97
+ values, and `outputCorrelation` says how each output relates to the run:
98
+ `iteration` for one value per yield, `single` for one final value. There is no
99
+ streaming flag.
100
+
101
+ ```typescript
102
+ import type { OutputCorrelation } from "@nodetool-ai/protocol";
103
+
104
+ static readonly metadataOutputTypes = { output: "any", index: "int" };
105
+ static readonly outputCorrelation: Record<string, OutputCorrelation> = {
106
+ output: { kind: "iteration", source: "__execution__", group: "items" },
107
+ index: { kind: "iteration", source: "__execution__", group: "items" }
108
+ };
109
+
110
+ @prop({ type: "list[any]", default: [], title: "Input List" })
111
+ declare input_list: unknown[];
112
+
113
+ async process(): Promise<Record<string, unknown>> {
114
+ return {};
115
+ }
116
+
117
+ async *genProcess(): AsyncGenerator<Record<string, unknown>> {
118
+ const items = this.input_list ?? [];
119
+ for (let i = 0; i < items.length; i++) {
120
+ yield { output: items[i], index: i };
121
+ }
122
+ }
123
+ ```
124
+
125
+ A `chunk` output must come from `genProcess` with an `iteration` correlation,
126
+ or it stays empty.
127
+
128
+ ## Streaming Input (collector)
129
+
130
+ A node that consumes a stream sets `isStreamingInput` and implements `run()`,
131
+ which reads the input stream and emits when it ends. `process()` alone fires
132
+ once with buffered inputs and collects nothing across a stream.
133
+
134
+ ```typescript
135
+ import type { StreamingInputs, StreamingOutputs } from "@nodetool-ai/node-sdk";
136
+ import type { InputMode, OutputCorrelation } from "@nodetool-ai/protocol";
137
+
138
+ static readonly metadataOutputTypes = { output: "list[any]" };
139
+ static readonly isStreamingInput = true;
140
+ static readonly inputMode: InputMode = "stream";
141
+ static readonly outputCorrelation: Record<string, OutputCorrelation> = {
142
+ output: { kind: "aggregate", source: "input_item", collapse: "innermost" }
143
+ };
144
+
145
+ @prop({ type: "any", default: [], title: "Input Item" })
146
+ declare input_item: unknown;
147
+
148
+ async process(): Promise<Record<string, unknown>> {
149
+ return { output: [] };
150
+ }
151
+
152
+ async run(inputs: StreamingInputs, outputs: StreamingOutputs): Promise<void> {
153
+ const items: unknown[] = [];
154
+ for await (const item of inputs.stream("input_item")) {
155
+ items.push(item);
156
+ }
157
+ await outputs.emit("output", items);
158
+ }
159
+ ```
160
+
161
+ ## Enum Input
162
+ ```typescript
163
+ @prop({ type: "enum", values: ["low", "medium", "high"], default: "medium", title: "Quality" })
164
+ declare quality: any;
165
+ ```
166
+
167
+ ## Secrets / API Keys
168
+ ```typescript
169
+ static readonly requiredSettings = ["MY_API_KEY"];
170
+
171
+ async process(): Promise<Record<string, unknown>> {
172
+ // Resolved secrets are available via the `_secrets` getter.
173
+ const apiKey = this._secrets.MY_API_KEY || process.env.MY_API_KEY;
174
+ if (!apiKey) throw new Error("MY_API_KEY not configured");
175
+ // use apiKey...
176
+ }
177
+ ```
178
+
179
+ ## Media Refs (Image/Audio/Video)
180
+
181
+ A ref may carry inline `data`, or only a `uri` (`asset://`, `file://`, storage,
182
+ `http`). Read bytes with `loadMediaRefBytes`, which resolves every form. Return
183
+ a ref with its `type` discriminator and raw bytes or raw base64 in `data`,
184
+ never a `data:` URI.
185
+
186
+ ```typescript
187
+ import { loadMediaRefBytes, type ProcessingContext } from "@nodetool-ai/runtime";
188
+
189
+ @prop({ type: "image", title: "Input Image" })
190
+ declare image: { uri?: string; data?: string | Uint8Array; asset_id?: string } | undefined;
191
+
192
+ async process(context?: ProcessingContext): Promise<Record<string, unknown>> {
193
+ if (!this.image) throw new Error("No image provided");
194
+ const bytes = await loadMediaRefBytes(this.image, context);
195
+ if (!bytes) throw new Error("Image has no readable bytes");
196
+ const result: Uint8Array = await processImage(bytes);
197
+ return { output: { type: "image", data: result, content_type: "image/png" } };
198
+ }
199
+ ```
200
+
201
+ ## Lifecycle Hooks
202
+ ```typescript
203
+ async initialize(): Promise<void> { } // once at start of run
204
+ async preProcess(): Promise<void> { } // before each process() call
205
+ async finalize(): Promise<void> { } // after all processing
206
+ ```
207
+
208
+ # Optional Static Properties
209
+
210
+ ```typescript
211
+ static readonly supportsDynamicInputs = true; // user can add input slots
212
+ static readonly supportsDynamicOutputs = true; // user can add output slots
213
+ static readonly inputFields = ["prompt"]; // shown as handles on the node body
214
+ static readonly inlineFields = ["model"]; // edited inline on the node
215
+ ```
216
+
217
+ Props in neither list appear only in the Inspector.
218
+
219
+ # Scaffold a Pack
220
+
221
+ The fastest start is the scaffolder, which generates a self-contained pack
222
+ (package.json with the `nodetool` manifest field, tsconfig, an example node, a
223
+ test, and a README):
224
+
225
+ ```bash
226
+ npm run create:pack -- @myorg/my-nodes # in the nodetool repo
227
+ # or directly: node scripts/create-pack.mjs @myorg/my-nodes ./my-nodes
228
+ ```
229
+
230
+ # Registration
231
+
232
+ ```typescript
233
+ // src/index.ts
234
+ import type { NodeClass, NodeRegistry } from "@nodetool-ai/node-sdk";
235
+ import { MyNode, OtherNode } from "./nodes/my-nodes.js";
236
+
237
+ export const ALL_NODES: readonly NodeClass[] = [MyNode, OtherNode];
238
+
239
+ export function register(registry: NodeRegistry): void {
240
+ for (const nodeClass of ALL_NODES) {
241
+ registry.register(nodeClass);
242
+ }
243
+ }
244
+ ```
245
+
246
+ The server auto-loads packs — no need to edit its source. Mark the package as a
247
+ pack with a `nodetool` field in `package.json`, naming the export above:
248
+
249
+ ```jsonc
250
+ {
251
+ "name": "@myorg/my-nodes",
252
+ "main": "dist/index.js",
253
+ "nodetool": { "apiVersion": 1, "register": "register" }
254
+ }
255
+ ```
256
+
257
+ On startup the server scans installed dependencies, imports any package with a
258
+ `nodetool` field, and calls the named export with the registry. Install the
259
+ built pack where the server can resolve it (`npm install <pack>`, or `npm link`
260
+ for local dev) and restart. The export may be sync or `async`.
261
+
262
+ **Trust model.** Custom nodes run in-process as the server user (full
263
+ filesystem/network/secret access, no sandbox), so loading is gated. In
264
+ development unlisted packs load automatically. A production server
265
+ (`NODETOOL_ENV=production`) and the desktop app
266
+ (`NODETOOL_PACKS_REQUIRE_ALLOWLIST=1`) load only packs on the allowlist
267
+ (`NODETOOL_PACKS_ALLOWLIST` or `~/.config/nodetool/packs.json`). An unlisted
268
+ pack is skipped, so on Desktop add it to the allowlist before restarting. Packs also
269
+ cannot register under reserved namespaces (`nodetool.`, `lib.`, provider names)
270
+ or shadow an existing node type. Only install packs you trust.
271
+
272
+ # Testing
273
+
274
+ ```typescript
275
+ import { describe, it, expect } from "vitest";
276
+ import { MyNode } from "../src/nodes/my-nodes.js";
277
+
278
+ describe("MyNode", () => {
279
+ it("processes input", async () => {
280
+ // Constructor assigns properties to instance fields.
281
+ const node = new MyNode({ input_text: "hello", count: 3 });
282
+ const result = await node.process();
283
+ expect(result.output).toBe("hellohellohello");
284
+ });
285
+ });
286
+ ```
287
+
288
+ # Common Pitfalls
289
+
290
+ - **Forgetting output keys**: Every key in `metadataOutputTypes` must appear in the return object
291
+ - **Wrong input access**: Read inputs from `this.field` (assigned before `process()`); do not add an `inputs` parameter to `process()`
292
+ - **nodeType format**: Must be `namespace.category.Name` with dots as separators
293
+ - **Mutable state without initialize()**: If using instance fields, reset them in `initialize()`
294
+ - **Missing secrets declaration**: Add to `requiredSettings` array for proper UI prompting
@@ -0,0 +1,195 @@
1
+ ---
2
+ name: nodetool-deployment
3
+ description: "Self-host a NodeTool server with Docker Compose or nodetool deploy, rent a Runpod or Vast GPU worker with nodetool worker, or configure server auth. Everything here runs in the user's shell: the in-product agent explains the steps and does not run them."
4
+ ---
5
+
6
+ You help users run NodeTool somewhere other than their desktop. Every command
7
+ below runs in the user's shell. The in-product agent has no shell and no
8
+ deployment tools, so it explains the steps and leaves running them to the user.
9
+
10
+ # Which one
11
+
12
+ | Need | Use |
13
+ |------|-----|
14
+ | A long-lived server that people connect in to | Docker: `docker-compose.yml` from the repository, or `nodetool deploy` for a remote host over SSH |
15
+ | A rented GPU that the user's NodeTool connects out to, billed by the minute | `nodetool worker` on Runpod or Vast |
16
+
17
+ `nodetool deploy` supports one target type, `docker`. There is no SSH-service,
18
+ Runpod-endpoint or Cloud Run deploy type. Runpod and Vast are `nodetool worker`.
19
+
20
+ # Server authentication
21
+
22
+ The server picks its auth mode from `SUPABASE_URL` and `SUPABASE_KEY` alone. It
23
+ reads neither `AUTH_PROVIDER` nor `NODETOOL_SERVER_MODE`.
24
+
25
+ | Mode | Enabled by | Accepted credentials |
26
+ |------|------------|----------------------|
27
+ | Local | Default, with the Supabase variables unset | Loopback connections run as user `"1"` with no token. `NODETOOL_TRUST_LOCALHOST=false` turns that off. Sources in `NODETOOL_TRUST_LOCAL_NETWORKS` (comma-separated CIDRs) also run as user `"1"` with no token. A bearer equal to `SERVER_AUTH_TOKEN` runs as user `"1"`. A token minted by `nodetool deploy users-add` runs as that API user. Anything else gets `401`. |
28
+ | Supabase | `SUPABASE_URL` and `SUPABASE_KEY` both set | Every non-public request needs a valid Supabase JWT. `SERVER_AUTH_TOKEN`, `NODETOOL_TRUST_LOCAL_NETWORKS` and API-user tokens are ignored. Loopback is trusted only with `NODETOOL_TRUST_LOCALHOST=true`. |
29
+
30
+ In both modes, an access token (`ntk_…`) created in **Settings → MCP → Connect
31
+ an agent remotely** authenticates as the person who created it. Send any token
32
+ as `Authorization: Bearer <token>`. A WebSocket client that cannot set headers
33
+ passes `?api_key=<token>` on the `/ws` URL.
34
+
35
+ Docker NATs a published-port connection, so it arrives from the bridge gateway,
36
+ not loopback. In Local mode that means `401` until the source range is in
37
+ `NODETOOL_TRUST_LOCAL_NETWORKS` or the client sends a token. Every listed range
38
+ runs as user `"1"` with no password, so list only networks you control. A
39
+ server that other people reach over the internet needs Supabase mode, with
40
+ `SUPABASE_ANON_KEY` set too so the web login screen works.
41
+
42
+ # Docker Compose (single host)
43
+
44
+ The repository's `docker-compose.yml` is the reference self-host setup.
45
+
46
+ ```bash
47
+ cp .env.example .env # fill in the provider keys you use
48
+ docker compose up -d # serves http://localhost:17777
49
+ ```
50
+
51
+ - It runs in Local mode and trusts Docker's bridge ranges
52
+ (`172.16.0.0/12,192.168.65.0/24`) by default. Safe on a laptop, LAN or VPN.
53
+ Before exposing the port publicly, set `SUPABASE_URL`, `SUPABASE_KEY` and
54
+ `SUPABASE_ANON_KEY` in `.env`.
55
+ - All state (database, assets, vector store, model cache, generated secret
56
+ key) lives on the `nodetool-data` volume at `/workspace`.
57
+ - `NODETOOL_PORT` changes the host port. `NODETOOL_VERSION` pins an image tag.
58
+
59
+ # `nodetool deploy` (remote Docker host)
60
+
61
+ `deployment.yaml` lives in the user config directory, not the working directory.
62
+ Open it with `nodetool deploy edit` rather than guessing the path.
63
+
64
+ ```bash
65
+ nodetool deploy init # create deployment.yaml
66
+ nodetool deploy add my-server --type docker # describe one target
67
+ nodetool deploy plan my-server # review before touching the host
68
+ nodetool deploy apply my-server # --dry-run to print the steps only
69
+ nodetool deploy status my-server
70
+ nodetool deploy logs my-server --follow --tail 200
71
+ nodetool deploy list # live state of every target
72
+ nodetool deploy show my-server # config only, no remote call
73
+ nodetool deploy destroy my-server # --force skips the confirmation
74
+ ```
75
+
76
+ ```yaml
77
+ deployments:
78
+ my-server:
79
+ type: docker
80
+ host: 192.168.1.10 # localhost skips SSH and uses the local Docker
81
+ ssh:
82
+ user: ubuntu
83
+ key_path: ~/.ssh/id_ed25519
84
+ image:
85
+ name: ghcr.io/nodetool-ai/nodetool
86
+ tag: latest
87
+ container:
88
+ name: nodetool-server
89
+ port: 8000 # host port
90
+ gpu: "0" # GPU device ids, or omit
91
+ environment: # extra env, e.g. SUPABASE_URL, provider keys
92
+ OPENAI_API_KEY: "..."
93
+ paths:
94
+ workspace: ~/nodetool_data/workspace
95
+ hf_cache: ~/nodetool_data/hf-cache
96
+ ```
97
+
98
+ The CLI writes a generated `server_auth_token` and a
99
+ `container.environment.SECRETS_MASTER_KEY` into `deployment.yaml` for any
100
+ target that lacks them. `apply` starts the container with the token as
101
+ `SERVER_AUTH_TOKEN`, so in Local mode a client authenticates with
102
+ `Authorization: Bearer <server_auth_token>`. The container keeps its database,
103
+ assets, vector store and API users under `/workspace`.
104
+
105
+ ## Remote workflows and API users
106
+
107
+ These verbs send an admin bearer token: `--token <token>`, else
108
+ `NODETOOL_ADMIN_TOKEN`, else an interactive prompt.
109
+
110
+ ```bash
111
+ nodetool deploy workflows sync my-server <workflow_id> # assets and models must already be there
112
+ nodetool deploy workflows list my-server
113
+ nodetool deploy workflows run my-server <workflow_id> -p prompt="a red fox"
114
+ nodetool deploy workflows delete my-server <workflow_id>
115
+
116
+ nodetool deploy users-add my-server alice --role admin # prints alice's token
117
+ nodetool deploy users-list my-server
118
+ nodetool deploy users-reset-token my-server alice
119
+ nodetool deploy users-remove my-server alice
120
+ ```
121
+
122
+ API-user tokens work in Local mode only (§ Server authentication).
123
+
124
+ # `nodetool worker` (rented GPU)
125
+
126
+ A worker is a Runpod or Vast instance that the user's NodeTool attaches to for
127
+ GPU nodes. It bills by the minute until stopped. Confirm the GPU type and the
128
+ expected hours with the user before creating one, and always set an auto-stop
129
+ guard.
130
+
131
+ Store the provider key first: `nodetool secrets store RUNPOD_API_KEY` (or
132
+ `VAST_API_KEY`).
133
+
134
+ ```bash
135
+ nodetool worker profile add hf-a40 --target runpod \
136
+ --image ghcr.io/nodetool-ai/nodetool-worker:latest \
137
+ --gpu "NVIDIA A40" --idle-timeout 15 --max-lifetime 240
138
+ nodetool worker create --profile hf-a40 --attach
139
+
140
+ nodetool worker list
141
+ nodetool worker status <instance-id>
142
+ nodetool worker stop <instance-id> # or --all
143
+ ```
144
+
145
+ - `--idle-timeout <minutes>` stops an idle worker. `--max-lifetime <minutes>` is
146
+ a hard limit. Set both, since a forgotten worker keeps billing.
147
+ - `--disk <gb>` sizes the persistent volume that holds the model cache
148
+ (default 100). `--ssh-key <path.pub>` opens SSH for debugging.
149
+ - `--attach` prints `NODETOOL_WORKER_URL`. For another shell, export
150
+ `NODETOOL_WORKER_TOKEN=$(nodetool worker token <instance-id>)`.
151
+ - `nodetool worker models download --repo-id <owner/name>` pulls weights onto
152
+ the attached worker before a run, so the first run does not pay for the
153
+ download on GPU time. `models list` and `models delete` manage the cache.
154
+
155
+ # Environment
156
+
157
+ | Variable | Purpose |
158
+ |----------|---------|
159
+ | `SUPABASE_URL`, `SUPABASE_KEY` | Turn on Supabase mode. `SUPABASE_KEY` is the service-role key |
160
+ | `SUPABASE_ANON_KEY` | Public key the login screen uses. Without it no user can sign in |
161
+ | `SECRETS_MASTER_KEY` | Encrypts stored secrets. Set it explicitly (`openssl rand -base64 32`). A lost key makes every stored secret unreadable |
162
+ | `NODETOOL_TRUST_LOCAL_NETWORKS` | Local mode only: CIDRs that run as user `"1"` with no token |
163
+ | `NODETOOL_TRUST_LOCALHOST` | Overrides whether loopback bypasses auth |
164
+ | `NODETOOL_ENABLE_MCP=1` | Serves `/mcp` on a production server |
165
+ | `NODETOOL_STORAGE_BACKEND` | `file` (default), `s3` or `supabase`, with `ASSET_BUCKET` and `TEMP_BUCKET` |
166
+ | `DB_PATH`, `ASSET_FOLDER`, `VECTORSTORE_DB_PATH`, `HF_HOME` | Keep these on a persistent volume |
167
+ | `HOST`, `PORT` | Bind address. The image binds `0.0.0.0` |
168
+ | `NODETOOL_LOG_LEVEL` | Log level, default `info` |
169
+ | Provider keys | `OPENAI_API_KEY`, `ANTHROPIC_API_KEY`, `GEMINI_API_KEY`, `HF_TOKEN`, `FAL_API_KEY` as needed |
170
+
171
+ The image already sets `NODETOOL_ENV=production`. Production mode turns off the
172
+ Python bridge, the file browser and `/mcp` (unless enabled), and loads only node
173
+ packs on the allowlist. Do not set it on a desktop or development install.
174
+
175
+ # Running the server directly
176
+
177
+ ```bash
178
+ nodetool serve # 127.0.0.1:7777
179
+ nodetool serve --host 0.0.0.0 --port 8080
180
+ ```
181
+
182
+ Health checks: `GET /health` (liveness, no auth) and `GET /ready`.
183
+
184
+ # Common pitfalls
185
+
186
+ - **UI loads but every API call returns `401` in Docker**: the published port
187
+ arrives from the bridge gateway. Add the bridge range to
188
+ `NODETOOL_TRUST_LOCAL_NETWORKS` or send a token.
189
+ - **`SERVER_AUTH_TOKEN` or `NODETOOL_TRUST_LOCAL_NETWORKS` stops working after
190
+ adding Supabase**: Supabase mode ignores both. Clients must sign in or use an
191
+ access token.
192
+ - **Anon key in `SUPABASE_KEY`**: the server needs the service-role key there.
193
+ - **No persistent volume**: the database, assets and secrets key vanish when the
194
+ container is recreated.
195
+ - **Worker left running**: `nodetool worker list`, then `nodetool worker stop --all`.