alissa-tools-github-devloop 0.8.37__py3-none-any.whl → 0.8.39__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.
- alissa/tools/github/devloop/fleet_vitals.py +153 -25
- alissa/tools/github/devloop/loop.py +13 -1
- alissa/tools/github/devloop/version +1 -1
- alissa/tools/github/devloop/webui/__main__.py +3 -1
- alissa/tools/github/devloop/webui/server.py +24 -1
- {alissa_tools_github_devloop-0.8.37.dist-info → alissa_tools_github_devloop-0.8.39.dist-info}/METADATA +1 -1
- {alissa_tools_github_devloop-0.8.37.dist-info → alissa_tools_github_devloop-0.8.39.dist-info}/RECORD +12 -12
- {alissa_tools_github_devloop-0.8.37.dist-info → alissa_tools_github_devloop-0.8.39.dist-info}/WHEEL +0 -0
- {alissa_tools_github_devloop-0.8.37.dist-info → alissa_tools_github_devloop-0.8.39.dist-info}/entry_points.txt +0 -0
- {alissa_tools_github_devloop-0.8.37.dist-info → alissa_tools_github_devloop-0.8.39.dist-info}/licenses/LICENSE +0 -0
- {alissa_tools_github_devloop-0.8.37.dist-info → alissa_tools_github_devloop-0.8.39.dist-info}/licenses/NOTICE +0 -0
- {alissa_tools_github_devloop-0.8.37.dist-info → alissa_tools_github_devloop-0.8.39.dist-info}/top_level.txt +0 -0
|
@@ -45,19 +45,34 @@ answers (`FleetVitalsPusher.push_now`). Until Studio's schema carries the
|
|
|
45
45
|
block (the design's lane L3), a strict 400 naming `idle` makes the pusher
|
|
46
46
|
resend without it and leave it out for IDLE_REPROBE_S: the vitals the Factory
|
|
47
47
|
already renders never go dark over a field nobody reads yet.
|
|
48
|
+
|
|
49
|
+
The managed fleet's smoke (issue #155, studio.alissa.app provisioner design
|
|
50
|
+
§2.7, lane L9) adds the `config` block -- what this seat DERIVED, in its own
|
|
51
|
+
words: the feed owner actor id, the effective repository allowlist, the
|
|
52
|
+
reviewer login(s) it requests and its own GitHub login. SM2-SM4 compare it to
|
|
53
|
+
what Studio provisioned. Four fields, every one of them an identity or a
|
|
54
|
+
name, never a credential: the block is built from typed config fields, not
|
|
55
|
+
from the environment, and any value that carries a secret the process holds
|
|
56
|
+
(`SECRET_ENV`) is blanked before it can leave. The daemon's own pass sends
|
|
57
|
+
it; the console's out-of-pass push leaves it out (that process never
|
|
58
|
+
resolves the feed authority nor refreshes the allowlist, so its answer would
|
|
59
|
+
be a different one). A Studio whose strict schema predates the block gets
|
|
60
|
+
the `idle` treatment: resent without it, offered again after BLOCK_REPROBE_S.
|
|
48
61
|
"""
|
|
49
62
|
|
|
50
63
|
from __future__ import annotations
|
|
51
64
|
|
|
52
65
|
import json
|
|
53
66
|
import logging
|
|
67
|
+
import os
|
|
68
|
+
import re
|
|
54
69
|
import time
|
|
55
70
|
from datetime import datetime, timezone
|
|
56
71
|
from typing import Callable, Mapping, Sequence
|
|
57
72
|
|
|
58
73
|
from . import parking
|
|
59
74
|
from .alissa_client import AlissaAuthError, AlissaClient, AlissaError
|
|
60
|
-
from .config import Config
|
|
75
|
+
from .config import REPOS_BOWS, Config
|
|
61
76
|
from .version import version as running_version
|
|
62
77
|
from .webui import sources as sources_mod
|
|
63
78
|
from .webui import sysinfo
|
|
@@ -84,11 +99,30 @@ PUSHED = "pushed"
|
|
|
84
99
|
SKIPPED = "skipped"
|
|
85
100
|
FAILED = "failed"
|
|
86
101
|
|
|
87
|
-
#
|
|
88
|
-
#
|
|
89
|
-
#
|
|
90
|
-
|
|
91
|
-
|
|
102
|
+
# The blocks Studio's schema gained after the base contract, each in its own
|
|
103
|
+
# lane: `idle` (seat parking L3) and `config` (provisioner L5). A strict 400
|
|
104
|
+
# naming one of them is an old Studio, not a bad snapshot.
|
|
105
|
+
OPTIONAL_BLOCKS = ("idle", "config")
|
|
106
|
+
|
|
107
|
+
# How long a snapshot leaves an optional block out after Studio refused it as
|
|
108
|
+
# an unknown key, before it offers the block again. An hour: one extra POST
|
|
109
|
+
# an hour against an old Studio, and a newly deployed one starts receiving
|
|
110
|
+
# the block within the hour.
|
|
111
|
+
BLOCK_REPROBE_S = 3600.0
|
|
112
|
+
IDLE_REPROBE_S = BLOCK_REPROBE_S
|
|
113
|
+
|
|
114
|
+
# The `config` block's keys (provisioner design §2.7 / lane L9), in order.
|
|
115
|
+
CONFIG_KEYS = ("feedOwnerActorId", "repos", "reviewersRequested", "ghLogin")
|
|
116
|
+
|
|
117
|
+
# The credentials a devloop container holds (the provisioner's variable
|
|
118
|
+
# collection, design §2.6, plus gh's own fallback name). No `config` value
|
|
119
|
+
# may carry one; a shorter value than SECRET_MIN is not a credential and is
|
|
120
|
+
# not matched (a passcode of "1" must not blank every login holding a 1).
|
|
121
|
+
SECRET_ENV = (
|
|
122
|
+
"GH_TOKEN", "GITHUB_TOKEN", "ALISSA_API_TOKEN", "ALISSA_UI_PASSCODE",
|
|
123
|
+
"ANTHROPIC_API_KEY", "ANTHROPIC_AUTH_TOKEN", "CLAUDE_CODE_OAUTH_TOKEN",
|
|
124
|
+
)
|
|
125
|
+
SECRET_MIN = 8
|
|
92
126
|
|
|
93
127
|
|
|
94
128
|
def _session_grammar() -> "Callable[[str], object]":
|
|
@@ -207,6 +241,7 @@ def build_snapshot(
|
|
|
207
241
|
parse_session: "Callable[[str], object] | None" = None,
|
|
208
242
|
queued: "int | None" = None,
|
|
209
243
|
drain: "Mapping | None" = None,
|
|
244
|
+
config: "Mapping | None" = None,
|
|
210
245
|
) -> "dict":
|
|
211
246
|
"""The contract body for one pass, from the console's builders.
|
|
212
247
|
|
|
@@ -222,7 +257,10 @@ def build_snapshot(
|
|
|
222
257
|
`queued` (the undispatched alissa queue items, None when unread) and
|
|
223
258
|
`drain` (the holding drain row, or None) feed the `idle` block, whose
|
|
224
259
|
owed / timers come from the latest poll snapshot and whose live /
|
|
225
|
-
managed come from `roster` -- the same reading as `sessions`.
|
|
260
|
+
managed come from `roster` -- the same reading as `sessions`.
|
|
261
|
+
|
|
262
|
+
`config` is the seat's `config_block`, or None to leave the key out
|
|
263
|
+
(the console's push, which has no derived answer to give)."""
|
|
226
264
|
parse = parse_session or _session_grammar()
|
|
227
265
|
now_ts = heartbeat_at if now is None else now
|
|
228
266
|
|
|
@@ -276,7 +314,7 @@ def build_snapshot(
|
|
|
276
314
|
)
|
|
277
315
|
|
|
278
316
|
drift = sources.drift()
|
|
279
|
-
|
|
317
|
+
snapshot = {
|
|
280
318
|
"schemaVersion": SCHEMA_VERSION,
|
|
281
319
|
"seat": SEAT,
|
|
282
320
|
"asOf": iso(now_ts),
|
|
@@ -294,6 +332,65 @@ def build_snapshot(
|
|
|
294
332
|
"inbox": inbox,
|
|
295
333
|
"idle": idle,
|
|
296
334
|
}
|
|
335
|
+
if config is not None:
|
|
336
|
+
snapshot["config"] = dict(config)
|
|
337
|
+
return snapshot
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
def _carries_secret(value: str, secrets: "Sequence[str]") -> bool:
|
|
341
|
+
return any(secret in value for secret in secrets)
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
def config_block(
|
|
345
|
+
config: Config,
|
|
346
|
+
*,
|
|
347
|
+
gh_login: "str | None",
|
|
348
|
+
environ: "Mapping[str, str] | None" = None,
|
|
349
|
+
) -> "dict":
|
|
350
|
+
"""The seat's own word on what it derived (issue #155): four fields,
|
|
351
|
+
each from the config the daemon is RUNNING on, never from the raw env.
|
|
352
|
+
|
|
353
|
+
* `feedOwnerActorId` -- the bodies-of-work authority under
|
|
354
|
+
`repos_source: bows` (the token's own actor unless `bow_owners` names
|
|
355
|
+
one). Null in static mode (no feed is read) and when several owners
|
|
356
|
+
are trusted (there is no single answer to quote).
|
|
357
|
+
* `repos` -- the effective allowlist, sorted: under bows the derived
|
|
358
|
+
union the last refresh bound, in static mode the configured list.
|
|
359
|
+
* `reviewersRequested` -- the login(s) the handoff requests
|
|
360
|
+
(`ALISSA_DEV_REVIEWERS` and its fallbacks).
|
|
361
|
+
* `ghLogin` -- the account the gh token belongs to; null when unknown.
|
|
362
|
+
|
|
363
|
+
`environ` (default: the process env) supplies the secrets to scan for:
|
|
364
|
+
a value carrying one is blanked -- a list entry dropped, a scalar
|
|
365
|
+
nulled -- and named in a WARNING by field only."""
|
|
366
|
+
env = os.environ if environ is None else environ
|
|
367
|
+
secrets = [
|
|
368
|
+
v for k in SECRET_ENV if len(v := (env.get(k) or "").strip()) >= SECRET_MIN
|
|
369
|
+
]
|
|
370
|
+
owners = config.bow_owners if config.repos_source == REPOS_BOWS else ()
|
|
371
|
+
block: "dict" = {
|
|
372
|
+
"feedOwnerActorId": owners[0] if len(owners) == 1 else None,
|
|
373
|
+
"repos": sorted(config.repos, key=str.casefold),
|
|
374
|
+
"reviewersRequested": list(config.reviewers),
|
|
375
|
+
"ghLogin": gh_login or None,
|
|
376
|
+
}
|
|
377
|
+
for key in CONFIG_KEYS:
|
|
378
|
+
value = block[key]
|
|
379
|
+
if isinstance(value, list):
|
|
380
|
+
kept = [v for v in value if not _carries_secret(v, secrets)]
|
|
381
|
+
blanked = len(kept) != len(value)
|
|
382
|
+
block[key] = kept
|
|
383
|
+
else:
|
|
384
|
+
blanked = value is not None and _carries_secret(value, secrets)
|
|
385
|
+
if blanked:
|
|
386
|
+
block[key] = None
|
|
387
|
+
if blanked:
|
|
388
|
+
log.warning(
|
|
389
|
+
"fleet vitals: the config block's %s carried a credential "
|
|
390
|
+
"this process holds -- blanked before sending; check the "
|
|
391
|
+
"variable it is derived from", key,
|
|
392
|
+
)
|
|
393
|
+
return block
|
|
297
394
|
|
|
298
395
|
|
|
299
396
|
def encoded_size(snapshot: "Mapping") -> int:
|
|
@@ -342,6 +439,7 @@ class FleetVitalsPusher:
|
|
|
342
439
|
sources: "Sources | None" = None,
|
|
343
440
|
memory_reader: "Callable[[], Mapping | None]" = sysinfo.memory_usage,
|
|
344
441
|
clock: "Callable[[], float]" = time.time,
|
|
442
|
+
config_reader: "Callable[[], Mapping | None] | None" = None,
|
|
345
443
|
):
|
|
346
444
|
self.config = config
|
|
347
445
|
self.client = client
|
|
@@ -350,10 +448,13 @@ class FleetVitalsPusher:
|
|
|
350
448
|
)
|
|
351
449
|
self._memory = memory_reader
|
|
352
450
|
self._clock = clock
|
|
451
|
+
# The `config` block's source (the daemon's `_vitals_config`), or
|
|
452
|
+
# None -- the console's pusher -- to leave the block out.
|
|
453
|
+
self._config_reader = config_reader
|
|
353
454
|
self._not_activated_warned = False
|
|
354
|
-
#
|
|
355
|
-
# unknown key)
|
|
356
|
-
self.
|
|
455
|
+
# Per optional block: until when it is left out (Studio refused it
|
|
456
|
+
# as an unknown key). Absent = offer it.
|
|
457
|
+
self._refused_until: "dict[str, float]" = {}
|
|
357
458
|
self.last: "dict | None" = None
|
|
358
459
|
|
|
359
460
|
def push_pass(self, roster: "Sequence[Mapping] | None") -> str:
|
|
@@ -373,6 +474,20 @@ class FleetVitalsPusher:
|
|
|
373
474
|
heartbeat = snaps[0]["ts"] if snaps else now
|
|
374
475
|
return self._push(roster, heartbeat, now)
|
|
375
476
|
|
|
477
|
+
def _config(self) -> "Mapping | None":
|
|
478
|
+
"""The `config` block, or None. A reader that fails costs the block,
|
|
479
|
+
never the snapshot: the rest of the vitals still go out."""
|
|
480
|
+
if self._config_reader is None:
|
|
481
|
+
return None
|
|
482
|
+
try:
|
|
483
|
+
return self._config_reader()
|
|
484
|
+
except Exception as exc:
|
|
485
|
+
log.warning(
|
|
486
|
+
"fleet vitals: could not derive the config block (%s) — "
|
|
487
|
+
"pushing the snapshot without it", exc,
|
|
488
|
+
)
|
|
489
|
+
return None
|
|
490
|
+
|
|
376
491
|
def _push(
|
|
377
492
|
self, roster: "Sequence[Mapping] | None", heartbeat: float, now: float,
|
|
378
493
|
) -> str:
|
|
@@ -381,6 +496,7 @@ class FleetVitalsPusher:
|
|
|
381
496
|
self.sources, roster=roster, heartbeat_at=heartbeat,
|
|
382
497
|
now=now, memory=self._memory(),
|
|
383
498
|
queued=self.sources.queued(), drain=self.sources.drain(),
|
|
499
|
+
config=self._config(),
|
|
384
500
|
))
|
|
385
501
|
except Exception as exc:
|
|
386
502
|
log.warning(
|
|
@@ -388,16 +504,25 @@ class FleetVitalsPusher:
|
|
|
388
504
|
"pushed this pass; the next pass builds a fresh one", exc,
|
|
389
505
|
)
|
|
390
506
|
return FAILED
|
|
391
|
-
|
|
392
|
-
|
|
507
|
+
for key, until in self._refused_until.items():
|
|
508
|
+
if now < until:
|
|
509
|
+
snapshot.pop(key, None)
|
|
393
510
|
self.last = snapshot
|
|
394
511
|
return self._post(snapshot)
|
|
395
512
|
|
|
396
513
|
@staticmethod
|
|
397
|
-
def
|
|
398
|
-
"""
|
|
399
|
-
predates seat parking
|
|
400
|
-
|
|
514
|
+
def _refused_blocks(exc: AlissaError, snapshot: "Mapping") -> "list[str]":
|
|
515
|
+
"""The optional blocks of `snapshot` a 400 names: a Studio whose
|
|
516
|
+
strict schema predates them (seat parking's lane L3 adds `idle`, the
|
|
517
|
+
provisioner's lane L5 adds `config`). A word match, so `config`
|
|
518
|
+
is not read into an unrelated message that merely contains it."""
|
|
519
|
+
if exc.status != 400:
|
|
520
|
+
return []
|
|
521
|
+
detail = str(exc.detail)
|
|
522
|
+
return [
|
|
523
|
+
key for key in OPTIONAL_BLOCKS
|
|
524
|
+
if key in snapshot and re.search(rf"\b{key}\b", detail)
|
|
525
|
+
]
|
|
401
526
|
|
|
402
527
|
def _post(self, snapshot: "dict") -> str:
|
|
403
528
|
try:
|
|
@@ -409,16 +534,19 @@ class FleetVitalsPusher:
|
|
|
409
534
|
self._warn_failed(exc)
|
|
410
535
|
return FAILED
|
|
411
536
|
except AlissaError as exc:
|
|
412
|
-
|
|
413
|
-
|
|
537
|
+
refused = self._refused_blocks(exc, snapshot)
|
|
538
|
+
if refused:
|
|
539
|
+
until = self._clock() + BLOCK_REPROBE_S
|
|
540
|
+
for key in refused:
|
|
541
|
+
self._refused_until[key] = until
|
|
414
542
|
log.warning(
|
|
415
|
-
"fleet vitals: Studio refused the
|
|
416
|
-
"
|
|
417
|
-
"
|
|
418
|
-
|
|
543
|
+
"fleet vitals: Studio refused the %s block(s) (status "
|
|
544
|
+
"%s: %s) — its schema predates them; resending without "
|
|
545
|
+
"them, and leaving them out for %d min",
|
|
546
|
+
", ".join(f"`{k}`" for k in refused), exc.status,
|
|
547
|
+
exc.detail, int(BLOCK_REPROBE_S // 60),
|
|
419
548
|
)
|
|
420
|
-
trimmed =
|
|
421
|
-
trimmed.pop("idle")
|
|
549
|
+
trimmed = {k: v for k, v in snapshot.items() if k not in refused}
|
|
422
550
|
self.last = trimmed
|
|
423
551
|
return self._post(trimmed)
|
|
424
552
|
self._warn_failed(exc)
|
|
@@ -3432,7 +3432,8 @@ class DevWatcher:
|
|
|
3432
3432
|
# injectable so a test never opens a socket.
|
|
3433
3433
|
self.fleet_vitals = fleet_vitals or (
|
|
3434
3434
|
fleet_vitals_mod.FleetVitalsPusher(
|
|
3435
|
-
config, AlissaClient(base=config.alissa_endpoint)
|
|
3435
|
+
config, AlissaClient(base=config.alissa_endpoint),
|
|
3436
|
+
config_reader=self._vitals_config,
|
|
3436
3437
|
)
|
|
3437
3438
|
if config.fleet_vitals_enabled
|
|
3438
3439
|
else None
|
|
@@ -11296,6 +11297,17 @@ class DevWatcher:
|
|
|
11296
11297
|
return fleet_vitals_mod.SKIPPED
|
|
11297
11298
|
return self.fleet_vitals.push_pass(roster.entries)
|
|
11298
11299
|
|
|
11300
|
+
def _vitals_config(self) -> dict:
|
|
11301
|
+
"""The snapshot's `config` block (issue #155), read off what this
|
|
11302
|
+
watcher RUNS on: `self.config` is re-bound by every allowlist
|
|
11303
|
+
refresh and carries the feed authority `resolve_feed_authority`
|
|
11304
|
+
settled at boot, so the block reports the derived answer, not the
|
|
11305
|
+
configured one. The login is the gh token's (memoised after the
|
|
11306
|
+
first read; a failed read fails the block, never the push)."""
|
|
11307
|
+
return fleet_vitals_mod.config_block(
|
|
11308
|
+
self.config, gh_login=self.github.login
|
|
11309
|
+
)
|
|
11310
|
+
|
|
11299
11311
|
def _emit_loop_events(
|
|
11300
11312
|
self,
|
|
11301
11313
|
issue_results: "list[tuple[str, Decision]]",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
0.8.
|
|
1
|
+
0.8.39
|
|
@@ -134,9 +134,11 @@ def main(argv: "list[str] | None" = None) -> int:
|
|
|
134
134
|
)
|
|
135
135
|
server = make_server(app, args.host, args.port)
|
|
136
136
|
|
|
137
|
+
# An IPv6 literal is bracketed in a URL (`http://[::]:8080`).
|
|
138
|
+
shown = f"[{args.host}]" if ":" in args.host else args.host
|
|
137
139
|
print(
|
|
138
140
|
f"alissa-devloop-ui {version.value} -- serving on "
|
|
139
|
-
f"http://{
|
|
141
|
+
f"http://{shown}:{args.port} (passcode required); "
|
|
140
142
|
f"watching {config.workspace_root}",
|
|
141
143
|
flush=True,
|
|
142
144
|
)
|
|
@@ -34,6 +34,7 @@ from __future__ import annotations
|
|
|
34
34
|
|
|
35
35
|
import json
|
|
36
36
|
import re
|
|
37
|
+
import socket
|
|
37
38
|
import sqlite3
|
|
38
39
|
import sys
|
|
39
40
|
import time
|
|
@@ -546,8 +547,30 @@ _REDIRECT_HOME = (
|
|
|
546
547
|
)
|
|
547
548
|
|
|
548
549
|
|
|
550
|
+
class _DualStackServer(ThreadingHTTPServer):
|
|
551
|
+
"""An AF_INET6 listener that also takes IPv4 (as IPv4-mapped addresses).
|
|
552
|
+
|
|
553
|
+
Railway's private network resolves `*.railway.internal` to IPv6 only, so
|
|
554
|
+
a console bound to `0.0.0.0` refuses every in-project caller (issue #159).
|
|
555
|
+
IPV6_V6ONLY is cleared explicitly rather than trusting the host's
|
|
556
|
+
net.ipv6.bindv6only default, so `::` is dual-stack wherever it binds."""
|
|
557
|
+
|
|
558
|
+
address_family = socket.AF_INET6
|
|
559
|
+
|
|
560
|
+
def server_bind(self) -> None:
|
|
561
|
+
try:
|
|
562
|
+
self.socket.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 0)
|
|
563
|
+
except (AttributeError, OSError):
|
|
564
|
+
pass # a v6-only stack: still serve IPv6 rather than refuse to boot
|
|
565
|
+
super().server_bind()
|
|
566
|
+
|
|
567
|
+
|
|
549
568
|
def make_server(app: App, host: str, port: int) -> ThreadingHTTPServer:
|
|
550
|
-
|
|
569
|
+
"""Bind `host:port`. A host containing ':' is an IPv6 literal (`::` is
|
|
570
|
+
the dual-stack wildcard); anything else -- `0.0.0.0`, `127.0.0.1` -- keeps
|
|
571
|
+
the IPv4-only AF_INET listener it always had."""
|
|
572
|
+
cls = _DualStackServer if ":" in host else ThreadingHTTPServer
|
|
573
|
+
server = cls((host, port), Handler)
|
|
551
574
|
server.daemon_threads = True
|
|
552
575
|
server.app = app # type: ignore[attr-defined]
|
|
553
576
|
return server
|
{alissa_tools_github_devloop-0.8.37.dist-info → alissa_tools_github_devloop-0.8.39.dist-info}/RECORD
RENAMED
|
@@ -4,29 +4,29 @@ alissa/tools/github/devloop/alissa.py,sha256=TJU4eSZc2MvP35HewCwBKvhPAel32vKjtt3
|
|
|
4
4
|
alissa/tools/github/devloop/alissa_client.py,sha256=wmpXYWf9ThcLeYnz6gGeDz7gj5BpYjduuVASMc-QOOY,14873
|
|
5
5
|
alissa/tools/github/devloop/bows.py,sha256=pzDNMRk_S78FX7vMNYMlXHxW6VPHPo1Xd2EzFMgVCUM,17325
|
|
6
6
|
alissa/tools/github/devloop/config.py,sha256=VKeyTsXfoRSDltuU1h9UqjrHGE6YvgCQDvimDt3BH58,73330
|
|
7
|
-
alissa/tools/github/devloop/fleet_vitals.py,sha256=
|
|
7
|
+
alissa/tools/github/devloop/fleet_vitals.py,sha256=Hl2xshweh7A9R0N7fyYRg2WzHN4aNefVlt0TiXCv8zk,24979
|
|
8
8
|
alissa/tools/github/devloop/ghclient.py,sha256=tYUHwmoy9CBBt-jSOz9nuAl8lpMvm1LH4A-wHx25bss,71842
|
|
9
9
|
alissa/tools/github/devloop/hubs.py,sha256=yjnsnSHLG7GNNP8X2hILeAMV6P5n4JGc_IaAxpSP_YQ,18974
|
|
10
10
|
alissa/tools/github/devloop/janitor.py,sha256=R8REoHBeY2cfW7fYJVhKr-F5z9Mjlc-vmv-z4KtvBZc,27360
|
|
11
|
-
alissa/tools/github/devloop/loop.py,sha256=
|
|
11
|
+
alissa/tools/github/devloop/loop.py,sha256=Md5uX1Euo1T1SZ8x8um-TfirPjeTQsbb8ndQJ36nREs,590672
|
|
12
12
|
alissa/tools/github/devloop/loop_events.py,sha256=tmso0OALqY2yAPW2Fo6ae0sYetQIdIeIYooJ2Sc2cc4,37921
|
|
13
13
|
alissa/tools/github/devloop/parking.py,sha256=2Sys1bh_hG8qbq7cO9RxW1F1kAu3dCWHnvwg_iAFVvY,7987
|
|
14
14
|
alissa/tools/github/devloop/proc.py,sha256=_dDr3g76WEf2DRyLUz1Dzl4KvLVptJant_UZOrRixNk,1649
|
|
15
15
|
alissa/tools/github/devloop/prompts.py,sha256=VzN6ec5eF2wwg7QSCRg52WLcxo-tGbGRgQbc4HSahUc,55169
|
|
16
16
|
alissa/tools/github/devloop/state.py,sha256=uVwhbkogUTjWXXFq-COKzUeYrpuGbHzi05CjfEzAtPA,88668
|
|
17
|
-
alissa/tools/github/devloop/version,sha256=
|
|
17
|
+
alissa/tools/github/devloop/version,sha256=G7Ocfbe12QLl5nILgm4wm9MiFgTZ-m2G2Eqgqg_CJWc,7
|
|
18
18
|
alissa/tools/github/devloop/version.py,sha256=znuuOaitXGrBpbskNy78_rlVpFdtmKmk_3k1ACxZrl0,1418
|
|
19
19
|
alissa/tools/github/devloop/webui/__init__.py,sha256=vdXYoyGYzD7BrJ5lV6XdH8Bpw_pej8-RK0hjfxop7-8,1513
|
|
20
|
-
alissa/tools/github/devloop/webui/__main__.py,sha256=
|
|
20
|
+
alissa/tools/github/devloop/webui/__main__.py,sha256=wORyW5AkgsniK0TZcrOrvm_P7Q4Mr-fH-4S6qj9XcgA,6137
|
|
21
21
|
alissa/tools/github/devloop/webui/auth.py,sha256=g9YEDao9nKt-cBEENp7vFqwJ-T8olN6uLiCzVIN8yhA,7890
|
|
22
22
|
alissa/tools/github/devloop/webui/page.py,sha256=4OD3e01ByvVM4Wo04AZyFuyOYTFEMT2lagktZAvyCKk,30862
|
|
23
|
-
alissa/tools/github/devloop/webui/server.py,sha256=
|
|
23
|
+
alissa/tools/github/devloop/webui/server.py,sha256=YuUmcng5qdWqSurcuyxH3FKKkBBfrpDtvtZLidJOGiU,26226
|
|
24
24
|
alissa/tools/github/devloop/webui/sources.py,sha256=Ud8357-Ag4qC39V3mlepqnArui9p1fTJ058bAdN29Jc,36730
|
|
25
25
|
alissa/tools/github/devloop/webui/sysinfo.py,sha256=ptlogH9alFv0vZ-pQfyUaA3w-K7LQS3tQHGv0d8Tdlk,9649
|
|
26
|
-
alissa_tools_github_devloop-0.8.
|
|
27
|
-
alissa_tools_github_devloop-0.8.
|
|
28
|
-
alissa_tools_github_devloop-0.8.
|
|
29
|
-
alissa_tools_github_devloop-0.8.
|
|
30
|
-
alissa_tools_github_devloop-0.8.
|
|
31
|
-
alissa_tools_github_devloop-0.8.
|
|
32
|
-
alissa_tools_github_devloop-0.8.
|
|
26
|
+
alissa_tools_github_devloop-0.8.39.dist-info/licenses/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
|
|
27
|
+
alissa_tools_github_devloop-0.8.39.dist-info/licenses/NOTICE,sha256=FKstNs3uw_tQod4_1XcFLD1FUTJQXf4FC0oXJAOA_HE,260
|
|
28
|
+
alissa_tools_github_devloop-0.8.39.dist-info/METADATA,sha256=2AAwmlUxY0R_doi3rOjmqej-3vvHiea0iRF7qoWktiU,8093
|
|
29
|
+
alissa_tools_github_devloop-0.8.39.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
|
|
30
|
+
alissa_tools_github_devloop-0.8.39.dist-info/entry_points.txt,sha256=8aC7Ajzx4bu9ZRR37a40IDfuStacvZP5KsAxY1dbFKU,145
|
|
31
|
+
alissa_tools_github_devloop-0.8.39.dist-info/top_level.txt,sha256=DodjDg-l-TWQnlxG-Vuc3G5sB4O7cWGQwe6XrVx3u-A,7
|
|
32
|
+
alissa_tools_github_devloop-0.8.39.dist-info/RECORD,,
|
{alissa_tools_github_devloop-0.8.37.dist-info → alissa_tools_github_devloop-0.8.39.dist-info}/WHEEL
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|