taskops-cli 0.3.2__py3-none-any.whl → 0.3.3__py3-none-any.whl

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.
taskops/_version.py CHANGED
@@ -1,4 +1,4 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  __title__ = "taskops"
4
- __version__ = "0.3.2" # single source of truth; pyproject reads it dynamically
4
+ __version__ = "0.3.3" # single source of truth; pyproject reads it dynamically
taskops/cli/commands.py CHANGED
@@ -15,7 +15,7 @@ from pathlib import Path
15
15
  from . import enrol, watch
16
16
  from .. import session, identity
17
17
  from .._json import query
18
- from ..board import DIR, find_root, open_board
18
+ from ..board import DIR, find_root, open_board, read_config
19
19
  from ..store import log
20
20
  from .._errors import TaskopsError
21
21
  from ..gitwork import remote, install, claudefiles
@@ -58,13 +58,15 @@ def join( # noqa: PLR0913 — one command, one config; each flag is a way IN
58
58
  invite: str = "",
59
59
  github: bool = False,
60
60
  ) -> int:
61
- """Connect this repo to a board. Bare like every other verb since the host is
62
- recorded and the key is discovered:
63
-
64
- taskops remote add https://host:8787 once per checkout
65
- taskops join my-project registered key: signs in, done
66
- taskops join my-project --invite <id> first time: enrols the key too
67
- taskops join my-project --github first time, no invite: GitHub vouches
61
+ """Connect this repo to a board. Bare like every other verb: the address is
62
+ CARRIED by the clone, the key is discovered, and the recorded remote is the
63
+ fallback for a checkout that carries nothing:
64
+
65
+ taskops join a clone: board.json travels, done
66
+ taskops join --github same clone, first time: GitHub vouches
67
+ taskops join my-project --invite <id> first time by invite: enrols the key
68
+ taskops remote add https://host:8787 no carried address? record the host…
69
+ taskops join my-project …and name the board
68
70
  taskops join my-project no key anywhere + public board: read-only
69
71
 
70
72
  The name defaults exactly as `board create`'s does (recorded name, else the
@@ -93,8 +95,17 @@ def join( # noqa: PLR0913 — one command, one config; each flag is a way IN
93
95
  root = find_root(here)
94
96
  bare = "://" not in target
95
97
  if bare:
96
- host, name = remote_cli.named(target)
97
- target = f"{host}/{name}"
98
+ # v1's whole ambition, restored (its join.py said it in one line: "a
99
+ # clone carries `.taskops/board.json`, so the second developer types
100
+ # two words"). The committed address is read FIRST, so a fresh clone
101
+ # joins with no URL and no `remote add` — the recorded remote is for
102
+ # the checkout that has no carried address, or a DIFFERENT board.
103
+ carried = str(read_config(root).get("url", ""))
104
+ if carried and target in ("", carried.rsplit("/", 1)[-1]):
105
+ target = carried
106
+ else:
107
+ host, name = remote_cli.named(target)
108
+ target = f"{host}/{name}"
98
109
  base = target.partition("?")[0]
99
110
  params = query(target)
100
111
  invite = invite or params.get("invite", "")
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: taskops-cli
3
- Version: 0.3.2
3
+ Version: 0.3.3
4
4
  Summary: A shared work board for teams of coding agents, with a human who decides.
5
5
  Project-URL: Repository, https://github.com/bernatch22/taskops
6
6
  License-Expression: MIT
@@ -47,8 +47,9 @@ can be misread:
47
47
  into it exactly once, to install it and register your key.
48
48
  * **A board**: one directory on that host. Created from your laptop with
49
49
  `taskops board create`.
50
- * **A repo**: joined to one board. `taskops remote add` once per checkout, then
51
- every command runs bare.
50
+ * **A repo**: joined to one board. A clone carries the board's address
51
+ (`.taskops/board.json`, committed), so `taskops join` is the whole step;
52
+ `taskops remote add` covers a checkout that carries none.
52
53
  * **Your agents**: they never touch any of this — they talk to the board through
53
54
  the eleven MCP tools.
54
55
 
@@ -90,10 +91,11 @@ Joining a hosted one is bare, like every other verb — the host is recorded onc
90
91
  and the key is discovered the way ssh discovers one:
91
92
 
92
93
  ```sh
93
- taskops remote add https://host:8787 # once per checkout
94
- taskops join my-project # your key is registered: that is the credential
95
- taskops join my-project --invite <id> # first time: the invite enrols your key too
96
- taskops join my-project --github # first time, no invite: GitHub vouches for you
94
+ taskops join # a clone: board.json carries the address, done
95
+ taskops join --github # same clone, first time: GitHub vouches for you
96
+ taskops join my-project --invite <id> # first time by invite: it enrols your key too
97
+ taskops remote add https://host:8787 # no carried address? record the host once…
98
+ taskops join my-project # …and name the board
97
99
  taskops join my-project # no key + public board: read-only window
98
100
  ```
99
101
 
@@ -222,6 +224,59 @@ until an owner records it and cleared again with `--clear`. Only a board that
222
224
  has been opted in answers `--github` at all; every other one is invite-only,
223
225
  exactly as before.
224
226
 
227
+ ### The whole flow, and why cloning is not enough
228
+
229
+ Cloning the repo gives you the board's ADDRESS — `.taskops/board.json` is
230
+ committed and travels with the code — but the host has never seen you: it is a
231
+ different server from GitHub, sharing no session and no cookie with it. So one
232
+ command — `taskops join --github`, no URL: the clone already carries it —
233
+ introduces you, once per checkout, and after that GitHub is not in the picture
234
+ at all.
235
+
236
+ ```
237
+ GitHub the board HOST
238
+ <owner>/<repo>, private principals + allowed_signers
239
+ | |
240
+ 1. git clone -> the code, and .taskops/board.json (the address).
241
+ remote.json is 0600 and gitignored: no credential travels.
242
+ | |
243
+ 2. taskops join --github (the carried address) |
244
+ | |
245
+ |-- the CLI finds YOUR token: `gh auth token`, else $GITHUB_TOKEN,
246
+ | else a hidden prompt. Never a flag value — the shell writes those
247
+ | into ~/.zsh_history before the process starts.
248
+ | |
249
+ |-- POST /<board>/join/github ------------------>|
250
+ | { github_token, principal, pubkey } |
251
+ | |
252
+ | the host asks GitHub ONCE, with your token:
253
+ | "does this user have <need> on <owner>/<repo>?"
254
+ | |
255
+ | yes -> it writes exactly two rows: |
256
+ | principals: <you>, member
257
+ | allowed_signers: <you> ssh-ed25519 AAAA...
258
+ | no -> refused, without saying whether the repo exists
259
+ | |
260
+ 3. the GitHub token dies with that request. It is on neither disk.
261
+
262
+ from here on GitHub never participates again:
263
+
264
+ every session: your key signs a challenge -> the host checks it
265
+ (~/.ssh/id_ed25519) against allowed_signers
266
+ -> a 12h session
267
+ ```
268
+
269
+ The alternative — re-checking GitHub on every request — would mean the host
270
+ holding somebody else's token, or being handed one on each call. That is the
271
+ whole category of problem this removes: there is no token to steal because
272
+ there is none stored.
273
+
274
+ **One consequence, stated plainly: access is granted automatically and taken
275
+ back by hand.** Losing push on the repo does not close the board, because the
276
+ credential is no longer GitHub — it is the enrolled key. Remove it with
277
+ `taskops revoke --key SHA256:…`, which is the same verb an invite-enrolled key
278
+ takes.
279
+
225
280
  No URL and no `--key` after `remote add`: the host is recorded in the checkout,
226
281
  `board create` records the name, and the key is **discovered** the way ssh
227
282
  discovers one — `~/.ssh/id_ed25519`, `id_ecdsa`, `id_rsa`, in that order.
@@ -4,7 +4,7 @@ taskops/_errors.py,sha256=hBN_ChWVY09f61wNoy2dZPkwhEAQEQF5JRanpENCrNA,1548
4
4
  taskops/_ids.py,sha256=97FnOiBVKJ87zAgHj8ia-HZkKqkhx2J6jYeMFjcKq2s,2460
5
5
  taskops/_json.py,sha256=8pBzo98KdOKvSV3y3f7H7MF9RNb7zriK3152j8hQCHs,1682
6
6
  taskops/_locate.py,sha256=QmmglRfv8rhlqKQWvRdAaiqBdiU05Lxo-varGDSona0,4500
7
- taskops/_version.py,sha256=iRRyl7Bv-yQ3hYy6KhCfAdbf126hpSkzWrkGC7A1yxI,138
7
+ taskops/_version.py,sha256=c41JEarmRmSUDrwqGrSJhwQs-REk0YwuPsv9lHWoQ5k,138
8
8
  taskops/_wire.py,sha256=NrzkgZ5_-vPwETdF2HM_4GgROSdp_L-4rgBbyx-86T0,5392
9
9
  taskops/board.py,sha256=GtcEaJmApGdqXpYSDbSyW-UU3zT437LwQgLGbWOq6B8,7867
10
10
  taskops/identity.py,sha256=pFNElIrYfbLHL6AFy6yKHvL_bL2gA6pV3rIr7Ugvusc,5351
@@ -14,7 +14,7 @@ taskops/cli/__init__.py,sha256=RyUqzo36ftkMDHiOlBBQpcBXE8iuvYVJLKNw8WZgDCw,150
14
14
  taskops/cli/__main__.py,sha256=JZbVGIEQpWgl9VEyTpQL6ORQvj3ODt3ui19x1_0r3Rw,163
15
15
  taskops/cli/admin.py,sha256=K04upZZUSms70NpwZ97e-vBSQb9qVeEVzOxprPvIpn8,4021
16
16
  taskops/cli/claude.py,sha256=oMeageiJ_wfNloIjhDDHBKWV0RjnYo9Dd9htemlj_m4,9440
17
- taskops/cli/commands.py,sha256=s8SlIIJRSrNLEBaAOb0fWNxXrm-SbHXORNMIETqi8-0,8574
17
+ taskops/cli/commands.py,sha256=DRBa0HsJVxHGo3dOWZm10T_jnCWEeMMdRdRpeXOejHU,9338
18
18
  taskops/cli/enrol.py,sha256=BgyrjT6BebnJILYL4n1JIIE0BD8BHwB29z9nVLw8Pnw,5347
19
19
  taskops/cli/grants.py,sha256=LGlDtJLkiMSaxbVvriDzezTQvSAuY2Dcx2J8dqhmghA,2892
20
20
  taskops/cli/hooks.py,sha256=C7UREKPZ3DP0PF-fRB2MunfhB9kau7aqgpjhD6sfack,1996
@@ -129,7 +129,7 @@ taskops/verbs/report.py,sha256=WeeeP3C7jO_5DKxIGdvhuCgWVdEREaUIa2CCUv6dUMg,4563
129
129
  taskops/verbs/review.py,sha256=L0E0jlW4r7qZU3aDZbW3IUbPUwF4ENMehpW6OUWVlJw,3522
130
130
  taskops/verbs/take.py,sha256=HSQVbKCmWxF94pMHxpf9o0SUZZir2IHxwNBahg8Ku2I,4156
131
131
  taskops/verbs/update.py,sha256=17HzEmWjr7sFIF11-fMbEI0vzHE9VlYCdokVpaszi7E,8014
132
- taskops_cli-0.3.2.dist-info/METADATA,sha256=RGfZ05S2xLwAAWFKABV3mB6WXp_ql4lebNpY5sdEiGo,18089
133
- taskops_cli-0.3.2.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
134
- taskops_cli-0.3.2.dist-info/entry_points.txt,sha256=vXgZOPfyYUPtaXX8EVEQd7JkXRYU82Nf-gdwV3Gaik0,45
135
- taskops_cli-0.3.2.dist-info/RECORD,,
132
+ taskops_cli-0.3.3.dist-info/METADATA,sha256=6qgUMO1b5jlIZ3vFIt5CaNGr6x74PPd_7xdYRL5WSI4,21052
133
+ taskops_cli-0.3.3.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87
134
+ taskops_cli-0.3.3.dist-info/entry_points.txt,sha256=vXgZOPfyYUPtaXX8EVEQd7JkXRYU82Nf-gdwV3Gaik0,45
135
+ taskops_cli-0.3.3.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: hatchling 1.31.0
2
+ Generator: hatchling 1.32.0
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any