@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/dist/ai-tools.d.ts +1 -0
- package/dist/ai-tools.js +4 -1
- package/dist/docs/API_SUMMARY.md +75 -5
- package/dist/docs/ai.md +24 -5
- package/dist/docs/app-objects.md +33 -0
- package/dist/docs/server-functions.md +629 -628
- package/dist/openapi.yaml +112 -4
- package/dist/types/ai.d.ts +76 -2
- package/docs/API_SUMMARY.md +75 -5
- package/docs/ai.md +24 -5
- package/docs/app-objects.md +33 -0
- package/docs/server-functions.md +629 -628
- package/openapi.yaml +112 -4
- package/package.json +1 -1
- package/scripts/publish.mjs +147 -99
package/openapi.yaml
CHANGED
|
@@ -17123,12 +17123,42 @@ components:
|
|
|
17123
17123
|
type: number
|
|
17124
17124
|
required:
|
|
17125
17125
|
- imageUrl
|
|
17126
|
-
|
|
17126
|
+
ImageOcrArgs:
|
|
17127
17127
|
type: object
|
|
17128
17128
|
properties:
|
|
17129
|
-
|
|
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:
|
|
17164
|
-
|
|
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
package/scripts/publish.mjs
CHANGED
|
@@ -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)
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
walk(dir)
|
|
53
|
-
return out
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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
|
+
}
|