@proveanything/smartlinks 2.0.33 → 2.0.35

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
@@ -74,6 +74,8 @@ tags:
74
74
  description: Product facet querying and aggregation.
75
75
  - name: form
76
76
  description: Form definitions and submissions.
77
+ - name: functions
78
+ description: functions API
77
79
  - name: integrations
78
80
  description: Third-party integration flows and imports.
79
81
  - name: interactions
@@ -10022,6 +10024,26 @@ paths:
10022
10024
  description: Unauthorized
10023
10025
  404:
10024
10026
  description: Not found
10027
+ /functions:
10028
+ get:
10029
+ tags:
10030
+ - functions
10031
+ summary: List the public functions available for a collection (discovery).
10032
+ operationId: functions_list
10033
+ security: []
10034
+ responses:
10035
+ 200:
10036
+ description: Success
10037
+ content:
10038
+ application/json:
10039
+ schema:
10040
+ $ref: "#/components/schemas/FunctionListResponse"
10041
+ 400:
10042
+ description: Bad request
10043
+ 401:
10044
+ description: Unauthorized
10045
+ 404:
10046
+ description: Not found
10025
10047
  /platform/location:
10026
10048
  post:
10027
10049
  tags:
@@ -17223,6 +17245,68 @@ components:
17223
17245
  additionalProperties: true
17224
17246
  required:
17225
17247
  - url
17248
+ PdfBox:
17249
+ type: object
17250
+ properties:
17251
+ x:
17252
+ type: object
17253
+ additionalProperties: true
17254
+ required:
17255
+ - x
17256
+ PdfEditArgs:
17257
+ type: object
17258
+ properties:
17259
+ url:
17260
+ type: string
17261
+ replaceText:
17262
+ type: array
17263
+ items:
17264
+ type: object
17265
+ additionalProperties: true
17266
+ addText:
17267
+ type: array
17268
+ items:
17269
+ type: object
17270
+ additionalProperties: true
17271
+ addImages:
17272
+ type: array
17273
+ items:
17274
+ type: object
17275
+ additionalProperties: true
17276
+ preserveColour:
17277
+ type: boolean
17278
+ required:
17279
+ - url
17280
+ PdfPreflightArgs:
17281
+ type: object
17282
+ properties:
17283
+ url:
17284
+ type: object
17285
+ additionalProperties: true
17286
+ required:
17287
+ - url
17288
+ PdfPrintReadyArgs:
17289
+ type: object
17290
+ properties:
17291
+ url:
17292
+ type: string
17293
+ profile:
17294
+ $ref: "#/components/schemas/PdfXProfile"
17295
+ outputIntent:
17296
+ type: string
17297
+ convertToCmyk:
17298
+ type: boolean
17299
+ embedFonts:
17300
+ type: boolean
17301
+ bleed:
17302
+ type: object
17303
+ additionalProperties: true
17304
+ flattenTransparency:
17305
+ type: boolean
17306
+ minDpi:
17307
+ type: number
17308
+ required:
17309
+ - url
17226
17310
  HttpRequestArgs:
17227
17311
  type: object
17228
17312
  properties:
@@ -17290,6 +17374,12 @@ components:
17290
17374
  $ref: "#/components/schemas/PdfDecodeBarcodesArgs"
17291
17375
  pdf.inspectGraphics:
17292
17376
  $ref: "#/components/schemas/PdfInspectGraphicsArgs"
17377
+ pdf.edit:
17378
+ $ref: "#/components/schemas/PdfEditArgs"
17379
+ pdf.preflight:
17380
+ $ref: "#/components/schemas/PdfPreflightArgs"
17381
+ pdf.printReady:
17382
+ $ref: "#/components/schemas/PdfPrintReadyArgs"
17293
17383
  http.request:
17294
17384
  $ref: "#/components/schemas/HttpRequestArgs"
17295
17385
  translate:
@@ -17316,6 +17406,9 @@ components:
17316
17406
  - pdf.extract
17317
17407
  - pdf.decodeBarcodes
17318
17408
  - pdf.inspectGraphics
17409
+ - pdf.edit
17410
+ - pdf.preflight
17411
+ - pdf.printReady
17319
17412
  - http.request
17320
17413
  - translate
17321
17414
  WebSearchResultItem:
@@ -17545,6 +17638,139 @@ components:
17545
17638
  additionalProperties: true
17546
17639
  required:
17547
17640
  - url
17641
+ PdfEditItemResult:
17642
+ type: object
17643
+ properties:
17644
+ id:
17645
+ type: string
17646
+ status:
17647
+ type: string
17648
+ enum:
17649
+ - done
17650
+ - failed
17651
+ note:
17652
+ type: string
17653
+ verified:
17654
+ type: boolean
17655
+ usedFallbackFont:
17656
+ type: boolean
17657
+ required:
17658
+ - id
17659
+ - status
17660
+ PdfEditResult:
17661
+ type: object
17662
+ properties:
17663
+ url:
17664
+ type: array
17665
+ items:
17666
+ type: object
17667
+ additionalProperties: true
17668
+ required:
17669
+ - url
17670
+ PdfPreflightFinding:
17671
+ type: object
17672
+ properties:
17673
+ severity:
17674
+ type: string
17675
+ enum:
17676
+ - error
17677
+ - warning
17678
+ required:
17679
+ - severity
17680
+ PdfPreflightResult:
17681
+ type: object
17682
+ properties:
17683
+ url:
17684
+ type: string
17685
+ compliant:
17686
+ type: boolean
17687
+ profile:
17688
+ $ref: "#/components/schemas/PdfXProfile"
17689
+ version:
17690
+ type: string
17691
+ findings:
17692
+ type: array
17693
+ items:
17694
+ $ref: "#/components/schemas/PdfPreflightFinding"
17695
+ stats:
17696
+ type: object
17697
+ additionalProperties: true
17698
+ pageCount:
17699
+ type: number
17700
+ fonts:
17701
+ type: array
17702
+ items:
17703
+ type: object
17704
+ additionalProperties: true
17705
+ spotColours:
17706
+ type: array
17707
+ items:
17708
+ type: string
17709
+ images:
17710
+ type: array
17711
+ items:
17712
+ type: object
17713
+ additionalProperties: true
17714
+ transparency:
17715
+ type: boolean
17716
+ outputIntent:
17717
+ type: object
17718
+ additionalProperties: true
17719
+ required:
17720
+ - url
17721
+ - compliant
17722
+ - profile
17723
+ - version
17724
+ - findings
17725
+ - stats
17726
+ - pageCount
17727
+ - fonts
17728
+ - spotColours
17729
+ - images
17730
+ - transparency
17731
+ - outputIntent
17732
+ PdfPrintReadyResult:
17733
+ type: object
17734
+ properties:
17735
+ url:
17736
+ type: string
17737
+ report:
17738
+ type: object
17739
+ additionalProperties: true
17740
+ compliant:
17741
+ type: boolean
17742
+ profile:
17743
+ $ref: "#/components/schemas/PdfXProfile"
17744
+ outputIntent:
17745
+ type: string
17746
+ findings:
17747
+ type: array
17748
+ items:
17749
+ $ref: "#/components/schemas/PdfPreflightFinding"
17750
+ changes:
17751
+ type: array
17752
+ items:
17753
+ type: object
17754
+ additionalProperties: true
17755
+ before:
17756
+ type: object
17757
+ additionalProperties: true
17758
+ stats:
17759
+ type: object
17760
+ additionalProperties: true
17761
+ note:
17762
+ type: string
17763
+ required:
17764
+ - url
17765
+ - report
17766
+ - compliant
17767
+ - profile
17768
+ - outputIntent
17769
+ - findings
17770
+ - changes
17771
+ - before
17772
+ - stats
17773
+ - note
17548
17774
  ResponsesAgentTrace:
17549
17775
  type: object
17550
17776
  properties:
@@ -17629,8 +17855,19 @@ components:
17629
17855
  - pdf.extract
17630
17856
  - pdf.decodeBarcodes
17631
17857
  - pdf.inspectGraphics
17858
+ - pdf.edit
17859
+ - pdf.preflight
17860
+ - pdf.printReady
17632
17861
  - http.request
17633
17862
  - translate
17863
+ PdfColour:
17864
+ type: object
17865
+ additionalProperties: true
17866
+ PdfXProfile:
17867
+ type: string
17868
+ enum:
17869
+ - pdfx-1a
17870
+ - pdfx-4
17634
17871
  AgentStreamEvent:
17635
17872
  type: object
17636
17873
  additionalProperties: true
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@proveanything/smartlinks",
3
- "version": "2.0.33",
3
+ "version": "2.0.35",
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
+ }