omarchy-ui 0.0.7-x86_64-linux → 0.0.8-x86_64-linux

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d389e6d9a8382cb8feec3a900ce2b4ee14001d214a069e96be8e7567e549f39e
4
- data.tar.gz: 77d33296ea0cc16f08a93bb94a17607a3b50d19e036f2649304a89fcc4b9f455
3
+ metadata.gz: bbe75336899d8f9674166011c929469db798869f88b32f45a3b61505b0713d01
4
+ data.tar.gz: bb5f2e4f0dfec4578992d17d5f3fdc5530d5b2c73d4aeebcfada553355077f94
5
5
  SHA512:
6
- metadata.gz: 2be91201712539398abbf26b03cc3298947d2ff040da32e7171009ef70fdb4127adb7ab5992088176a5f28d5f901cdf4bcdfec68e65c13123bcb3fc61c2206f7
7
- data.tar.gz: 89953562fbc3a943290234a297e5cd0f6ecf98b9f7b17a9188a5072716cf42e39e7281c724b97d2b4926df7aabd973485dbbc012ad3285447aaf36de9a7f5514
6
+ metadata.gz: 9776119537e37548ea84ca4129647a6f94b64944ae75315e776db661220aefeb4cc80527ed93a1b1c9410e0b80d2a547c77a2aae6674f770decc354bdee2a505
7
+ data.tar.gz: 6856581d58533845967d37daae2679cbf9601f9555aa724b9a9cbf04da0a62a8b15cd146df6d3b36551dec85ad29a13189ba1bdb01a143c2a3cfed87f46d2bb4
data/README.md CHANGED
@@ -10,7 +10,7 @@
10
10
  </p>
11
11
 
12
12
  <p align="center">
13
- <img alt="Omarchy UI 0.0.7" src="https://img.shields.io/badge/Omarchy_UI-0.0.7-b7ff5a?style=flat-square&labelColor=111711">
13
+ <img alt="Omarchy UI 0.0.8" src="https://img.shields.io/badge/Omarchy_UI-0.0.8-b7ff5a?style=flat-square&labelColor=111711">
14
14
  <img alt="Ruby 3.1 or newer" src="https://img.shields.io/badge/Ruby-3.1%2B-cc342d?style=flat-square&logo=ruby&logoColor=white">
15
15
  <img alt="Zui 0.0.10" src="https://img.shields.io/badge/Zui-0.0.10-7ee14b?style=flat-square&labelColor=111711">
16
16
  <img alt="241 components" src="https://img.shields.io/badge/components-241-75d943?style=flat-square&labelColor=111711">
@@ -53,8 +53,8 @@ and the Omarchy adapter.
53
53
 
54
54
  | Build | Use it for | Primary surface | Delivery |
55
55
  | --- | --- | --- | --- |
56
- | Omarchy application | Full desktop tools and standalone interfaces | `app :main` | `omarchy_ui run` or `omarchy_ui bundle` |
57
- | Omarchy plugin | Bar widgets, panels, and shell-integrated tools | `bar_widget` and `panel` | `omarchy_ui validate` and `omarchy_ui push` |
56
+ | Omarchy application | Full desktop tools and standalone interfaces | `app :main` | `omarchy_ui new NAME`, then `run` or `bundle` |
57
+ | Omarchy plugin | Bar widgets, panels, and shell-integrated tools | `bar_widget` and `panel` | `omarchy_ui new NAME --plugin`, then `push` or `bundle` |
58
58
  | Portable Zui application | The same application UI outside Omarchy | `app :main` | The platform-neutral [Zui toolchain](https://github.com/AdamMusa/zui#developer-workflow) |
59
59
 
60
60
  ## Requirements
@@ -63,10 +63,11 @@ and the Omarchy adapter.
63
63
  - Ruby 3.1 or newer while installing the gem and developing applications;
64
64
  - optional Qt modules for components that use features such as Quick 3D or Multimedia.
65
65
 
66
- The finished Omarchy bundle includes the Omarchy adapter, mruby executable, and a Zui-generated QML
67
- runtime tree-shaken to the components referenced by that application. The destination does not need
68
- Ruby or framework gems. A component fails with an explicit error when its required Qt module or
69
- resource is unavailable; Omarchy UI never substitutes a different component.
66
+ Omarchy UI generates a complete Omarchy-compatible application or plugin package from the
67
+ developer's Ruby and assets. It owns the Omarchy metadata, entry points, adapter, validation,
68
+ tree-shaken Zui QML, and embedded mruby runtime. The finished package does not need Ruby or framework
69
+ gems. A component fails with an explicit error when its required Qt module or resource is unavailable;
70
+ Omarchy UI never substitutes a different component.
70
71
 
71
72
  ## Quick start
72
73
 
@@ -78,20 +79,37 @@ gem install omarchy-ui
78
79
 
79
80
  The gem installs the compatible Zui version as a dependency.
80
81
 
81
- ### 2. Generate an application
82
+ ### 2. Generate a complete project
82
83
 
83
84
  ```bash
84
85
  omarchy_ui new Counter
85
86
  cd counter
86
87
  ```
87
88
 
89
+ For an integrated Omarchy plugin instead:
90
+
91
+ ```bash
92
+ omarchy_ui new "System Status" --plugin
93
+ cd system-status
94
+ ```
95
+
96
+ Both commands create a ready-to-run Ruby project. Omarchy UI generates the complete compliant
97
+ application or plugin package; developers do not write QML, shell entry points, or an Omarchy
98
+ manifest by hand.
99
+
100
+ Each project includes a Ruby-native `config.rb` containing its type, identity, release version,
101
+ entry point, and—for plugins—public marketplace metadata. `bundle`, `validate`, and `push` read this
102
+ single configuration when assembling the complete package.
103
+
88
104
  The generator creates a small, host-independent project:
89
105
 
90
106
  ```text
91
107
  counter/
108
+ ├── LICENSE
92
109
  ├── app.rb # reusable Zui application
93
110
  ├── components/
94
111
  │ └── welcome.rb # application-owned Ruby UI module
112
+ ├── config.rb # project identity and build settings
95
113
  ├── main.rb # Omarchy launcher
96
114
  └── README.md
97
115
  ```
@@ -228,48 +246,18 @@ QML is not required.
228
246
 
229
247
  ## Omarchy plugins
230
248
 
231
- A Ruby-powered plugin is an ordinary Omarchy plugin repository with `manifest.json` and `main.rb`
232
- at its root. Omarchy UI adds the generated service, panel, bar-widget, framework, and runtime files
233
- while staging the plugin; do not commit generated copies to the application source.
249
+ Generate a complete plugin project with one command:
234
250
 
235
- ```text
236
- system-status/
237
- ├── manifest.json
238
- ├── main.rb
239
- └── assets/
251
+ ```bash
252
+ omarchy_ui new "System Status" --plugin
253
+ cd system-status
240
254
  ```
241
255
 
242
- ### Manifest
243
-
244
- This complete manifest declares the adapter surfaces used by the Ruby definition below:
245
-
246
- ```json
247
- {
248
- "schemaVersion": 1,
249
- "id": "example.system-status",
250
- "name": "System Status",
251
- "version": "0.1.0",
252
- "author": "Example Author",
253
- "license": "MIT",
254
- "description": "A Ruby-powered status panel for Omarchy.",
255
- "kinds": ["service", "bar-widget", "panel"],
256
- "keepLoaded": true,
257
- "entryPoints": {
258
- "service": "Service.qml",
259
- "barWidget": "BarWidget.qml",
260
- "panel": "Panel.qml"
261
- },
262
- "barWidget": {
263
- "displayName": "System Status",
264
- "description": "Open the current system status.",
265
- "category": "System",
266
- "allowMultiple": false,
267
- "defaultSection": "right"
268
- }
269
- }
270
- ```
256
+ The generated project already satisfies Omarchy's structural requirements. Developers work in Ruby
257
+ and add their own assets; Omarchy UI owns the required plugin metadata, shell surfaces, native host,
258
+ tree-shaken Zui package, validation, and install lifecycle.
271
259
 
272
- ### Ruby entry point
260
+ The Ruby entry point uses ordinary Zui components:
273
261
 
274
262
  ```ruby
275
263
  # main.rb
@@ -294,35 +282,29 @@ OmarchyUI.plugin do
294
282
  end
295
283
  ```
296
284
 
297
- `bar_widget` declares the shell-owned bar surface. `panel :status` declares the panel opened by
298
- `open_panel :status`. The manifest must declare the corresponding entry points.
285
+ `bar_widget` and `panel :status` are the complete developer-facing surface declarations. Omarchy UI
286
+ maps them to the required Omarchy shell entry points when it builds or stages the plugin.
299
287
 
300
288
  ### Validate and install
301
289
 
302
290
  ```bash
303
- omarchy_ui validate path/to/system-status
304
- omarchy_ui push path/to/system-status
291
+ omarchy_ui validate
292
+ omarchy_ui push
305
293
  ```
306
294
 
307
- `validate` checks a fully staged copy, including the bundled Ruby entry point and generated adapter.
308
- `push` then:
309
-
310
- 1. stages and validates the complete plugin;
311
- 2. backs up an existing installation when present;
312
- 3. installs it atomically under `~/.config/omarchy/plugins/<plugin-id>`;
313
- 4. enables the plugin; and
314
- 5. restarts the Omarchy shell.
295
+ `validate` generates and checks the complete plugin without mutating the Ruby project. `push` safely
296
+ builds, validates, installs, enables, and reloads the plugin for development.
315
297
 
316
298
  During development, suppress either lifecycle action when needed:
317
299
 
318
300
  ```bash
319
- omarchy_ui push path/to/system-status --no-enable
320
- omarchy_ui push path/to/system-status --no-restart
301
+ omarchy_ui push --no-enable
302
+ omarchy_ui push --no-restart
321
303
  ```
322
304
 
323
305
  > [!WARNING]
324
306
  > Omarchy plugins execute as unsandboxed user code inside the shell environment. Review the Ruby,
325
- > assets, manifest, generated package, and provenance before installing a third-party plugin.
307
+ > assets, generated package, and provenance before installing a third-party plugin.
326
308
 
327
309
  ## Command-line reference
328
310
 
@@ -332,9 +314,10 @@ omarchy_ui <command> [arguments]
332
314
 
333
315
  | Command | Arguments | Result |
334
316
  | --- | --- | --- |
335
- | `omarchy_ui new NAME` | One application name | Creates a new application directory using a URL-safe form of the name |
317
+ | `omarchy_ui new NAME` | One project name | Creates a complete Omarchy application project |
318
+ | `omarchy_ui new NAME --plugin` | One project name and `--plugin` | Creates a complete Omarchy-compliant plugin project |
336
319
  | `omarchy_ui run FILE` | One Ruby entry point | Runs an application through the Omarchy/Quickshell host |
337
- | `omarchy_ui bundle [DIRECTORY]` | Zero or one project directory | Creates `dist/<project-name>` with the application, Omarchy adapter, runtime, and tree-shaken Zui QML |
320
+ | `omarchy_ui bundle [DIRECTORY]` | Zero or one project directory | Generates the complete distributable package and prints its location |
338
321
  | `omarchy_ui validate [DIRECTORY]` | Zero or one plugin directory | Stages the complete plugin and delegates validation to `omarchy plugin validate` |
339
322
  | `omarchy_ui push [DIRECTORY]` | Optional `--no-enable` and `--no-restart` | Atomically installs and activates a validated plugin |
340
323
  | `omarchy_ui version` | None | Prints the installed Omarchy UI version |
@@ -344,38 +327,19 @@ not forward arbitrary Ruby arguments.
344
327
 
345
328
  ## Packaging
346
329
 
347
- Create a distributable application from the project root:
330
+ Create the complete distributable package from the project root:
348
331
 
349
332
  ```bash
350
333
  omarchy_ui bundle
351
- ./dist/my-application/run
352
- ```
353
-
354
- The bundle contains:
355
-
356
- ```text
357
- dist/my-application/
358
- ├── main.rb # bundled application source
359
- ├── assets/ # application-owned resources
360
- ├── App.qml # Omarchy host entry point
361
- ├── Service.qml # process and surface bridge
362
- ├── Panel.qml # shell panel adapter
363
- ├── BarWidget.qml # shell bar adapter
364
- ├── ControlNode.qml # generated Zui protocol renderer
365
- ├── Components/Builtins/ # only component adapters selected by Zui
366
- ├── Controls/ Theme/ Fonts/ # generated renderer dependencies
367
- ├── zui-tree-shake.json # auditable selected-component report
368
- ├── Commons@ Ui@ # links to Omarchy's shell modules
369
- ├── omarchy-ui-runtime # embedded mruby executable
370
- ├── omarchy-ui-runtime.sha256
371
- ├── RUNTIME_PROVENANCE.md
372
- └── run
373
334
  ```
374
335
 
375
- Plugin bundles use the same runtime package and are validated against their manifest before the
376
- bundle command succeeds. Generated packages exclude `.git`, previous `dist` output, and stale
377
- adapter/runtime files before regenerating the minimal QML package. Omarchy UI pins Zui 0.0.10 so the
378
- tree-shaken QML exactly matches the Zui core embedded in the deterministic mruby runtime.
336
+ Omarchy UI prints the generated package location. It automatically assembles the bundled Ruby and
337
+ assets, Omarchy metadata and entry points, native host, checksummed runtime provenance, and only the
338
+ Zui QML components used by the project. Application packages receive a direct launcher; plugin
339
+ packages are validated against Omarchy's requirements before the command succeeds. Omarchy UI pins
340
+ Zui 0.0.10 so generated QML exactly matches the Zui core embedded in the deterministic mruby runtime.
341
+ The source `config.rb` remains the authoritative build configuration; plugin manifests are generated
342
+ from it in the distributable package and are never maintained separately by the developer.
379
343
 
380
344
  ## Component catalog
381
345
 
@@ -504,11 +468,11 @@ bodies are never recorded. Per-plugin logs rotate at 128 KiB under
504
468
  | `Ruby file not found` | Pass an existing entry point to `omarchy_ui run`, normally `main.rb` |
505
469
  | `Omarchy QML module not found` | Confirm the Omarchy shell installation provides `/usr/share/omarchy/shell/Commons` and `Ui` |
506
470
  | A component reports a missing module | Install or enable the Qt module required by that declared component |
507
- | Plugin validation fails | Run `omarchy_ui validate` and correct the manifest or staged project error before pushing |
471
+ | Plugin validation fails | Run `omarchy_ui validate` and correct the reported Ruby, asset, or generated-metadata error before pushing |
508
472
  | A pushed plugin is installed but inactive | Run `omarchy plugin enable <id>` and `omarchy restart shell` after resolving the reported shell error |
509
- | The bundle destination already exists | Review or move the existing `dist/<project-name>` directory before bundling again |
473
+ | The bundle destination already exists | Review or move the previously generated package before bundling again |
510
474
 
511
- Omarchy UI reports unsupported properties, components, events, paths, manifests, and runtime
475
+ Omarchy UI reports unsupported properties, components, events, paths, generated metadata, and runtime
512
476
  resources directly. Preserve the original error when opening an issue.
513
477
 
514
478
  ## Development
@@ -27,7 +27,7 @@ module OmarchyUI
27
27
  when "validate" then validate(arguments)
28
28
  when "version", "--version", "-v" then @out.puts(OmarchyUI::VERSION); 0
29
29
  else
30
- @err.puts("Usage: omarchy_ui <new NAME|run FILE|bundle [DIRECTORY]|push [DIRECTORY]|validate [DIRECTORY]|version>")
30
+ @err.puts("Usage: omarchy_ui <new NAME [--plugin]|run FILE|bundle [DIRECTORY]|push [DIRECTORY]|validate [DIRECTORY]|version>")
31
31
  command.nil? ? 0 : 64
32
32
  end
33
33
  rescue Interrupt
@@ -67,14 +67,30 @@ module OmarchyUI
67
67
  end
68
68
 
69
69
  def new_project(arguments)
70
+ plugin = !arguments.delete("--plugin").nil?
70
71
  name = arguments.shift || raise(ArgumentError, "new requires a project name")
71
- raise ArgumentError, "new accepts only an application name" unless arguments.empty?
72
+ raise ArgumentError, "new accepts a name and optional --plugin" unless arguments.empty?
72
73
  destination = File.expand_path(slug(name))
73
- Generator.new(path: destination, name: name).create
74
- @out.puts("Created standalone Omarchy UI app in #{destination}")
74
+ kind = plugin ? :plugin : :application
75
+ Generator.new(path: destination, name: name, kind:, author: default_author).create
76
+ label = plugin ? "Omarchy-compliant plugin" : "self-contained Omarchy application"
77
+ @out.puts("Created #{label} in #{destination}")
75
78
  0
76
79
  end
77
80
 
81
+ def default_author
82
+ configured = ENV["OMARCHY_UI_AUTHOR"].to_s.strip
83
+ return configured unless configured.empty?
84
+
85
+ output, _error, status = Open3.capture3("git", "config", "--get", "user.name")
86
+ git_author = output.to_s.strip
87
+ return git_author if status.success? && !git_author.empty?
88
+
89
+ ENV["USER"].to_s.strip.then { |user| user.empty? ? "Omarchy UI Developer" : user }
90
+ rescue SystemCallError
91
+ "Omarchy UI Developer"
92
+ end
93
+
78
94
  def slug(value)
79
95
  result = value.to_s.downcase.gsub(/[^a-z0-9]+/, "-").gsub(/\A-|\-\z/, "")
80
96
  raise ArgumentError, "name must contain letters or numbers" if result.empty?
@@ -83,6 +99,10 @@ module OmarchyUI
83
99
 
84
100
  def validate(arguments)
85
101
  source = File.expand_path(arguments.shift || Dir.pwd)
102
+ raise ArgumentError, "validate accepts one directory" unless arguments.empty?
103
+ config = ProjectConfig.load(source)
104
+ raise ArgumentError, "validate requires a plugin project" if config&.application?
105
+
86
106
  with_staged_project(source) do |staging|
87
107
  system("omarchy", "plugin", "validate", staging) ? 0 : 1
88
108
  end
@@ -91,15 +111,19 @@ module OmarchyUI
91
111
  def bundle_project(arguments)
92
112
  source = File.expand_path(arguments.shift || Dir.pwd)
93
113
  raise ArgumentError, "bundle accepts one directory" unless arguments.empty?
94
- raise ArgumentError, "main.rb not found: #{source}" unless File.file?(File.join(source, "main.rb"))
95
- destination = File.join(source, "dist", File.basename(source))
114
+ config = ProjectConfig.load(source)
115
+ entrypoint = config&.entrypoint || "main.rb"
116
+ raise ArgumentError, "#{entrypoint} not found: #{source}" unless File.file?(File.join(source, entrypoint))
117
+ destination = File.join(source, "dist", config&.slug || File.basename(source))
96
118
  raise ArgumentError, "bundle destination already exists: #{destination}" if File.exist?(destination)
97
119
  FileUtils.mkdir_p(destination)
98
- entries = application_entries(source)
120
+ entries = application_entries(source, config:)
99
121
  FileUtils.cp_r(entries.map { |entry| File.join(source, entry) }, destination)
100
- bundle_ruby_entrypoint(source, destination)
122
+ config.write_manifest(destination) if config&.plugin?
123
+ bundle_ruby_entrypoint(source, destination, entrypoint:)
101
124
  Runtime.install_package(destination)
102
- if File.file?(File.join(destination, "manifest.json"))
125
+ plugin = config ? config.plugin? : File.file?(File.join(destination, "manifest.json"))
126
+ if plugin
103
127
  raise ArgumentError, "bundled plugin validation failed" unless system("omarchy", "plugin", "validate", destination)
104
128
  @out.puts("Bundled plugin in #{destination}")
105
129
  return 0
@@ -115,7 +139,7 @@ module OmarchyUI
115
139
  app_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
116
140
  export OMARCHY_UI_RUNTIME="$app_dir/omarchy-ui-runtime"
117
141
  export OMARCHY_UI_PROJECT_DIR="$app_dir"
118
- export OMARCHY_UI_RUBY_PROGRAM="$app_dir/main.rb"
142
+ export OMARCHY_UI_RUBY_PROGRAM="$app_dir/#{entrypoint}"
119
143
  export QT_LOGGING_RULES="${QT_LOGGING_RULES:+$QT_LOGGING_RULES;}qt.qpa.services.warning=false"
120
144
  exec quickshell --path "$app_dir/App.qml"
121
145
  SH
@@ -131,8 +155,9 @@ module OmarchyUI
131
155
  [ENV["QT_LOGGING_RULES"], "qt.qpa.services.warning=false"].compact.reject(&:empty?).join(";")
132
156
  end
133
157
 
134
- def application_entries(source)
158
+ def application_entries(source, config: ProjectConfig.load(source))
135
159
  generated = Runtime::GENERATED_ENTRIES + Runtime::AUDIT_FILES + %w[run Commons Ui]
160
+ generated.concat([ProjectConfig::FILE_NAME, "manifest.json"]) if config
136
161
  Dir.children(source).reject { |entry| %w[.git dist].include?(entry) || generated.include?(entry) }
137
162
  end
138
163
 
@@ -140,8 +165,11 @@ module OmarchyUI
140
165
  enable = !arguments.delete("--no-enable")
141
166
  restart = !arguments.delete("--no-restart")
142
167
  source = File.expand_path(arguments.shift || Dir.pwd)
168
+ raise ArgumentError, "push accepts one directory" unless arguments.empty?
169
+ config = ProjectConfig.load(source)
170
+ raise ArgumentError, "push requires a plugin project" if config&.application?
143
171
  manifest_path = File.join(source, "manifest.json")
144
- manifest = JSON.parse(File.read(manifest_path))
172
+ manifest = config ? config.manifest : JSON.parse(File.read(manifest_path))
145
173
  plugin_id = manifest.fetch("id")
146
174
  raise ArgumentError, "invalid plugin id" unless VALID_ID.match?(plugin_id)
147
175
  plugin_root = File.expand_path("~/.config/omarchy/plugins")
@@ -184,11 +212,14 @@ module OmarchyUI
184
212
 
185
213
  def stage_project(source, parent: nil, prefix: ".omarchy-ui-staging-")
186
214
  raise ArgumentError, "project directory not found: #{source}" unless File.directory?(source)
215
+ config = ProjectConfig.load(source)
187
216
  staging = parent ? Dir.mktmpdir(prefix, parent) : Dir.mktmpdir(prefix)
188
- entries = application_entries(source)
217
+ entries = application_entries(source, config:)
189
218
  FileUtils.cp_r(entries.map { |entry| File.join(source, entry) }, staging) unless entries.empty?
190
- if File.file?(File.join(staging, "main.rb"))
191
- bundle_ruby_entrypoint(source, staging)
219
+ config.write_manifest(staging) if config&.plugin?
220
+ entrypoint = config&.entrypoint || "main.rb"
221
+ if File.file?(File.join(staging, entrypoint))
222
+ bundle_ruby_entrypoint(source, staging, entrypoint:)
192
223
  Runtime.install_package(staging)
193
224
  end
194
225
  staging
@@ -197,9 +228,11 @@ module OmarchyUI
197
228
  raise
198
229
  end
199
230
 
200
- def bundle_ruby_entrypoint(source, destination)
201
- entrypoint = File.join(source, "main.rb")
202
- File.write(File.join(destination, "main.rb"), SourceBundle.new(entrypoint, root: source).call)
231
+ def bundle_ruby_entrypoint(source, destination, entrypoint: "main.rb")
232
+ source_entrypoint = File.join(source, entrypoint)
233
+ destination_entrypoint = File.join(destination, entrypoint)
234
+ FileUtils.mkdir_p(File.dirname(destination_entrypoint))
235
+ File.write(destination_entrypoint, SourceBundle.new(source_entrypoint, root: source).call)
203
236
  end
204
237
 
205
238
  def activate_plugin(plugin_id)
@@ -4,9 +4,15 @@ require "fileutils"
4
4
 
5
5
  module OmarchyUI
6
6
  class Generator
7
- def initialize(path:, name: nil)
7
+ PROJECT_KINDS = %i[application plugin].freeze
8
+
9
+ def initialize(path:, name: nil, kind: :application, plugin_id: nil, author: nil)
8
10
  @path = File.expand_path(path)
9
11
  @name = name || File.basename(@path).split(/[-_]/).map(&:capitalize).join(" ")
12
+ @kind = kind.to_sym
13
+ @plugin_id = plugin_id || "local.#{project_slug}"
14
+ @author = author.to_s.strip.empty? ? "Omarchy UI Developer" : author.to_s.strip
15
+ raise ArgumentError, "unsupported project kind: #{@kind}" unless PROJECT_KINDS.include?(@kind)
10
16
  end
11
17
 
12
18
  def create
@@ -14,9 +20,11 @@ module OmarchyUI
14
20
  created = true
15
21
  FileUtils.mkdir_p(File.join(@path, "components"))
16
22
  File.write(File.join(@path, "main.rb"), main_program)
17
- File.write(File.join(@path, "app.rb"), application_program)
23
+ File.write(File.join(@path, "app.rb"), application_program) unless plugin?
24
+ File.write(File.join(@path, ProjectConfig::FILE_NAME), project_config)
18
25
  File.write(File.join(@path, "components", "welcome.rb"), welcome_component)
19
26
  File.write(File.join(@path, "README.md"), readme)
27
+ File.write(File.join(@path, "LICENSE"), license)
20
28
  @path
21
29
  rescue StandardError
22
30
  FileUtils.remove_entry(@path) if created && File.directory?(@path)
@@ -26,6 +34,8 @@ module OmarchyUI
26
34
  private
27
35
 
28
36
  def main_program
37
+ return plugin_program if plugin?
38
+
29
39
  <<~RUBY
30
40
  # frozen_string_literal: true
31
41
 
@@ -36,6 +46,33 @@ module OmarchyUI
36
46
  RUBY
37
47
  end
38
48
 
49
+ def plugin_program
50
+ <<~RUBY
51
+ # frozen_string_literal: true
52
+
53
+ require "omarchy_ui"
54
+ require_relative "components/welcome"
55
+
56
+ OmarchyUI.plugin(ui: #{constant_name}::UI) do
57
+ bar_widget do
58
+ row spacing: 7 do
59
+ text "#{@name}"
60
+ end
61
+ on_click { open_panel :#{surface_name} }
62
+ end
63
+
64
+ panel :#{surface_name} do
65
+ scroll width: 560, height: 620 do
66
+ welcome_card(
67
+ title: "#{@name}",
68
+ message: "Your Omarchy plugin is ready. Build the interface with Ruby and Zui components."
69
+ )
70
+ end
71
+ end
72
+ end
73
+ RUBY
74
+ end
75
+
39
76
  def application_program
40
77
  <<~RUBY
41
78
  # frozen_string_literal: true
@@ -80,6 +117,8 @@ module OmarchyUI
80
117
  end
81
118
 
82
119
  def readme
120
+ return plugin_readme if plugin?
121
+
83
122
  <<~MARKDOWN
84
123
  # #{@name}
85
124
 
@@ -92,10 +131,97 @@ module OmarchyUI
92
131
  ```
93
132
 
94
133
  Reusable Ruby UI components live in `components/` and load with ordinary Ruby
95
- `require_relative`. Run `omarchy_ui bundle` to create a self-contained distribution.
134
+ `require_relative`. Project identity and build settings live in `config.rb`.
135
+
136
+ ## Build
137
+
138
+ ```bash
139
+ omarchy_ui bundle
140
+ ```
141
+
142
+ Omarchy UI generates the complete self-contained application package required by Omarchy.
143
+ MARKDOWN
144
+ end
145
+
146
+ def plugin_readme
147
+ <<~MARKDOWN
148
+ # #{@name}
149
+
150
+ A pure Ruby Omarchy plugin built with Omarchy UI and Zui.
151
+
152
+ ## Validate
153
+
154
+ ```bash
155
+ omarchy_ui validate
156
+ ```
157
+
158
+ ## Install for development
159
+
160
+ ```bash
161
+ omarchy_ui push
162
+ ```
163
+
164
+ ## Build
165
+
166
+ ```bash
167
+ omarchy_ui bundle
168
+ ```
169
+
170
+ Write the plugin interface and behavior in Ruby under `main.rb` and `components/`.
171
+ Public plugin identity and release metadata live in `config.rb`. Omarchy UI uses it to
172
+ generate and validate the complete Omarchy-compatible plugin package.
96
173
  MARKDOWN
97
174
  end
98
175
 
176
+ def project_config
177
+ config = ProjectConfig.generate(
178
+ name: @name,
179
+ slug: project_slug,
180
+ kind: @kind,
181
+ author: @author,
182
+ plugin_id: @plugin_id
183
+ )
184
+ config
185
+ end
186
+
187
+ def license
188
+ <<~TEXT
189
+ MIT License
190
+
191
+ Copyright (c) #{Time.now.year} #{@author}
192
+
193
+ Permission is hereby granted, free of charge, to any person obtaining a copy
194
+ of this software and associated documentation files (the "Software"), to deal
195
+ in the Software without restriction, including without limitation the rights
196
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
197
+ copies of the Software, and to permit persons to whom the Software is
198
+ furnished to do so, subject to the following conditions:
199
+
200
+ The above copyright notice and this permission notice shall be included in all
201
+ copies or substantial portions of the Software.
202
+
203
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
204
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
205
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
206
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
207
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
208
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
209
+ SOFTWARE.
210
+ TEXT
211
+ end
212
+
213
+ def plugin?
214
+ @kind == :plugin
215
+ end
216
+
217
+ def project_slug
218
+ File.basename(@path).downcase.gsub(/[^a-z0-9]+/, "-").gsub(/\A-|\-\z/, "")
219
+ end
220
+
221
+ def surface_name
222
+ project_slug.tr("-", "_")
223
+ end
224
+
99
225
  def constant_name
100
226
  @name.gsub(/[^a-zA-Z0-9]+/, " ").split.map(&:capitalize).join.then do |name|
101
227
  name.empty? || name.match?(/\A\d/) ? "OmarchyApplication" : name
@@ -0,0 +1,207 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+
5
+ module OmarchyUI
6
+ class ProjectConfig
7
+ FILE_NAME = "config.rb"
8
+ PROJECT_TYPES = %i[application plugin].freeze
9
+ SLUG = /\A[a-z0-9]+(?:-[a-z0-9]+)*\z/
10
+ SECTIONS = %i[left center right].freeze
11
+
12
+ class Builder
13
+ FIELDS = %i[type id name slug version author license description entrypoint].freeze
14
+
15
+ FIELDS.each do |field|
16
+ define_method(field) { |value| @values[field] = value }
17
+ end
18
+
19
+ def initialize
20
+ @values = {}
21
+ end
22
+
23
+ def bar_widget(&definition)
24
+ widget = WidgetBuilder.new
25
+ widget.instance_eval(&definition)
26
+ @values[:bar_widget] = widget.to_h
27
+ end
28
+
29
+ def build(path: FILE_NAME)
30
+ ProjectConfig.new(@values, path:).tap(&:validate!)
31
+ end
32
+ end
33
+
34
+ class WidgetBuilder
35
+ FIELDS = %i[display_name description category default_section].freeze
36
+
37
+ FIELDS.each do |field|
38
+ define_method(field) { |value| @values[field] = value }
39
+ end
40
+
41
+ def initialize
42
+ @values = {}
43
+ end
44
+
45
+ def to_h
46
+ @values.dup
47
+ end
48
+ end
49
+
50
+ attr_reader :path
51
+
52
+ def self.define(&definition)
53
+ raise ArgumentError, "project config requires a block" unless definition
54
+
55
+ Builder.new.tap { |builder| builder.instance_eval(&definition) }.build
56
+ end
57
+
58
+ def self.generate(name:, slug:, kind:, author:, plugin_id: nil)
59
+ type = kind.to_sym
60
+ raise ArgumentError, "unsupported project type: #{type}" unless PROJECT_TYPES.include?(type)
61
+
62
+ lines = [
63
+ "# frozen_string_literal: true",
64
+ "",
65
+ "OmarchyUI.configure do",
66
+ " type #{type.inspect}"
67
+ ]
68
+ lines << " id #{(plugin_id || "local.#{slug}").inspect}" if type == :plugin
69
+ lines.concat([
70
+ " name #{name.inspect}",
71
+ " slug #{slug.inspect}",
72
+ " version \"0.1.0\""
73
+ ])
74
+ if type == :plugin
75
+ lines.concat([
76
+ " author #{author.inspect}",
77
+ " license \"MIT\"",
78
+ " description #{"A Ruby-powered #{name} plugin generated by Omarchy UI.".inspect}",
79
+ " entrypoint \"main.rb\"",
80
+ "",
81
+ " bar_widget do",
82
+ " display_name #{name.inspect}",
83
+ " description #{"Open #{name}.".inspect}",
84
+ " category \"System\"",
85
+ " default_section :right",
86
+ " end"
87
+ ])
88
+ else
89
+ lines << " entrypoint \"main.rb\""
90
+ end
91
+ lines << "end"
92
+ lines.join("\n") + "\n"
93
+ end
94
+
95
+ def self.load(root)
96
+ path = File.join(File.expand_path(root), FILE_NAME)
97
+ return nil unless File.file?(path)
98
+
99
+ value = Module.new.module_eval(File.read(path), path, 1)
100
+ unless value.is_a?(ProjectConfig)
101
+ raise ArgumentError, "#{FILE_NAME} must return an OmarchyUI::ProjectConfig"
102
+ end
103
+
104
+ new(value.to_h, path:).tap(&:validate!)
105
+ rescue SyntaxError, NameError, NoMethodError => error
106
+ raise ArgumentError, "invalid #{FILE_NAME}: #{error.message}"
107
+ end
108
+
109
+ def initialize(data, path:)
110
+ @data = data
111
+ @path = path
112
+ end
113
+
114
+ def type
115
+ value = @data[:type]
116
+ value.respond_to?(:to_sym) ? value.to_sym : value
117
+ end
118
+ def application? = type == :application
119
+ def plugin? = type == :plugin
120
+ def name = @data.fetch(:name)
121
+ def slug = @data.fetch(:slug)
122
+ def entrypoint = @data.fetch(:entrypoint)
123
+
124
+ def to_h
125
+ @data.dup
126
+ end
127
+
128
+ def manifest
129
+ raise ArgumentError, "project config describes an application, not a plugin" unless plugin?
130
+
131
+ widget = @data.fetch(:bar_widget)
132
+ {
133
+ "schemaVersion" => 1,
134
+ "id" => @data.fetch(:id),
135
+ "name" => name,
136
+ "version" => @data.fetch(:version),
137
+ "author" => @data.fetch(:author),
138
+ "license" => @data.fetch(:license),
139
+ "description" => @data.fetch(:description),
140
+ "kinds" => ["service", "bar-widget", "panel"],
141
+ "keepLoaded" => true,
142
+ "entryPoints" => {
143
+ "service" => "Service.qml",
144
+ "barWidget" => "BarWidget.qml",
145
+ "panel" => "Panel.qml"
146
+ },
147
+ "barWidget" => {
148
+ "displayName" => widget.fetch(:display_name),
149
+ "description" => widget.fetch(:description),
150
+ "category" => widget.fetch(:category),
151
+ "allowMultiple" => false,
152
+ "defaultSection" => widget.fetch(:default_section).to_s
153
+ }
154
+ }
155
+ end
156
+
157
+ def write_manifest(destination)
158
+ File.write(File.join(destination, "manifest.json"), JSON.pretty_generate(manifest) + "\n")
159
+ end
160
+
161
+ def validate!
162
+ raise ArgumentError, "invalid project config" unless @data.is_a?(Hash)
163
+ raise ArgumentError, "invalid project type" unless PROJECT_TYPES.include?(type)
164
+ required_string!(:name)
165
+ required_string!(:slug)
166
+ raise ArgumentError, "invalid project slug" unless SLUG.match?(@data[:slug])
167
+ required_string!(:version)
168
+ required_string!(:entrypoint)
169
+ validate_entrypoint!
170
+ validate_plugin! if plugin?
171
+ self
172
+ end
173
+
174
+ private
175
+
176
+ def required_string!(key, object = @data)
177
+ value = object[key]
178
+ raise ArgumentError, "invalid project config field: #{key}" unless value.is_a?(String) && !value.strip.empty?
179
+
180
+ value
181
+ end
182
+
183
+ def validate_entrypoint!
184
+ parts = entrypoint.split(/[\\\/]/)
185
+ if entrypoint.start_with?("/") || parts.empty? || parts.include?("..") || !entrypoint.end_with?(".rb")
186
+ raise ArgumentError, "invalid project entrypoint"
187
+ end
188
+ end
189
+
190
+ def validate_plugin!
191
+ %i[id author license description].each { |key| required_string!(key) }
192
+ raise ArgumentError, "invalid plugin id" unless VALID_ID.match?(@data[:id])
193
+
194
+ widget = @data[:bar_widget]
195
+ raise ArgumentError, "plugin project config is missing bar widget metadata" unless widget.is_a?(Hash)
196
+
197
+ %i[display_name description category].each { |key| required_string!(key, widget) }
198
+ section = widget[:default_section]
199
+ valid_section = section.respond_to?(:to_sym) && SECTIONS.include?(section.to_sym)
200
+ raise ArgumentError, "invalid default bar section" unless valid_section
201
+ end
202
+ end
203
+
204
+ def self.configure(&definition)
205
+ ProjectConfig.define(&definition)
206
+ end
207
+ end
data/lib/omarchy_ui.rb CHANGED
@@ -11,7 +11,7 @@ rescue LoadError
11
11
  end
12
12
 
13
13
  module OmarchyUI
14
- VERSION = "0.0.7"
14
+ VERSION = "0.0.8"
15
15
  ZUI_VERSION = Zui::VERSION
16
16
  FRAMEWORK_ROOT = File.expand_path("..", __dir__)
17
17
 
@@ -126,5 +126,6 @@ module Zui
126
126
  end
127
127
  end
128
128
 
129
+ require_relative "omarchy_ui/project_config"
129
130
  require_relative "omarchy_ui/generator"
130
131
  require_relative "omarchy_ui/runtime"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: omarchy-ui
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.7
4
+ version: 0.0.8
5
5
  platform: x86_64-linux
6
6
  authors:
7
7
  - Adam Moussa Ali
@@ -39,6 +39,7 @@ files:
39
39
  - lib/omarchy_ui.rb
40
40
  - lib/omarchy_ui/cli.rb
41
41
  - lib/omarchy_ui/generator.rb
42
+ - lib/omarchy_ui/project_config.rb
42
43
  - lib/omarchy_ui/runtime.rb
43
44
  - vendor/runtime/x86_64-linux/RUNTIME_PROVENANCE.md
44
45
  - vendor/runtime/x86_64-linux/omarchy-ui-runtime