weinc 0.1.0
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.
- checksums.yaml +7 -0
- data/LICENSE +21 -0
- data/README.md +99 -0
- data/lib/weinc/client.rb +223 -0
- data/lib/weinc.rb +12 -0
- metadata +51 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: c0f85cf01f891fcff5ed7911b51acc5594c5ed0b5c63d979a09b536c06a44b07
|
|
4
|
+
data.tar.gz: 636f171cb639c1a5d1b4e930977341bf2275655f85b63933b1c521354f454dcb
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: a7af57efdb8bb6913f1df4b631e7d6acefeb4150d02af3f4207d594a23457d4d229b7a395c851133dbcf150110f4cc3aa40b3cfc16b2b854876a7d30b1b27b1a
|
|
7
|
+
data.tar.gz: 040fbd9a5d86c8f05a7894ec481dc35ec526ecf287f70af54fcf74f0edc424ccfc8aed9a554054af1f544debb705e86e9d35fe30182edae850e7a3e71de2e1c5
|
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 WeInc
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# weinc
|
|
2
|
+
|
|
3
|
+
**WeInc AI website builder API client** for Ruby. Zero dependencies (pure standard library, `Net::HTTP` only).
|
|
4
|
+
|
|
5
|
+
WeInc ([we.inc](https://we.inc)) is an AI website builder: describe an app in natural language and get a live, deployable React + Vite + Tailwind site. This gem is a thin, honest client for the WeInc v1 REST API — it wraps the documented endpoints and adds nothing else.
|
|
6
|
+
|
|
7
|
+
- API docs: <https://my.we.inc/docs/api>
|
|
8
|
+
- OpenAPI spec: <https://my.we.inc/api/v1/docs>
|
|
9
|
+
- Website: <https://we.inc>
|
|
10
|
+
|
|
11
|
+
## Install
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
gem install weinc
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Or in your Gemfile:
|
|
18
|
+
|
|
19
|
+
```ruby
|
|
20
|
+
gem "weinc"
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Requires Ruby 2.6+.
|
|
24
|
+
|
|
25
|
+
## Quickstart
|
|
26
|
+
|
|
27
|
+
Get an API key (starts with `wk_`) from your WeInc agency dashboard.
|
|
28
|
+
|
|
29
|
+
```ruby
|
|
30
|
+
require "weinc"
|
|
31
|
+
|
|
32
|
+
weinc = WeInc::Client.new(api_key: ENV["WEINC_API_KEY"])
|
|
33
|
+
|
|
34
|
+
# 1. List your projects
|
|
35
|
+
result = weinc.list_projects(limit: 10)
|
|
36
|
+
puts result["total"]
|
|
37
|
+
|
|
38
|
+
# 2. Fetch one project
|
|
39
|
+
project = weinc.get_project(result["projects"].first["id"])["project"]
|
|
40
|
+
|
|
41
|
+
# 3. Create a project for a client
|
|
42
|
+
created = weinc.create_project(
|
|
43
|
+
"client_email" => "client@example.com",
|
|
44
|
+
"name" => "New Site"
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
# 4. Where is it live?
|
|
48
|
+
preview = weinc.get_preview_urls(project["id"])
|
|
49
|
+
puts preview["published_url"] # nil if never published
|
|
50
|
+
|
|
51
|
+
# 5. Clients, templates, plans, analytics
|
|
52
|
+
clients = weinc.list_clients
|
|
53
|
+
templates = weinc.list_templates
|
|
54
|
+
plans = weinc.list_plans
|
|
55
|
+
analytics = weinc.get_analytics(days: 30)
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## API
|
|
59
|
+
|
|
60
|
+
All methods return parsed JSON (Hash/Array) and raise `WeInc::Error` on any failure (with `#status` and `#body` when the server responded).
|
|
61
|
+
|
|
62
|
+
| Method | Endpoint |
|
|
63
|
+
|---|---|
|
|
64
|
+
| `list_projects(limit:, offset:, client_id:, status:)` | `GET /projects` |
|
|
65
|
+
| `get_project(project_id)` | `GET /projects/{id}` |
|
|
66
|
+
| `create_project(data)` | `POST /projects` (`client_email`, `name` required) |
|
|
67
|
+
| `update_project(project_id, data)` | `PATCH /projects/{id}` |
|
|
68
|
+
| `delete_project(project_id)` | `DELETE /projects/{id}` |
|
|
69
|
+
| `get_preview_urls(project_id)` | `GET /projects/{id}/preview` |
|
|
70
|
+
| `list_clients` | `GET /clients` |
|
|
71
|
+
| `create_client(data)` | `POST /clients` (`email` required) |
|
|
72
|
+
| `get_client(client_id)` | `GET /clients/{id}` |
|
|
73
|
+
| `update_client(client_id, data)` | `PATCH /clients/{id}` |
|
|
74
|
+
| `list_templates` | `GET /templates` |
|
|
75
|
+
| `list_plans` | `GET /plans` |
|
|
76
|
+
| `get_analytics(days:, project_id:)` | `GET /analytics` |
|
|
77
|
+
|
|
78
|
+
An escape hatch is included for endpoints this client does not wrap:
|
|
79
|
+
|
|
80
|
+
```ruby
|
|
81
|
+
webhooks = weinc.request("GET", "/webhooks")
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Notes on accuracy
|
|
85
|
+
|
|
86
|
+
- All wrapped endpoints match the published OpenAPI spec at <https://my.we.inc/api/v1/docs>, except `get_preview_urls`, which is implemented by the API but **not yet listed in the OpenAPI document**; its shape was verified against the WeInc server source on 2026-08-04. If the spec later documents it differently, the spec wins.
|
|
87
|
+
- Authentication: `Authorization: Bearer wk_...` org API key.
|
|
88
|
+
|
|
89
|
+
## Testing
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
ruby test/smoke.rb
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
The smoke test uses a mocked transport — no live API calls are made.
|
|
96
|
+
|
|
97
|
+
## License
|
|
98
|
+
|
|
99
|
+
MIT
|
data/lib/weinc/client.rb
ADDED
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
require "net/http"
|
|
5
|
+
require "uri"
|
|
6
|
+
|
|
7
|
+
module WeInc
|
|
8
|
+
# Raised for every failed request (non-2xx response, network failure, or
|
|
9
|
+
# invalid input).
|
|
10
|
+
#
|
|
11
|
+
# @!attribute [r] status
|
|
12
|
+
# @return [Integer, nil] HTTP status code, if a response was received.
|
|
13
|
+
# @!attribute [r] body
|
|
14
|
+
# @return [Object, nil] Parsed response body (Hash/Array if JSON, else raw String).
|
|
15
|
+
class Error < StandardError
|
|
16
|
+
attr_reader :status, :body
|
|
17
|
+
|
|
18
|
+
def initialize(message, status: nil, body: nil)
|
|
19
|
+
super(message)
|
|
20
|
+
@status = status
|
|
21
|
+
@body = body
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# weinc — thin client for the WeInc v1 REST API.
|
|
26
|
+
#
|
|
27
|
+
# Base URL: https://my.we.inc/api/v1
|
|
28
|
+
# Auth: org API key passed as +Authorization: Bearer wk_...+
|
|
29
|
+
#
|
|
30
|
+
# Endpoint shapes were taken from the live OpenAPI spec
|
|
31
|
+
# (https://my.we.inc/api/v1/docs) where available. The preview endpoint is
|
|
32
|
+
# implemented by the API but not yet listed in the OpenAPI document; its
|
|
33
|
+
# shape was verified against the server source on 2026-08-04.
|
|
34
|
+
#
|
|
35
|
+
# Pure standard library: uses Net::HTTP only.
|
|
36
|
+
#
|
|
37
|
+
# @example
|
|
38
|
+
# weinc = WeInc::Client.new(api_key: ENV["WEINC_API_KEY"])
|
|
39
|
+
# result = weinc.list_projects(limit: 10)
|
|
40
|
+
# puts result["total"]
|
|
41
|
+
class Client
|
|
42
|
+
DEFAULT_BASE_URL = "https://my.we.inc/api/v1"
|
|
43
|
+
|
|
44
|
+
attr_reader :base_url
|
|
45
|
+
|
|
46
|
+
# @param api_key [String] Org API key from the agency dashboard (starts with +wk_+).
|
|
47
|
+
# @param base_url [String] Override the API base URL.
|
|
48
|
+
# @param transport [#call, nil] Custom transport for tests. Called with
|
|
49
|
+
# (method, uri, headers, body) and must return [Integer status, String body].
|
|
50
|
+
def initialize(api_key:, base_url: DEFAULT_BASE_URL, transport: nil)
|
|
51
|
+
raise Error, "api_key must be a non-empty string" unless api_key.is_a?(String) && !api_key.empty?
|
|
52
|
+
|
|
53
|
+
@api_key = api_key
|
|
54
|
+
@base_url = base_url.sub(%r{/+\z}, "")
|
|
55
|
+
@transport = transport
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# ------------------------------------------------------------- Projects
|
|
59
|
+
|
|
60
|
+
# List projects in your org. +GET /projects+.
|
|
61
|
+
#
|
|
62
|
+
# @param limit [Integer, nil] Max results (server default 50).
|
|
63
|
+
# @param offset [Integer, nil] Pagination offset.
|
|
64
|
+
# @param client_id [String, nil] Filter by client UUID.
|
|
65
|
+
# @param status [String, nil] Filter by project status.
|
|
66
|
+
# @return [Hash] +{"projects" => [...], "total" => Integer}+
|
|
67
|
+
def list_projects(limit: nil, offset: nil, client_id: nil, status: nil)
|
|
68
|
+
request("GET", "/projects", query: { limit: limit, offset: offset, client_id: client_id, status: status })
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Get one project by ID. +GET /projects/{projectId}+.
|
|
72
|
+
def get_project(project_id)
|
|
73
|
+
request("GET", "/projects/#{id!(project_id, 'project_id')}")
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Create a project for a client. +POST /projects+.
|
|
77
|
+
#
|
|
78
|
+
# Required keys: +client_email+, +name+. Optional: +description+,
|
|
79
|
+
# +template_id+ (clone files from an org template).
|
|
80
|
+
def create_project(data)
|
|
81
|
+
request("POST", "/projects", body: data)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Update a project. +PATCH /projects/{projectId}+.
|
|
85
|
+
def update_project(project_id, data)
|
|
86
|
+
request("PATCH", "/projects/#{id!(project_id, 'project_id')}", body: data)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Delete a project. +DELETE /projects/{projectId}+.
|
|
90
|
+
def delete_project(project_id)
|
|
91
|
+
request("DELETE", "/projects/#{id!(project_id, 'project_id')}")
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Get preview / published URLs for a project.
|
|
95
|
+
#
|
|
96
|
+
# +GET /projects/{projectId}/preview+ — implemented by the API but not yet
|
|
97
|
+
# in the OpenAPI spec (shape verified against server source 2026-08-04).
|
|
98
|
+
#
|
|
99
|
+
# @return [Hash] +{"published_url" => String|nil, "has_published" => Boolean, "embed_preview_path" => String}+
|
|
100
|
+
def get_preview_urls(project_id)
|
|
101
|
+
request("GET", "/projects/#{id!(project_id, 'project_id')}/preview")
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# -------------------------------------------------------------- Clients
|
|
105
|
+
|
|
106
|
+
# List clients. +GET /clients+.
|
|
107
|
+
def list_clients
|
|
108
|
+
request("GET", "/clients")
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# Create a client. +POST /clients+. Required key: +email+.
|
|
112
|
+
# Optional: +name+, +company+, +plan_id+.
|
|
113
|
+
def create_client(data)
|
|
114
|
+
request("POST", "/clients", body: data)
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# Get client details. +GET /clients/{clientId}+.
|
|
118
|
+
def get_client(client_id)
|
|
119
|
+
request("GET", "/clients/#{id!(client_id, 'client_id')}")
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# Update a client. +PATCH /clients/{clientId}+.
|
|
123
|
+
def update_client(client_id, data)
|
|
124
|
+
request("PATCH", "/clients/#{id!(client_id, 'client_id')}", body: data)
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# --------------------------------------------------- Templates / Plans
|
|
128
|
+
|
|
129
|
+
# List org templates. +GET /templates+.
|
|
130
|
+
def list_templates
|
|
131
|
+
request("GET", "/templates")
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# List client plans. +GET /plans+.
|
|
135
|
+
def list_plans
|
|
136
|
+
request("GET", "/plans")
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# ------------------------------------------------------------ Analytics
|
|
140
|
+
|
|
141
|
+
# Get aggregated analytics. +GET /analytics+.
|
|
142
|
+
#
|
|
143
|
+
# @param days [Integer, nil]
|
|
144
|
+
# @param project_id [String, nil]
|
|
145
|
+
def get_analytics(days: nil, project_id: nil)
|
|
146
|
+
request("GET", "/analytics", query: { days: days, project_id: project_id })
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# ------------------------------------------------------------- Plumbing
|
|
150
|
+
|
|
151
|
+
# Perform an authenticated JSON request. Exposed for calling endpoints
|
|
152
|
+
# this client does not wrap yet.
|
|
153
|
+
#
|
|
154
|
+
# @param method [String] HTTP method.
|
|
155
|
+
# @param path [String] Path relative to the base URL, e.g. +"/projects"+.
|
|
156
|
+
# @param query [Hash] Query parameters (nil values are dropped).
|
|
157
|
+
# @param body [Hash, nil] JSON request body.
|
|
158
|
+
# @return [Object] Parsed JSON response body.
|
|
159
|
+
def request(method, path, query: {}, body: nil)
|
|
160
|
+
uri = URI.parse(@base_url + path)
|
|
161
|
+
pairs = query.reject { |_k, v| v.nil? }
|
|
162
|
+
uri.query = URI.encode_www_form(pairs) unless pairs.empty?
|
|
163
|
+
|
|
164
|
+
headers = {
|
|
165
|
+
"Authorization" => "Bearer #{@api_key}",
|
|
166
|
+
"Accept" => "application/json"
|
|
167
|
+
}
|
|
168
|
+
payload = nil
|
|
169
|
+
unless body.nil?
|
|
170
|
+
headers["Content-Type"] = "application/json"
|
|
171
|
+
payload = JSON.generate(body)
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
status, text = send_request(method, uri, headers, payload)
|
|
175
|
+
|
|
176
|
+
data =
|
|
177
|
+
if text.nil? || text.empty?
|
|
178
|
+
nil
|
|
179
|
+
else
|
|
180
|
+
begin
|
|
181
|
+
JSON.parse(text)
|
|
182
|
+
rescue JSON::ParserError
|
|
183
|
+
text
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
unless (200..299).cover?(status)
|
|
188
|
+
server_message = data.is_a?(Hash) && data["error"].is_a?(String) ? data["error"] : "HTTP #{status}"
|
|
189
|
+
raise Error.new("#{method} #{path} failed: #{server_message}", status: status, body: data)
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
data
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
private
|
|
196
|
+
|
|
197
|
+
def send_request(method, uri, headers, payload)
|
|
198
|
+
return @transport.call(method, uri, headers, payload) if @transport
|
|
199
|
+
|
|
200
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
|
201
|
+
http.use_ssl = uri.scheme == "https"
|
|
202
|
+
http.open_timeout = 10
|
|
203
|
+
http.read_timeout = 30
|
|
204
|
+
|
|
205
|
+
req = Net::HTTP.const_get(method.capitalize).new(uri.request_uri, headers)
|
|
206
|
+
req.body = payload if payload
|
|
207
|
+
|
|
208
|
+
begin
|
|
209
|
+
res = http.request(req)
|
|
210
|
+
rescue StandardError => e
|
|
211
|
+
raise Error, "Network error calling #{method} #{uri.path}: #{e.message}"
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
[res.code.to_i, res.body.to_s]
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
def id!(value, name)
|
|
218
|
+
raise Error, "#{name} must be a non-empty string" unless value.is_a?(String) && !value.empty?
|
|
219
|
+
|
|
220
|
+
URI.encode_www_form_component(value).gsub("+", "%20")
|
|
221
|
+
end
|
|
222
|
+
end
|
|
223
|
+
end
|
data/lib/weinc.rb
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# weinc — WeInc AI website builder API client.
|
|
4
|
+
#
|
|
5
|
+
# Thin, zero-dependency client for the WeInc v1 REST API
|
|
6
|
+
# (https://my.we.inc/api/v1). See https://my.we.inc/docs/api for API docs
|
|
7
|
+
# and https://we.inc for the product.
|
|
8
|
+
module WeInc
|
|
9
|
+
VERSION = "0.1.0"
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
require_relative "weinc/client"
|
metadata
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: weinc
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- WeInc
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2026-08-15 00:00:00.000000000 Z
|
|
12
|
+
dependencies: []
|
|
13
|
+
description: 'Thin, zero-dependency Ruby client (Net::HTTP only) for the WeInc v1
|
|
14
|
+
REST API: projects, clients, templates, plans, and analytics.'
|
|
15
|
+
email:
|
|
16
|
+
- support@weinc.dev
|
|
17
|
+
executables: []
|
|
18
|
+
extensions: []
|
|
19
|
+
extra_rdoc_files: []
|
|
20
|
+
files:
|
|
21
|
+
- LICENSE
|
|
22
|
+
- README.md
|
|
23
|
+
- lib/weinc.rb
|
|
24
|
+
- lib/weinc/client.rb
|
|
25
|
+
homepage: https://github.com/umairalisadaqat/weinc-ruby
|
|
26
|
+
licenses:
|
|
27
|
+
- MIT
|
|
28
|
+
metadata:
|
|
29
|
+
homepage_uri: https://github.com/umairalisadaqat/weinc-ruby
|
|
30
|
+
source_code_uri: https://github.com/umairalisadaqat/weinc-ruby
|
|
31
|
+
documentation_uri: https://my.we.inc/docs/api
|
|
32
|
+
post_install_message:
|
|
33
|
+
rdoc_options: []
|
|
34
|
+
require_paths:
|
|
35
|
+
- lib
|
|
36
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - ">="
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '2.6'
|
|
41
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
42
|
+
requirements:
|
|
43
|
+
- - ">="
|
|
44
|
+
- !ruby/object:Gem::Version
|
|
45
|
+
version: '0'
|
|
46
|
+
requirements: []
|
|
47
|
+
rubygems_version: 3.0.3.1
|
|
48
|
+
signing_key:
|
|
49
|
+
specification_version: 4
|
|
50
|
+
summary: WeInc AI website builder API client
|
|
51
|
+
test_files: []
|