intent 0.7.0 → 0.8.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 +4 -4
- data/README.md +5 -1
- data/bin/project +10 -0
- data/intent.gemspec +1 -0
- data/lib/intent/args.rb +32 -0
- data/lib/intent/commands/intent.rb +11 -2
- data/lib/intent/commands/inventory.rb +55 -10
- data/lib/intent/commands/project.rb +91 -0
- data/lib/intent/commands/projects.rb +9 -4
- data/lib/intent/commands.rb +1 -1
- data/lib/intent/core/directory.rb +37 -0
- data/lib/intent/core/documents.rb +17 -0
- data/lib/intent/core/inbox.rb +29 -0
- data/lib/intent/core/inventory.rb +101 -0
- data/lib/intent/core/projects.rb +58 -0
- data/lib/intent/core.rb +23 -162
- data/lib/intent/desktop.rb +5 -3
- data/lib/intent/dispatcher.rb +20 -0
- data/lib/intent/env.rb +26 -0
- data/lib/intent/text/intent.help.txt +82 -0
- data/{bin/projects_active → lib/intent/ui/projects_active.rb} +0 -0
- data/lib/intent/ui/term_color.rb +80 -0
- data/lib/intent/version.rb +1 -1
- data/lib/intent.rb +22 -6
- metadata +30 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: eceb2c98af31fff1168ee446ea327a674c510e96d5bcfb948cae895e391a8399
|
4
|
+
data.tar.gz: a8225ec76a08ba859b7f8abff5729527e0426e51fff1757c26e00a63f5b5a9af
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7151b6b4e09485ade42fc3dc8756aeaf933d72b96acdb5d216764866d9cf52dd04cfacb828bc4f8498e2a5d90397d7db30add5ae44f3370afb0f9c5916f24765
|
7
|
+
data.tar.gz: fb6b9209b34d67417a232b68e2a02dea9faf99e822252becc5d1b7dfc5a6fad5c8885ba4050258a587abc6e7afdc366e97afaf10be0ebbb768e45f2ba014987d
|
data/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# Intent
|
2
2
|
|
3
|
-
Automation engine with todo.txt
|
3
|
+
> **Automation engine with todo.txt**
|
4
4
|
|
5
5
|
This tool is mired deep in the obscurity and pedantry of the text-based computing culture that originated in the 1970s and still continues today. You probably don’t want to use this.
|
6
6
|
|
@@ -16,6 +16,10 @@ gem install intent
|
|
16
16
|
intent help
|
17
17
|
```
|
18
18
|
|
19
|
+
## The ledger concept
|
20
|
+
|
21
|
+
Docs and an essay about managing text-based computational ledgers ‘coming soon’ (eventually).
|
22
|
+
|
19
23
|
## Copyright & License
|
20
24
|
|
21
25
|
Copyright 2024 Mark Rickerby <https://maetl.net>.
|
data/bin/project
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
path = __FILE__
|
3
|
+
while File.symlink?(path)
|
4
|
+
path = File.expand_path(File.readlink(path), File.dirname(path))
|
5
|
+
end
|
6
|
+
$:.unshift(File.join(File.dirname(File.expand_path(path)), '..', 'lib'))
|
7
|
+
|
8
|
+
require 'intent'
|
9
|
+
|
10
|
+
Intent::Dispatcher.exec_command(:project, ARGV.dup)
|
data/intent.gemspec
CHANGED
@@ -32,6 +32,7 @@ Gem::Specification.new do |spec|
|
|
32
32
|
spec.add_runtime_dependency "tty-table", "~> 0.12.0"
|
33
33
|
spec.add_runtime_dependency "tty-tree", "~> 0.4.0"
|
34
34
|
spec.add_runtime_dependency "nanoid", "~> 2.0.0"
|
35
|
+
spec.add_runtime_dependency "paint", "~> 2.3.0"
|
35
36
|
|
36
37
|
spec.add_development_dependency "bundler"
|
37
38
|
spec.add_development_dependency "rake"
|
data/lib/intent/args.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
module Intent
|
2
|
+
class Args
|
3
|
+
def initialize(input)
|
4
|
+
@arity = input.length
|
5
|
+
|
6
|
+
unless empty?
|
7
|
+
case @arity
|
8
|
+
when 1 then interpret_args_unary(input[0])
|
9
|
+
when 2 then interpret_args_binary(input[0], input[1])
|
10
|
+
when 3 then interpret_args_ternary(input[0], input[1], input[2])
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def empty?
|
16
|
+
@arity == 0
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def interpret_args_unary(arg1)
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
def interpret_args_binary(arg1, arg2)
|
26
|
+
end
|
27
|
+
|
28
|
+
def interpret_args_ternary(arg1, arg2, arg3)
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -1,8 +1,17 @@
|
|
1
1
|
module Intent
|
2
2
|
module Commands
|
3
|
-
class Intent
|
3
|
+
class Intent < Base
|
4
4
|
def run(args, output)
|
5
|
-
|
5
|
+
if args.empty?
|
6
|
+
print_help(output)
|
7
|
+
else
|
8
|
+
case args.first.to_sym
|
9
|
+
when :help
|
10
|
+
print_help(output)
|
11
|
+
else
|
12
|
+
raise Core::Errors::COMMAND_NOT_FOUND
|
13
|
+
end
|
14
|
+
end
|
6
15
|
end
|
7
16
|
end
|
8
17
|
end
|
@@ -1,5 +1,3 @@
|
|
1
|
-
require 'tty-reader'
|
2
|
-
|
3
1
|
module Intent
|
4
2
|
module Commands
|
5
3
|
class Inventory < Base
|
@@ -28,6 +26,12 @@ module Intent
|
|
28
26
|
when :folder then assign_folder(args, output)
|
29
27
|
when :box then assign_box(args, output)
|
30
28
|
end
|
29
|
+
# CONCEPT:
|
30
|
+
# Verbs::Assign.invoke_rewrite()
|
31
|
+
# verbs.assign.invoke_rewrite(documents.inventory, noun)
|
32
|
+
when :sync then sync_inventory(args, output)
|
33
|
+
else
|
34
|
+
raise Errors:COMMAND_NOT_FOUND
|
31
35
|
end
|
32
36
|
end
|
33
37
|
end
|
@@ -35,7 +39,7 @@ module Intent
|
|
35
39
|
private
|
36
40
|
|
37
41
|
def inventory_tree
|
38
|
-
|
42
|
+
color = ::Intent::UI::TermColor.new
|
39
43
|
root = {}
|
40
44
|
documents.inventory.boxes.each do |box|
|
41
45
|
color_code = case box.tags[:id][0].downcase.to_sym
|
@@ -46,21 +50,23 @@ module Intent
|
|
46
50
|
else
|
47
51
|
:white
|
48
52
|
end
|
49
|
-
box_key = "#{
|
53
|
+
box_key = "#{color.bold.decorate(box.tags[:id], color_code)} #{box.text}"
|
50
54
|
child_items = documents.inventory.items_in(box.tags[:id]).map do |item|
|
51
|
-
"#{
|
55
|
+
"#{color.bold.decorate(item.tags[:id], color_code)} #{item.text}"
|
52
56
|
end
|
53
57
|
root[box_key] = child_items
|
54
58
|
end
|
55
59
|
root
|
56
60
|
end
|
57
61
|
|
62
|
+
# ui format reader
|
58
63
|
def inventory_units_of(type)
|
59
64
|
documents.inventory.units_of(type).map do |unit|
|
60
65
|
[unit.text, unit.tags[:sku]]
|
61
66
|
end.to_h
|
62
67
|
end
|
63
68
|
|
69
|
+
# ui format reader
|
64
70
|
def inventory_unassigned_folders
|
65
71
|
folder_types = documents.inventory.units_of(:folder)
|
66
72
|
documents.inventory.unassigned_folders.map do |folder|
|
@@ -69,6 +75,16 @@ module Intent
|
|
69
75
|
end.to_h
|
70
76
|
end
|
71
77
|
|
78
|
+
# ui format reader
|
79
|
+
def inventory_assigned_boxes
|
80
|
+
box_types = documents.inventory.units_of(:box)
|
81
|
+
documents.inventory.assigned_boxes.map do |box|
|
82
|
+
unit_label = box_types.find { |f| f.tags[:sku] == box.tags[:sku] }
|
83
|
+
["#{box.text} #{box.tags[:id]} (#{unit_label.text})", box.tags[:id]]
|
84
|
+
end.to_h
|
85
|
+
end
|
86
|
+
|
87
|
+
# ui format reader
|
72
88
|
def inventory_unassigned_boxes
|
73
89
|
box_types = documents.inventory.units_of(:box)
|
74
90
|
documents.inventory.unassigned_boxes.map do |box|
|
@@ -98,6 +114,7 @@ module Intent
|
|
98
114
|
|
99
115
|
def add_folder(args, output)
|
100
116
|
skus = inventory_units_of(:folder)
|
117
|
+
projects = documents.projects.all_tokens
|
101
118
|
prompt = TTY::Prompt.new
|
102
119
|
ref = self
|
103
120
|
|
@@ -105,12 +122,35 @@ module Intent
|
|
105
122
|
key(:sku).select('sku:', skus, filter: true)
|
106
123
|
key(:id).ask('id:', default: ref.generate_id)
|
107
124
|
key(:label).ask('label:', default: '[Unlabelled Folder]')
|
108
|
-
key(:active).yes?('is active:')
|
109
125
|
end
|
110
|
-
|
111
|
-
label = item[:active] ? "#{item[:label]} @active" : item[:label]
|
112
126
|
|
113
|
-
|
127
|
+
should_assign_projects = prompt.yes?('assign to projects:')
|
128
|
+
|
129
|
+
if should_assign_projects
|
130
|
+
assigned_projects = prompt.multi_select('projects:', projects, filter: true)
|
131
|
+
label = "#{item[:label]} #{assigned_projects.join(' ')}"
|
132
|
+
else
|
133
|
+
label = item[:label]
|
134
|
+
end
|
135
|
+
|
136
|
+
should_file_in = prompt.select('file in:', {
|
137
|
+
"Active, not in box" => :active,
|
138
|
+
"Unassigned box" => :unassigned,
|
139
|
+
"Assigned project box" => :assigned
|
140
|
+
})
|
141
|
+
|
142
|
+
case should_file_in
|
143
|
+
when :active
|
144
|
+
label << " @active"
|
145
|
+
in_box = nil
|
146
|
+
when :unassigned
|
147
|
+
in_box = prompt.select('box', inventory_unassigned_boxes)
|
148
|
+
when :assigned
|
149
|
+
# TODO: filter on selected projects only
|
150
|
+
in_box = prompt.select('box:', inventory_assigned_boxes)
|
151
|
+
end
|
152
|
+
|
153
|
+
documents.inventory.add_folder!(label, item[:id], item[:sku], in_box)
|
114
154
|
end
|
115
155
|
|
116
156
|
def add_box(args, output)
|
@@ -125,7 +165,7 @@ module Intent
|
|
125
165
|
end
|
126
166
|
|
127
167
|
# Repository write pattern
|
128
|
-
documents.inventory.add_box!(label, item[:id], item[:sku])
|
168
|
+
documents.inventory.add_box!(item[:label], item[:id], item[:sku])
|
129
169
|
|
130
170
|
# Alternative design
|
131
171
|
# noun = create_noun(:box, label, tags)
|
@@ -169,6 +209,11 @@ module Intent
|
|
169
209
|
box.projects.concat(details[:projects])
|
170
210
|
documents.inventory.save!
|
171
211
|
end
|
212
|
+
|
213
|
+
def sync_inventory(args, output)
|
214
|
+
result = documents.inventory.sync!
|
215
|
+
output.puts "Sync inventory result: #{result}"
|
216
|
+
end
|
172
217
|
end
|
173
218
|
end
|
174
219
|
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
module Intent
|
2
|
+
module Commands
|
3
|
+
class Project < Projects
|
4
|
+
def run(args, output)
|
5
|
+
if args.empty?
|
6
|
+
print_help(output)
|
7
|
+
else
|
8
|
+
case args.first.to_sym
|
9
|
+
when :help
|
10
|
+
print_help(output)
|
11
|
+
when :link
|
12
|
+
if args[1].nil?
|
13
|
+
# launch into projects selection?
|
14
|
+
raise "Need to link to a noun at this stage, sorry"
|
15
|
+
else
|
16
|
+
case args[1].to_sym
|
17
|
+
when :notes then link_notes(args, output)
|
18
|
+
else
|
19
|
+
raise "Noun not implemented"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
else
|
23
|
+
raise Errors:COMMAND_NOT_FOUND
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def link_notes(args, output, needs_assign=false)
|
31
|
+
prompt = TTY::Prompt.new
|
32
|
+
|
33
|
+
project = if args.last.start_with?('+')
|
34
|
+
# args.last
|
35
|
+
# if documents.working_directory.is_linked?
|
36
|
+
# documents.working_directory.project
|
37
|
+
# else
|
38
|
+
# end
|
39
|
+
raise "Good idea and should work but not implemented properly, sorry"
|
40
|
+
else
|
41
|
+
# TODO: need to test if working_directory is documents_dir
|
42
|
+
#
|
43
|
+
if documents.working_directory.is_linked?
|
44
|
+
documents.working_directory.project
|
45
|
+
else
|
46
|
+
needs_assign = true
|
47
|
+
p_name = prompt.select('assign directory to project:', documents.projects.all_tokens)
|
48
|
+
p_id = prompt.ask('id for this directory:', default: generate_id)
|
49
|
+
p_name
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
if needs_assign
|
54
|
+
c_id = documents.inventory.local_computer_id
|
55
|
+
documents.projects.add_directory!(documents.working_directory.path, p_name, p_id, c_id)
|
56
|
+
end
|
57
|
+
|
58
|
+
project_ref = project.sub('+', '')
|
59
|
+
|
60
|
+
# Symlink done using maetl local notes convention, this could be improved
|
61
|
+
# and made more flexible. Would also be good to delegate this outside
|
62
|
+
# of the UI routine link_notes()
|
63
|
+
target_path = File.join(Env.documents_dir, project.sub('+', ''), 'notes.md')
|
64
|
+
|
65
|
+
if File.exist?(target_path)
|
66
|
+
# TODO: use directory instance given by input filtering so that
|
67
|
+
# operations can work on both working dir and a path provided in the UI
|
68
|
+
symbolic_path = File.join(documents.working_directory.path, 'NOTES.md')
|
69
|
+
File.symlink(target_path, symbolic_path)
|
70
|
+
|
71
|
+
# ignore_path = File.join(documents.working_directory.path, '.gitignore')
|
72
|
+
# if File.exist?(ignore_path)
|
73
|
+
# unless File.read(ignore_path).to_s.include?('NOTES.md')
|
74
|
+
# File.write(ignore_path, "NOTES.md\n", mode: 'a+')
|
75
|
+
# end
|
76
|
+
# end
|
77
|
+
else
|
78
|
+
output.puts "#{project} does not have notes"
|
79
|
+
end
|
80
|
+
|
81
|
+
# output.puts Dir.pwd
|
82
|
+
# output.puts ::Intent::Env.documents_dir
|
83
|
+
# output.puts ::Intent::Env.computer_serial
|
84
|
+
# Check if current working directory is a linked directory
|
85
|
+
#item = documents.projects.linked_directory(Dir.pwd)
|
86
|
+
#output.puts item
|
87
|
+
#File.symlink("DOCUMENT_DIR/project-name/notes.md", "Projects/project-name/NOTES.md")
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
@@ -1,5 +1,3 @@
|
|
1
|
-
require 'readline'
|
2
|
-
|
3
1
|
module Intent
|
4
2
|
module Commands
|
5
3
|
class Projects < Base
|
@@ -14,8 +12,10 @@ module Intent
|
|
14
12
|
documents.projects.all.each do |task|
|
15
13
|
output.puts task.highlight_as_project
|
16
14
|
end
|
17
|
-
when :add
|
18
|
-
|
15
|
+
when :add then add_project(args, output)
|
16
|
+
when :sync then sync_project(args, output)
|
17
|
+
else
|
18
|
+
raise Errors:COMMAND_NOT_FOUND
|
19
19
|
end
|
20
20
|
end
|
21
21
|
end
|
@@ -29,6 +29,11 @@ module Intent
|
|
29
29
|
name = "+#{name}" unless name.start_with?('+')
|
30
30
|
output.puts name
|
31
31
|
end
|
32
|
+
|
33
|
+
def sync_project(args, output)
|
34
|
+
result = documents.projects.sync!
|
35
|
+
output.puts "Sync projects result: #{result}"
|
36
|
+
end
|
32
37
|
end
|
33
38
|
end
|
34
39
|
end
|
data/lib/intent/commands.rb
CHANGED
@@ -3,7 +3,7 @@ require 'intent/commands/errors'
|
|
3
3
|
require 'intent/commands/intent'
|
4
4
|
require 'intent/commands/todo'
|
5
5
|
require 'intent/commands/projects'
|
6
|
-
|
6
|
+
require 'intent/commands/project'
|
7
7
|
#require 'intent/commands/ideas'
|
8
8
|
#require 'intent/commands/idea'
|
9
9
|
require 'intent/commands/inventory'
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Intent
|
2
|
+
module Core
|
3
|
+
class Directory
|
4
|
+
attr_reader :path
|
5
|
+
|
6
|
+
def initialize(path, projects)
|
7
|
+
@path = path
|
8
|
+
@ledger = projects
|
9
|
+
@record = projects.all.find { |d| d.tags[:is] == 'directory' && d.text == @path }
|
10
|
+
end
|
11
|
+
|
12
|
+
def project
|
13
|
+
record.projects.first
|
14
|
+
end
|
15
|
+
|
16
|
+
def is_linked?
|
17
|
+
!record.nil? && record.projects.any?
|
18
|
+
end
|
19
|
+
|
20
|
+
def assign!(project)
|
21
|
+
if record.nil?
|
22
|
+
@record = Record.new("#{Date.today} #{@path} is:directory type:#{type} sku:#{sku}")
|
23
|
+
ledger.append(record)
|
24
|
+
else
|
25
|
+
record.projects << project
|
26
|
+
end
|
27
|
+
|
28
|
+
ledger.save!
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
attr_reader :ledger
|
34
|
+
attr_reader :record
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Intent
|
2
|
+
module Core
|
3
|
+
class Documents
|
4
|
+
attr_reader :projects
|
5
|
+
attr_reader :inventory
|
6
|
+
attr_reader :inbox
|
7
|
+
attr_reader :working_directory
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@projects = Projects.new("#{Intent::Env.documents_dir}/projects.txt")
|
11
|
+
@inventory = Inventory.new("#{Intent::Env.documents_dir}/inventory.txt")
|
12
|
+
@inbox = Inbox.new("#{Intent::Env.documents_dir}/todo.txt")
|
13
|
+
@working_directory = Directory.new(Dir.pwd, @projects)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Intent
|
2
|
+
module Core
|
3
|
+
class Inbox
|
4
|
+
attr_reader :list
|
5
|
+
|
6
|
+
def initialize(db_path)
|
7
|
+
@list = List.new(db_path)
|
8
|
+
end
|
9
|
+
|
10
|
+
def all
|
11
|
+
@list.by_not_done
|
12
|
+
end
|
13
|
+
|
14
|
+
def focused
|
15
|
+
@list.by_context('@focus').by_not_done
|
16
|
+
end
|
17
|
+
|
18
|
+
def focused_projects
|
19
|
+
focused.map { |t| t.projects }.flatten.uniq
|
20
|
+
end
|
21
|
+
|
22
|
+
def add_line!(line)
|
23
|
+
record = Record.new("#{Date.today} #{line}")
|
24
|
+
@list.prepend(record)
|
25
|
+
@list.save!
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
module Intent
|
2
|
+
module Core
|
3
|
+
class Inventory
|
4
|
+
attr_reader :list
|
5
|
+
attr_reader :ledger_path
|
6
|
+
|
7
|
+
def initialize(db_path)
|
8
|
+
@ledger_path = db_path
|
9
|
+
@list = List.new(db_path)
|
10
|
+
end
|
11
|
+
|
12
|
+
def all
|
13
|
+
list.by_not_done
|
14
|
+
end
|
15
|
+
|
16
|
+
def folder_by_id(id)
|
17
|
+
all.find { |i| i.tags[:is] == 'folder' && i.tags[:id] == id }
|
18
|
+
end
|
19
|
+
|
20
|
+
def folders
|
21
|
+
all.filter { |i| i.tags[:is] == 'folder' }
|
22
|
+
end
|
23
|
+
|
24
|
+
def items_in(id)
|
25
|
+
all.filter { |i| i.tags[:in] == id }
|
26
|
+
end
|
27
|
+
|
28
|
+
def unassigned_folders
|
29
|
+
all.filter { |i| i.tags[:is] == 'folder' && i.projects.empty? }
|
30
|
+
end
|
31
|
+
|
32
|
+
def assigned_folders
|
33
|
+
all.filter { |i| i.tags[:is] == 'folder' && i.projects.any? }
|
34
|
+
end
|
35
|
+
|
36
|
+
def boxes
|
37
|
+
all.filter { |i| i.tags[:is] == 'box' }
|
38
|
+
end
|
39
|
+
|
40
|
+
def unassigned_boxes
|
41
|
+
all.filter { |i| i.tags[:is] == 'box' && i.projects.empty? }
|
42
|
+
end
|
43
|
+
|
44
|
+
def assigned_boxes
|
45
|
+
all.filter { |i| i.tags[:is] == 'box' && i.projects.any? }
|
46
|
+
end
|
47
|
+
|
48
|
+
def units_of(noun)
|
49
|
+
all.filter { |i| i.tags[:is] == 'unit' && i.tags[:type] == noun.to_s }
|
50
|
+
end
|
51
|
+
|
52
|
+
def local_computer_id
|
53
|
+
all.find { |i| i.tags[:is] == 'computer' && i.tags[:serial] == Env.computer_serial }.tags[:id]
|
54
|
+
end
|
55
|
+
|
56
|
+
def add_unit!(description, type, sku)
|
57
|
+
record = Record.new("#{Date.today} #{description} is:unit type:#{type} sku:#{sku}")
|
58
|
+
@list.append(record)
|
59
|
+
@list.save!
|
60
|
+
end
|
61
|
+
|
62
|
+
def add_item!(description, id, type, sku, box=nil)
|
63
|
+
description << " in:#{box}" unless box.nil?
|
64
|
+
record = Record.new("#{Date.today} #{description} id:#{id} is:#{type} sku:#{sku}")
|
65
|
+
@list.append(record)
|
66
|
+
@list.save!
|
67
|
+
end
|
68
|
+
|
69
|
+
def add_folder!(description, id, sku, box=nil)
|
70
|
+
add_item!(description, id, :folder, sku, box)
|
71
|
+
end
|
72
|
+
|
73
|
+
def add_box!(description, id, sku)
|
74
|
+
add_item!(description, id, :box, sku)
|
75
|
+
end
|
76
|
+
|
77
|
+
def save!
|
78
|
+
@list.save!
|
79
|
+
end
|
80
|
+
|
81
|
+
def sync!
|
82
|
+
ledger_file = File.basename(ledger_path)
|
83
|
+
|
84
|
+
if repo.status.changed?(ledger_file)
|
85
|
+
repo.add(ledger_file)
|
86
|
+
repo.commit("Synchronizing inventory [#{Time.new}]")
|
87
|
+
repo.push
|
88
|
+
true # Result:OK
|
89
|
+
else
|
90
|
+
false # Result::NO_CHANGES
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
private
|
95
|
+
|
96
|
+
def repo
|
97
|
+
@repo ||= Git.open(Env.documents_dir, :log => Logger.new(STDERR, level: Logger::INFO))
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
module Intent
|
2
|
+
module Core
|
3
|
+
class Projects
|
4
|
+
attr_reader :list
|
5
|
+
attr_reader :ledger_path
|
6
|
+
|
7
|
+
def initialize(db_path)
|
8
|
+
@ledger_path = db_path
|
9
|
+
@list = List.new(db_path)
|
10
|
+
end
|
11
|
+
|
12
|
+
def all
|
13
|
+
list.by_not_done
|
14
|
+
end
|
15
|
+
|
16
|
+
def all_tokens
|
17
|
+
all.map { |item| item.projects.first }.uniq
|
18
|
+
end
|
19
|
+
|
20
|
+
def linked_directory(path)
|
21
|
+
all.find { |item| item.text == path && ['repository', 'directory'].include?(item.tags[:is]) }
|
22
|
+
end
|
23
|
+
|
24
|
+
def sync!
|
25
|
+
ledger_file = File.basename(ledger_path)
|
26
|
+
|
27
|
+
if repo.status.changed?(ledger_file)
|
28
|
+
repo.add(ledger_file)
|
29
|
+
repo.commit("Synchronizing projects [#{Time.new}]")
|
30
|
+
repo.push
|
31
|
+
true # Result:OK
|
32
|
+
else
|
33
|
+
false # Result::NO_CHANGES
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def add_directory!(path, project, id, computer)
|
38
|
+
record = Record.new([
|
39
|
+
Date.today,
|
40
|
+
path,
|
41
|
+
project,
|
42
|
+
"is:directory",
|
43
|
+
"id:#{id}",
|
44
|
+
"computer:#{computer}"
|
45
|
+
].join(' '))
|
46
|
+
|
47
|
+
@list.append(record)
|
48
|
+
@list.save!
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
def repo
|
54
|
+
@repo ||= Git.open(Env.documents_dir, :log => Logger.new(STDERR, level: Logger::INFO))
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
data/lib/intent/core.rb
CHANGED
@@ -1,25 +1,25 @@
|
|
1
1
|
module Intent
|
2
|
-
module
|
3
|
-
|
4
|
-
|
5
|
-
end
|
2
|
+
module Core
|
3
|
+
List = ::Todo::List
|
4
|
+
Record = ::Todo::Task
|
6
5
|
|
7
|
-
|
8
|
-
ENV['INTENT_INBOX_DIR']
|
9
|
-
end
|
6
|
+
VERBS = [:add, :assign, :list, :link, :sync]
|
10
7
|
|
11
|
-
|
12
|
-
|
13
|
-
|
8
|
+
NOUNS = {
|
9
|
+
inventory: [:unit, :box, :folder, :computer],
|
10
|
+
projects: [:project, :directory, :repository],
|
11
|
+
todo: [:task]
|
12
|
+
}
|
13
|
+
|
14
|
+
class Action
|
15
|
+
attr_reader :verb
|
16
|
+
attr_reader :noun
|
14
17
|
|
15
|
-
|
16
|
-
|
18
|
+
def initialize(verb_sym, noun_r)
|
19
|
+
@verb = Verbs.instance_for(verb_sym)
|
20
|
+
@noun = Noun.new(noun_r.type, noun_r.label, noun_r.tags)
|
21
|
+
end
|
17
22
|
end
|
18
|
-
end
|
19
|
-
|
20
|
-
module Core
|
21
|
-
List = ::Todo::List
|
22
|
-
Record = ::Todo::Task
|
23
23
|
|
24
24
|
class Noun
|
25
25
|
def initialize(type, label, tags)
|
@@ -35,150 +35,11 @@ module Intent
|
|
35
35
|
p tags
|
36
36
|
end
|
37
37
|
end
|
38
|
-
|
39
|
-
class Projects
|
40
|
-
attr_reader :list
|
41
|
-
|
42
|
-
def initialize(db_path)
|
43
|
-
@list = List.new(db_path)
|
44
|
-
end
|
45
|
-
|
46
|
-
def all
|
47
|
-
list.by_not_done
|
48
|
-
end
|
49
|
-
|
50
|
-
def all_tokens
|
51
|
-
all.map { |project| project.projects.first }
|
52
|
-
end
|
53
|
-
end
|
54
|
-
|
55
|
-
class Inventory
|
56
|
-
attr_reader :list
|
57
|
-
|
58
|
-
def initialize(db_path)
|
59
|
-
@list = List.new(db_path)
|
60
|
-
end
|
61
|
-
|
62
|
-
def all
|
63
|
-
list.by_not_done
|
64
|
-
end
|
65
|
-
|
66
|
-
def folder_by_id(id)
|
67
|
-
all.find { |i| i.tags[:is] == 'folder' && i.tags[:id] == id }
|
68
|
-
end
|
69
|
-
|
70
|
-
def folders
|
71
|
-
all.filter { |i| i.tags[:is] == 'folder' }
|
72
|
-
end
|
73
|
-
|
74
|
-
def items_in(id)
|
75
|
-
all.filter { |i| i.tags[:in] == id }
|
76
|
-
end
|
77
|
-
|
78
|
-
def unassigned_folders
|
79
|
-
all.filter { |i| i.tags[:is] == 'folder' && i.projects.empty? }
|
80
|
-
end
|
81
|
-
|
82
|
-
def assigned_folders
|
83
|
-
all.filter { |i| i.tags[:is] == 'folder' && i.projects.any? }
|
84
|
-
end
|
85
|
-
|
86
|
-
def boxes
|
87
|
-
all.filter { |i| i.tags[:is] == 'box' }
|
88
|
-
end
|
89
|
-
|
90
|
-
def unassigned_boxes
|
91
|
-
all.filter { |i| i.tags[:is] == 'box' && i.projects.empty? }
|
92
|
-
end
|
93
|
-
|
94
|
-
def assigned_boxes
|
95
|
-
all.filter { |i| i.tags[:is] == 'box' && i.projects.any? }
|
96
|
-
end
|
97
|
-
|
98
|
-
def units_of(noun)
|
99
|
-
all.filter { |i| i.tags[:is] == 'unit' && i.tags[:type] == noun.to_s }
|
100
|
-
end
|
101
|
-
|
102
|
-
def add_unit!(description, type, sku)
|
103
|
-
record = Record.new("#{Date.today} #{description} is:unit type:#{type} sku:#{sku}")
|
104
|
-
@list.append(record)
|
105
|
-
@list.save!
|
106
|
-
end
|
107
|
-
|
108
|
-
def add_item!(description, id, type, sku)
|
109
|
-
record = Record.new("#{Date.today} #{description} id:#{id} is:#{type} sku:#{sku}")
|
110
|
-
@list.append(record)
|
111
|
-
@list.save!
|
112
|
-
end
|
113
|
-
|
114
|
-
def add_folder!(description, id, sku)
|
115
|
-
add_item!(description, id, :folder, sku)
|
116
|
-
end
|
117
|
-
|
118
|
-
def add_box!(description, id, sku)
|
119
|
-
add_item!(description, id, :box, sku)
|
120
|
-
end
|
121
|
-
|
122
|
-
def save!
|
123
|
-
@list.save!
|
124
|
-
end
|
125
|
-
end
|
126
|
-
|
127
|
-
class Inbox
|
128
|
-
attr_reader :list
|
129
|
-
|
130
|
-
def initialize(db_path)
|
131
|
-
@list = List.new(db_path)
|
132
|
-
end
|
133
|
-
|
134
|
-
def all
|
135
|
-
@list.by_not_done
|
136
|
-
end
|
137
|
-
|
138
|
-
def focused
|
139
|
-
@list.by_context('@focus').by_not_done
|
140
|
-
end
|
141
|
-
|
142
|
-
def focused_projects
|
143
|
-
focused.map { |t| t.projects }.flatten.uniq
|
144
|
-
end
|
145
|
-
|
146
|
-
def add_line!(line)
|
147
|
-
record = Record.new("#{Date.today} #{line}")
|
148
|
-
@list.prepend(record)
|
149
|
-
@list.save!
|
150
|
-
end
|
151
|
-
end
|
152
|
-
|
153
|
-
class Documents
|
154
|
-
attr_reader :projects
|
155
|
-
attr_reader :inventory
|
156
|
-
attr_reader :inbox
|
157
|
-
|
158
|
-
def initialize
|
159
|
-
@projects = Projects.new("#{Intent::Env.documents_dir}/projects.txt")
|
160
|
-
@inventory = Inventory.new("#{Intent::Env.documents_dir}/inventory.txt")
|
161
|
-
@inbox = Inbox.new("#{Intent::Env.documents_dir}/todo.txt")
|
162
|
-
end
|
163
|
-
end
|
164
|
-
end
|
165
|
-
|
166
|
-
class Dispatcher
|
167
|
-
def self.exec_command(command, args, output=STDOUT)
|
168
|
-
command = init_command(command).new
|
169
|
-
command.run(args, output)
|
170
|
-
end
|
171
|
-
|
172
|
-
def self.init_command(command)
|
173
|
-
case command
|
174
|
-
when :intent then return Commands::Intent
|
175
|
-
when :inventory then return Commands::Inventory
|
176
|
-
when :projects then return Commands::Projects
|
177
|
-
when :project then return Commands::Project
|
178
|
-
when :todo then return Commands::Todo
|
179
|
-
else
|
180
|
-
raise Commands::Errors::COMMAND_NOT_FOUND
|
181
|
-
end
|
182
|
-
end
|
183
38
|
end
|
184
39
|
end
|
40
|
+
|
41
|
+
require 'intent/core/projects'
|
42
|
+
require 'intent/core/inventory'
|
43
|
+
require 'intent/core/inbox'
|
44
|
+
require 'intent/core/directory'
|
45
|
+
require 'intent/core/documents'
|
data/lib/intent/desktop.rb
CHANGED
@@ -0,0 +1,20 @@
|
|
1
|
+
module Intent
|
2
|
+
class Dispatcher
|
3
|
+
def self.exec_command(command, args, output=STDOUT)
|
4
|
+
command = init_command(command).new
|
5
|
+
command.run(args, output)
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.init_command(command)
|
9
|
+
case command
|
10
|
+
when :intent then return Commands::Intent
|
11
|
+
when :inventory then return Commands::Inventory
|
12
|
+
when :projects then return Commands::Projects
|
13
|
+
when :project then return Commands::Project
|
14
|
+
when :todo then return Commands::Todo
|
15
|
+
else
|
16
|
+
raise Commands::Errors::COMMAND_NOT_FOUND
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
data/lib/intent/env.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
module Intent
|
2
|
+
module Env
|
3
|
+
def self.documents_dir
|
4
|
+
File.expand_path(ENV['INTENT_DOCUMENTS_DIR'] || "").to_s
|
5
|
+
end
|
6
|
+
|
7
|
+
def self.inbox_dir
|
8
|
+
ENV['INTENT_INBOX_DIR']
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.assets_dir
|
12
|
+
ENV['INTENT_ARCHIVE_DIR']
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.projects_dir
|
16
|
+
ENV['INTENT_PROJECTS_DIR']
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.computer_serial
|
20
|
+
# macOS: `system_profiler SPHardwareDataType`
|
21
|
+
# Win: `wmic bios get serialnumber`
|
22
|
+
# Linux: `ls /sys/devices/virtual/dmi/id`
|
23
|
+
ENV['INTENT_COMPUTER_SERIAL']
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
====================================================================
|
2
|
+
Intent . Havin a blast on the command line
|
3
|
+
====================================================================
|
4
|
+
|
5
|
+
Welcome to the only task manager you will (n)ever need.
|
6
|
+
|
7
|
+
These docs are not written for a wide audience, but here’s the
|
8
|
+
essentials:
|
9
|
+
|
10
|
+
Verbs
|
11
|
+
-----
|
12
|
+
|
13
|
+
### `add [noun]`
|
14
|
+
|
15
|
+
Adds [noun] to the ledger denoted by the given command.
|
16
|
+
|
17
|
+
```
|
18
|
+
projects add
|
19
|
+
projects add project
|
20
|
+
projects add directory
|
21
|
+
projects add repository
|
22
|
+
|
23
|
+
project add
|
24
|
+
|
25
|
+
inventory add
|
26
|
+
inventory add folder
|
27
|
+
inventory add box
|
28
|
+
inventory add unit
|
29
|
+
inventory add computer
|
30
|
+
|
31
|
+
todo add
|
32
|
+
|
33
|
+
inbox add
|
34
|
+
```
|
35
|
+
|
36
|
+
### `assign [noun]`
|
37
|
+
|
38
|
+
Assigns projects, contexts, tags and associations to ap.
|
39
|
+
|
40
|
+
```
|
41
|
+
projects assign
|
42
|
+
|
43
|
+
project assign
|
44
|
+
|
45
|
+
inventory assign
|
46
|
+
```
|
47
|
+
|
48
|
+
### `list [noun]`
|
49
|
+
|
50
|
+
Lists elements in the ledger.
|
51
|
+
|
52
|
+
```
|
53
|
+
projects list
|
54
|
+
|
55
|
+
project list
|
56
|
+
|
57
|
+
inventory list
|
58
|
+
|
59
|
+
todo list
|
60
|
+
|
61
|
+
inbox list
|
62
|
+
```
|
63
|
+
|
64
|
+
### `link [noun]`
|
65
|
+
|
66
|
+
Links elements from a local directory to an assigned project.
|
67
|
+
|
68
|
+
```
|
69
|
+
project link
|
70
|
+
```
|
71
|
+
|
72
|
+
### `sync [noun]`
|
73
|
+
|
74
|
+
Synchronises changes to the Git origin.
|
75
|
+
|
76
|
+
```
|
77
|
+
projects sync
|
78
|
+
|
79
|
+
inventory sync
|
80
|
+
```
|
81
|
+
|
82
|
+
Documentation is patchy and not always up to date.
|
File without changes
|
@@ -0,0 +1,80 @@
|
|
1
|
+
module Intent
|
2
|
+
module UI
|
3
|
+
# Shim to convert between Pastel to Paint gems
|
4
|
+
# without needing to edit existing call sites.
|
5
|
+
#
|
6
|
+
# It might be helpful to normalize this convention with an API
|
7
|
+
# anyway as we generally want to use the 8/16 colour defaults
|
8
|
+
# as they pick up user terminal customisation properly, whereas
|
9
|
+
# going full 256 or 24 bit colour means generic RGB values are likely
|
10
|
+
# to look shit on customised terminal backgrounds.
|
11
|
+
#
|
12
|
+
# This way we get the benefits of *mostly* sticking to the terminal
|
13
|
+
# defaults, while extending the range of colours with a few carefully
|
14
|
+
# chosen values.
|
15
|
+
class TermColor
|
16
|
+
def initialize
|
17
|
+
@decoration_scope = []
|
18
|
+
end
|
19
|
+
|
20
|
+
def decorate(text, *args)
|
21
|
+
decoration_scope.push(*args)
|
22
|
+
return_decorator(text)
|
23
|
+
end
|
24
|
+
|
25
|
+
def bold(text=nil)
|
26
|
+
decoration_scope.push(:bold)
|
27
|
+
return_decorator(text)
|
28
|
+
end
|
29
|
+
|
30
|
+
def red(text=nil)
|
31
|
+
decoration_scope.push(:red)
|
32
|
+
return_decorator(text)
|
33
|
+
end
|
34
|
+
|
35
|
+
def green(text)
|
36
|
+
decoration_scope.push(:green)
|
37
|
+
return_decorator(text)
|
38
|
+
end
|
39
|
+
|
40
|
+
def blue(text)
|
41
|
+
decoration_scope.push(:blue)
|
42
|
+
return_decorator(text)
|
43
|
+
end
|
44
|
+
|
45
|
+
def yellow(text)
|
46
|
+
decoration_scope.push(:yellow)
|
47
|
+
return_decorator(text)
|
48
|
+
end
|
49
|
+
|
50
|
+
def cyan(text)
|
51
|
+
decoration_scope.push(:cyan)
|
52
|
+
return_decorator(text)
|
53
|
+
end
|
54
|
+
|
55
|
+
def orange(text)
|
56
|
+
decoration_scope.push('orange')
|
57
|
+
return_decorator(text)
|
58
|
+
end
|
59
|
+
|
60
|
+
def brown(text)
|
61
|
+
decoration_scope.push('tan')
|
62
|
+
return_decorator(text)
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
attr_reader :decoration_scope
|
68
|
+
|
69
|
+
def return_decorator(text)
|
70
|
+
if text.nil?
|
71
|
+
self
|
72
|
+
else
|
73
|
+
decorated = Paint[text, *decoration_scope]
|
74
|
+
decoration_scope.clear
|
75
|
+
decorated
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
data/lib/intent/version.rb
CHANGED
data/lib/intent.rb
CHANGED
@@ -1,21 +1,37 @@
|
|
1
1
|
#require 'random'
|
2
|
-
require '
|
2
|
+
#require 'pathname'
|
3
|
+
|
4
|
+
# Pastel still required but deprecated
|
3
5
|
require 'pastel'
|
6
|
+
require 'paint'
|
7
|
+
|
8
|
+
# CLI UI tools
|
4
9
|
require 'tty-prompt'
|
5
10
|
require 'tty-table'
|
6
11
|
require 'tty-tree'
|
12
|
+
|
13
|
+
# Identifiers
|
7
14
|
require 'nanoid'
|
15
|
+
#require 'calyx'
|
16
|
+
|
17
|
+
# Interact with git repos
|
18
|
+
require 'logger'
|
19
|
+
require 'git'
|
20
|
+
|
21
|
+
# todo.txt data structure support
|
22
|
+
require 'todo-txt'
|
8
23
|
require 'gem_ext/todo-txt'
|
9
24
|
|
10
25
|
Todo.customize do |options|
|
11
26
|
options.require_completed_on = false
|
12
27
|
end
|
13
28
|
|
29
|
+
# Intent library
|
14
30
|
require 'intent/version'
|
31
|
+
require 'intent/env'
|
32
|
+
require 'intent/args'
|
15
33
|
require 'intent/core'
|
34
|
+
#require 'intent/verbs'
|
16
35
|
require 'intent/commands'
|
17
|
-
|
18
|
-
|
19
|
-
# require 'intent/projects'
|
20
|
-
# require 'intent/desktop'
|
21
|
-
|
36
|
+
require 'intent/ui/term_color'
|
37
|
+
require 'intent/dispatcher'
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: intent
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.8.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Mark Rickerby
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-02-
|
11
|
+
date: 2024-02-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: todo-txt
|
@@ -206,6 +206,20 @@ dependencies:
|
|
206
206
|
- - "~>"
|
207
207
|
- !ruby/object:Gem::Version
|
208
208
|
version: 2.0.0
|
209
|
+
- !ruby/object:Gem::Dependency
|
210
|
+
name: paint
|
211
|
+
requirement: !ruby/object:Gem::Requirement
|
212
|
+
requirements:
|
213
|
+
- - "~>"
|
214
|
+
- !ruby/object:Gem::Version
|
215
|
+
version: 2.3.0
|
216
|
+
type: :runtime
|
217
|
+
prerelease: false
|
218
|
+
version_requirements: !ruby/object:Gem::Requirement
|
219
|
+
requirements:
|
220
|
+
- - "~>"
|
221
|
+
- !ruby/object:Gem::Version
|
222
|
+
version: 2.3.0
|
209
223
|
- !ruby/object:Gem::Dependency
|
210
224
|
name: bundler
|
211
225
|
requirement: !ruby/object:Gem::Requirement
|
@@ -254,9 +268,9 @@ email:
|
|
254
268
|
executables:
|
255
269
|
- intent
|
256
270
|
- inventory
|
271
|
+
- project
|
257
272
|
- project_doc
|
258
273
|
- projects
|
259
|
-
- projects_active
|
260
274
|
- todo
|
261
275
|
- todo_review
|
262
276
|
extensions: []
|
@@ -269,30 +283,42 @@ files:
|
|
269
283
|
- Rakefile
|
270
284
|
- bin/intent
|
271
285
|
- bin/inventory
|
286
|
+
- bin/project
|
272
287
|
- bin/project_doc
|
273
288
|
- bin/projects
|
274
|
-
- bin/projects_active
|
275
289
|
- bin/todo
|
276
290
|
- bin/todo_review
|
277
291
|
- intent.gemspec
|
278
292
|
- lib/gem_ext/todo-txt.rb
|
279
293
|
- lib/intent.rb
|
294
|
+
- lib/intent/args.rb
|
280
295
|
- lib/intent/commands.rb
|
281
296
|
- lib/intent/commands/base.rb
|
282
297
|
- lib/intent/commands/errors.rb
|
283
298
|
- lib/intent/commands/intent.rb
|
284
299
|
- lib/intent/commands/inventory.rb
|
300
|
+
- lib/intent/commands/project.rb
|
285
301
|
- lib/intent/commands/projects.rb
|
286
302
|
- lib/intent/commands/todo.rb
|
287
303
|
- lib/intent/core.rb
|
304
|
+
- lib/intent/core/directory.rb
|
305
|
+
- lib/intent/core/documents.rb
|
306
|
+
- lib/intent/core/inbox.rb
|
307
|
+
- lib/intent/core/inventory.rb
|
308
|
+
- lib/intent/core/projects.rb
|
288
309
|
- lib/intent/desktop.rb
|
310
|
+
- lib/intent/dispatcher.rb
|
311
|
+
- lib/intent/env.rb
|
289
312
|
- lib/intent/projects.rb
|
290
313
|
- lib/intent/review.rb
|
314
|
+
- lib/intent/text/intent.help.txt
|
291
315
|
- lib/intent/text/inventory.help.txt
|
292
316
|
- lib/intent/text/project.help.txt
|
293
317
|
- lib/intent/text/projects.help.txt
|
294
318
|
- lib/intent/text/todo.help.txt
|
295
319
|
- lib/intent/todo.rb
|
320
|
+
- lib/intent/ui/projects_active.rb
|
321
|
+
- lib/intent/ui/term_color.rb
|
296
322
|
- lib/intent/ui/ttyui.rb
|
297
323
|
- lib/intent/verbs/add.rb
|
298
324
|
- lib/intent/verbs/cite.rb
|