Class: Textus::Surfaces::CLI::Verb

Inherits:
Object
  • Object
show all
Defined in:
lib/textus/surfaces/cli/verb.rb,
lib/textus/surfaces/cli/verb/get.rb,
lib/textus/surfaces/cli/verb/put.rb,
lib/textus/surfaces/cli/verb/init.rb,
lib/textus/surfaces/cli/verb/watch.rb,
lib/textus/surfaces/cli/verb/doctor.rb,
lib/textus/surfaces/cli/verb/mcp_serve.rb,
lib/textus/surfaces/cli/verb/schema_diff.rb,
lib/textus/surfaces/cli/verb/schema_init.rb,
lib/textus/surfaces/cli/verb/schema_migrate.rb

Overview

Subclasses must implement #call(store) and return an integer exit code. Use #emit(obj) for normal JSON output (returns 0). Subclasses that don’t need a Textus store (e.g. Init) override ‘.needs_store?` to return false; dispatch will pass nil instead.

Defined Under Namespace

Classes: Doctor, Get, Init, MCPServe, Put, SchemaDiff, SchemaInit, SchemaMigrate, Watch

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(stdin:, stdout:, stderr:, cwd: nil) ⇒ Verb

Returns a new instance of Verb.



62
63
64
65
66
67
# File 'lib/textus/surfaces/cli/verb.rb', line 62

def initialize(stdin:, stdout:, stderr:, cwd: nil)
  @stdin = stdin
  @stdout = stdout
  @stderr = stderr
  @cwd = cwd
end

Instance Attribute Details

#positionalObject (readonly)

Returns the value of attribute positional.



83
84
85
# File 'lib/textus/surfaces/cli/verb.rb', line 83

def positional
  @positional
end

#stdinObject (readonly)

The input stream — the source for a ‘cli_stdin` envelope (ADR 0068).



107
108
109
# File 'lib/textus/surfaces/cli/verb.rb', line 107

def stdin
  @stdin
end

Class Method Details

.command_name(name = nil) ⇒ Object

Declarative CLI name. Reader returns the registered name (or nil for verbs that aren’t directly invokable, like the abstract Verb/Group base classes). Writer registers it.



29
30
31
32
33
34
35
# File 'lib/textus/surfaces/cli/verb.rb', line 29

def command_name(name = nil)
  if name.nil?
    @command_name
  else
    @command_name = name.to_s
  end
end

.descendantsObject

Recursive subclass enumeration. Ruby 3.1 ships Class#subclasses but not Class#descendants, so we expand it ourselves.



57
58
59
# File 'lib/textus/surfaces/cli/verb.rb', line 57

def descendants
  subclasses.flat_map { |k| [k] + k.descendants }
end

.inherited(subclass) ⇒ Object



48
49
50
51
52
53
# File 'lib/textus/surfaces/cli/verb.rb', line 48

def inherited(subclass)
  super
  subclass.instance_variable_set(:@options, [])
  subclass.instance_variable_set(:@command_name, nil)
  subclass.instance_variable_set(:@parent_group, nil)
end

.needs_store?Boolean

Returns:

  • (Boolean)


22
23
24
# File 'lib/textus/surfaces/cli/verb.rb', line 22

def needs_store?
  true
end

.option(name, optspec) ⇒ Object



13
14
15
16
# File 'lib/textus/surfaces/cli/verb.rb', line 13

def option(name, optspec)
  options << [name, optspec]
  attr_accessor(name)
end

.optionsObject



18
19
20
# File 'lib/textus/surfaces/cli/verb.rb', line 18

def options
  @options ||= []
end

.parent_group(group_klass = nil) ⇒ Object

Declares that this verb is a subcommand of ‘group_klass`. When set, the verb is NOT a top-level CLI verb — it’s listed under the group’s subcommands instead.



40
41
42
43
44
45
46
# File 'lib/textus/surfaces/cli/verb.rb', line 40

def parent_group(group_klass = nil)
  if group_klass.nil?
    @parent_group
  else
    @parent_group = group_klass
  end
end

Instance Method Details

#emit(obj, exit_code: 0) ⇒ Object

Hashes get “protocol” => PROTOCOL prepended unless they already carry one (Store envelopes do). Caller’s value wins on collision.



87
88
89
90
91
# File 'lib/textus/surfaces/cli/verb.rb', line 87

def emit(obj, exit_code: 0)
  payload = obj.is_a?(Hash) ? { "protocol" => PROTOCOL }.merge(obj) : obj
  @stdout.puts(JSON.generate(payload))
  exit_code
end

#gate_dispatch(cmd, store) ⇒ Object

Builds a Command from spec + inputs and dispatches through Gate.



102
103
104
# File 'lib/textus/surfaces/cli/verb.rb', line 102

def gate_dispatch(cmd, store)
  store.gate.dispatch(cmd)
end

#parse(argv) ⇒ Object

Raises:



69
70
71
72
73
74
75
76
77
78
79
80
81
# File 'lib/textus/surfaces/cli/verb.rb', line 69

def parse(argv)
  fmt = "json"
  OptionParser.new do |o|
    self.class.options.each do |name, optspec|
      o.on(optspec) { |v| public_send(:"#{name}=", v) }
    end
    o.on("--output=FMT") { |v| fmt = v }
    o.on("--format=FMT") { |_v| raise FlagRenamed.new("--format", "--output") }
  end.permute!(argv)
  raise UsageError.new("only --output=json is supported in v1") unless fmt == "json"

  @positional = argv.dup
end

#resolved_role(store, default: Role::DEFAULT) ⇒ Object

Resolves the active role for this invocation. Honors the verb’s ‘–as` flag if declared, then TEXTUS_ROLE, then the project default. Pass `default:` to override the fallback (e.g. MCPServe uses AGENT).



96
97
98
99
# File 'lib/textus/surfaces/cli/verb.rb', line 96

def resolved_role(store, default: Role::DEFAULT)
  flag = respond_to?(:as_flag) ? as_flag : nil
  Role.resolve(flag: flag, env: ENV, root: store.root, default: default)
end