code-puppy 0.0.365__py3-none-any.whl → 0.0.367__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.
@@ -23,20 +23,78 @@ from .constants import (
23
23
  logger = logging.getLogger(__name__)
24
24
 
25
25
 
26
- def _inline_refs(
27
- schema: dict, convert_unions: bool = False, simplify_for_claude: bool = False
28
- ) -> dict:
26
+ def _flatten_union_to_object(union_items: list, defs: dict, resolve_fn) -> dict:
27
+ """Flatten a union of object types into a single object with all properties.
28
+
29
+ For discriminated unions like EditFilePayload (ContentPayload | ReplacementsPayload | DeleteSnippetPayload),
30
+ we merge all object types into one with all properties marked as optional.
31
+ """
32
+ merged_properties = {}
33
+ has_string_type = False
34
+
35
+ for item in union_items:
36
+ if not isinstance(item, dict):
37
+ continue
38
+
39
+ # Resolve $ref first
40
+ if "$ref" in item:
41
+ ref_path = item["$ref"]
42
+ ref_name = None
43
+ if ref_path.startswith("#/$defs/"):
44
+ ref_name = ref_path[8:]
45
+ elif ref_path.startswith("#/definitions/"):
46
+ ref_name = ref_path[14:]
47
+ if ref_name and ref_name in defs:
48
+ item = copy.deepcopy(defs[ref_name])
49
+ else:
50
+ continue
51
+
52
+ # Check for string type (common fallback)
53
+ if item.get("type") == "string":
54
+ has_string_type = True
55
+ continue
56
+
57
+ # Skip null types
58
+ if item.get("type") == "null":
59
+ continue
60
+
61
+ # Merge properties from object types
62
+ if item.get("type") == "object" or "properties" in item:
63
+ props = item.get("properties", {})
64
+ for prop_name, prop_schema in props.items():
65
+ if prop_name not in merged_properties:
66
+ # Resolve the property schema
67
+ merged_properties[prop_name] = resolve_fn(
68
+ copy.deepcopy(prop_schema)
69
+ )
70
+
71
+ if not merged_properties:
72
+ # No object properties found, return string type as fallback
73
+ return {"type": "string"} if has_string_type else {"type": "object"}
74
+
75
+ # Build merged object - no required fields since any subset is valid
76
+ result = {
77
+ "type": "object",
78
+ "properties": merged_properties,
79
+ }
80
+
81
+ return result
82
+
83
+
84
+ def _inline_refs(schema: dict, simplify_unions: bool = False) -> dict:
29
85
  """Inline $ref references and transform schema for Antigravity compatibility.
30
86
 
31
87
  - Inlines $ref references
32
88
  - Removes $defs, definitions, $schema, $id
33
- - Optionally converts anyOf/oneOf/allOf to any_of/one_of/all_of (only for Gemini)
34
89
  - Removes unsupported fields like 'default', 'examples', 'const'
35
- - For Claude: simplifies anyOf unions to single types
90
+ - When simplify_unions=True: flattens anyOf/oneOf unions:
91
+ - For unions of objects: merges into single object with all properties
92
+ - For simple unions (string | null): picks first non-null type
93
+ (required for both Gemini AND Claude - neither supports union types in function schemas!)
36
94
 
37
95
  Args:
38
- convert_unions: If True, convert anyOf->any_of etc. (for Gemini).
39
- simplify_for_claude: If True, simplify anyOf to single types.
96
+ simplify_unions: If True, simplify anyOf/oneOf unions.
97
+ Required for Gemini and Claude models.
40
98
  """
41
99
  if not isinstance(schema, dict):
42
100
  return schema
@@ -47,31 +105,73 @@ def _inline_refs(
47
105
  # Extract $defs for reference resolution
48
106
  defs = schema.pop("$defs", schema.pop("definitions", {}))
49
107
 
50
- def resolve_refs(
51
- obj, convert_unions=convert_unions, simplify_for_claude=simplify_for_claude
52
- ):
108
+ def resolve_refs(obj, simplify_unions=simplify_unions):
53
109
  """Recursively resolve $ref references and transform schema."""
54
110
  if isinstance(obj, dict):
55
- # For Claude: simplify anyOf/oneOf unions to first non-null type
56
- if simplify_for_claude:
111
+ # Handle anyOf/oneOf unions
112
+ if simplify_unions:
57
113
  for union_key in ["anyOf", "oneOf"]:
58
114
  if union_key in obj:
59
115
  union = obj[union_key]
60
116
  if isinstance(union, list):
61
- # Find first non-null type
117
+ # Check if this is a complex union of objects (discriminated union)
118
+ # vs a simple nullable type (string | null)
119
+ object_count = 0
120
+ has_refs = False
121
+ for item in union:
122
+ if isinstance(item, dict):
123
+ if "$ref" in item:
124
+ has_refs = True
125
+ object_count += 1
126
+ elif (
127
+ item.get("type") == "object"
128
+ or "properties" in item
129
+ ):
130
+ object_count += 1
131
+
132
+ # If multiple objects or has refs, flatten to single object
133
+ if object_count > 1 or has_refs:
134
+ flattened = _flatten_union_to_object(
135
+ union, defs, resolve_refs
136
+ )
137
+ # Keep description if present
138
+ if "description" in obj:
139
+ flattened["description"] = obj["description"]
140
+ return flattened
141
+
142
+ # Simple union - pick first non-null type
62
143
  for item in union:
63
144
  if (
64
145
  isinstance(item, dict)
65
146
  and item.get("type") != "null"
66
147
  ):
67
- # Replace the whole object with this type
68
148
  result = dict(item)
69
- # Keep description if present
70
149
  if "description" in obj:
71
150
  result["description"] = obj["description"]
72
- return resolve_refs(
73
- result, convert_unions, simplify_for_claude
74
- )
151
+ return resolve_refs(result, simplify_unions)
152
+
153
+ # Also handle allOf by merging all schemas
154
+ if simplify_unions and "allOf" in obj:
155
+ all_of = obj["allOf"]
156
+ if isinstance(all_of, list):
157
+ merged = {}
158
+ merged_properties = {}
159
+ for item in all_of:
160
+ if isinstance(item, dict):
161
+ resolved_item = resolve_refs(item, simplify_unions)
162
+ # Deep merge properties dicts
163
+ if "properties" in resolved_item:
164
+ merged_properties.update(
165
+ resolved_item.pop("properties")
166
+ )
167
+ merged.update(resolved_item)
168
+ if merged_properties:
169
+ merged["properties"] = merged_properties
170
+ # Keep other fields from original object (except allOf)
171
+ for k, v in obj.items():
172
+ if k != "allOf":
173
+ merged[k] = v
174
+ return resolve_refs(merged, simplify_unions)
75
175
 
76
176
  # Check for $ref
77
177
  if "$ref" in obj:
@@ -111,34 +211,23 @@ def _inline_refs(
111
211
  ):
112
212
  continue
113
213
 
114
- # For Claude: skip additionalProperties
115
- if simplify_for_claude and key == "additionalProperties":
214
+ # Skip additionalProperties (not supported by Gemini or Claude)
215
+ if simplify_unions and key == "additionalProperties":
116
216
  continue
117
217
 
118
- # Optionally transform union types for Gemini
119
- new_key = key
120
- if convert_unions:
121
- if key == "anyOf":
122
- new_key = "any_of"
123
- elif key == "oneOf":
124
- new_key = "one_of"
125
- elif key == "allOf":
126
- new_key = "all_of"
127
- elif key == "additionalProperties":
128
- new_key = "additional_properties"
129
-
130
- result[new_key] = resolve_refs(
131
- value, convert_unions, simplify_for_claude
132
- )
218
+ # Skip any remaining union type keys that weren't simplified above
219
+ # (This shouldn't happen normally, but just in case)
220
+ if simplify_unions and key in ("anyOf", "oneOf", "allOf"):
221
+ continue
222
+
223
+ result[key] = resolve_refs(value, simplify_unions)
133
224
  return result
134
225
  elif isinstance(obj, list):
135
- return [
136
- resolve_refs(item, convert_unions, simplify_for_claude) for item in obj
137
- ]
226
+ return [resolve_refs(item, simplify_unions) for item in obj]
138
227
  else:
139
228
  return obj
140
229
 
141
- return resolve_refs(schema, convert_unions, simplify_for_claude)
230
+ return resolve_refs(schema, simplify_unions)
142
231
 
143
232
 
144
233
  class UnwrappedResponse(httpx.Response):
@@ -264,17 +353,91 @@ class UnwrappedSSEResponse(httpx.Response):
264
353
 
265
354
 
266
355
  class AntigravityClient(httpx.AsyncClient):
267
- """Custom httpx client that handles Antigravity request/response wrapping."""
356
+ """Custom httpx client that handles Antigravity request/response wrapping.
357
+
358
+ Supports proactive token refresh to prevent expiry during long sessions.
359
+ """
268
360
 
269
361
  def __init__(
270
362
  self,
271
363
  project_id: str = "",
272
364
  model_name: str = "",
365
+ refresh_token: str = "",
366
+ expires_at: Optional[float] = None,
367
+ on_token_refreshed: Optional[Any] = None,
273
368
  **kwargs: Any,
274
369
  ):
275
370
  super().__init__(**kwargs)
276
371
  self.project_id = project_id
277
372
  self.model_name = model_name
373
+ self._refresh_token = refresh_token
374
+ self._expires_at = expires_at
375
+ self._on_token_refreshed = on_token_refreshed
376
+ self._refresh_lock = None # Lazy init for async lock
377
+
378
+ async def _ensure_valid_token(self) -> None:
379
+ """Proactively refresh the access token if it's expired or about to expire.
380
+
381
+ This prevents 401 errors during long-running sessions by checking and
382
+ refreshing the token BEFORE making requests, not after they fail.
383
+ """
384
+ import asyncio
385
+
386
+ from .token import is_token_expired, refresh_access_token
387
+
388
+ # Skip if no refresh token configured
389
+ if not self._refresh_token:
390
+ return
391
+
392
+ # Check if token needs refresh (includes 60-second buffer)
393
+ if not is_token_expired(self._expires_at):
394
+ return
395
+
396
+ # Lazy init the async lock
397
+ if self._refresh_lock is None:
398
+ self._refresh_lock = asyncio.Lock()
399
+
400
+ async with self._refresh_lock:
401
+ # Double-check after acquiring lock (another coroutine may have refreshed)
402
+ if not is_token_expired(self._expires_at):
403
+ return
404
+
405
+ logger.debug("Proactively refreshing Antigravity access token...")
406
+
407
+ try:
408
+ # Run the synchronous refresh in a thread pool to avoid blocking
409
+ loop = asyncio.get_event_loop()
410
+ new_tokens = await loop.run_in_executor(
411
+ None, refresh_access_token, self._refresh_token
412
+ )
413
+
414
+ if new_tokens:
415
+ # Update internal state
416
+ new_access_token = new_tokens.access_token
417
+ self._expires_at = new_tokens.expires_at
418
+ self._refresh_token = new_tokens.refresh_token
419
+
420
+ # Update the Authorization header
421
+ self.headers["Authorization"] = f"Bearer {new_access_token}"
422
+
423
+ logger.info(
424
+ "Proactively refreshed Antigravity token (expires in %ds)",
425
+ int(self._expires_at - __import__("time").time()),
426
+ )
427
+
428
+ # Notify callback (e.g., to persist updated tokens)
429
+ if self._on_token_refreshed:
430
+ try:
431
+ self._on_token_refreshed(new_tokens)
432
+ except Exception as e:
433
+ logger.warning("Token refresh callback failed: %s", e)
434
+ else:
435
+ logger.warning(
436
+ "Failed to proactively refresh token - request may fail with 401"
437
+ )
438
+
439
+ except Exception as e:
440
+ logger.warning("Proactive token refresh error: %s", e)
278
441
 
279
442
  def _wrap_request(self, content: bytes, url: str) -> tuple[bytes, str, str, bool]:
280
443
  """Wrap request body in Antigravity envelope and transform URL.
@@ -331,15 +494,14 @@ class AntigravityClient(httpx.AsyncClient):
331
494
  )
332
495
 
333
496
  # Inline $refs and remove $defs from parameters
334
- # Convert unions (anyOf->any_of) only for Gemini
335
- # Simplify schemas for Claude (no anyOf, no additionalProperties)
497
+ # Simplify union types (anyOf/oneOf/allOf) for BOTH Gemini and Claude
498
+ # Neither API supports union types in function schemas!
336
499
  if "parameters" in func_decl:
337
500
  is_gemini = "gemini" in model.lower()
338
501
  is_claude = "claude" in model.lower()
339
502
  func_decl["parameters"] = _inline_refs(
340
503
  func_decl["parameters"],
341
- convert_unions=is_gemini,
342
- simplify_for_claude=is_claude,
504
+ simplify_unions=(is_gemini or is_claude),
343
505
  )
344
506
 
345
507
  # Fix generationConfig for Antigravity compatibility
@@ -425,6 +587,9 @@ class AntigravityClient(httpx.AsyncClient):
425
587
  """Override send to intercept at the lowest level with endpoint fallback."""
426
588
  import asyncio
427
589
 
590
+ # Proactively refresh token BEFORE making the request
591
+ await self._ensure_valid_token()
592
+
428
593
  # Transform POST requests to Antigravity format
429
594
  if request.method == "POST" and request.content:
430
595
  new_content, new_path, new_query, is_claude_thinking = self._wrap_request(
@@ -638,14 +803,36 @@ class AntigravityClient(httpx.AsyncClient):
638
803
  return None
639
804
 
640
805
 
806
+ # Type alias for token refresh callback
807
+ TokenRefreshCallback = Any # Callable[[OAuthTokens], None]
808
+
809
+
641
810
  def create_antigravity_client(
642
811
  access_token: str,
643
812
  project_id: str = "",
644
813
  model_name: str = "",
645
814
  base_url: str = "https://daily-cloudcode-pa.sandbox.googleapis.com",
646
815
  headers: Optional[Dict[str, str]] = None,
816
+ refresh_token: str = "",
817
+ expires_at: Optional[float] = None,
818
+ on_token_refreshed: Optional[TokenRefreshCallback] = None,
647
819
  ) -> AntigravityClient:
648
- """Create an httpx client configured for Antigravity API."""
820
+ """Create an httpx client configured for Antigravity API.
821
+
822
+ Args:
823
+ access_token: The OAuth access token for authentication
824
+ project_id: The GCP project ID
825
+ model_name: The model name being used
826
+ base_url: The API base URL
827
+ headers: Additional headers to include
828
+ refresh_token: The OAuth refresh token for proactive token refresh
829
+ expires_at: Unix timestamp when the access token expires
830
+ on_token_refreshed: Callback called when token is proactively refreshed,
831
+ receives OAuthTokens object to persist the new tokens
832
+
833
+ Returns:
834
+ An AntigravityClient configured for API requests with proactive token refresh
835
+ """
649
836
  # Start with Antigravity-specific headers
650
837
  default_headers = {
651
838
  "Authorization": f"Bearer {access_token}",
@@ -659,6 +846,9 @@ def create_antigravity_client(
659
846
  return AntigravityClient(
660
847
  project_id=project_id,
661
848
  model_name=model_name,
849
+ refresh_token=refresh_token,
850
+ expires_at=expires_at,
851
+ on_token_refreshed=on_token_refreshed,
662
852
  base_url=base_url,
663
853
  headers=default_headers,
664
854
  timeout=httpx.Timeout(180.0, connect=30.0),
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: code-puppy
3
- Version: 0.0.365
3
+ Version: 0.0.367
4
4
  Summary: Code generation agent
5
5
  Project-URL: repository, https://github.com/mpfaffenberger/code_puppy
6
6
  Project-URL: HomePage, https://github.com/mpfaffenberger/code_puppy
@@ -17,7 +17,6 @@ Classifier: Topic :: Software Development :: Code Generators
17
17
  Requires-Python: <3.14,>=3.11
18
18
  Requires-Dist: dbos>=2.5.0
19
19
  Requires-Dist: fastapi>=0.109.0
20
- Requires-Dist: google-genai>=1.20.0
21
20
  Requires-Dist: httpx[http2]>=0.24.1
22
21
  Requires-Dist: json-repair>=0.46.2
23
22
  Requires-Dist: mcp>=1.9.4
@@ -25,11 +24,12 @@ Requires-Dist: openai>=1.99.1
25
24
  Requires-Dist: pillow>=10.0.0
26
25
  Requires-Dist: playwright>=1.40.0
27
26
  Requires-Dist: prompt-toolkit>=3.0.52
28
- Requires-Dist: pydantic-ai-slim[anthropic,google,openai]==1.25.0
27
+ Requires-Dist: pydantic-ai-slim[anthropic,openai]==1.26.0
29
28
  Requires-Dist: pydantic>=2.4.0
30
29
  Requires-Dist: pyfiglet>=0.8.post1
31
30
  Requires-Dist: python-dotenv>=1.0.0
32
31
  Requires-Dist: rapidfuzz>=3.13.0
32
+ Requires-Dist: requests>=2.28.0
33
33
  Requires-Dist: rich>=13.4.2
34
34
  Requires-Dist: ripgrep==14.1.0
35
35
  Requires-Dist: tenacity>=8.2.0
@@ -7,10 +7,11 @@ code_puppy/cli_runner.py,sha256=w5CLKgQYYaT7My3Cga2StXYol-u6DBxNzzUuhhsfhsA,3495
7
7
  code_puppy/config.py,sha256=blowBU3bBOdQSuLYKBUrb7f7CxHH_e25a_A4lQGsjgk,53494
8
8
  code_puppy/error_logging.py,sha256=a80OILCUtJhexI6a9GM-r5LqIdjvSRzggfgPp2jv1X0,3297
9
9
  code_puppy/gemini_code_assist.py,sha256=KGS7sO5OLc83nDF3xxS-QiU6vxW9vcm6hmzilu79Ef8,13867
10
- code_puppy/http_utils.py,sha256=H3N5Qz2B1CcsGUYOycGWAqoNMr2P1NCVluKX3aRwRqI,10358
10
+ code_puppy/gemini_model.py,sha256=i8XXmx9s1eWEXpJ8U288w0yayTt6Nq8V-hxpUHhti4s,25984
11
+ code_puppy/http_utils.py,sha256=SAH6EOdbR6Cbfmi-4EtHDqRDBUV5bWtGc-5nr44F0Is,10418
11
12
  code_puppy/keymap.py,sha256=IvMkTlB_bIqOWpbTpmftkdyjhtD5todXuEIw1zCZ4u0,3584
12
13
  code_puppy/main.py,sha256=82r3vZy_XcyEsenLn82BnUusaoyL3Bpm_Th_jKgqecE,273
13
- code_puppy/model_factory.py,sha256=ZLJFL1LWc-45vR6vb-RarAEjOuC0UVXZ1ButEtsnp08,38636
14
+ code_puppy/model_factory.py,sha256=854Bo8wx59dOirAMhH0YuSXVHs-IxQBT_yrJGyVjKcA,39924
14
15
  code_puppy/model_utils.py,sha256=55TKNnGTXQlHJNqejL2PfQqQmChXfzOjJg-hlarfR7w,5551
15
16
  code_puppy/models.json,sha256=FMQdE_yvP_8y0xxt3K918UkFL9cZMYAqW1SfXcQkU_k,3105
16
17
  code_puppy/models_dev_api.json,sha256=wHjkj-IM_fx1oHki6-GqtOoCrRMR0ScK0f-Iz0UEcy8,548187
@@ -147,7 +148,7 @@ code_puppy/plugins/__init__.py,sha256=gWgrXWoFpl-3Mxz2DAvxKW6SkCWrOnw-hKsY9O7nHc
147
148
  code_puppy/plugins/oauth_puppy_html.py,sha256=Wpa-V_NlRiBAvo_OXHuR7wvOH_jSt8L9HSFGiab6xI0,13058
148
149
  code_puppy/plugins/antigravity_oauth/__init__.py,sha256=1miHihSqRNXO20Vh_Gn9M3Aa2szh0gtdSCaKKj9nq0Q,362
149
150
  code_puppy/plugins/antigravity_oauth/accounts.py,sha256=GQit2-K24bsopmTZyscFUq3M0cAEO5WutHWnipVdgz8,14304
150
- code_puppy/plugins/antigravity_oauth/antigravity_model.py,sha256=T81dp_j59BAKxcE8lIt3XKJQVB9Wb0IFugcNeR8mdVQ,29195
151
+ code_puppy/plugins/antigravity_oauth/antigravity_model.py,sha256=ZFarvPYgYixQxEmEylIIbsSl7fmL8bYD8RXL_9wzDRA,26281
151
152
  code_puppy/plugins/antigravity_oauth/config.py,sha256=BoQgqf5I2XoHWnBBo9vhCIc_XwPj9Mbp0Z95ygWwt78,1362
152
153
  code_puppy/plugins/antigravity_oauth/constants.py,sha256=qsrA10JJvzNuY0OobvvwCQcoGpILBninllcUUMKkUrQ,4644
153
154
  code_puppy/plugins/antigravity_oauth/oauth.py,sha256=ZHXJtZP63l6brOpX1WdLfuUClIleA79-4y36YUJc6Wo,15137
@@ -155,7 +156,7 @@ code_puppy/plugins/antigravity_oauth/register_callbacks.py,sha256=uKIvfzH-dXj1g_
155
156
  code_puppy/plugins/antigravity_oauth/storage.py,sha256=LW1DkY6Z-GRbBDrIitT6glKemZptp3NzldIrLRqTAK0,8971
156
157
  code_puppy/plugins/antigravity_oauth/test_plugin.py,sha256=n0kjFG8Vt2n1j0GgTRSdSyhF0t9xxE8Ht60SH5CSwzw,11027
157
158
  code_puppy/plugins/antigravity_oauth/token.py,sha256=WbiFCkrZvChpGXvwIYsJMgqU9xdJ81KwR062lFlnL3U,5038
158
- code_puppy/plugins/antigravity_oauth/transport.py,sha256=GykwfwDmru5bw5WqGyVgxALQ_UyshMSmtDXsUfdgbD4,27729
159
+ code_puppy/plugins/antigravity_oauth/transport.py,sha256=XgeG2Ys_Ui0_9cnrYWN7nENehFv76Rr2_QPAcIbPROI,35723
159
160
  code_puppy/plugins/antigravity_oauth/utils.py,sha256=mXHRv0l07r27VjtSsIy9rlpkUheP88RaM4x4M0O1mMY,5401
160
161
  code_puppy/plugins/chatgpt_oauth/__init__.py,sha256=Kjc6Hsz1sWvMD2OdAlWZvJRiKJSj4fx22boa-aVFKjA,189
161
162
  code_puppy/plugins/chatgpt_oauth/config.py,sha256=H_wAH9Duyn8WH2Kq8oe72uda-_4qu1uXLPun_SDdtsk,2023
@@ -207,10 +208,10 @@ code_puppy/tools/browser/chromium_terminal_manager.py,sha256=w1thQ_ACb6oV45L93TS
207
208
  code_puppy/tools/browser/terminal_command_tools.py,sha256=9byOZku-dwvTtCl532xt7Lumed_jTn0sLvUe_X75XCQ,19068
208
209
  code_puppy/tools/browser/terminal_screenshot_tools.py,sha256=J_21YO_495NvYgNFu9KQP6VYg2K_f8CtSdZuF94Yhnw,18448
209
210
  code_puppy/tools/browser/terminal_tools.py,sha256=F5LjVH3udSCFHmqC3O1UJLoLozZFZsEdX42jOmkqkW0,17853
210
- code_puppy-0.0.365.data/data/code_puppy/models.json,sha256=FMQdE_yvP_8y0xxt3K918UkFL9cZMYAqW1SfXcQkU_k,3105
211
- code_puppy-0.0.365.data/data/code_puppy/models_dev_api.json,sha256=wHjkj-IM_fx1oHki6-GqtOoCrRMR0ScK0f-Iz0UEcy8,548187
212
- code_puppy-0.0.365.dist-info/METADATA,sha256=cMC5fk4Nm2d_ZWj-1oLtK7vVJujRFjMOP-y3nNvRLh8,27611
213
- code_puppy-0.0.365.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
214
- code_puppy-0.0.365.dist-info/entry_points.txt,sha256=Tp4eQC99WY3HOKd3sdvb22vZODRq0XkZVNpXOag_KdI,91
215
- code_puppy-0.0.365.dist-info/licenses/LICENSE,sha256=31u8x0SPgdOq3izJX41kgFazWsM43zPEF9eskzqbJMY,1075
216
- code_puppy-0.0.365.dist-info/RECORD,,
211
+ code_puppy-0.0.367.data/data/code_puppy/models.json,sha256=FMQdE_yvP_8y0xxt3K918UkFL9cZMYAqW1SfXcQkU_k,3105
212
+ code_puppy-0.0.367.data/data/code_puppy/models_dev_api.json,sha256=wHjkj-IM_fx1oHki6-GqtOoCrRMR0ScK0f-Iz0UEcy8,548187
213
+ code_puppy-0.0.367.dist-info/METADATA,sha256=fgyQYowvJcFxYCStXVQwHncMqvLo1JBztvtaAl2U7Yg,27600
214
+ code_puppy-0.0.367.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
215
+ code_puppy-0.0.367.dist-info/entry_points.txt,sha256=Tp4eQC99WY3HOKd3sdvb22vZODRq0XkZVNpXOag_KdI,91
216
+ code_puppy-0.0.367.dist-info/licenses/LICENSE,sha256=31u8x0SPgdOq3izJX41kgFazWsM43zPEF9eskzqbJMY,1075
217
+ code_puppy-0.0.367.dist-info/RECORD,,