@wevion/cli 1.0.2
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 +46 -0
- package/openapi.json +230849 -0
- package/package.json +24 -0
- package/selftest.mjs +183 -0
- package/src/index.mjs +524 -0
package/package.json
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@wevion/cli",
|
|
3
|
+
"version": "1.0.2",
|
|
4
|
+
"description": "Wevion API command-line interface — generated from the public OpenAPI spec",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"wevion": "src/index.mjs"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"src",
|
|
11
|
+
"selftest.mjs",
|
|
12
|
+
"openapi.json"
|
|
13
|
+
],
|
|
14
|
+
"engines": {
|
|
15
|
+
"node": ">=22"
|
|
16
|
+
},
|
|
17
|
+
"scripts": {
|
|
18
|
+
"prepack": "node -e \"require('fs').accessSync('openapi.json')\"",
|
|
19
|
+
"test": "node selftest.mjs"
|
|
20
|
+
},
|
|
21
|
+
"publishConfig": {
|
|
22
|
+
"access": "public"
|
|
23
|
+
}
|
|
24
|
+
}
|
package/selftest.mjs
ADDED
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
// Minimal runnable check for the spec->request mapping. Run: node selftest.mjs
|
|
2
|
+
import assert from 'node:assert/strict'
|
|
3
|
+
import { mkdtempSync, symlinkSync } from 'node:fs'
|
|
4
|
+
import { readFile } from 'node:fs/promises'
|
|
5
|
+
import { tmpdir } from 'node:os'
|
|
6
|
+
import { join } from 'node:path'
|
|
7
|
+
import { execFileSync } from 'node:child_process'
|
|
8
|
+
import { kebab, listOperations, buildRequest, configPath, resolveApiKey } from './src/index.mjs'
|
|
9
|
+
|
|
10
|
+
const spec = {
|
|
11
|
+
openapi: '3.0.3',
|
|
12
|
+
security: [{ apiKeyAuth: [] }],
|
|
13
|
+
paths: {
|
|
14
|
+
'/api/v1/ad-accounts/{id}': {
|
|
15
|
+
get: {
|
|
16
|
+
operationId: 'getAdAccountById',
|
|
17
|
+
tags: ['ad-accounts'],
|
|
18
|
+
parameters: [
|
|
19
|
+
{ name: 'id', in: 'path', required: true, schema: { type: 'string' } },
|
|
20
|
+
{ name: 'fields', in: 'query', schema: { type: 'string' } },
|
|
21
|
+
],
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
'/api/v1/search': {
|
|
25
|
+
get: {
|
|
26
|
+
operationId: 'searchThings',
|
|
27
|
+
tags: ['search'],
|
|
28
|
+
parameters: [
|
|
29
|
+
{ name: 'q', in: 'query', required: true, schema: { type: 'string' } },
|
|
30
|
+
{ name: 'ids', in: 'query', schema: { type: 'array', items: { type: 'string' } } },
|
|
31
|
+
{ name: 'x-test-header', in: 'header', required: true, schema: { type: 'string' } },
|
|
32
|
+
],
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
'/api/v1/campaigns': {
|
|
36
|
+
post: {
|
|
37
|
+
operationId: 'createCampaign',
|
|
38
|
+
tags: ['campaigns'],
|
|
39
|
+
requestBody: {
|
|
40
|
+
content: {
|
|
41
|
+
'application/json': {
|
|
42
|
+
schema: {
|
|
43
|
+
type: 'object',
|
|
44
|
+
properties: {
|
|
45
|
+
name: { type: 'string' },
|
|
46
|
+
budget: { type: 'number' },
|
|
47
|
+
enabled: { type: 'boolean' },
|
|
48
|
+
labels: { type: 'array', items: { type: 'string' } },
|
|
49
|
+
settings: { type: 'object' },
|
|
50
|
+
},
|
|
51
|
+
required: ['name'],
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
'/api/v1/sessions/{id}/messages': {
|
|
59
|
+
post: {
|
|
60
|
+
operationId: 'createSessionMessage',
|
|
61
|
+
tags: ['chat'],
|
|
62
|
+
parameters: [{ name: 'id', in: 'path', required: true, schema: { type: 'string' } }],
|
|
63
|
+
requestBody: {
|
|
64
|
+
content: {
|
|
65
|
+
'application/json': {
|
|
66
|
+
schema: {
|
|
67
|
+
type: 'object',
|
|
68
|
+
properties: { id: { type: 'string' }, content: { type: 'string' } },
|
|
69
|
+
required: ['id', 'content'],
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
'/api/v1/uploads': {
|
|
77
|
+
post: {
|
|
78
|
+
operationId: 'uploadFile',
|
|
79
|
+
tags: ['uploads'],
|
|
80
|
+
requestBody: {
|
|
81
|
+
content: {
|
|
82
|
+
'multipart/form-data': {
|
|
83
|
+
schema: { type: 'object', properties: { file: { type: 'string', format: 'binary' } } },
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
assert.equal(kebab('getAdAccountById'), 'get-ad-account-by-id')
|
|
93
|
+
|
|
94
|
+
const ops = listOperations(spec)
|
|
95
|
+
assert.equal(ops.size, 5)
|
|
96
|
+
|
|
97
|
+
// path + query mapping
|
|
98
|
+
const get = buildRequest(ops.get('get-ad-account-by-id'), { id: 'act_9', fields: 'name,status' }, 'https://api.wevion.ai')
|
|
99
|
+
assert.equal(get.method, 'GET')
|
|
100
|
+
assert.equal(get.url, 'https://api.wevion.ai/api/v1/ad-accounts/act_9?fields=name%2Cstatus')
|
|
101
|
+
assert.equal(get.body, undefined)
|
|
102
|
+
assert.deepEqual(get.headers, {})
|
|
103
|
+
|
|
104
|
+
// missing path param throws
|
|
105
|
+
assert.throws(() => buildRequest(ops.get('get-ad-account-by-id'), {}, 'x'), /missing required path param --id/)
|
|
106
|
+
|
|
107
|
+
// required query/header and query array mapping
|
|
108
|
+
assert.throws(() => buildRequest(ops.get('search-things'), { q: 'abc' }, 'https://api.wevion.ai'), /missing required header param --x-test-header/)
|
|
109
|
+
const search = buildRequest(
|
|
110
|
+
ops.get('search-things'),
|
|
111
|
+
{ q: 'abc', ids: ['act_1', 'act_2,act_3'], 'x-test-header': 'secret' },
|
|
112
|
+
'https://api.wevion.ai',
|
|
113
|
+
)
|
|
114
|
+
assert.equal(search.url, 'https://api.wevion.ai/api/v1/search?q=abc&ids=act_1&ids=act_2&ids=act_3')
|
|
115
|
+
assert.deepEqual(search.headers, { 'x-test-header': 'secret' })
|
|
116
|
+
|
|
117
|
+
// body assembly from flags with schema-aware coercion
|
|
118
|
+
const post = buildRequest(
|
|
119
|
+
ops.get('create-campaign'),
|
|
120
|
+
{
|
|
121
|
+
name: 'Q3',
|
|
122
|
+
budget: '100.5',
|
|
123
|
+
enabled: 'false',
|
|
124
|
+
labels: '["launch","q3"]',
|
|
125
|
+
settings: '{"daily":true}',
|
|
126
|
+
},
|
|
127
|
+
'https://api.wevion.ai/',
|
|
128
|
+
)
|
|
129
|
+
assert.equal(post.method, 'POST')
|
|
130
|
+
assert.equal(post.url, 'https://api.wevion.ai/api/v1/campaigns')
|
|
131
|
+
assert.deepEqual(JSON.parse(post.body), {
|
|
132
|
+
name: 'Q3',
|
|
133
|
+
budget: 100.5,
|
|
134
|
+
enabled: false,
|
|
135
|
+
labels: ['launch', 'q3'],
|
|
136
|
+
settings: { daily: true },
|
|
137
|
+
})
|
|
138
|
+
assert.throws(() => buildRequest(ops.get('create-campaign'), { budget: '100' }, 'x'), /missing required body field --name/)
|
|
139
|
+
|
|
140
|
+
// raw --json overrides body but is still parsed for top-level required fields
|
|
141
|
+
const raw = buildRequest(ops.get('create-campaign'), { json: '{"name":"raw"}' }, 'x')
|
|
142
|
+
assert.equal(raw.body, '{"name":"raw"}')
|
|
143
|
+
assert.throws(() => buildRequest(ops.get('create-campaign'), { json: '{"budget":10}' }, 'x'), /missing required body field --name/)
|
|
144
|
+
|
|
145
|
+
// body fields that collide with path/query/header flags are exposed as --body-*
|
|
146
|
+
const message = buildRequest(
|
|
147
|
+
ops.get('create-session-message'),
|
|
148
|
+
{ id: 'sess_1', 'body-id': 'msg_1', content: 'ciao' },
|
|
149
|
+
'https://api.wevion.ai',
|
|
150
|
+
)
|
|
151
|
+
assert.equal(message.url, 'https://api.wevion.ai/api/v1/sessions/sess_1/messages')
|
|
152
|
+
assert.deepEqual(JSON.parse(message.body), { id: 'msg_1', content: 'ciao' })
|
|
153
|
+
|
|
154
|
+
// unsupported request body media types fail before fetch
|
|
155
|
+
assert.throws(() => buildRequest(ops.get('upload-file'), {}, 'x'), /request body media type is not supported/)
|
|
156
|
+
|
|
157
|
+
// config path honours XDG_CONFIG_HOME, falls back to ~/.config
|
|
158
|
+
assert.equal(configPath({ XDG_CONFIG_HOME: '/x' }), '/x/wevion/config.json')
|
|
159
|
+
assert.equal(configPath({ HOME: '/home/u' }), '/home/u/.config/wevion/config.json')
|
|
160
|
+
assert.equal(configPath({ APPDATA: 'C:\\Users\\u\\AppData\\Roaming' }, 'win32'), 'C:\\Users\\u\\AppData\\Roaming/Wevion/config.json')
|
|
161
|
+
|
|
162
|
+
// api key precedence: env > config
|
|
163
|
+
assert.equal(resolveApiKey({ WEVION_API_KEY: 'env' }, { apiKey: 'cfg' }), 'env')
|
|
164
|
+
assert.equal(resolveApiKey({}, { apiKey: 'cfg' }), 'cfg')
|
|
165
|
+
assert.equal(resolveApiKey({}, {}), null)
|
|
166
|
+
|
|
167
|
+
// bundled snapshot must exist and keep list/help offline.
|
|
168
|
+
const bundled = JSON.parse(await readFile(new URL('./openapi.json', import.meta.url), 'utf8'))
|
|
169
|
+
assert.match(bundled.openapi, /^3\./)
|
|
170
|
+
assert.ok(Object.keys(bundled.paths || {}).length > 0)
|
|
171
|
+
const knownCommand = listOperations(bundled).keys().next().value
|
|
172
|
+
assert.ok(knownCommand)
|
|
173
|
+
|
|
174
|
+
const tmp = mkdtempSync(join(tmpdir(), 'wevion-cli-'))
|
|
175
|
+
const symlink = join(tmp, 'wevion')
|
|
176
|
+
symlinkSync(new URL('./src/index.mjs', import.meta.url), symlink)
|
|
177
|
+
const help = execFileSync(process.execPath, [symlink, 'help', knownCommand, '--base-url=http://127.0.0.1:9'], {
|
|
178
|
+
encoding: 'utf8',
|
|
179
|
+
env: { ...process.env, WEVION_API_KEY: '' },
|
|
180
|
+
})
|
|
181
|
+
assert.match(help, /Flags:/)
|
|
182
|
+
|
|
183
|
+
console.log('ok')
|