shapeup-cli 0.10.5 → 0.11.1

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: a45ee37e8d065758ff406860f939921f4ddb7341bed82732cee1454ab2cde0c1
4
- data.tar.gz: 9b8230e7e221798866fa6c1ca1fbe947f2aacef3a6ae03500a32cdfd494a08cd
3
+ metadata.gz: aaa4bfac80b5b904dc087542495c2b208af118e757c186154decdc5f10a27202
4
+ data.tar.gz: 5807b66ff822944751b0261953465424413dec6d3017141502fc46c8090ef0ba
5
5
  SHA512:
6
- metadata.gz: 03bb70e47f6b4322602c8e86d8e129ad153dc9e6f0596dd9fa54f6127f910a8fa428b51cd723c19389f4b2a31b698469afdc851c862a97390aa94f272af4bf63
7
- data.tar.gz: 6eba12bf921de8b62efbda85d1d410ece152c11399872384ef54b9daae70adc64bc1ced1fcf8fb808cfb0e1343a2d506154d9db53ef6c6c39ed7a023c1fa4907
6
+ metadata.gz: 0cbb7d0347602e616ad0212a2e802df002b8e79e9c9732055b00a2c734a6f3b3ee7b9624ea6ff77cf7d1effaeeaf17473d33701532ef8b7c34446e2b11130c31
7
+ data.tar.gz: 1efa2c97485f0c93b655be01e04f5f2be20ba828f2bb735a33e12b93ce8074dafc94d6eddb78a2571327975da25d639437b8869394a41fae36de4249946c472c
@@ -16,12 +16,16 @@ module ShapeupCli
16
16
  { name: "as", type: "string", usage: "Which of your agents, by handle (default: your only one)" },
17
17
  { name: "interval", type: "integer", usage: "Seconds between checks (default: #{DEFAULT_INTERVAL})" },
18
18
  { name: "trust", type: "string", usage: "Whose requests to pass on: owner (default) or members" },
19
- { name: "once", type: "bool", usage: "Check once and exit" }
19
+ { name: "once", type: "bool", usage: "Check once and exit" },
20
+ { name: "until-request", type: "bool", usage: "Exit after the first check that passes on a request, remembering what was handed out" },
21
+ { name: "fresh", type: "bool", usage: "With --until-request, forget what earlier runs handed out" },
22
+ { name: "here", type: "bool", usage: "Use this folder even though another of your agents last listened in it" }
20
23
  ],
21
24
  examples: [
22
25
  "shapeup connect",
23
26
  "shapeup connect --as bond --trust members",
24
- "SHAPEUP_PROFILE=bond shapeup connect --once"
27
+ "SHAPEUP_PROFILE=bond shapeup connect --once",
28
+ "shapeup connect --until-request --fresh"
25
29
  ]
26
30
  }
27
31
  end
@@ -32,13 +36,15 @@ module ShapeupCli
32
36
  @trust = extract_option("--trust") || "owner"
33
37
  abort "--trust must be one of: #{TRUST_LEVELS.join(", ")}" unless TRUST_LEVELS.include?(@trust)
34
38
  once = consume_flag("--once")
39
+ @until_request = consume_flag("--until-request")
40
+ @fresh = consume_flag("--fresh")
41
+ @here = consume_flag("--here")
35
42
 
36
43
  $stdout.sync = true
37
- @handed_out = Set.new
38
44
  @profile, @agent_client = keep_trying(forever: !once) { sign_in_as_agent(handle) }
39
45
 
40
46
  poll
41
- until once
47
+ until once || @requested
42
48
  pause @interval
43
49
  poll
44
50
  end
@@ -97,25 +103,70 @@ module ShapeupCli
97
103
  agent = inbox["agent"]
98
104
 
99
105
  announce(agent) unless @connected
106
+ @handed_out ||= Set.new(@until_request && !@fresh ? read_handed_out(agent) : [])
100
107
 
101
108
  inbox["items"].reverse_each do |item|
102
109
  next unless @handed_out.add?(item["id"])
103
110
 
104
111
  if trusted?(item["requester"], agent)
105
112
  emit(type: "request", **item.transform_keys(&:to_sym))
113
+ @requested = @until_request
106
114
  else
107
115
  emit(type: "ignored", id: item["id"], requester: item["requester"], why: "#{item["requester"]["name"]} is not trusted (--trust #{@trust})")
108
116
  end
109
117
  end
118
+
119
+ write_handed_out(agent, inbox["items"].map { |item| item["id"] }) if @until_request
110
120
  rescue Client::NetworkError, Client::RateLimitError => error
111
121
  emit(type: "error", message: error.message, retryable: true)
112
122
  end
113
123
 
124
+ def handed_out_path(agent)
125
+ File.join(Config::CONFIG_DIR, "connect", "#{agent["name"]}.json")
126
+ end
127
+
128
+ def read_handed_out(agent)
129
+ JSON.parse(File.read(handed_out_path(agent)))
130
+ rescue Errno::ENOENT, JSON::ParserError
131
+ []
132
+ end
133
+
134
+ def write_handed_out(agent, waiting)
135
+ @handed_out &= waiting
136
+ FileUtils.mkdir_p(File.dirname(handed_out_path(agent)))
137
+ File.write(handed_out_path(agent), JSON.generate(@handed_out.to_a))
138
+ end
139
+
114
140
  def announce(agent)
115
- emit(type: "connected", agent: agent, profile: @profile, organisation_id: org_id.to_s, trust: @trust, interval: @interval)
141
+ claim_folder(agent)
142
+ emit(type: "connected", agent: agent, profile: @profile, organisation_id: org_id.to_s, trust: @trust, interval: @interval, folder: Dir.pwd)
116
143
  @connected = true
117
144
  end
118
145
 
146
+ def claim_folder(agent)
147
+ folders = read_folders
148
+ previous = folders[Dir.pwd]
149
+
150
+ if previous && previous != agent["name"] && !@here
151
+ emit(type: "error", code: "folder", retryable: false, folder: Dir.pwd, last_used_by: previous,
152
+ message: "@#{previous} last listened in this folder. Run again with --here to use it for @#{agent["name"]} instead.")
153
+ exit EXIT_USAGE
154
+ end
155
+
156
+ FileUtils.mkdir_p(File.dirname(folders_path))
157
+ File.write(folders_path, JSON.pretty_generate(folders.merge(Dir.pwd => agent["name"])))
158
+ end
159
+
160
+ def folders_path
161
+ File.join(Config::CONFIG_DIR, "connect", "folders.json")
162
+ end
163
+
164
+ def read_folders
165
+ JSON.parse(File.read(folders_path))
166
+ rescue Errno::ENOENT, JSON::ParserError
167
+ {}
168
+ end
169
+
119
170
  def trusted?(requester, agent)
120
171
  @trust == "members" || requester["id"] == agent.dig("owner", "id")
121
172
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ShapeupCli
4
- VERSION = "0.10.5"
4
+ VERSION = "0.11.1"
5
5
  end
@@ -93,42 +93,36 @@ themselves.
93
93
 
94
94
  ## Listen
95
95
 
96
- Start the connector as a background command and keep it running:
96
+ Start the connector in the background, as a command you are told about when it finishes (in
97
+ Claude Code: Bash with `run_in_background`):
97
98
 
98
99
  ```bash
99
- shapeup connect [--as <handle>]
100
+ shapeup connect --until-request --fresh [--as <handle>]
100
101
  ```
101
102
 
102
103
  Signed in as the user, it gives the agent a fresh token, saves it as a CLI profile named after
103
104
  the agent, and listens as the agent. Add `--trust members` only if the user asked for anyone in
104
105
  the organisation to be able to use the agent. The default trusts only the agent's owner.
105
106
 
106
- Watch its output with the Monitor tool when you have it, using the longest timeout it allows and
107
- exactly this command, with `OUTPUT` set to the connector's output file:
107
+ It checks the inbox every 30 seconds and finishes as soon as it has passed on a request. When it
108
+ finishes, read its whole output and act on every line, then start it again straight away, in the
109
+ background, as the agent and without `--fresh` (keep `--trust members` if you used it):
108
110
 
109
111
  ```bash
110
- f=OUTPUT; seen="$f.seen"; n=$(cat "$seen" 2>/dev/null || echo 0)
111
- while true; do
112
- m=$(wc -l < "$f")
113
- if [ "$m" -gt "$n" ]; then
114
- sed -n "$((n + 1)),${m}p" "$f" | grep -v '"retryable":true'
115
- n=$m; echo "$n" > "$seen"
116
- fi
117
- sleep 3
118
- done
112
+ shapeup --profile <profile> connect --until-request
119
113
  ```
120
114
 
121
- Don't use `tail -f` or `tail -F` on the output file: they can miss lines. The command keeps its place
122
- in `OUTPUT.seen`, so when the watch ends but the connector is still running, start it again
123
- straight away, without telling the user, and nothing is missed or repeated. If the connector
124
- exits, you are told separately. Every line is one JSON event:
115
+ `--fresh` is only for the first start in a session: it hands out again anything an earlier
116
+ session left unfinished. Without it, the connector never hands out the same request twice.
117
+ Don't watch its output with Monitor or `tail`: the connector finishing is what wakes you, even
118
+ when your last turn ended in an error. Every line is one JSON event:
125
119
 
126
120
  | `type` | What to do |
127
121
  |--------|------------|
128
- | `connected` | Tell the user the agent is listening. Note `profile`: from now on, run every ShapeUp command for this agent as `shapeup --profile <profile> ...` (leave out `--profile` when it is null). |
122
+ | `connected` | The first time, tell the user the agent is listening; after a restart, say nothing. Note `profile`: from now on, run every ShapeUp command for this agent as `shapeup --profile <profile> ...` (leave out `--profile` when it is null). |
129
123
  | `request` | Handle it, below. |
130
124
  | `ignored` | Tell the user in one line who asked and that they are not trusted. Do nothing else. |
131
- | `error` | Nothing while `retryable` is true: the connector keeps retrying by itself, pausing longer each time, up to two minutes. Otherwise tell the user the `message`. If the connector exits after a network error (`code` is `network`, or the message mentions a connection), wait a minute and restart it; give up and tell the user after five restarts in a row that never reach `connected`. For any other exit, restart it once. |
125
+ | `error` | If `code` is `folder`, another of the user's agents last listened in this folder: ask the user whether this folder is right for this agent, and only if they say yes, start again with `--here`. Otherwise, nothing while `retryable` is true: the connector keeps retrying by itself, pausing longer each time, up to two minutes. Otherwise tell the user the `message`. If the connector exits after a network error (`code` is `network`, or the message mentions a connection), wait a minute and restart it; give up and tell the user after five restarts in a row that never reach `connected`. For any other exit, restart it once. |
132
126
 
133
127
  Don't poll the inbox yourself. The connector does that.
134
128
 
@@ -148,7 +142,7 @@ A `request` line carries `id`, `reason` (mentioned, replied, assigned or watchin
148
142
  `shapeup --profile <profile> issues move <issue_id> --column "<name>"`. If there is none, skip this.
149
143
  3. **Hand it off.** Start one background subagent per request, with the request line and the
150
144
  worker brief below. Tell the user in one line, for example "@bond picked up #42 from @miles".
151
- 4. **Keep listening.**
145
+ 4. **Keep listening.** Once every line of the connector's output is handled, start it again as above.
152
146
 
153
147
  ## Worker brief
154
148
 
@@ -165,7 +159,9 @@ Give the subagent this, filled in:
165
159
  > `agent/<issue.id>-<short-slug>` from the latest default branch on the remote (for example
166
160
  > `origin/main`), not from whatever is checked out, and commit there. Never push, deploy, merge or delete branches unless the owner said so in this
167
161
  > session.
168
- > 4. If the ask is unclear or you are blocked, ask on the issue rather than guess.
162
+ > 4. If the ask is unclear or you are blocked, ask on the issue rather than guess. If the issue is
163
+ > clearly about a different product or codebase than this repository, say so on the issue and
164
+ > stop.
169
165
  > 5. If the work changes anything people see, compare it the way a careful reviewer would:
170
166
  > - From your diff, list the screens and states it touches, including the ones it could break.
171
167
  > - Run the main branch beside yours: check main out into a git worktree outside this
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: shapeup-cli
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.10.5
4
+ version: 0.11.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - ShapeUp