dkit 0.2.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/bin/dkit +485 -0
- metadata +46 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 53e0c6ec379900435c8ecf139f2960b5438df945e674a6709be32a71732fd9e9
|
|
4
|
+
data.tar.gz: f2b8ad870c59fc01acba6640e6c02cb13216a303373cfb7e008a2dd3d1e4d7fd
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 788dcd77ebde166204346894dacc6772fbd04fe613acef1a6b72f16fff26f7894a238948d3d26c59377a942c9b28081d28a9b03b366b59a8cddf0372df1dd536
|
|
7
|
+
data.tar.gz: 1d19f03a3b15edf459ad0d20806ed4abce82983b3db85fedd98be820f1b6a9b96a6ee351fc4294908b8cdec56ab496a432bb1eb8bf1a67bb665af86a2fe639e7
|
data/bin/dkit
ADDED
|
@@ -0,0 +1,485 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# dkit — DevKit CLI: routes shell commands into a running devcontainer
|
|
3
|
+
#
|
|
4
|
+
# Install:
|
|
5
|
+
# gem install dkit
|
|
6
|
+
# echo 'eval "$(dkit hook)"' >> ~/.zshrc && exec zsh
|
|
7
|
+
#
|
|
8
|
+
# Install from source:
|
|
9
|
+
# gem build dkit.gemspec && gem install dkit-*.gem
|
|
10
|
+
#
|
|
11
|
+
# Usage: dkit help
|
|
12
|
+
|
|
13
|
+
require 'json'
|
|
14
|
+
require 'yaml'
|
|
15
|
+
require 'pathname'
|
|
16
|
+
require 'shellwords'
|
|
17
|
+
require 'fileutils'
|
|
18
|
+
|
|
19
|
+
VERSION = "0.2.0"
|
|
20
|
+
DC_CONFIG = ".devcontainer/devcontainer.json"
|
|
21
|
+
DC_INTERCEPT = ".devcontainer/dkit-intercept"
|
|
22
|
+
|
|
23
|
+
SPECIAL_COMMANDS = %w[code claude].freeze
|
|
24
|
+
|
|
25
|
+
# ── Helpers ────────────────────────────────────────────────────────────────────
|
|
26
|
+
|
|
27
|
+
def abort_err(msg)
|
|
28
|
+
warn "dkit: #{msg}"
|
|
29
|
+
exit 1
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def docker(*args, capture: false)
|
|
33
|
+
cmd = ["docker", *args]
|
|
34
|
+
if capture
|
|
35
|
+
out = `#{cmd.map(&:shellescape).join(" ")} 2>/dev/null`.strip
|
|
36
|
+
out.empty? ? nil : out
|
|
37
|
+
else
|
|
38
|
+
system(*cmd)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# ── Project root (lightweight — no docker) ─────────────────────────────────────
|
|
43
|
+
|
|
44
|
+
def find_project_root(from: Dir.pwd)
|
|
45
|
+
if (cached = ENV["DKIT_PROJECT_ROOT"]) && !cached.empty? &&
|
|
46
|
+
File.exist?(File.join(cached, DC_CONFIG))
|
|
47
|
+
return cached
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
path = Pathname.new(from)
|
|
51
|
+
loop do
|
|
52
|
+
return path.to_s if (path + DC_CONFIG).exist?
|
|
53
|
+
parent = path.parent
|
|
54
|
+
return nil if parent == path
|
|
55
|
+
path = parent
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# ── Per-project intercept file ─────────────────────────────────────────────────
|
|
60
|
+
|
|
61
|
+
def intercept_file(project_root)
|
|
62
|
+
File.join(project_root, DC_INTERCEPT)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def intercept_list(project_root)
|
|
66
|
+
f = intercept_file(project_root)
|
|
67
|
+
return [] unless File.exist?(f)
|
|
68
|
+
File.readlines(f, chomp: true)
|
|
69
|
+
.reject { |l| l.strip.empty? || l.strip.start_with?("#") }
|
|
70
|
+
.map(&:strip)
|
|
71
|
+
.uniq
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def intercept_add(project_root, cmd)
|
|
75
|
+
list = intercept_list(project_root)
|
|
76
|
+
if list.include?(cmd)
|
|
77
|
+
puts "dkit: '#{cmd}' is already in the intercept list"
|
|
78
|
+
return
|
|
79
|
+
end
|
|
80
|
+
File.open(intercept_file(project_root), "a") { |f| f.puts cmd }
|
|
81
|
+
puts "dkit: added '#{cmd}' — reload shell to activate (exec zsh)"
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def intercept_remove(project_root, cmd)
|
|
85
|
+
f = intercept_file(project_root)
|
|
86
|
+
unless intercept_list(project_root).include?(cmd)
|
|
87
|
+
puts "dkit: '#{cmd}' is not in the intercept list"
|
|
88
|
+
return
|
|
89
|
+
end
|
|
90
|
+
lines = File.readlines(f).reject { |l| l.strip == cmd }
|
|
91
|
+
File.write(f, lines.join)
|
|
92
|
+
puts "dkit: removed '#{cmd}' — reload shell to deactivate (exec zsh)"
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# ── Devcontainer config ────────────────────────────────────────────────────────
|
|
96
|
+
|
|
97
|
+
def load_dc_config(project_root)
|
|
98
|
+
raw = File.read(File.join(project_root, DC_CONFIG))
|
|
99
|
+
raw = raw.gsub(%r{/\*.*?\*/}m, "").gsub(%r{//[^\n]*}, "")
|
|
100
|
+
JSON.parse(raw)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def resolve_container_name(project_root, cfg)
|
|
104
|
+
service = cfg["service"]
|
|
105
|
+
compose_files = Array(cfg["dockerComposeFile"]).map do |f|
|
|
106
|
+
File.expand_path(f, File.join(project_root, ".devcontainer"))
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Strategy A: container_name from compose YAML
|
|
110
|
+
compose_files.each do |cf|
|
|
111
|
+
next unless File.exist?(cf)
|
|
112
|
+
data = YAML.safe_load(File.read(cf))
|
|
113
|
+
name = data.dig("services", service, "container_name")
|
|
114
|
+
return name if name
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# Strategy B: docker label query
|
|
118
|
+
project_name = File.basename(project_root).downcase.gsub(/[^a-z0-9]/, "")
|
|
119
|
+
name = docker("ps",
|
|
120
|
+
"--filter", "label=com.docker.compose.service=#{service}",
|
|
121
|
+
"--filter", "label=com.docker.compose.project=#{project_name}",
|
|
122
|
+
"--format", "{{.Names}}",
|
|
123
|
+
capture: true
|
|
124
|
+
)
|
|
125
|
+
return name if name
|
|
126
|
+
|
|
127
|
+
# Strategy C: docker compose ps -q
|
|
128
|
+
first_file = compose_files.first
|
|
129
|
+
if first_file && File.exist?(first_file)
|
|
130
|
+
id = docker("compose", "-f", first_file, "ps", "-q", service, capture: true)
|
|
131
|
+
return id if id
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
nil
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def container_running?(name)
|
|
138
|
+
status = docker("inspect", "--format", "{{.State.Status}}", name, capture: true)
|
|
139
|
+
status == "running"
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def container_cwd(project_root, workspace)
|
|
143
|
+
rel = Pathname.new(Dir.pwd).relative_path_from(Pathname.new(project_root)).to_s
|
|
144
|
+
rel.start_with?("..") ? workspace : File.join(workspace, rel)
|
|
145
|
+
rescue ArgumentError
|
|
146
|
+
workspace
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# ── Context ────────────────────────────────────────────────────────────────────
|
|
150
|
+
|
|
151
|
+
Context = Struct.new(:project_root, :container, :user, :workspace, :cwd, :compose_files, keyword_init: true)
|
|
152
|
+
|
|
153
|
+
def resolve!(quiet: false)
|
|
154
|
+
root = find_project_root
|
|
155
|
+
unless root
|
|
156
|
+
quiet ? exit(1) : abort_err("no #{DC_CONFIG} found in #{Dir.pwd} or any parent directory")
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
cfg = load_dc_config(root)
|
|
160
|
+
service = cfg["service"] || "app"
|
|
161
|
+
workspace = cfg["workspaceFolder"] || "/workspace"
|
|
162
|
+
user = cfg["remoteUser"] || "root"
|
|
163
|
+
|
|
164
|
+
container = resolve_container_name(root, cfg)
|
|
165
|
+
unless container
|
|
166
|
+
quiet ? exit(1) : abort_err("could not determine container name for service '#{service}'")
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
unless container_running?(container)
|
|
170
|
+
quiet ? exit(1) : abort_err("container '#{container}' is not running. Try: dkit up")
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
compose_files = Array(cfg["dockerComposeFile"]).map do |f|
|
|
174
|
+
File.expand_path(f, File.join(root, ".devcontainer"))
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
Context.new(
|
|
178
|
+
project_root: root,
|
|
179
|
+
container: container,
|
|
180
|
+
user: user,
|
|
181
|
+
workspace: workspace,
|
|
182
|
+
cwd: container_cwd(root, workspace),
|
|
183
|
+
compose_files: compose_files
|
|
184
|
+
)
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
# ── Subcommands ────────────────────────────────────────────────────────────────
|
|
188
|
+
|
|
189
|
+
def cmd_root
|
|
190
|
+
root = find_project_root
|
|
191
|
+
root ? puts(root) : exit(1)
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
def cmd_init
|
|
195
|
+
root = find_project_root
|
|
196
|
+
abort_err("no #{DC_CONFIG} found — are you inside a devcontainer project?") unless root
|
|
197
|
+
|
|
198
|
+
f = intercept_file(root)
|
|
199
|
+
if File.exist?(f)
|
|
200
|
+
puts "dkit: #{f} already exists:"
|
|
201
|
+
puts File.read(f)
|
|
202
|
+
return
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
cmds = []
|
|
206
|
+
cmds += %w[rails bundle rspec rubocop rake] if File.exist?(File.join(root, "Gemfile"))
|
|
207
|
+
cmds += %w[yarn node npx] if File.exist?(File.join(root, "package.json"))
|
|
208
|
+
cmds = %w[bash] if cmds.empty?
|
|
209
|
+
|
|
210
|
+
File.write(f, cmds.join("\n") + "\n")
|
|
211
|
+
puts "dkit: created #{f}"
|
|
212
|
+
puts "Commands: #{cmds.join(", ")}"
|
|
213
|
+
puts "Tip: commit this file to share with your team"
|
|
214
|
+
puts " git add #{DC_INTERCEPT} && git commit -m 'chore: add dkit intercept config'"
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
def cmd_hook
|
|
218
|
+
puts <<~'ZSH'
|
|
219
|
+
# Generated by: dkit hook
|
|
220
|
+
# Add to ~/.zshrc: eval "$(dkit hook)"
|
|
221
|
+
|
|
222
|
+
_DKIT_ROOT=""
|
|
223
|
+
_DKIT_ACTIVE_CMDS=()
|
|
224
|
+
|
|
225
|
+
_dkit_reset() {
|
|
226
|
+
local cmd
|
|
227
|
+
for cmd in "${_DKIT_ACTIVE_CMDS[@]}"; do
|
|
228
|
+
unfunction "$cmd" 2>/dev/null
|
|
229
|
+
done
|
|
230
|
+
_DKIT_ACTIVE_CMDS=()
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
_dkit_load() {
|
|
234
|
+
local root="$1"
|
|
235
|
+
local intercept="$root/.devcontainer/dkit-intercept"
|
|
236
|
+
[[ -f "$intercept" ]] || return
|
|
237
|
+
local cmd
|
|
238
|
+
while IFS= read -r cmd; do
|
|
239
|
+
[[ -z "$cmd" || "${cmd[1]}" == "#" ]] && continue
|
|
240
|
+
# Trim whitespace
|
|
241
|
+
cmd="${cmd## }"
|
|
242
|
+
cmd="${cmd%% }"
|
|
243
|
+
[[ -z "$cmd" ]] && continue
|
|
244
|
+
eval "function ${cmd}() {
|
|
245
|
+
if dkit status --quiet 2>/dev/null; then
|
|
246
|
+
dkit run ${cmd} \"\$@\"
|
|
247
|
+
else
|
|
248
|
+
command ${cmd} \"\$@\"
|
|
249
|
+
fi
|
|
250
|
+
}"
|
|
251
|
+
_DKIT_ACTIVE_CMDS+=("${cmd}")
|
|
252
|
+
done < "$intercept"
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
_dkit_chpwd() {
|
|
256
|
+
# Fast path: still inside the same project root
|
|
257
|
+
if [[ -n "$_DKIT_ROOT" && "$PWD" == "$_DKIT_ROOT"* ]]; then
|
|
258
|
+
return
|
|
259
|
+
fi
|
|
260
|
+
local new_root
|
|
261
|
+
new_root="$(dkit root 2>/dev/null || echo '')"
|
|
262
|
+
[[ "$new_root" == "$_DKIT_ROOT" ]] && return
|
|
263
|
+
_dkit_reset
|
|
264
|
+
_DKIT_ROOT="$new_root"
|
|
265
|
+
[[ -n "$new_root" ]] && _dkit_load "$new_root"
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
# Special commands — always available, always route to devcontainer
|
|
269
|
+
code() {
|
|
270
|
+
if dkit status --quiet 2>/dev/null; then
|
|
271
|
+
dkit code "$@"
|
|
272
|
+
else
|
|
273
|
+
command code "$@"
|
|
274
|
+
fi
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
claude() {
|
|
278
|
+
if dkit status --quiet 2>/dev/null; then
|
|
279
|
+
dkit claude "$@"
|
|
280
|
+
else
|
|
281
|
+
command claude "$@"
|
|
282
|
+
fi
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
autoload -U add-zsh-hook
|
|
286
|
+
add-zsh-hook chpwd _dkit_chpwd
|
|
287
|
+
_dkit_chpwd
|
|
288
|
+
ZSH
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
def cmd_exec(ctx, args)
|
|
292
|
+
abort_err("exec: no command given") if args.empty?
|
|
293
|
+
system("docker", "exec", "--user", ctx.user, "--workdir", ctx.cwd, ctx.container, *args)
|
|
294
|
+
exit $?.exitstatus
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
def cmd_run(ctx, args)
|
|
298
|
+
abort_err("run: no command given") if args.empty?
|
|
299
|
+
exec("docker", "exec", "-it", "--user", ctx.user, "--workdir", ctx.cwd, ctx.container, *args)
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
def cmd_shell(ctx)
|
|
303
|
+
exec("docker", "exec", "-it", "--user", ctx.user, "--workdir", ctx.cwd, ctx.container, "zsh", "-l")
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
def cmd_claude(ctx, args)
|
|
307
|
+
exec("docker", "exec", "-it", "--user", ctx.user, "--workdir", ctx.cwd, ctx.container, "claude", *args)
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
def cmd_code(ctx, path_arg)
|
|
311
|
+
host_path = path_arg ? File.expand_path(path_arg) : ctx.project_root
|
|
312
|
+
rel = begin
|
|
313
|
+
Pathname(host_path).relative_path_from(Pathname(ctx.project_root)).to_s
|
|
314
|
+
rescue ArgumentError
|
|
315
|
+
"."
|
|
316
|
+
end
|
|
317
|
+
container_path = (rel == ".") ? ctx.workspace : File.join(ctx.workspace, rel)
|
|
318
|
+
|
|
319
|
+
payload = JSON.generate({ "hostPath" => ctx.project_root })
|
|
320
|
+
hex = payload.unpack1("H*")
|
|
321
|
+
uri = "vscode-remote://dev-container+#{hex}#{container_path}"
|
|
322
|
+
|
|
323
|
+
if system("which code > /dev/null 2>&1")
|
|
324
|
+
exec("code", "--folder-uri", uri)
|
|
325
|
+
elsif system("which devcontainer > /dev/null 2>&1")
|
|
326
|
+
exec("devcontainer", "open", ctx.project_root)
|
|
327
|
+
else
|
|
328
|
+
abort_err("'code' CLI not found. In VS Code: Shell Command: Install 'code' command in PATH")
|
|
329
|
+
end
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
def cmd_status(ctx, quiet:)
|
|
333
|
+
return if quiet
|
|
334
|
+
puts "Project root : #{ctx.project_root}"
|
|
335
|
+
puts "Container : #{ctx.container} (running)"
|
|
336
|
+
puts "Remote user : #{ctx.user}"
|
|
337
|
+
puts "Workspace : #{ctx.workspace}"
|
|
338
|
+
puts "Exec CWD : #{ctx.cwd}"
|
|
339
|
+
puts "Compose files : #{ctx.compose_files.join(", ")}"
|
|
340
|
+
f = intercept_file(ctx.project_root)
|
|
341
|
+
if File.exist?(f)
|
|
342
|
+
puts "Intercept : #{intercept_list(ctx.project_root).join(", ")}"
|
|
343
|
+
else
|
|
344
|
+
puts "Intercept : (none — run 'dkit init')"
|
|
345
|
+
end
|
|
346
|
+
end
|
|
347
|
+
|
|
348
|
+
def cmd_compose(ctx, args)
|
|
349
|
+
files_flags = ctx.compose_files.flat_map { |f| ["-f", f] }
|
|
350
|
+
exec("docker", "compose", *files_flags, *args)
|
|
351
|
+
end
|
|
352
|
+
|
|
353
|
+
def cmd_help
|
|
354
|
+
puts <<~HELP
|
|
355
|
+
dkit #{VERSION} — DevKit: routes commands into your devcontainer
|
|
356
|
+
|
|
357
|
+
Usage:
|
|
358
|
+
dkit exec <cmd> [args] Run command without TTY (scripting)
|
|
359
|
+
dkit run <cmd> [args] Run command interactively (TTY)
|
|
360
|
+
dkit shell Open interactive shell (zsh) in container
|
|
361
|
+
dkit code [path] Open VS Code attached to devcontainer
|
|
362
|
+
dkit claude [args] Run claude in container (interactive)
|
|
363
|
+
|
|
364
|
+
dkit status Show resolved devcontainer context
|
|
365
|
+
dkit status --quiet Exit 0 if container running, 1 otherwise
|
|
366
|
+
dkit root Print project root (no docker needed)
|
|
367
|
+
|
|
368
|
+
dkit up [service] docker compose up -d
|
|
369
|
+
dkit down [flags] docker compose down
|
|
370
|
+
dkit logs [service] docker compose logs -f
|
|
371
|
+
|
|
372
|
+
dkit init Create .devcontainer/dkit-intercept with auto-detected defaults
|
|
373
|
+
dkit intercept list List intercepted commands for current project
|
|
374
|
+
dkit intercept add <cmd> Add command to current project's intercept list
|
|
375
|
+
dkit intercept remove <cmd> Remove command from current project's intercept list
|
|
376
|
+
|
|
377
|
+
dkit hook Emit shell hook code for ~/.zshrc
|
|
378
|
+
dkit version Print version
|
|
379
|
+
dkit help Show this help
|
|
380
|
+
|
|
381
|
+
Shell integration (add to ~/.zshrc):
|
|
382
|
+
eval "$(dkit hook)"
|
|
383
|
+
|
|
384
|
+
Project setup:
|
|
385
|
+
cd ~/projects/my-app
|
|
386
|
+
dkit init # creates .devcontainer/dkit-intercept
|
|
387
|
+
git add .devcontainer/dkit-intercept && git commit -m "chore: add dkit config"
|
|
388
|
+
|
|
389
|
+
Adding a new command to a project:
|
|
390
|
+
dkit intercept add terraform
|
|
391
|
+
exec zsh
|
|
392
|
+
HELP
|
|
393
|
+
end
|
|
394
|
+
|
|
395
|
+
# ── Entry point ────────────────────────────────────────────────────────────────
|
|
396
|
+
|
|
397
|
+
command = ARGV.shift&.downcase
|
|
398
|
+
|
|
399
|
+
case command
|
|
400
|
+
when "hook"
|
|
401
|
+
cmd_hook
|
|
402
|
+
|
|
403
|
+
when "root"
|
|
404
|
+
cmd_root
|
|
405
|
+
|
|
406
|
+
when "init"
|
|
407
|
+
cmd_init
|
|
408
|
+
|
|
409
|
+
when "intercept"
|
|
410
|
+
root = find_project_root
|
|
411
|
+
abort_err("no #{DC_CONFIG} found — are you inside a devcontainer project?") unless root
|
|
412
|
+
|
|
413
|
+
sub = ARGV.shift&.downcase
|
|
414
|
+
case sub
|
|
415
|
+
when "list"
|
|
416
|
+
list = intercept_list(root)
|
|
417
|
+
f = intercept_file(root)
|
|
418
|
+
if list.empty?
|
|
419
|
+
puts "No intercept file found. Run: dkit init"
|
|
420
|
+
else
|
|
421
|
+
puts "Intercepted commands (#{f}):"
|
|
422
|
+
list.each { |c| puts " #{c}" }
|
|
423
|
+
end
|
|
424
|
+
puts "\nSpecial (always active): #{SPECIAL_COMMANDS.join(", ")}"
|
|
425
|
+
when "add"
|
|
426
|
+
abort_err("intercept add: command name required") if ARGV.empty?
|
|
427
|
+
intercept_add(root, ARGV.first)
|
|
428
|
+
when "remove"
|
|
429
|
+
abort_err("intercept remove: command name required") if ARGV.empty?
|
|
430
|
+
intercept_remove(root, ARGV.first)
|
|
431
|
+
else
|
|
432
|
+
abort_err("intercept: unknown subcommand '#{sub}'. Use: list, add, remove")
|
|
433
|
+
end
|
|
434
|
+
|
|
435
|
+
when "status"
|
|
436
|
+
quiet = ARGV.include?("--quiet")
|
|
437
|
+
ctx = resolve!(quiet: quiet)
|
|
438
|
+
cmd_status(ctx, quiet: quiet)
|
|
439
|
+
|
|
440
|
+
when "exec"
|
|
441
|
+
ctx = resolve!
|
|
442
|
+
cmd_exec(ctx, ARGV)
|
|
443
|
+
|
|
444
|
+
when "run"
|
|
445
|
+
ctx = resolve!
|
|
446
|
+
cmd_run(ctx, ARGV)
|
|
447
|
+
|
|
448
|
+
when "shell"
|
|
449
|
+
ctx = resolve!
|
|
450
|
+
cmd_shell(ctx)
|
|
451
|
+
|
|
452
|
+
when "claude"
|
|
453
|
+
ctx = resolve!
|
|
454
|
+
cmd_claude(ctx, ARGV)
|
|
455
|
+
|
|
456
|
+
when "code"
|
|
457
|
+
ctx = resolve!
|
|
458
|
+
cmd_code(ctx, ARGV.first)
|
|
459
|
+
|
|
460
|
+
when "up"
|
|
461
|
+
ctx = resolve!(quiet: true) rescue nil
|
|
462
|
+
if ctx
|
|
463
|
+
cmd_compose(ctx, ["up", "-d", *ARGV])
|
|
464
|
+
else
|
|
465
|
+
exec("docker", "compose", "up", "-d", *ARGV)
|
|
466
|
+
end
|
|
467
|
+
|
|
468
|
+
when "down"
|
|
469
|
+
ctx = resolve!
|
|
470
|
+
cmd_compose(ctx, ["down", *ARGV])
|
|
471
|
+
|
|
472
|
+
when "logs"
|
|
473
|
+
ctx = resolve!
|
|
474
|
+
cmd_compose(ctx, ["logs", "-f", *ARGV])
|
|
475
|
+
|
|
476
|
+
when "version", "--version", "-v"
|
|
477
|
+
puts "dkit #{VERSION}"
|
|
478
|
+
|
|
479
|
+
when nil, "help", "--help", "-h"
|
|
480
|
+
cmd_help
|
|
481
|
+
|
|
482
|
+
else
|
|
483
|
+
warn "dkit: unknown command '#{command}'. Run 'dkit help'."
|
|
484
|
+
exit 1
|
|
485
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: dkit
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.2.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Augusto Stroligo
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2026-04-13 00:00:00.000000000 Z
|
|
12
|
+
dependencies: []
|
|
13
|
+
description: Routes shell commands transparently into a running devcontainer with
|
|
14
|
+
shell hook integration, per-project intercept lists, VS Code attachment, and docker
|
|
15
|
+
compose helpers.
|
|
16
|
+
email:
|
|
17
|
+
executables:
|
|
18
|
+
- dkit
|
|
19
|
+
extensions: []
|
|
20
|
+
extra_rdoc_files: []
|
|
21
|
+
files:
|
|
22
|
+
- bin/dkit
|
|
23
|
+
homepage:
|
|
24
|
+
licenses:
|
|
25
|
+
- MIT
|
|
26
|
+
metadata: {}
|
|
27
|
+
post_install_message:
|
|
28
|
+
rdoc_options: []
|
|
29
|
+
require_paths:
|
|
30
|
+
- lib
|
|
31
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
32
|
+
requirements:
|
|
33
|
+
- - ">="
|
|
34
|
+
- !ruby/object:Gem::Version
|
|
35
|
+
version: 2.7.0
|
|
36
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - ">="
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '0'
|
|
41
|
+
requirements: []
|
|
42
|
+
rubygems_version: 3.5.22
|
|
43
|
+
signing_key:
|
|
44
|
+
specification_version: 4
|
|
45
|
+
summary: 'DevKit CLI: routes shell commands into a running devcontainer'
|
|
46
|
+
test_files: []
|