@fluxfiles/node 0.1.17 → 0.1.19

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/README.md CHANGED
@@ -68,6 +68,28 @@ The core then accepts `POST /api/fm/import-url` for that token (SSRF-guarded,
68
68
  sharing the quota/dedup/variants pipeline). Server-wide defaults come from
69
69
  `FLUXFILES_IMPORT_*` env vars on the core service.
70
70
 
71
+ ### SFTP disk: chmod & SSH terminal
72
+
73
+ When the token targets a FluxFiles server that has an **SFTP disk** configured
74
+ (`SFTP_*` env on that server — see the
75
+ [core README](https://github.com/thai-pc/fluxfiles#sftp-disk-vps--shared-hosting)),
76
+ you can hand a token the SFTP file-manager tools. `chmod` is on by default for SFTP;
77
+ the **SSH terminal** is opt-in (it grants shell access as the SSH user):
78
+
79
+ ```ts
80
+ const token = createToken({
81
+ userId: 'admin-7',
82
+ perms: ['read', 'write'],
83
+ disks: ['sftp'], // an SFTP disk configured on the FluxFiles server
84
+ allowChmod: true, // cPanel-style permissions (default on for SFTP)
85
+ allowTerminal: true, // SSH terminal — opt-in, off by default
86
+ });
87
+ ```
88
+
89
+ These are **standalone-core** features: `@fluxfiles/node` mints for a real
90
+ FluxFiles server (Docker / standalone), which serves them — they aren't available
91
+ behind the WordPress / Laravel-proxy adapters.
92
+
71
93
  ### BYOB — encrypt a user's own bucket credentials
72
94
 
73
95
  ```ts
package/dist/index.d.mts CHANGED
@@ -100,17 +100,20 @@ interface BaseTokenOptions {
100
100
  * access as the SSH user, so it's opt-in. Core-standalone: the token must target a real core
101
101
  * (this SDK does), not a proxy adapter that doesn't serve /api/fm/terminal. */
102
102
  allowTerminal?: boolean;
103
- /** Allow the paid Optimization module (POST /api/fm/optimize). Default **false** opt-in.
104
- * Even when true, the optimize module must be installed + licensed on the server. */
103
+ /** Optional self-hosted PTY terminal URL (ttyd / gotty / wetty, or any PTY-over-WebSocket
104
+ * server the operator runs). When set (and `allowTerminal` is true), the UI embeds it for a
105
+ * true interactive terminal instead of the built-in command-runner. Free; must be http(s).
106
+ * Core-standalone only (like `allowTerminal`). */
107
+ terminalPtyUrl?: string;
108
+ /** Allow the Optimization feature (POST /api/fm/optimize) — recompress images to WebP +
109
+ * compress PDFs. **Free/core**; default **false** because it replaces/deletes originals, so
110
+ * it's an opt-in capability. */
105
111
  allowOptimize?: boolean;
106
- /** Auto-optimize images on upload (recompress to WebP in the pipeline). Default false;
107
- * only effective when the optimize module is installed + licensed on the server. */
112
+ /** Auto-optimize images on upload (recompress to WebP in the pipeline). Default false.
113
+ * Free/core. */
108
114
  autoOptimize?: boolean;
109
115
  /** WebP quality for optimization, 40–95. `0`/omitted = inherit the server default (82). */
110
116
  optimizeQuality?: number;
111
- /** Target format for optimization: `'webp'` (default) or `'avif'` (falls back to
112
- * WebP when the server build lacks AVIF). */
113
- optimizeFormat?: 'webp' | 'avif';
114
117
  /** Keep the original file when optimizing (default false = replace in place). */
115
118
  optimizeKeepOriginal?: boolean;
116
119
  /** Skip optimizing a file larger than this many MB (0/omitted = no limit). */
@@ -134,6 +137,7 @@ interface BaseTokenOptions {
134
137
  /** Paid-module claims (3-layer gate: code installed + licensed + this claim).
135
138
  * All default off; inert unless the module is installed & licensed on the server. */
136
139
  allowShare?: boolean;
140
+ allowIntake?: boolean;
137
141
  allowAiVision?: boolean;
138
142
  allowOcr?: boolean;
139
143
  allowVirusScan?: boolean;
@@ -171,6 +175,11 @@ interface BaseTokenOptions {
171
175
  usageTopFoldersCount?: number;
172
176
  /** Folder grouping depth for the usage breakdown. `0`/omitted = inherit (1). */
173
177
  usageFolderDepth?: number;
178
+ /** Generic escape hatch: any JWT claim by its raw snake_case name (e.g.
179
+ * `{ allow_terminal: true, terminal_pty_url: '…', upload_collision: 'overwrite' }`).
180
+ * Merged last so explicit claims win; the server sanitizes on decode. The single
181
+ * place to set claims that don't have a typed option here. See docs/CONFIG.md. */
182
+ claims?: Record<string, unknown>;
174
183
  }
175
184
  interface CreateTokenOptions extends BaseTokenOptions {
176
185
  /** Disk names the token may access. */
package/dist/index.d.ts CHANGED
@@ -100,17 +100,20 @@ interface BaseTokenOptions {
100
100
  * access as the SSH user, so it's opt-in. Core-standalone: the token must target a real core
101
101
  * (this SDK does), not a proxy adapter that doesn't serve /api/fm/terminal. */
102
102
  allowTerminal?: boolean;
103
- /** Allow the paid Optimization module (POST /api/fm/optimize). Default **false** opt-in.
104
- * Even when true, the optimize module must be installed + licensed on the server. */
103
+ /** Optional self-hosted PTY terminal URL (ttyd / gotty / wetty, or any PTY-over-WebSocket
104
+ * server the operator runs). When set (and `allowTerminal` is true), the UI embeds it for a
105
+ * true interactive terminal instead of the built-in command-runner. Free; must be http(s).
106
+ * Core-standalone only (like `allowTerminal`). */
107
+ terminalPtyUrl?: string;
108
+ /** Allow the Optimization feature (POST /api/fm/optimize) — recompress images to WebP +
109
+ * compress PDFs. **Free/core**; default **false** because it replaces/deletes originals, so
110
+ * it's an opt-in capability. */
105
111
  allowOptimize?: boolean;
106
- /** Auto-optimize images on upload (recompress to WebP in the pipeline). Default false;
107
- * only effective when the optimize module is installed + licensed on the server. */
112
+ /** Auto-optimize images on upload (recompress to WebP in the pipeline). Default false.
113
+ * Free/core. */
108
114
  autoOptimize?: boolean;
109
115
  /** WebP quality for optimization, 40–95. `0`/omitted = inherit the server default (82). */
110
116
  optimizeQuality?: number;
111
- /** Target format for optimization: `'webp'` (default) or `'avif'` (falls back to
112
- * WebP when the server build lacks AVIF). */
113
- optimizeFormat?: 'webp' | 'avif';
114
117
  /** Keep the original file when optimizing (default false = replace in place). */
115
118
  optimizeKeepOriginal?: boolean;
116
119
  /** Skip optimizing a file larger than this many MB (0/omitted = no limit). */
@@ -134,6 +137,7 @@ interface BaseTokenOptions {
134
137
  /** Paid-module claims (3-layer gate: code installed + licensed + this claim).
135
138
  * All default off; inert unless the module is installed & licensed on the server. */
136
139
  allowShare?: boolean;
140
+ allowIntake?: boolean;
137
141
  allowAiVision?: boolean;
138
142
  allowOcr?: boolean;
139
143
  allowVirusScan?: boolean;
@@ -171,6 +175,11 @@ interface BaseTokenOptions {
171
175
  usageTopFoldersCount?: number;
172
176
  /** Folder grouping depth for the usage breakdown. `0`/omitted = inherit (1). */
173
177
  usageFolderDepth?: number;
178
+ /** Generic escape hatch: any JWT claim by its raw snake_case name (e.g.
179
+ * `{ allow_terminal: true, terminal_pty_url: '…', upload_collision: 'overwrite' }`).
180
+ * Merged last so explicit claims win; the server sanitizes on decode. The single
181
+ * place to set claims that don't have a typed option here. See docs/CONFIG.md. */
182
+ claims?: Record<string, unknown>;
174
183
  }
175
184
  interface CreateTokenOptions extends BaseTokenOptions {
176
185
  /** Disk names the token may access. */
package/dist/index.js CHANGED
@@ -137,9 +137,9 @@ function sanitizeVariants(v) {
137
137
  return Object.keys(out).length ? out : null;
138
138
  }
139
139
  var EDITION_PRESETS = {
140
- pro: { allow_optimize: true, allow_share: true },
141
- agency: { allow_optimize: true, allow_share: true },
142
- enterprise: { allow_optimize: true, allow_share: true, allow_virus_scan: true }
140
+ pro: { allow_optimize: true, allow_share: true, allow_intake: true },
141
+ agency: { allow_optimize: true, allow_share: true, allow_intake: true },
142
+ enterprise: { allow_optimize: true, allow_share: true, allow_intake: true, allow_virus_scan: true, allow_c2pa: true }
143
143
  };
144
144
  function applyTenantOverrides(payload, opts) {
145
145
  const preset = opts.edition ? EDITION_PRESETS[String(opts.edition).toLowerCase()] : void 0;
@@ -172,8 +172,10 @@ function applyTenantOverrides(payload, opts) {
172
172
  if (opts.allowChmod !== void 0) payload.allow_chmod = !!opts.allowChmod;
173
173
  if (opts.allowCodeEdit !== void 0) payload.allow_code_edit = !!opts.allowCodeEdit;
174
174
  if (opts.allowTerminal !== void 0) payload.allow_terminal = !!opts.allowTerminal;
175
+ if (opts.terminalPtyUrl) payload.terminal_pty_url = String(opts.terminalPtyUrl);
175
176
  if (opts.allowOptimize !== void 0) payload.allow_optimize = !!opts.allowOptimize;
176
177
  if (opts.allowShare !== void 0) payload.allow_share = !!opts.allowShare;
178
+ if (opts.allowIntake !== void 0) payload.allow_intake = !!opts.allowIntake;
177
179
  if (opts.allowAiVision !== void 0) payload.allow_ai_vision = !!opts.allowAiVision;
178
180
  if (opts.allowOcr !== void 0) payload.allow_ocr = !!opts.allowOcr;
179
181
  if (opts.allowVirusScan !== void 0) payload.allow_virus_scan = !!opts.allowVirusScan;
@@ -181,7 +183,6 @@ function applyTenantOverrides(payload, opts) {
181
183
  if (opts.allowC2pa !== void 0) payload.allow_c2pa = !!opts.allowC2pa;
182
184
  if (opts.autoOptimize !== void 0) payload.auto_optimize = !!opts.autoOptimize;
183
185
  if (opts.optimizeQuality && opts.optimizeQuality > 0) payload.optimize_quality = Math.trunc(opts.optimizeQuality);
184
- if (opts.optimizeFormat === "avif") payload.optimize_format = "avif";
185
186
  if (opts.optimizeKeepOriginal !== void 0) payload.optimize_keep_original = !!opts.optimizeKeepOriginal;
186
187
  if (opts.optimizeMaxMb && opts.optimizeMaxMb > 0) payload.optimize_max_mb = Math.trunc(opts.optimizeMaxMb);
187
188
  if (opts.pdfLevel && ["screen", "ebook", "printer", "prepress", "default"].includes(opts.pdfLevel)) {
@@ -210,6 +211,9 @@ function applyTenantOverrides(payload, opts) {
210
211
  if (opts.usageCriticalThreshold && opts.usageCriticalThreshold > 0) payload.usage_critical_threshold = Math.trunc(opts.usageCriticalThreshold);
211
212
  if (opts.usageTopFoldersCount && opts.usageTopFoldersCount > 0) payload.usage_top_folders_count = Math.trunc(opts.usageTopFoldersCount);
212
213
  if (opts.usageFolderDepth && opts.usageFolderDepth > 0) payload.usage_folder_depth = Math.trunc(opts.usageFolderDepth);
214
+ if (opts.claims) {
215
+ for (const [k, v] of Object.entries(opts.claims)) if (v !== void 0 && v !== null) payload[k] = v;
216
+ }
213
217
  }
214
218
  function validateByobDisk(name, config) {
215
219
  if (!config || config.driver !== "s3" && config.driver !== "sftp") {
package/dist/index.mjs CHANGED
@@ -115,9 +115,9 @@ function sanitizeVariants(v) {
115
115
  return Object.keys(out).length ? out : null;
116
116
  }
117
117
  var EDITION_PRESETS = {
118
- pro: { allow_optimize: true, allow_share: true },
119
- agency: { allow_optimize: true, allow_share: true },
120
- enterprise: { allow_optimize: true, allow_share: true, allow_virus_scan: true }
118
+ pro: { allow_optimize: true, allow_share: true, allow_intake: true },
119
+ agency: { allow_optimize: true, allow_share: true, allow_intake: true },
120
+ enterprise: { allow_optimize: true, allow_share: true, allow_intake: true, allow_virus_scan: true, allow_c2pa: true }
121
121
  };
122
122
  function applyTenantOverrides(payload, opts) {
123
123
  const preset = opts.edition ? EDITION_PRESETS[String(opts.edition).toLowerCase()] : void 0;
@@ -150,8 +150,10 @@ function applyTenantOverrides(payload, opts) {
150
150
  if (opts.allowChmod !== void 0) payload.allow_chmod = !!opts.allowChmod;
151
151
  if (opts.allowCodeEdit !== void 0) payload.allow_code_edit = !!opts.allowCodeEdit;
152
152
  if (opts.allowTerminal !== void 0) payload.allow_terminal = !!opts.allowTerminal;
153
+ if (opts.terminalPtyUrl) payload.terminal_pty_url = String(opts.terminalPtyUrl);
153
154
  if (opts.allowOptimize !== void 0) payload.allow_optimize = !!opts.allowOptimize;
154
155
  if (opts.allowShare !== void 0) payload.allow_share = !!opts.allowShare;
156
+ if (opts.allowIntake !== void 0) payload.allow_intake = !!opts.allowIntake;
155
157
  if (opts.allowAiVision !== void 0) payload.allow_ai_vision = !!opts.allowAiVision;
156
158
  if (opts.allowOcr !== void 0) payload.allow_ocr = !!opts.allowOcr;
157
159
  if (opts.allowVirusScan !== void 0) payload.allow_virus_scan = !!opts.allowVirusScan;
@@ -159,7 +161,6 @@ function applyTenantOverrides(payload, opts) {
159
161
  if (opts.allowC2pa !== void 0) payload.allow_c2pa = !!opts.allowC2pa;
160
162
  if (opts.autoOptimize !== void 0) payload.auto_optimize = !!opts.autoOptimize;
161
163
  if (opts.optimizeQuality && opts.optimizeQuality > 0) payload.optimize_quality = Math.trunc(opts.optimizeQuality);
162
- if (opts.optimizeFormat === "avif") payload.optimize_format = "avif";
163
164
  if (opts.optimizeKeepOriginal !== void 0) payload.optimize_keep_original = !!opts.optimizeKeepOriginal;
164
165
  if (opts.optimizeMaxMb && opts.optimizeMaxMb > 0) payload.optimize_max_mb = Math.trunc(opts.optimizeMaxMb);
165
166
  if (opts.pdfLevel && ["screen", "ebook", "printer", "prepress", "default"].includes(opts.pdfLevel)) {
@@ -188,6 +189,9 @@ function applyTenantOverrides(payload, opts) {
188
189
  if (opts.usageCriticalThreshold && opts.usageCriticalThreshold > 0) payload.usage_critical_threshold = Math.trunc(opts.usageCriticalThreshold);
189
190
  if (opts.usageTopFoldersCount && opts.usageTopFoldersCount > 0) payload.usage_top_folders_count = Math.trunc(opts.usageTopFoldersCount);
190
191
  if (opts.usageFolderDepth && opts.usageFolderDepth > 0) payload.usage_folder_depth = Math.trunc(opts.usageFolderDepth);
192
+ if (opts.claims) {
193
+ for (const [k, v] of Object.entries(opts.claims)) if (v !== void 0 && v !== null) payload[k] = v;
194
+ }
191
195
  }
192
196
  function validateByobDisk(name, config) {
193
197
  if (!config || config.driver !== "s3" && config.driver !== "sftp") {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fluxfiles/node",
3
- "version": "0.1.17",
3
+ "version": "0.1.19",
4
4
  "description": "Server-side Node/TypeScript SDK for minting FluxFiles JWTs (plain + BYOB), byte-compatible with the PHP core",
5
5
  "license": "MIT",
6
6
  "sideEffects": false,
package/src/token.ts CHANGED
@@ -99,9 +99,9 @@ function sanitizeVariants(v: BaseTokenOptions['variants']): Record<string, numbe
99
99
 
100
100
  /** Edition preset (DX sugar): default a tier's claims; explicit opts below win. */
101
101
  const EDITION_PRESETS: Record<string, Record<string, boolean>> = {
102
- pro: { allow_optimize: true, allow_share: true },
103
- agency: { allow_optimize: true, allow_share: true },
104
- enterprise: { allow_optimize: true, allow_share: true, allow_virus_scan: true },
102
+ pro: { allow_optimize: true, allow_share: true, allow_intake: true },
103
+ agency: { allow_optimize: true, allow_share: true, allow_intake: true },
104
+ enterprise: { allow_optimize: true, allow_share: true, allow_intake: true, allow_virus_scan: true, allow_c2pa: true },
105
105
  };
106
106
 
107
107
  /** Copy the optional per-tenant override claims into a payload when set. */
@@ -144,9 +144,11 @@ function applyTenantOverrides(payload: Record<string, unknown>, opts: BaseTokenO
144
144
  // SSH terminal (SFTP disks). Core-standalone — the token must target a real core
145
145
  // (Node mints for one), not a proxy adapter that doesn't serve /api/fm/terminal.
146
146
  if (opts.allowTerminal !== undefined) payload.allow_terminal = !!opts.allowTerminal;
147
+ if (opts.terminalPtyUrl) payload.terminal_pty_url = String(opts.terminalPtyUrl);
147
148
  if (opts.allowOptimize !== undefined) payload.allow_optimize = !!opts.allowOptimize;
148
149
  // Other paid-module claims (inert unless the module is installed + licensed).
149
150
  if (opts.allowShare !== undefined) payload.allow_share = !!opts.allowShare;
151
+ if (opts.allowIntake !== undefined) payload.allow_intake = !!opts.allowIntake;
150
152
  if (opts.allowAiVision !== undefined) payload.allow_ai_vision = !!opts.allowAiVision;
151
153
  if (opts.allowOcr !== undefined) payload.allow_ocr = !!opts.allowOcr;
152
154
  if (opts.allowVirusScan !== undefined) payload.allow_virus_scan = !!opts.allowVirusScan;
@@ -154,7 +156,6 @@ function applyTenantOverrides(payload: Record<string, unknown>, opts: BaseTokenO
154
156
  if (opts.allowC2pa !== undefined) payload.allow_c2pa = !!opts.allowC2pa;
155
157
  if (opts.autoOptimize !== undefined) payload.auto_optimize = !!opts.autoOptimize;
156
158
  if (opts.optimizeQuality && opts.optimizeQuality > 0) payload.optimize_quality = Math.trunc(opts.optimizeQuality);
157
- if (opts.optimizeFormat === 'avif') payload.optimize_format = 'avif';
158
159
  if (opts.optimizeKeepOriginal !== undefined) payload.optimize_keep_original = !!opts.optimizeKeepOriginal;
159
160
  if (opts.optimizeMaxMb && opts.optimizeMaxMb > 0) payload.optimize_max_mb = Math.trunc(opts.optimizeMaxMb);
160
161
  if (opts.pdfLevel && ['screen', 'ebook', 'printer', 'prepress', 'default'].includes(opts.pdfLevel)) {
@@ -185,6 +186,11 @@ function applyTenantOverrides(payload: Record<string, unknown>, opts: BaseTokenO
185
186
  if (opts.usageCriticalThreshold && opts.usageCriticalThreshold > 0) payload.usage_critical_threshold = Math.trunc(opts.usageCriticalThreshold);
186
187
  if (opts.usageTopFoldersCount && opts.usageTopFoldersCount > 0) payload.usage_top_folders_count = Math.trunc(opts.usageTopFoldersCount);
187
188
  if (opts.usageFolderDepth && opts.usageFolderDepth > 0) payload.usage_folder_depth = Math.trunc(opts.usageFolderDepth);
189
+
190
+ // Generic escape hatch: ANY claim by its raw (snake_case) name. Merged last so an
191
+ // explicit claim wins over a preset/group default. The server sanitizes on decode.
192
+ // See docs/CONFIG.md for the full claim list.
193
+ if (opts.claims) for (const [k, v] of Object.entries(opts.claims)) if (v !== undefined && v !== null) payload[k] = v;
188
194
  }
189
195
 
190
196
  /**
package/src/types.ts CHANGED
@@ -104,17 +104,20 @@ export interface BaseTokenOptions {
104
104
  * access as the SSH user, so it's opt-in. Core-standalone: the token must target a real core
105
105
  * (this SDK does), not a proxy adapter that doesn't serve /api/fm/terminal. */
106
106
  allowTerminal?: boolean;
107
- /** Allow the paid Optimization module (POST /api/fm/optimize). Default **false** opt-in.
108
- * Even when true, the optimize module must be installed + licensed on the server. */
107
+ /** Optional self-hosted PTY terminal URL (ttyd / gotty / wetty, or any PTY-over-WebSocket
108
+ * server the operator runs). When set (and `allowTerminal` is true), the UI embeds it for a
109
+ * true interactive terminal instead of the built-in command-runner. Free; must be http(s).
110
+ * Core-standalone only (like `allowTerminal`). */
111
+ terminalPtyUrl?: string;
112
+ /** Allow the Optimization feature (POST /api/fm/optimize) — recompress images to WebP +
113
+ * compress PDFs. **Free/core**; default **false** because it replaces/deletes originals, so
114
+ * it's an opt-in capability. */
109
115
  allowOptimize?: boolean;
110
- /** Auto-optimize images on upload (recompress to WebP in the pipeline). Default false;
111
- * only effective when the optimize module is installed + licensed on the server. */
116
+ /** Auto-optimize images on upload (recompress to WebP in the pipeline). Default false.
117
+ * Free/core. */
112
118
  autoOptimize?: boolean;
113
119
  /** WebP quality for optimization, 40–95. `0`/omitted = inherit the server default (82). */
114
120
  optimizeQuality?: number;
115
- /** Target format for optimization: `'webp'` (default) or `'avif'` (falls back to
116
- * WebP when the server build lacks AVIF). */
117
- optimizeFormat?: 'webp' | 'avif';
118
121
  /** Keep the original file when optimizing (default false = replace in place). */
119
122
  optimizeKeepOriginal?: boolean;
120
123
  /** Skip optimizing a file larger than this many MB (0/omitted = no limit). */
@@ -138,6 +141,7 @@ export interface BaseTokenOptions {
138
141
  /** Paid-module claims (3-layer gate: code installed + licensed + this claim).
139
142
  * All default off; inert unless the module is installed & licensed on the server. */
140
143
  allowShare?: boolean;
144
+ allowIntake?: boolean;
141
145
  allowAiVision?: boolean;
142
146
  allowOcr?: boolean;
143
147
  allowVirusScan?: boolean;
@@ -175,6 +179,11 @@ export interface BaseTokenOptions {
175
179
  usageTopFoldersCount?: number;
176
180
  /** Folder grouping depth for the usage breakdown. `0`/omitted = inherit (1). */
177
181
  usageFolderDepth?: number;
182
+ /** Generic escape hatch: any JWT claim by its raw snake_case name (e.g.
183
+ * `{ allow_terminal: true, terminal_pty_url: '…', upload_collision: 'overwrite' }`).
184
+ * Merged last so explicit claims win; the server sanitizes on decode. The single
185
+ * place to set claims that don't have a typed option here. See docs/CONFIG.md. */
186
+ claims?: Record<string, unknown>;
178
187
  }
179
188
 
180
189
  export interface CreateTokenOptions extends BaseTokenOptions {