@fluxfiles/node 0.1.16 → 0.1.18

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
@@ -96,17 +96,24 @@ interface BaseTokenOptions {
96
96
  /** Allow editing a file's text content (GET/PUT /api/fm/content). Default **false** — editing
97
97
  * config/executable files (wp-config.php, .env, nginx.conf, deploy.sh) is powerful, so it's opt-in. */
98
98
  allowCodeEdit?: boolean;
99
- /** Allow the paid Optimization module (POST /api/fm/optimize). Default **false** — opt-in.
100
- * Even when true, the optimize module must be installed + licensed on the server. */
99
+ /** Allow the SSH terminal (POST /api/fm/terminal) on an SFTP disk. Default **false** — grants shell
100
+ * access as the SSH user, so it's opt-in. Core-standalone: the token must target a real core
101
+ * (this SDK does), not a proxy adapter that doesn't serve /api/fm/terminal. */
102
+ allowTerminal?: boolean;
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. */
101
111
  allowOptimize?: boolean;
102
- /** Auto-optimize images on upload (recompress to WebP in the pipeline). Default false;
103
- * 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. */
104
114
  autoOptimize?: boolean;
105
115
  /** WebP quality for optimization, 40–95. `0`/omitted = inherit the server default (82). */
106
116
  optimizeQuality?: number;
107
- /** Target format for optimization: `'webp'` (default) or `'avif'` (falls back to
108
- * WebP when the server build lacks AVIF). */
109
- optimizeFormat?: 'webp' | 'avif';
110
117
  /** Keep the original file when optimizing (default false = replace in place). */
111
118
  optimizeKeepOriginal?: boolean;
112
119
  /** Skip optimizing a file larger than this many MB (0/omitted = no limit). */
@@ -167,6 +174,11 @@ interface BaseTokenOptions {
167
174
  usageTopFoldersCount?: number;
168
175
  /** Folder grouping depth for the usage breakdown. `0`/omitted = inherit (1). */
169
176
  usageFolderDepth?: number;
177
+ /** Generic escape hatch: any JWT claim by its raw snake_case name (e.g.
178
+ * `{ allow_terminal: true, terminal_pty_url: '…', upload_collision: 'overwrite' }`).
179
+ * Merged last so explicit claims win; the server sanitizes on decode. The single
180
+ * place to set claims that don't have a typed option here. See docs/CONFIG.md. */
181
+ claims?: Record<string, unknown>;
170
182
  }
171
183
  interface CreateTokenOptions extends BaseTokenOptions {
172
184
  /** Disk names the token may access. */
package/dist/index.d.ts CHANGED
@@ -96,17 +96,24 @@ interface BaseTokenOptions {
96
96
  /** Allow editing a file's text content (GET/PUT /api/fm/content). Default **false** — editing
97
97
  * config/executable files (wp-config.php, .env, nginx.conf, deploy.sh) is powerful, so it's opt-in. */
98
98
  allowCodeEdit?: boolean;
99
- /** Allow the paid Optimization module (POST /api/fm/optimize). Default **false** — opt-in.
100
- * Even when true, the optimize module must be installed + licensed on the server. */
99
+ /** Allow the SSH terminal (POST /api/fm/terminal) on an SFTP disk. Default **false** — grants shell
100
+ * access as the SSH user, so it's opt-in. Core-standalone: the token must target a real core
101
+ * (this SDK does), not a proxy adapter that doesn't serve /api/fm/terminal. */
102
+ allowTerminal?: boolean;
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. */
101
111
  allowOptimize?: boolean;
102
- /** Auto-optimize images on upload (recompress to WebP in the pipeline). Default false;
103
- * 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. */
104
114
  autoOptimize?: boolean;
105
115
  /** WebP quality for optimization, 40–95. `0`/omitted = inherit the server default (82). */
106
116
  optimizeQuality?: number;
107
- /** Target format for optimization: `'webp'` (default) or `'avif'` (falls back to
108
- * WebP when the server build lacks AVIF). */
109
- optimizeFormat?: 'webp' | 'avif';
110
117
  /** Keep the original file when optimizing (default false = replace in place). */
111
118
  optimizeKeepOriginal?: boolean;
112
119
  /** Skip optimizing a file larger than this many MB (0/omitted = no limit). */
@@ -167,6 +174,11 @@ interface BaseTokenOptions {
167
174
  usageTopFoldersCount?: number;
168
175
  /** Folder grouping depth for the usage breakdown. `0`/omitted = inherit (1). */
169
176
  usageFolderDepth?: number;
177
+ /** Generic escape hatch: any JWT claim by its raw snake_case name (e.g.
178
+ * `{ allow_terminal: true, terminal_pty_url: '…', upload_collision: 'overwrite' }`).
179
+ * Merged last so explicit claims win; the server sanitizes on decode. The single
180
+ * place to set claims that don't have a typed option here. See docs/CONFIG.md. */
181
+ claims?: Record<string, unknown>;
170
182
  }
171
183
  interface CreateTokenOptions extends BaseTokenOptions {
172
184
  /** Disk names the token may access. */
package/dist/index.js CHANGED
@@ -171,6 +171,8 @@ function applyTenantOverrides(payload, opts) {
171
171
  if (opts.allowDownload !== void 0) payload.allow_download = !!opts.allowDownload;
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
+ if (opts.allowTerminal !== void 0) payload.allow_terminal = !!opts.allowTerminal;
175
+ if (opts.terminalPtyUrl) payload.terminal_pty_url = String(opts.terminalPtyUrl);
174
176
  if (opts.allowOptimize !== void 0) payload.allow_optimize = !!opts.allowOptimize;
175
177
  if (opts.allowShare !== void 0) payload.allow_share = !!opts.allowShare;
176
178
  if (opts.allowAiVision !== void 0) payload.allow_ai_vision = !!opts.allowAiVision;
@@ -180,7 +182,6 @@ function applyTenantOverrides(payload, opts) {
180
182
  if (opts.allowC2pa !== void 0) payload.allow_c2pa = !!opts.allowC2pa;
181
183
  if (opts.autoOptimize !== void 0) payload.auto_optimize = !!opts.autoOptimize;
182
184
  if (opts.optimizeQuality && opts.optimizeQuality > 0) payload.optimize_quality = Math.trunc(opts.optimizeQuality);
183
- if (opts.optimizeFormat === "avif") payload.optimize_format = "avif";
184
185
  if (opts.optimizeKeepOriginal !== void 0) payload.optimize_keep_original = !!opts.optimizeKeepOriginal;
185
186
  if (opts.optimizeMaxMb && opts.optimizeMaxMb > 0) payload.optimize_max_mb = Math.trunc(opts.optimizeMaxMb);
186
187
  if (opts.pdfLevel && ["screen", "ebook", "printer", "prepress", "default"].includes(opts.pdfLevel)) {
@@ -209,6 +210,9 @@ function applyTenantOverrides(payload, opts) {
209
210
  if (opts.usageCriticalThreshold && opts.usageCriticalThreshold > 0) payload.usage_critical_threshold = Math.trunc(opts.usageCriticalThreshold);
210
211
  if (opts.usageTopFoldersCount && opts.usageTopFoldersCount > 0) payload.usage_top_folders_count = Math.trunc(opts.usageTopFoldersCount);
211
212
  if (opts.usageFolderDepth && opts.usageFolderDepth > 0) payload.usage_folder_depth = Math.trunc(opts.usageFolderDepth);
213
+ if (opts.claims) {
214
+ for (const [k, v] of Object.entries(opts.claims)) if (v !== void 0 && v !== null) payload[k] = v;
215
+ }
212
216
  }
213
217
  function validateByobDisk(name, config) {
214
218
  if (!config || config.driver !== "s3" && config.driver !== "sftp") {
package/dist/index.mjs CHANGED
@@ -149,6 +149,8 @@ function applyTenantOverrides(payload, opts) {
149
149
  if (opts.allowDownload !== void 0) payload.allow_download = !!opts.allowDownload;
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
+ if (opts.allowTerminal !== void 0) payload.allow_terminal = !!opts.allowTerminal;
153
+ if (opts.terminalPtyUrl) payload.terminal_pty_url = String(opts.terminalPtyUrl);
152
154
  if (opts.allowOptimize !== void 0) payload.allow_optimize = !!opts.allowOptimize;
153
155
  if (opts.allowShare !== void 0) payload.allow_share = !!opts.allowShare;
154
156
  if (opts.allowAiVision !== void 0) payload.allow_ai_vision = !!opts.allowAiVision;
@@ -158,7 +160,6 @@ function applyTenantOverrides(payload, opts) {
158
160
  if (opts.allowC2pa !== void 0) payload.allow_c2pa = !!opts.allowC2pa;
159
161
  if (opts.autoOptimize !== void 0) payload.auto_optimize = !!opts.autoOptimize;
160
162
  if (opts.optimizeQuality && opts.optimizeQuality > 0) payload.optimize_quality = Math.trunc(opts.optimizeQuality);
161
- if (opts.optimizeFormat === "avif") payload.optimize_format = "avif";
162
163
  if (opts.optimizeKeepOriginal !== void 0) payload.optimize_keep_original = !!opts.optimizeKeepOriginal;
163
164
  if (opts.optimizeMaxMb && opts.optimizeMaxMb > 0) payload.optimize_max_mb = Math.trunc(opts.optimizeMaxMb);
164
165
  if (opts.pdfLevel && ["screen", "ebook", "printer", "prepress", "default"].includes(opts.pdfLevel)) {
@@ -187,6 +188,9 @@ function applyTenantOverrides(payload, opts) {
187
188
  if (opts.usageCriticalThreshold && opts.usageCriticalThreshold > 0) payload.usage_critical_threshold = Math.trunc(opts.usageCriticalThreshold);
188
189
  if (opts.usageTopFoldersCount && opts.usageTopFoldersCount > 0) payload.usage_top_folders_count = Math.trunc(opts.usageTopFoldersCount);
189
190
  if (opts.usageFolderDepth && opts.usageFolderDepth > 0) payload.usage_folder_depth = Math.trunc(opts.usageFolderDepth);
191
+ if (opts.claims) {
192
+ for (const [k, v] of Object.entries(opts.claims)) if (v !== void 0 && v !== null) payload[k] = v;
193
+ }
190
194
  }
191
195
  function validateByobDisk(name, config) {
192
196
  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.16",
3
+ "version": "0.1.18",
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
@@ -141,6 +141,10 @@ function applyTenantOverrides(payload: Record<string, unknown>, opts: BaseTokenO
141
141
  if (opts.allowDownload !== undefined) payload.allow_download = !!opts.allowDownload;
142
142
  if (opts.allowChmod !== undefined) payload.allow_chmod = !!opts.allowChmod;
143
143
  if (opts.allowCodeEdit !== undefined) payload.allow_code_edit = !!opts.allowCodeEdit;
144
+ // SSH terminal (SFTP disks). Core-standalone — the token must target a real core
145
+ // (Node mints for one), not a proxy adapter that doesn't serve /api/fm/terminal.
146
+ if (opts.allowTerminal !== undefined) payload.allow_terminal = !!opts.allowTerminal;
147
+ if (opts.terminalPtyUrl) payload.terminal_pty_url = String(opts.terminalPtyUrl);
144
148
  if (opts.allowOptimize !== undefined) payload.allow_optimize = !!opts.allowOptimize;
145
149
  // Other paid-module claims (inert unless the module is installed + licensed).
146
150
  if (opts.allowShare !== undefined) payload.allow_share = !!opts.allowShare;
@@ -151,7 +155,6 @@ function applyTenantOverrides(payload: Record<string, unknown>, opts: BaseTokenO
151
155
  if (opts.allowC2pa !== undefined) payload.allow_c2pa = !!opts.allowC2pa;
152
156
  if (opts.autoOptimize !== undefined) payload.auto_optimize = !!opts.autoOptimize;
153
157
  if (opts.optimizeQuality && opts.optimizeQuality > 0) payload.optimize_quality = Math.trunc(opts.optimizeQuality);
154
- if (opts.optimizeFormat === 'avif') payload.optimize_format = 'avif';
155
158
  if (opts.optimizeKeepOriginal !== undefined) payload.optimize_keep_original = !!opts.optimizeKeepOriginal;
156
159
  if (opts.optimizeMaxMb && opts.optimizeMaxMb > 0) payload.optimize_max_mb = Math.trunc(opts.optimizeMaxMb);
157
160
  if (opts.pdfLevel && ['screen', 'ebook', 'printer', 'prepress', 'default'].includes(opts.pdfLevel)) {
@@ -182,6 +185,11 @@ function applyTenantOverrides(payload: Record<string, unknown>, opts: BaseTokenO
182
185
  if (opts.usageCriticalThreshold && opts.usageCriticalThreshold > 0) payload.usage_critical_threshold = Math.trunc(opts.usageCriticalThreshold);
183
186
  if (opts.usageTopFoldersCount && opts.usageTopFoldersCount > 0) payload.usage_top_folders_count = Math.trunc(opts.usageTopFoldersCount);
184
187
  if (opts.usageFolderDepth && opts.usageFolderDepth > 0) payload.usage_folder_depth = Math.trunc(opts.usageFolderDepth);
188
+
189
+ // Generic escape hatch: ANY claim by its raw (snake_case) name. Merged last so an
190
+ // explicit claim wins over a preset/group default. The server sanitizes on decode.
191
+ // See docs/CONFIG.md for the full claim list.
192
+ if (opts.claims) for (const [k, v] of Object.entries(opts.claims)) if (v !== undefined && v !== null) payload[k] = v;
185
193
  }
186
194
 
187
195
  /**
package/src/types.ts CHANGED
@@ -100,17 +100,24 @@ export interface BaseTokenOptions {
100
100
  /** Allow editing a file's text content (GET/PUT /api/fm/content). Default **false** — editing
101
101
  * config/executable files (wp-config.php, .env, nginx.conf, deploy.sh) is powerful, so it's opt-in. */
102
102
  allowCodeEdit?: 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
+ /** Allow the SSH terminal (POST /api/fm/terminal) on an SFTP disk. Default **false** — grants shell
104
+ * access as the SSH user, so it's opt-in. Core-standalone: the token must target a real core
105
+ * (this SDK does), not a proxy adapter that doesn't serve /api/fm/terminal. */
106
+ allowTerminal?: boolean;
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. */
105
115
  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. */
116
+ /** Auto-optimize images on upload (recompress to WebP in the pipeline). Default false.
117
+ * Free/core. */
108
118
  autoOptimize?: boolean;
109
119
  /** WebP quality for optimization, 40–95. `0`/omitted = inherit the server default (82). */
110
120
  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
121
  /** Keep the original file when optimizing (default false = replace in place). */
115
122
  optimizeKeepOriginal?: boolean;
116
123
  /** Skip optimizing a file larger than this many MB (0/omitted = no limit). */
@@ -171,6 +178,11 @@ export interface BaseTokenOptions {
171
178
  usageTopFoldersCount?: number;
172
179
  /** Folder grouping depth for the usage breakdown. `0`/omitted = inherit (1). */
173
180
  usageFolderDepth?: number;
181
+ /** Generic escape hatch: any JWT claim by its raw snake_case name (e.g.
182
+ * `{ allow_terminal: true, terminal_pty_url: '…', upload_collision: 'overwrite' }`).
183
+ * Merged last so explicit claims win; the server sanitizes on decode. The single
184
+ * place to set claims that don't have a typed option here. See docs/CONFIG.md. */
185
+ claims?: Record<string, unknown>;
174
186
  }
175
187
 
176
188
  export interface CreateTokenOptions extends BaseTokenOptions {