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.
Files changed (6) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +21 -0
  3. data/README.md +99 -0
  4. data/lib/weinc/client.rb +223 -0
  5. data/lib/weinc.rb +12 -0
  6. 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
@@ -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: []