space-architect 5.3.0 → 5.4.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/CHANGELOG.md +29 -0
- data/lib/space_architect/cli/architect.rb +9 -6
- data/lib/space_architect/conversations_client.rb +1 -8
- data/lib/space_architect/session_sync/plist.rb +16 -6
- data/lib/space_architect/session_sync.rb +11 -0
- data/lib/space_core/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 20aaf646a8056b8d5975cb789f55a49f9f9c0ca5deca38f9866a0e3e220e8904
|
|
4
|
+
data.tar.gz: a89104abc977aaeae263c09f922800252867df448050262c557d6f60780275f0
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f0a104ab6b75ec7170e5c4bc7a57c4406f002e167ba13b3d913d21a97f3c020a13d3912d2f2fada42edcd0ffeac876a8fdff2fe0c38ad163f617700927767493
|
|
7
|
+
data.tar.gz: ea287608ae6cb4a560d3b38e51e4d8b0c3d4dc04985e429046fe83b1588c8f7dd34608a7ede28ed45457ff8e0456226d4130ddfb6b9541dde875cc3976898a1b
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,35 @@ 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
|
+
## [5.4.0] - 2026-07-21
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
|
|
12
|
+
- **`architect sessions agent install` resolves an `op://` token at install
|
|
13
|
+
time** — the ref is resolved once via `op read` and the raw value is written
|
|
14
|
+
to the plist's `EnvironmentVariables` dict (`SPACE_ARCHITECT_INGEST_TOKEN`),
|
|
15
|
+
with the plist written mode 0600 and ProgramArguments carrying no token
|
|
16
|
+
material. Interval runs no longer invoke `op` at all, so a locked 1Password
|
|
17
|
+
can no longer fail the sync (previously the ref rode argv and every fire ran
|
|
18
|
+
`op read`). Reinstall existing agents to migrate; a non-`op://` token passes
|
|
19
|
+
through to the env dict unresolved.
|
|
20
|
+
|
|
21
|
+
### Added
|
|
22
|
+
|
|
23
|
+
- **`architect sessions sync` env-token fallback** — `--token` is now
|
|
24
|
+
optional: when absent, the token is read from `SPACE_ARCHITECT_INGEST_TOKEN`.
|
|
25
|
+
An explicit `--token` always wins; with neither present, the error names
|
|
26
|
+
both sources.
|
|
27
|
+
- **Server: conversation session links** — importers preserve a session file's
|
|
28
|
+
own `session_id` and record a differing content-derived id as
|
|
29
|
+
`parent_session_id`; conversations resolve `parent`/`children` links
|
|
30
|
+
(owner-scoped) and Conversations/Show renders "part of" / "Subagent
|
|
31
|
+
transcripts" navigation between them.
|
|
32
|
+
- **Server: denormalized `turns_count` on conversations** — the conversations
|
|
33
|
+
index reads a counter column recomputed at import instead of loading every
|
|
34
|
+
conversation's messages, fixing index-page timeouts (502s) at
|
|
35
|
+
thousand-conversation scale.
|
|
36
|
+
|
|
8
37
|
## [5.3.0] - 2026-07-20
|
|
9
38
|
|
|
10
39
|
### Added
|
|
@@ -950,13 +950,13 @@ module Space::Architect
|
|
|
950
950
|
# explicitly rather than relying on dry-cli's `required:` option flag.
|
|
951
951
|
def self.require_credentials!(host, token)
|
|
952
952
|
raise Space::Core::Error, "--host is required" unless host
|
|
953
|
-
raise Space::Core::Error, "--token is required" unless token
|
|
953
|
+
raise Space::Core::Error, "--token or #{SessionSync::TOKEN_ENV} is required" unless token
|
|
954
954
|
end
|
|
955
955
|
|
|
956
956
|
class Sync < BaseCommand
|
|
957
957
|
desc "Scan pi/claude session files and upload new/grown conversations to a space-server"
|
|
958
958
|
option :host, required: true, desc: "Base URL of the space-server"
|
|
959
|
-
option :token,
|
|
959
|
+
option :token, default: nil, desc: "Bearer token for authorization (defaults to $#{SessionSync::TOKEN_ENV}; an explicit --token wins; an op:// ref is resolved once via `op read`)"
|
|
960
960
|
option :state_file, default: nil, desc: "Cursor YAML path (default: $XDG_STATE_HOME/space-architect/session-sync.yaml)"
|
|
961
961
|
option :pi_root, default: nil, desc: "Override the pi sessions root (default: ~/.pi/agent/sessions)"
|
|
962
962
|
option :claude_root, default: nil, desc: "Override the claude projects root (default: ~/.claude/projects)"
|
|
@@ -965,9 +965,10 @@ module Space::Architect
|
|
|
965
965
|
def call(host: nil, token: nil, state_file: nil, pi_root: nil, claude_root: nil, dry_run: false, **opts)
|
|
966
966
|
setup_terminal(**opts.slice(:color, :colors))
|
|
967
967
|
handle_errors do
|
|
968
|
-
|
|
968
|
+
resolved_token = token || ENV[SessionSync::TOKEN_ENV]
|
|
969
|
+
Sessions.require_credentials!(host, resolved_token)
|
|
969
970
|
runner = SessionSync::Runner.new(
|
|
970
|
-
client: ConversationsClient.new(host,
|
|
971
|
+
client: ConversationsClient.new(host, resolved_token),
|
|
971
972
|
state_path: state_file || SessionSync.default_state_file,
|
|
972
973
|
pi_root: pi_root || SessionSync.default_pi_root,
|
|
973
974
|
claude_root: claude_root || SessionSync.default_claude_root,
|
|
@@ -997,13 +998,14 @@ module Space::Architect
|
|
|
997
998
|
include Helpers
|
|
998
999
|
desc "Install the launchd agent that periodically runs `architect sessions sync`"
|
|
999
1000
|
option :host, required: true, desc: "Base URL of the space-server"
|
|
1000
|
-
option :token, required: true, desc: "Bearer token for authorization (an op:// ref is
|
|
1001
|
+
option :token, required: true, desc: "Bearer token for authorization (an op:// ref is resolved once at install time; the raw value is stored in the plist's EnvironmentVariables, not argv)"
|
|
1001
1002
|
option :interval, default: "900", desc: "StartInterval in seconds (default 900)"
|
|
1002
1003
|
|
|
1003
1004
|
def call(host: nil, token: nil, interval: "900", **opts)
|
|
1004
1005
|
setup_terminal(**opts.slice(:color, :colors))
|
|
1005
1006
|
handle_errors do
|
|
1006
1007
|
Sessions.require_credentials!(host, token)
|
|
1008
|
+
resolved_token = token.start_with?("op://") ? SessionSync.resolve_token(token) : token
|
|
1007
1009
|
pp = plist_path
|
|
1008
1010
|
xml = SessionSync::Plist.call(
|
|
1009
1011
|
label: SessionSync::LABEL,
|
|
@@ -1011,10 +1013,11 @@ module Space::Architect
|
|
|
1011
1013
|
log_dir: SessionSync.log_dir,
|
|
1012
1014
|
bin_path: SessionSync::BinPath.detect,
|
|
1013
1015
|
host: host,
|
|
1014
|
-
|
|
1016
|
+
env: {SessionSync::TOKEN_ENV => resolved_token}
|
|
1015
1017
|
)
|
|
1016
1018
|
FileUtils.mkdir_p(File.dirname(pp))
|
|
1017
1019
|
File.write(pp, xml)
|
|
1020
|
+
File.chmod(0o600, pp)
|
|
1018
1021
|
|
|
1019
1022
|
result = Space::Src::Launchd::Agent.new(label: SessionSync::LABEL).install(pp)
|
|
1020
1023
|
raise Space::Core::Error, "bootstrap failed: #{format_failure(result.failure)}" if result.failure?
|
|
@@ -16,7 +16,7 @@ module Space::Architect
|
|
|
16
16
|
@host = host.chomp("/")
|
|
17
17
|
@token = token
|
|
18
18
|
@client = client
|
|
19
|
-
@op_resolver = op_resolver || method(:
|
|
19
|
+
@op_resolver = op_resolver || SessionSync.method(:resolve_token)
|
|
20
20
|
@resolved_token = nil
|
|
21
21
|
end
|
|
22
22
|
|
|
@@ -62,13 +62,6 @@ module Space::Architect
|
|
|
62
62
|
@resolved_token ||= @token.start_with?("op://") ? @op_resolver.call(@token) : @token
|
|
63
63
|
end
|
|
64
64
|
|
|
65
|
-
def op_read(ref)
|
|
66
|
-
value = IO.popen(["op", "read", ref], &:read)
|
|
67
|
-
raise Space::Core::Error, "op read failed for #{ref}" unless $?.success?
|
|
68
|
-
|
|
69
|
-
value.chomp
|
|
70
|
-
end
|
|
71
|
-
|
|
72
65
|
def build_multipart(boundary, path, session_id)
|
|
73
66
|
crlf = "\r\n"
|
|
74
67
|
filename = File.basename(path)
|
|
@@ -23,10 +23,11 @@ module Space::Architect
|
|
|
23
23
|
# @param log_dir [String] Absolute directory for stdout/stderr logs.
|
|
24
24
|
# @param bin_path [String] Absolute path to the architect bin script.
|
|
25
25
|
# @param host [String] --host value passed to `sessions sync`.
|
|
26
|
-
# @param
|
|
27
|
-
#
|
|
26
|
+
# @param env [Hash] EnvironmentVariables dict; must contain a non-empty
|
|
27
|
+
# SessionSync::TOKEN_ENV value (resolved by the caller —
|
|
28
|
+
# never resolved here).
|
|
28
29
|
# @return [String] the full plist XML.
|
|
29
|
-
def call(label:, refresh_interval:, log_dir:, bin_path:, host:,
|
|
30
|
+
def call(label:, refresh_interval:, log_dir:, bin_path:, host:, env:)
|
|
30
31
|
raise ArgumentError, "label is required" if label.to_s.empty?
|
|
31
32
|
raise ArgumentError, "refresh_interval must be > 0" unless refresh_interval.is_a?(Integer) && refresh_interval > 0
|
|
32
33
|
%w[log_dir bin_path].each do |k|
|
|
@@ -34,7 +35,7 @@ module Space::Architect
|
|
|
34
35
|
raise ArgumentError, "#{k} must be absolute (got #{v.inspect})" unless v.is_a?(String) && File.absolute_path?(v)
|
|
35
36
|
end
|
|
36
37
|
raise ArgumentError, "host is required" if host.to_s.empty?
|
|
37
|
-
raise ArgumentError, "
|
|
38
|
+
raise ArgumentError, "env[#{SessionSync::TOKEN_ENV}] is required" if env.to_h[SessionSync::TOKEN_ENV].to_s.empty?
|
|
38
39
|
|
|
39
40
|
out_log = File.join(log_dir, "#{label}.out.log")
|
|
40
41
|
err_log = File.join(log_dir, "#{label}.err.log")
|
|
@@ -45,9 +46,9 @@ module Space::Architect
|
|
|
45
46
|
bin_path,
|
|
46
47
|
"sessions",
|
|
47
48
|
"sync",
|
|
48
|
-
"--host", host
|
|
49
|
-
"--token", token
|
|
49
|
+
"--host", host
|
|
50
50
|
])
|
|
51
|
+
body << key("EnvironmentVariables") << "\n" << dict(env)
|
|
51
52
|
body << key("StartInterval") << integer(refresh_interval) << "\n"
|
|
52
53
|
body << key("RunAtLoad") << boolean(true) << "\n"
|
|
53
54
|
body << key("ProcessType") << string("Background") << "\n"
|
|
@@ -89,6 +90,15 @@ module Space::Architect
|
|
|
89
90
|
items.each { |arg| out << " <string>#{escape(arg)}</string>\n" }
|
|
90
91
|
out << " </array>"
|
|
91
92
|
end
|
|
93
|
+
|
|
94
|
+
def dict(hash)
|
|
95
|
+
out = +" <dict>\n"
|
|
96
|
+
hash.each do |k, v|
|
|
97
|
+
out << " <key>#{escape(k)}</key>\n"
|
|
98
|
+
out << " <string>#{escape(v)}</string>\n"
|
|
99
|
+
end
|
|
100
|
+
out << " </dict>"
|
|
101
|
+
end
|
|
92
102
|
end
|
|
93
103
|
end
|
|
94
104
|
end
|
|
@@ -15,9 +15,20 @@ module Space::Architect
|
|
|
15
15
|
module SessionSync
|
|
16
16
|
LABEL = "io.github.jetpks.space-architect.session-sync"
|
|
17
17
|
APP_NAME = "space-architect"
|
|
18
|
+
TOKEN_ENV = "SPACE_ARCHITECT_INGEST_TOKEN"
|
|
18
19
|
|
|
19
20
|
module_function
|
|
20
21
|
|
|
22
|
+
# The one op-read seam under lib/: resolves an op:// ref via `op read`.
|
|
23
|
+
# Shared by ConversationsClient's default op_resolver and
|
|
24
|
+
# Sessions::Agent::Install (both test-injectable around this call).
|
|
25
|
+
def resolve_token(ref)
|
|
26
|
+
value = IO.popen(["op", "read", ref], &:read)
|
|
27
|
+
raise Space::Core::Error, "op read failed for #{ref}" unless $?.success?
|
|
28
|
+
|
|
29
|
+
value.chomp
|
|
30
|
+
end
|
|
31
|
+
|
|
21
32
|
def state_dir(env: ENV)
|
|
22
33
|
File.join(Space::Core::XDG.state_home(env: env).to_s, APP_NAME)
|
|
23
34
|
end
|
data/lib/space_core/version.rb
CHANGED