space-architect 2.0.1 → 2.0.2

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: 4ded62e199397e162bba1ff6ee37a5c6a9c7ee83a446d154107036aa4d628768
4
- data.tar.gz: 592a6bb0c3b10b2773873e7fc3fd79cf7197bad78ada5520c5c9dc77e60aae2f
3
+ metadata.gz: 7251e017d780c7fc4accd3d4ee367d2b5063bb4d0c1692c0c69bc81a228c9fa7
4
+ data.tar.gz: ad25da0cf6025716953685d7483e86898368170967a29465625335337f8427a3
5
5
  SHA512:
6
- metadata.gz: 91b5a865cb40557085399853e723e7763c2a54cb183d95b9744f9886753579aa841309f3f2dc490d28a3ad10d4693db06cfb615a9716b2b13fe68ded86b932f6
7
- data.tar.gz: 6ebb0c24e6e603252aab77da49ff2b904d22084e0c840f7d46d5ac0eb66ec7c315ec04816d6446dc537cac5b3dbc58c7663d8bbbb563443566cd6e84de5ee05a
6
+ metadata.gz: baf43e94bcd35987bb133f3958709bfc4083851732ed2d47ef72b25b92e042cfb72c2594f1067189e9581c554140cd2d6145dc7d607f9137931250522ba445dc
7
+ data.tar.gz: 0ecd90d05ea7dc4c3832fea90f7af76650b1c8279ced9e7e48242b825aaeef8e7291d6dc9b4dacd6bfac00ad647ecca887a2f27abb3823181287885ccede7210
data/CHANGELOG.md CHANGED
@@ -5,6 +5,18 @@ All notable changes to this project are documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [2.0.2] - 2026-07-01
9
+
10
+ ### Added
11
+
12
+ - **Declarative runtime env forwarding for `space run`.** A space can declare
13
+ `run.env:` in `space.yaml` — a list of host env vars forwarded into the
14
+ container as bare `-e VAR` passthrough (value never lands in argv/`ps`), on top
15
+ of the always-on substrate auth vars. `space run --env VAR` (repeatable) adds
16
+ more ad hoc. A requested-but-unset var warns on stderr instead of failing
17
+ opaquely in-guest. This lets credentialed payloads (e.g. a Fireworks/OpenAI key)
18
+ run via a plain `space run <cmd>` with no wall of flags, without baking secrets.
19
+
8
20
  ## [2.0.1] - 2026-07-01
9
21
 
10
22
  Fast-follow completing the `pack.persist:` story from 2.0.0.
@@ -316,6 +328,7 @@ the **BREAKING** items under Changed. The repository is now a monorepo; the
316
328
  lanes, worktrees, and headless `claude -p` dispatch, with variant sets and the
317
329
  claude-code and opencode harnesses.
318
330
 
331
+ [2.0.2]: https://github.com/jetpks/space-architect/compare/v2.0.1...v2.0.2
319
332
  [2.0.1]: https://github.com/jetpks/space-architect/compare/v2.0.0...v2.0.1
320
333
  [2.0.0]: https://github.com/jetpks/space-architect/compare/v1.3.0...v2.0.0
321
334
  [1.3.0]: https://github.com/jetpks/space-architect/compare/v1.2.0...v1.3.0
data/README.md CHANGED
@@ -158,6 +158,19 @@ variables that are actually set — `ANTHROPIC_API_KEY`, `CLAUDE_CODE_OAUTH_TOKE
158
158
  `ANTHROPIC_BASE_URL` — with `-e` at run time, so credentials live in your shell,
159
159
  never in the image. 🗝️
160
160
 
161
+ **Forwarding payload credentials.** Beyond the always-on auth trio, a space can declare
162
+ `run.env:` in `space.yaml` — a list of host env var names forwarded into the container at
163
+ run time. You can also pass `space run --env VAR` (repeatable) for ad hoc additions. All
164
+ forwarding is bare `-e VAR` passthrough: values never appear in argv, `ps`, or the image.
165
+ A requested-but-unset var warns on stderr instead of silently failing inside the guest.
166
+
167
+ ```yaml
168
+ run:
169
+ env: # run-time: host var names, forwarded as bare -e VAR
170
+ - FIREWORKS_API_KEY
171
+ - OPENAI_API_KEY
172
+ ```
173
+
161
174
  **Declaring provisioning & persistence** — two optional keys in `space.yaml`:
162
175
 
163
176
  ```yaml
@@ -169,10 +182,19 @@ pack:
169
182
  - /root/.local/state
170
183
  ```
171
184
 
172
- `provision` scripts must live under the space root and run as `RUN /space/<script>`
173
- while the image builds. `persist` paths must be absolute; `space run` bind-mounts
174
- each one from `<space>/.state<path>` on the host (created on first run) so a
175
- container's mutable state survives across runs. Both are validated at pack time.
185
+ `provision` scripts must live under the space root and be executable. Each script is
186
+ copied into the image individually (`COPY <script> /space/<script>`) and then invoked
187
+ (`RUN /space/<script>`) **before** the full space tree lands and **before** the gem is
188
+ installed. This ordering is the cache-hygiene guarantee: editing any other space file
189
+ leaves the provision and gem-install layers cached, so a rebuild completes in seconds
190
+ instead of minutes. Scripts must therefore be self-contained — they run with only the
191
+ base system layers and any earlier provision scripts' outputs; they cannot read other
192
+ space files or call `architect`/`space`. Scripts must have the executable bit set; COPY
193
+ preserves the mode from the build context.
194
+
195
+ `persist` paths must be absolute; `space run` bind-mounts each one from
196
+ `<space>/.state<path>` on the host (created on first run) so a container's mutable state
197
+ survives across runs. Both are validated at pack time.
176
198
 
177
199
  Like everything else, these are reachable as `architect space pack|build|run`
178
200
  from inside a project.
@@ -9,21 +9,35 @@ class Run < BaseCommand
9
9
 
10
10
  argument :command, type: :array, required: false, desc: "Command to run in the container (default: login shell)"
11
11
  option :tty, type: :boolean, default: nil, desc: "Force interactive TTY (default: auto-detect)"
12
+ option :env, type: :array, desc: "Host env var to forward into the container (repeatable; adds to run.env)"
12
13
 
13
- def call(command: [], tty: nil, **opts)
14
+ def call(command: [], tty: nil, env: [], **opts)
14
15
  setup_terminal(**opts.slice(:color, :colors))
15
16
  handle_errors do
16
17
  result = store.current.bind do |space|
17
- runner = Space::Core::OciRunner.new(space: space, interactive: tty.nil? ? CLI.tty?(out) : tty)
18
+ runner = Space::Core::OciRunner.new(
19
+ space: space, interactive: tty.nil? ? CLI.tty?(out) : tty, env_vars: env
20
+ )
18
21
  runner.command(command).fmap { |argv| { argv: argv, runner: runner } }
19
22
  end
20
23
  render(result) do |r|
21
24
  r[:runner].host_dirs.each { |d| FileUtils.mkdir_p(d) }
25
+ warn_missing_env(r[:runner].missing_env)
22
26
  terminal.say "Running: #{r[:argv].join(' ')}"
23
27
  out.flush # Kernel.exec replaces the process without flushing buffered IO
24
28
  Kernel.exec(*r[:argv])
25
29
  end
26
30
  end
27
31
  end
32
+
33
+ private
34
+
35
+ # A requested var absent from the host env is almost always a forgotten export;
36
+ # forwarding silently omits it and the payload fails opaquely in-guest. Warn, don't fail.
37
+ def warn_missing_env(missing)
38
+ return if missing.empty?
39
+
40
+ terminal.error("Warning: requested env not set on host, not forwarded: #{missing.join(', ')}")
41
+ end
28
42
  end
29
43
  end
@@ -84,6 +84,10 @@ module Space::Core
84
84
  [entrypoint_content].pack("m0")
85
85
  end
86
86
 
87
+ def local_space_architect?
88
+ space.path.join("repos/space-architect").directory?
89
+ end
90
+
87
91
  def render_template(name)
88
92
  template_path = TEMPLATE_DIR.join(name)
89
93
  ERB.new(template_path.read, trim_mode: "-").result(binding)
@@ -7,12 +7,15 @@ module Space::Core
7
7
  class OciRunner
8
8
  include Dry::Monads[:result]
9
9
 
10
+ # Always-forwarded substrate auth (the in-image architect/claude reach for these).
11
+ # Payload-specific vars are declared per-space via `run.env:` or the --env flag.
10
12
  AUTH_ENV = %w[ANTHROPIC_API_KEY CLAUDE_CODE_OAUTH_TOKEN ANTHROPIC_BASE_URL].freeze
11
13
 
12
- def initialize(space:, env: ENV.to_h, interactive: true)
14
+ def initialize(space:, env: ENV.to_h, interactive: true, env_vars: [])
13
15
  @space = space
14
16
  @env = env
15
17
  @interactive = interactive
18
+ @env_vars = (space.run_env + Array(env_vars)).map(&:to_s)
16
19
  end
17
20
 
18
21
  def image
@@ -36,7 +39,7 @@ module Space::Core
36
39
  argv = [
37
40
  "container", "run", "--rm",
38
41
  *(@interactive ? ["-i", "-t"] : []),
39
- *auth_flags,
42
+ *env_flags,
40
43
  *mount_flags,
41
44
  image,
42
45
  *extra
@@ -44,17 +47,30 @@ module Space::Core
44
47
  Success(argv)
45
48
  end
46
49
 
50
+ # Explicitly-requested vars (run.env: + --env) that are unset/empty in the host
51
+ # env, so the caller can warn — silence here would let a missing key fail opaquely
52
+ # inside the guest. AUTH_ENV is opportunistic and intentionally excluded.
53
+ def missing_env
54
+ @env_vars.uniq.reject { |var| present?(var) }
55
+ end
56
+
47
57
  private
48
58
 
49
59
  attr_reader :space, :env, :interactive
50
60
 
51
- def auth_flags
52
- AUTH_ENV.each_with_object([]) do |var, flags|
53
- val = env[var]
54
- flags.push("-e", var) if val && !val.empty?
61
+ # Bare `-e VAR` passthrough (no =value on the command line, so the secret never
62
+ # lands in argv/ps): substrate auth first, then the space's declared payload vars.
63
+ def env_flags
64
+ (AUTH_ENV + @env_vars).uniq.each_with_object([]) do |var, flags|
65
+ flags.push("-e", var) if present?(var)
55
66
  end
56
67
  end
57
68
 
69
+ def present?(var)
70
+ val = env[var]
71
+ val && !val.empty?
72
+ end
73
+
58
74
  def mount_flags
59
75
  mounts.each_with_object([]) do |(host, guest), flags|
60
76
  flags.push("-v", "#{host}:#{guest}")
@@ -56,6 +56,12 @@ module Space::Core
56
56
  Array(data.dig("pack", "persist")).map(&:to_s)
57
57
  end
58
58
 
59
+ # Host env vars to forward into the container at `space run` (bare passthrough).
60
+ # Declared per-space so payloads needing credentials run without a wall of flags.
61
+ def run_env
62
+ Array(data.dig("run", "env")).map(&:to_s)
63
+ end
64
+
59
65
  def architect
60
66
  data["project"]
61
67
  end
@@ -30,18 +30,6 @@ RUN apt-get update -qq \
30
30
  # Claude Code CLI (https://claude.ai/install.sh)
31
31
  RUN curl -fsSL https://claude.ai/install.sh | bash
32
32
 
33
- # Copy the space tree into the image (filtered by Dockerfile.dockerignore alongside this Dockerfile)
34
- COPY . /space
35
-
36
- # Install space-architect gem: prefer in-space checkout for a pinned version, fall back to RubyGems
37
- RUN if [ -d /space/repos/space-architect ]; then \
38
- cd /space/repos/space-architect \
39
- && rake build \
40
- && gem install --no-document pkg/*.gem; \
41
- else \
42
- gem install --no-document space-architect; \
43
- fi
44
-
45
33
  # Write entrypoint: sets git safe.directory, execs requested command or a login shell
46
34
  RUN echo '<%= entrypoint_b64 %>' | base64 -d > /entrypoint.sh \
47
35
  && chmod +x /entrypoint.sh
@@ -52,10 +40,25 @@ RUN printf 'export PATH="/usr/local/bundle/bin:/root/.local/bin:$PATH"\n' \
52
40
  <% if provision_scripts.any? -%>
53
41
 
54
42
  # Space-declared build-time provisioning (pack.provision)
43
+ # Each script is COPY'd individually — a space-content edit elsewhere does not bust its cache layer.
55
44
  <% provision_scripts.each do |script| -%>
45
+ COPY <%= script %> /space/<%= script %>
56
46
  RUN /space/<%= script %>
57
47
  <% end -%>
58
48
  <% end -%>
49
+
50
+ # Install space-architect gem
51
+ <% if local_space_architect? -%>
52
+ COPY repos/space-architect /space/repos/space-architect
53
+ RUN cd /space/repos/space-architect \
54
+ && rake build \
55
+ && gem install --no-document pkg/*.gem
56
+ <% else -%>
57
+ RUN gem install --no-document space-architect
58
+ <% end -%>
59
+
60
+ # Copy the space tree into the image (filtered by Dockerfile.dockerignore alongside this Dockerfile)
61
+ COPY . /space
59
62
  <% if persist_paths.any? -%>
60
63
 
61
64
  # Seed snapshot: capture baked content of persisted dirs so a first-run (empty) mount is seeded from it
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Space
4
4
  module Core
5
- VERSION = "2.0.1"
5
+ VERSION = "2.0.2"
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: space-architect
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.1
4
+ version: 2.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Eric Jacobs