@proveanything/smartlinks 2.0.32 → 2.0.34

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/openapi.yaml CHANGED
@@ -17123,12 +17123,42 @@ components:
17123
17123
  type: number
17124
17124
  required:
17125
17125
  - imageUrl
17126
- PdfCreateArgs:
17126
+ ImageOcrArgs:
17127
17127
  type: object
17128
17128
  properties:
17129
- html:
17129
+ imageUrl:
17130
+ type: string
17131
+ url:
17132
+ type: string
17133
+ page:
17134
+ type: number
17135
+ dpi:
17136
+ type: number
17137
+ clip:
17138
+ $ref: "#/components/schemas/PdfClip"
17139
+ languages:
17140
+ type: array
17141
+ items:
17142
+ type: string
17143
+ PdfClip:
17144
+ type: object
17145
+ properties:
17146
+ x0:
17130
17147
  type: object
17131
17148
  additionalProperties: true
17149
+ required:
17150
+ - x0
17151
+ PdfCreateArgs:
17152
+ type: object
17153
+ properties:
17154
+ html:
17155
+ type: string
17156
+ format:
17157
+ type: string
17158
+ landscape:
17159
+ type: boolean
17160
+ margin:
17161
+ type: string
17132
17162
  required:
17133
17163
  - html
17134
17164
  PdfFillArgs:
@@ -17160,8 +17190,13 @@ components:
17160
17190
  type: object
17161
17191
  properties:
17162
17192
  url:
17163
- type: object
17164
- additionalProperties: true
17193
+ type: string
17194
+ page:
17195
+ type: number
17196
+ dpi:
17197
+ type: number
17198
+ clip:
17199
+ $ref: "#/components/schemas/PdfClip"
17165
17200
  required:
17166
17201
  - url
17167
17202
  PdfExtractArgs:
@@ -17237,6 +17272,8 @@ components:
17237
17272
  $ref: "#/components/schemas/ImageSearchStockArgs"
17238
17273
  image.transform:
17239
17274
  $ref: "#/components/schemas/ImageTransformArgs"
17275
+ image.ocr:
17276
+ $ref: "#/components/schemas/ImageOcrArgs"
17240
17277
  pdf.create:
17241
17278
  $ref: "#/components/schemas/PdfCreateArgs"
17242
17279
  pdf.fill:
@@ -17270,6 +17307,7 @@ components:
17270
17307
  - image.fromReference
17271
17308
  - image.searchStock
17272
17309
  - image.transform
17310
+ - image.ocr
17273
17311
  - pdf.create
17274
17312
  - pdf.fill
17275
17313
  - pdf.merge
@@ -17386,6 +17424,75 @@ components:
17386
17424
  additionalProperties: true
17387
17425
  required:
17388
17426
  - url
17427
+ PdfRenderTooLarge:
17428
+ type: object
17429
+ properties:
17430
+ error:
17431
+ type: object
17432
+ additionalProperties: true
17433
+ required:
17434
+ - error
17435
+ OcrBox:
17436
+ type: object
17437
+ properties:
17438
+ x:
17439
+ type: object
17440
+ additionalProperties: true
17441
+ required:
17442
+ - x
17443
+ OcrWord:
17444
+ type: object
17445
+ properties:
17446
+ text:
17447
+ type: object
17448
+ additionalProperties: true
17449
+ required:
17450
+ - text
17451
+ OcrLine:
17452
+ type: object
17453
+ properties:
17454
+ text:
17455
+ type: object
17456
+ additionalProperties: true
17457
+ required:
17458
+ - text
17459
+ ImageOcrResult:
17460
+ type: object
17461
+ properties:
17462
+ words:
17463
+ type: array
17464
+ items:
17465
+ $ref: "#/components/schemas/OcrWord"
17466
+ lines:
17467
+ type: array
17468
+ items:
17469
+ $ref: "#/components/schemas/OcrLine"
17470
+ text:
17471
+ type: string
17472
+ width:
17473
+ type: number
17474
+ height:
17475
+ type: number
17476
+ languages:
17477
+ type: array
17478
+ items:
17479
+ type: string
17480
+ page:
17481
+ type: number
17482
+ dpi:
17483
+ type: number
17484
+ clip:
17485
+ $ref: "#/components/schemas/PdfClip"
17486
+ engine:
17487
+ type: string
17488
+ required:
17489
+ - words
17490
+ - lines
17491
+ - text
17492
+ - width
17493
+ - height
17494
+ - languages
17495
+ - engine
17389
17496
  PdfFieldMeta:
17390
17497
  type: object
17391
17498
  properties:
@@ -17513,6 +17620,7 @@ components:
17513
17620
  - image.fromReference
17514
17621
  - image.searchStock
17515
17622
  - image.transform
17623
+ - image.ocr
17516
17624
  - pdf.create
17517
17625
  - pdf.fill
17518
17626
  - pdf.merge
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@proveanything/smartlinks",
3
- "version": "2.0.32",
3
+ "version": "2.0.34",
4
4
  "description": "Official JavaScript/TypeScript SDK for the Smartlinks API",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -1,99 +1,147 @@
1
- #!/usr/bin/env node
2
- /**
3
- * publish — the FAST dev publish for a SmartLinks app. Builds nothing itself: it takes an already-
4
- * built `dist/`, uploads it to the SmartLinks publish endpoint, which writes it straight to the CDN
5
- * (dev = no-store, instantly fresh) and registers the release. One call = host + register, no Cloud
6
- * Build, no Lovable. Works from anywhere: local, Claude, CI, or a build step.
7
- *
8
- * smartlinks-publish # build dist/ yourself first, then this
9
- * smartlinks-publish --watch # re-publish on file change (save → live)
10
- *
11
- * Env:
12
- * SMARTLINKS_APP_ID the platform app id (required)
13
- * SMARTLINKS_DEPLOY_KEY channel-scoped deploy key (dev key is dev-only, safe to keep locally)
14
- * SMARTLINKS_API API host (default https://smartlinks.app)
15
- * SMARTLINKS_CHANNEL channel (default dev; only dev is supported by this fast path today)
16
- * Flags: --dir <dist> --watch --channel <dev>
17
- */
18
-
19
- import { readFileSync, readdirSync, statSync, watch } from 'node:fs'
20
- import { join, relative, sep } from 'node:path'
21
-
22
- const args = process.argv.slice(2)
23
- const flag = (name, def) => { const i = args.indexOf(name); return i >= 0 ? (args[i + 1] && !args[i + 1].startsWith('--') ? args[i + 1] : true) : def }
24
-
25
- const API = process.env.SMARTLINKS_API || 'https://smartlinks.app'
26
- const KEY = process.env.SMARTLINKS_DEPLOY_KEY
27
- const APP_ID = process.env.SMARTLINKS_APP_ID
28
- const CHANNEL = flag('--channel', process.env.SMARTLINKS_CHANNEL || 'dev')
29
- const DIR = flag('--dir', 'dist')
30
- const WATCH = args.includes('--watch')
31
-
32
- // Mirror of the server's write allowlist — a bundle is static web assets only.
33
- const ALLOWED = new Set(['.js', '.mjs', '.css', '.json', '.map', '.wasm', '.html', '.txt',
34
- '.svg', '.png', '.jpg', '.jpeg', '.gif', '.webp', '.avif', '.ico', '.woff', '.woff2', '.ttf', '.otf'])
35
- const extOf = (p) => { const i = p.lastIndexOf('.'); return i < 0 ? '' : p.slice(i).toLowerCase() }
36
-
37
- function fail(msg) { console.error(`❌ ${msg}`); process.exit(1) }
38
- if (!APP_ID) fail('SMARTLINKS_APP_ID is required')
39
- if (!KEY) fail('SMARTLINKS_DEPLOY_KEY is required')
40
-
41
- function collectFiles(dir) {
42
- const out = {}
43
- const walk = (d) => {
44
- for (const e of readdirSync(d, { withFileTypes: true })) {
45
- const full = join(d, e.name)
46
- if (e.isDirectory()) { walk(full); continue }
47
- if (!ALLOWED.has(extOf(e.name))) continue
48
- const rel = relative(dir, full).split(sep).join('/') // forward slashes for the bucket
49
- out[rel] = readFileSync(full).toString('base64')
50
- }
51
- }
52
- walk(dir)
53
- return out
54
- }
55
-
56
- async function publishOnce() {
57
- let stat
58
- try { stat = statSync(DIR) } catch { fail(`build dir not found: ${DIR} (build first)`) }
59
- if (!stat.isDirectory()) fail(`${DIR} is not a directory`)
60
-
61
- const files = collectFiles(DIR)
62
- if (!files['app.manifest.json']) fail(`${DIR}/app.manifest.json not found — is this a built app bundle?`)
63
-
64
- const t0 = Date.now()
65
- const res = await fetch(`${API}/api/v1/apps/${APP_ID}/publish?channel=${encodeURIComponent(CHANNEL)}`, {
66
- method: 'POST',
67
- headers: { 'content-type': 'application/json', 'x-smartlinks-deploy-key': KEY },
68
- body: JSON.stringify({ files }),
69
- }).catch((e) => fail(`request failed — ${e.message}`))
70
-
71
- const b = await res.json().catch(() => ({}))
72
- if (!res.ok || !b.ok) {
73
- console.error(`❌ publish failed (${res.status}):`)
74
- for (const e of b.errors || []) console.error(` • ${e.path || ''}: ${e.message}`)
75
- if (WATCH) return false // keep watching; don't exit
76
- process.exit(1)
77
- }
78
- for (const w of b.warnings || []) console.warn(` ⚠︎ ${w.path}: ${w.message}`)
79
- console.log(`✅ ${APP_ID}@${b.version} → ${CHANNEL} (${Object.keys(files).length} files, ${Date.now() - t0}ms) — functions: ${(b.functions || []).join(', ') || 'none'}`)
80
- console.log(` ${b.bundleBaseUrl}`)
81
- return true
82
- }
83
-
84
- if (!WATCH) {
85
- await publishOnce()
86
- } else {
87
- console.log(`👀 watching ${DIR} — publishing ${APP_ID} to ${CHANNEL} on change (Ctrl+C to stop)`)
88
- await publishOnce()
89
- let timer = null
90
- let publishing = false
91
- watch(DIR, { recursive: true }, () => {
92
- clearTimeout(timer)
93
- timer = setTimeout(async () => {
94
- if (publishing) return
95
- publishing = true
96
- try { await publishOnce() } finally { publishing = false }
97
- }, 300) // debounce a burst of file writes from one build
98
- })
99
- }
1
+ #!/usr/bin/env node
2
+ /**
3
+ * publish — the FAST dev publish for a SmartLinks app. Builds nothing itself: it takes an already-
4
+ * built `dist/`, uploads it to the SmartLinks publish endpoint, which writes it straight to the CDN
5
+ * (dev = no-store, instantly fresh) and registers the release. One call = host + register, no Cloud
6
+ * Build, no Lovable. Works from anywhere: local, Claude, CI, or a build step.
7
+ *
8
+ * smartlinks-publish # build dist/ yourself first, then this
9
+ * smartlinks-publish --watch # re-publish on file change (save → live)
10
+ *
11
+ * Env:
12
+ * SMARTLINKS_APP_ID the platform app id (required)
13
+ * SMARTLINKS_DEPLOY_KEY channel-scoped deploy key (dev key is dev-only, safe to keep locally)
14
+ * SMARTLINKS_API API host (default https://smartlinks.app)
15
+ * SMARTLINKS_CHANNEL channel (default dev; only dev is supported by this fast path today)
16
+ * Flags: --dir <dist> --watch --channel <dev>
17
+ */
18
+
19
+ import { readFileSync, readdirSync, statSync, watch } from 'node:fs'
20
+ import { join, relative, sep } from 'node:path'
21
+
22
+ const args = process.argv.slice(2)
23
+ const flag = (name, def) => { const i = args.indexOf(name); return i >= 0 ? (args[i + 1] && !args[i + 1].startsWith('--') ? args[i + 1] : true) : def }
24
+
25
+ const API = process.env.SMARTLINKS_API || 'https://smartlinks.app'
26
+ const KEY = process.env.SMARTLINKS_DEPLOY_KEY
27
+ const APP_ID = process.env.SMARTLINKS_APP_ID
28
+ const CHANNEL = flag('--channel', process.env.SMARTLINKS_CHANNEL || 'dev')
29
+ const DIR = flag('--dir', 'dist')
30
+ const WATCH = args.includes('--watch')
31
+
32
+ // Mirror of the server's write allowlist — a bundle is static web assets only.
33
+ const ALLOWED = new Set(['.js', '.mjs', '.css', '.json', '.map', '.wasm', '.html', '.txt',
34
+ '.svg', '.png', '.jpg', '.jpeg', '.gif', '.webp', '.avif', '.ico', '.woff', '.woff2', '.ttf', '.otf'])
35
+ const extOf = (p) => { const i = p.lastIndexOf('.'); return i < 0 ? '' : p.slice(i).toLowerCase() }
36
+
37
+ function fail(msg) { console.error(`❌ ${msg}`); process.exit(1) }
38
+ if (!APP_ID) fail('SMARTLINKS_APP_ID is required')
39
+ if (!KEY) fail('SMARTLINKS_DEPLOY_KEY is required')
40
+
41
+ function collectFiles(dir) {
42
+ const out = {}
43
+ const walk = (d) => {
44
+ for (const e of readdirSync(d, { withFileTypes: true })) {
45
+ const full = join(d, e.name)
46
+ if (e.isDirectory()) { walk(full); continue }
47
+ if (!ALLOWED.has(extOf(e.name))) continue
48
+ const rel = relative(dir, full).split(sep).join('/') // forward slashes for the bucket
49
+ out[rel] = readFileSync(full)
50
+ }
51
+ }
52
+ walk(dir)
53
+ return out
54
+ }
55
+
56
+ // One request carries at most this much file content (decoded). The server caps a request at 8 MB of
57
+ // content and ~10 MB of JSON; base64 adds a third, so 6 MB keeps every request comfortably under both.
58
+ const BATCH_BYTES = 6 * 1024 * 1024
59
+ const BATCH_FILES = 150
60
+
61
+ async function post(path, body) {
62
+ const res = await fetch(`${API}/api/v1/apps/${APP_ID}${path}`, {
63
+ method: 'POST',
64
+ headers: { 'content-type': 'application/json', 'x-smartlinks-deploy-key': KEY },
65
+ body: JSON.stringify(body),
66
+ }).catch((e) => ({ ok: false, status: 0, json: async () => ({ errors: [{ message: `request failed — ${e.message}` }] }) }))
67
+ const b = await res.json().catch(() => ({}))
68
+ return { res, b }
69
+ }
70
+
71
+ function reportFailure(res, b) {
72
+ console.error(`❌ publish failed (${res.status}):`)
73
+ for (const e of b.errors || []) console.error(` • ${e.path || ''}: ${e.message}`)
74
+ if (!b.errors && (b.message || b.error)) console.error(` • ${b.message || b.error}`)
75
+ }
76
+
77
+ const toB64 = (entries) => Object.fromEntries(entries.map(([p, buf]) => [p, buf.toString('base64')]))
78
+
79
+ async function publishOnce() {
80
+ let stat
81
+ try { stat = statSync(DIR) } catch { fail(`build dir not found: ${DIR} (build first)`) }
82
+ if (!stat.isDirectory()) fail(`${DIR} is not a directory`)
83
+
84
+ const files = collectFiles(DIR)
85
+ if (!files['app.manifest.json']) fail(`${DIR}/app.manifest.json not found — is this a built app bundle?`)
86
+
87
+ const t0 = Date.now()
88
+ const entries = Object.entries(files)
89
+ const totalBytes = entries.reduce((n, [, buf]) => n + buf.length, 0)
90
+ const qs = `?channel=${encodeURIComponent(CHANNEL)}`
91
+ let finalBody
92
+
93
+ if (totalBytes <= BATCH_BYTES && entries.length <= BATCH_FILES) {
94
+ finalBody = { files: toB64(entries) } // small bundle: one request, as before
95
+ } else {
96
+ // Large bundle: upload everything except the manifest in batches, then send the manifest last —
97
+ // the server only registers the release once every referenced file is confirmed uploaded.
98
+ const rest = entries.filter(([p]) => p !== 'app.manifest.json').sort((a, b) => b[1].length - a[1].length)
99
+ const batches = []
100
+ let cur = [], curBytes = 0
101
+ for (const e of rest) {
102
+ if (cur.length && (curBytes + e[1].length > BATCH_BYTES || cur.length >= BATCH_FILES)) { batches.push(cur); cur = []; curBytes = 0 }
103
+ if (e[1].length > BATCH_BYTES) console.warn(` ⚠︎ ${e[0]} is ${(e[1].length / 1048576).toFixed(1)} MB — larger than one request allows; the server will likely reject it`)
104
+ cur.push(e); curBytes += e[1].length
105
+ }
106
+ if (cur.length) batches.push(cur)
107
+ console.log(`📦 ${APP_ID}: ${(totalBytes / 1048576).toFixed(1)} MB in ${entries.length} files — uploading in ${batches.length} batch(es)`)
108
+ for (let i = 0; i < batches.length; i++) {
109
+ const { res, b } = await post(`/publish/files${qs}`, { files: toB64(batches[i]) })
110
+ if (!res.ok || !b.ok) {
111
+ reportFailure(res, b)
112
+ if (WATCH) return false
113
+ process.exit(1)
114
+ }
115
+ console.log(` ↑ batch ${i + 1}/${batches.length} (${batches[i].length} files)`)
116
+ }
117
+ finalBody = { files: toB64([['app.manifest.json', files['app.manifest.json']]]), uploaded: rest.map(([p]) => p) }
118
+ }
119
+
120
+ const { res, b } = await post(`/publish${qs}`, finalBody)
121
+ if (!res.ok || !b.ok) {
122
+ reportFailure(res, b)
123
+ if (WATCH) return false // keep watching; don't exit
124
+ process.exit(1)
125
+ }
126
+ for (const w of b.warnings || []) console.warn(` ⚠︎ ${w.path}: ${w.message}`)
127
+ console.log(`✅ ${APP_ID}@${b.version} → ${CHANNEL} (${entries.length} files, ${Date.now() - t0}ms) — functions: ${(b.functions || []).join(', ') || 'none'}`)
128
+ console.log(` ${b.bundleBaseUrl}`)
129
+ return true
130
+ }
131
+
132
+ if (!WATCH) {
133
+ await publishOnce()
134
+ } else {
135
+ console.log(`👀 watching ${DIR} — publishing ${APP_ID} to ${CHANNEL} on change (Ctrl+C to stop)`)
136
+ await publishOnce()
137
+ let timer = null
138
+ let publishing = false
139
+ watch(DIR, { recursive: true }, () => {
140
+ clearTimeout(timer)
141
+ timer = setTimeout(async () => {
142
+ if (publishing) return
143
+ publishing = true
144
+ try { await publishOnce() } finally { publishing = false }
145
+ }, 300) // debounce a burst of file writes from one build
146
+ })
147
+ }