invar-tools 1.15.0__py3-none-any.whl → 1.15.1__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.
invar/shell/pi_tools.py CHANGED
@@ -35,6 +35,12 @@ def install_pi_tools(
35
35
  - invar_guard: Wrapper for invar guard command
36
36
  - invar_sig: Wrapper for invar sig command
37
37
  - invar_map: Wrapper for invar map command
38
+ - invar_doc_toc: Extract document structure
39
+ - invar_doc_read: Read specific section
40
+ - invar_doc_find: Find sections by pattern
41
+ - invar_doc_replace: Replace section content
42
+ - invar_doc_insert: Insert content relative to section
43
+ - invar_doc_delete: Delete section
38
44
  """
39
45
  tools_dir = project_path / PI_TOOLS_DIR
40
46
  tools_dir.mkdir(parents=True, exist_ok=True)
@@ -44,6 +50,7 @@ def install_pi_tools(
44
50
  console.print(" ✓ invar_guard - Smart verification (static + doctests + symbolic)")
45
51
  console.print(" ✓ invar_sig - Show function signatures and contracts")
46
52
  console.print(" ✓ invar_map - Symbol map with reference counts")
53
+ console.print(" ✓ 6 doc tools - Structured markdown editing (toc, read, find, replace, insert, delete)")
47
54
  console.print("")
48
55
 
49
56
  template_path = get_pi_tools_template_path()
@@ -9,14 +9,20 @@ import { Type } from "@sinclair/typebox";
9
9
  import type { CustomToolFactory } from "@mariozechner/pi-coding-agent";
10
10
 
11
11
  const factory: CustomToolFactory = (pi) => {
12
- // Helper to check if invar is available
13
- async function checkInvarInstalled(): Promise<boolean> {
12
+ // Helper to resolve invar command (with uvx fallback)
13
+ async function resolveInvarCommand(): Promise<{ command: string; args: string[] }> {
14
+ // Try direct invar command first
14
15
  try {
15
16
  const result = await pi.exec("which", ["invar"]);
16
- return result.exitCode === 0;
17
+ if (result.exitCode === 0) {
18
+ return { command: "invar", args: [] };
19
+ }
17
20
  } catch {
18
- return false;
21
+ // Fall through to uvx
19
22
  }
23
+
24
+ // Fallback to uvx invar-tools
25
+ return { command: "uvx", args: ["invar-tools"] };
20
26
  }
21
27
 
22
28
  // Helper to validate path/target parameters (defense-in-depth)
@@ -58,12 +64,8 @@ const factory: CustomToolFactory = (pi) => {
58
64
  })),
59
65
  }),
60
66
  async execute(toolCallId, params, onUpdate, ctx, signal) {
61
- const installed = await checkInvarInstalled();
62
- if (!installed) {
63
- throw new Error("Invar not installed. Run: pip install invar-tools");
64
- }
65
-
66
- const args = ["guard"];
67
+ const cmd = await resolveInvarCommand();
68
+ const args = [...cmd.args, "guard"];
67
69
 
68
70
  // Default is --changed (check modified files)
69
71
  if (params.changed === false) {
@@ -80,7 +82,7 @@ const factory: CustomToolFactory = (pi) => {
80
82
  args.push("--strict");
81
83
  }
82
84
 
83
- const result = await pi.exec("invar", args, { cwd: pi.cwd, signal });
85
+ const result = await pi.exec(cmd.command, args, { cwd: pi.cwd, signal });
84
86
 
85
87
  if (result.killed) {
86
88
  throw new Error("Guard verification was cancelled");
@@ -111,16 +113,13 @@ const factory: CustomToolFactory = (pi) => {
111
113
  }),
112
114
  }),
113
115
  async execute(toolCallId, params, onUpdate, ctx, signal) {
114
- const installed = await checkInvarInstalled();
115
- if (!installed) {
116
- throw new Error("Invar not installed. Run: pip install invar-tools");
117
- }
116
+ const cmd = await resolveInvarCommand();
118
117
 
119
118
  if (!isValidPath(params.target)) {
120
119
  throw new Error("Invalid target path: contains unsafe characters or path traversal");
121
120
  }
122
121
 
123
- const result = await pi.exec("invar", ["sig", params.target], {
122
+ const result = await pi.exec(cmd.command, [...cmd.args, "sig", params.target], {
124
123
  cwd: pi.cwd,
125
124
  signal,
126
125
  });
@@ -160,16 +159,13 @@ const factory: CustomToolFactory = (pi) => {
160
159
  })),
161
160
  }),
162
161
  async execute(toolCallId, params, onUpdate, ctx, signal) {
163
- const installed = await checkInvarInstalled();
164
- if (!installed) {
165
- throw new Error("Invar not installed. Run: pip install invar-tools");
166
- }
162
+ const cmd = await resolveInvarCommand();
167
163
 
168
164
  if (params.path && params.path !== "." && !isValidPath(params.path)) {
169
165
  throw new Error("Invalid path: contains unsafe characters or path traversal");
170
166
  }
171
167
 
172
- const args = ["map"];
168
+ const args = [...cmd.args, "map"];
173
169
 
174
170
  if (params.path && params.path !== ".") {
175
171
  args.push(params.path);
@@ -179,7 +175,7 @@ const factory: CustomToolFactory = (pi) => {
179
175
  args.push("--top", params.top.toString());
180
176
  }
181
177
 
182
- const result = await pi.exec("invar", args, {
178
+ const result = await pi.exec(cmd.command, args, {
183
179
  cwd: pi.cwd,
184
180
  signal,
185
181
  });
@@ -201,6 +197,399 @@ const factory: CustomToolFactory = (pi) => {
201
197
  };
202
198
  },
203
199
  },
200
+
201
+ // =========================================================================
202
+ // invar_doc_toc - Extract document structure (Table of Contents)
203
+ // =========================================================================
204
+ {
205
+ name: "invar_doc_toc",
206
+ label: "Invar Doc TOC",
207
+ description: "Extract document structure (Table of Contents) from markdown files. Shows headings hierarchy with line numbers and character counts. Use this INSTEAD of Read() to understand markdown structure.",
208
+ parameters: Type.Object({
209
+ file: Type.String({
210
+ description: "Path to markdown file",
211
+ }),
212
+ depth: Type.Optional(Type.Number({
213
+ description: "Maximum heading depth to include (1-6)",
214
+ default: 6,
215
+ minimum: 1,
216
+ maximum: 6,
217
+ })),
218
+ }),
219
+ async execute(toolCallId, params, onUpdate, ctx, signal) {
220
+ const cmd = await resolveInvarCommand();
221
+
222
+ if (!isValidPath(params.file)) {
223
+ throw new Error("Invalid file path: contains unsafe characters or path traversal");
224
+ }
225
+
226
+ const args = [...cmd.args, "doc", "toc", params.file];
227
+
228
+ if (params.depth && params.depth !== 6) {
229
+ args.push("--depth", params.depth.toString());
230
+ }
231
+
232
+ const result = await pi.exec(cmd.command, args, {
233
+ cwd: pi.cwd,
234
+ signal,
235
+ });
236
+
237
+ if (result.killed) {
238
+ throw new Error("Doc toc command was cancelled");
239
+ }
240
+
241
+ if (result.exitCode !== 0) {
242
+ throw new Error(`Failed to extract TOC: ${result.stderr}`);
243
+ }
244
+
245
+ return {
246
+ content: [{ type: "text", text: result.stdout }],
247
+ details: {
248
+ file: params.file,
249
+ depth: params.depth || 6,
250
+ },
251
+ };
252
+ },
253
+ },
254
+
255
+ // =========================================================================
256
+ // invar_doc_read - Read a specific section from a document
257
+ // =========================================================================
258
+ {
259
+ name: "invar_doc_read",
260
+ label: "Invar Doc Read",
261
+ description: "Read a specific section from a markdown document. Supports multiple addressing formats: slug path, fuzzy match, index (#0/#1), or line anchor (@48). Use this INSTEAD of Read() with manual line counting.",
262
+ parameters: Type.Object({
263
+ file: Type.String({
264
+ description: "Path to markdown file",
265
+ }),
266
+ section: Type.String({
267
+ description: "Section path: slug ('requirements/auth'), fuzzy ('auth'), index ('#0/#1'), or line ('@48')",
268
+ }),
269
+ children: Type.Optional(Type.Boolean({
270
+ description: "Include child sections in output",
271
+ default: true,
272
+ })),
273
+ }),
274
+ async execute(toolCallId, params, onUpdate, ctx, signal) {
275
+ const cmd = await resolveInvarCommand();
276
+
277
+ if (!isValidPath(params.file) || !isValidPath(params.section)) {
278
+ throw new Error("Invalid file or section path: contains unsafe characters or path traversal");
279
+ }
280
+
281
+ const args = [...cmd.args, "doc", "read", params.file, params.section, "--json"];
282
+
283
+ if (params.children === false) {
284
+ args.push("--no-children");
285
+ }
286
+
287
+ const result = await pi.exec(cmd.command, args, {
288
+ cwd: pi.cwd,
289
+ signal,
290
+ });
291
+
292
+ if (result.killed) {
293
+ throw new Error("Doc read command was cancelled");
294
+ }
295
+
296
+ if (result.exitCode !== 0) {
297
+ throw new Error(`Failed to read section: ${result.stderr}`);
298
+ }
299
+
300
+ return {
301
+ content: [{ type: "text", text: result.stdout }],
302
+ details: {
303
+ file: params.file,
304
+ section: params.section,
305
+ },
306
+ };
307
+ },
308
+ },
309
+
310
+ // =========================================================================
311
+ // invar_doc_find - Find sections matching a pattern
312
+ // =========================================================================
313
+ {
314
+ name: "invar_doc_find",
315
+ label: "Invar Doc Find",
316
+ description: "Find sections in markdown documents matching a pattern. Supports glob patterns for titles and optional content search. Use this INSTEAD of Grep in markdown files.",
317
+ parameters: Type.Object({
318
+ file: Type.String({
319
+ description: "Path to markdown file",
320
+ }),
321
+ pattern: Type.String({
322
+ description: "Title pattern (glob-style, e.g., '*auth*')",
323
+ }),
324
+ content: Type.Optional(Type.String({
325
+ description: "Optional content search pattern",
326
+ })),
327
+ level: Type.Optional(Type.Number({
328
+ description: "Filter by heading level (1-6)",
329
+ minimum: 1,
330
+ maximum: 6,
331
+ })),
332
+ }),
333
+ async execute(toolCallId, params, onUpdate, ctx, signal) {
334
+ const cmd = await resolveInvarCommand();
335
+
336
+ if (!isValidPath(params.file)) {
337
+ throw new Error("Invalid file path: contains unsafe characters or path traversal");
338
+ }
339
+
340
+ const args = [...cmd.args, "doc", "find", params.pattern, params.file, "--json"];
341
+
342
+ if (params.content) {
343
+ args.push("--content", params.content);
344
+ }
345
+
346
+ if (params.level) {
347
+ args.push("--level", params.level.toString());
348
+ }
349
+
350
+ const result = await pi.exec(cmd.command, args, {
351
+ cwd: pi.cwd,
352
+ signal,
353
+ });
354
+
355
+ if (result.killed) {
356
+ throw new Error("Doc find command was cancelled");
357
+ }
358
+
359
+ if (result.exitCode !== 0) {
360
+ throw new Error(`Failed to find sections: ${result.stderr}`);
361
+ }
362
+
363
+ return {
364
+ content: [{ type: "text", text: result.stdout }],
365
+ details: {
366
+ file: params.file,
367
+ pattern: params.pattern,
368
+ },
369
+ };
370
+ },
371
+ },
372
+
373
+ // =========================================================================
374
+ // invar_doc_replace - Replace a section's content
375
+ // =========================================================================
376
+ {
377
+ name: "invar_doc_replace",
378
+ label: "Invar Doc Replace",
379
+ description: "Replace a section's content in a markdown document. Use this INSTEAD of Edit()/Write() for section replacement.",
380
+ parameters: Type.Object({
381
+ file: Type.String({
382
+ description: "Path to markdown file",
383
+ }),
384
+ section: Type.String({
385
+ description: "Section path to replace (slug, fuzzy, index, or line anchor)",
386
+ }),
387
+ content: Type.String({
388
+ description: "New content to replace the section with",
389
+ }),
390
+ keep_heading: Type.Optional(Type.Boolean({
391
+ description: "If true, preserve the original heading line",
392
+ default: true,
393
+ })),
394
+ }),
395
+ async execute(toolCallId, params, onUpdate, ctx, signal) {
396
+ const cmd = await resolveInvarCommand();
397
+
398
+ if (!isValidPath(params.file) || !isValidPath(params.section)) {
399
+ throw new Error("Invalid file or section path: contains unsafe characters or path traversal");
400
+ }
401
+
402
+ // Write content to temporary file to avoid shell injection
403
+ const fs = require("fs");
404
+ const path = require("path");
405
+ const tmpFile = path.join(pi.cwd, `.invar-tmp-${Date.now()}.txt`);
406
+
407
+ try {
408
+ fs.writeFileSync(tmpFile, params.content, "utf-8");
409
+
410
+ const args = [
411
+ ...cmd.args,
412
+ "doc",
413
+ "replace",
414
+ params.file,
415
+ params.section,
416
+ "--content",
417
+ tmpFile,
418
+ ];
419
+
420
+ if (params.keep_heading === false) {
421
+ args.push("--no-keep-heading");
422
+ }
423
+
424
+ const result = await pi.exec(cmd.command, args, {
425
+ cwd: pi.cwd,
426
+ signal,
427
+ });
428
+
429
+ if (result.killed) {
430
+ throw new Error("Doc replace command was cancelled");
431
+ }
432
+
433
+ if (result.exitCode !== 0) {
434
+ throw new Error(`Failed to replace section: ${result.stderr}`);
435
+ }
436
+
437
+ return {
438
+ content: [{ type: "text", text: result.stdout || "Section replaced successfully" }],
439
+ details: {
440
+ file: params.file,
441
+ section: params.section,
442
+ },
443
+ };
444
+ } finally {
445
+ // Clean up temp file
446
+ try {
447
+ fs.unlinkSync(tmpFile);
448
+ } catch {
449
+ // Ignore cleanup errors
450
+ }
451
+ }
452
+ },
453
+ },
454
+
455
+ // =========================================================================
456
+ // invar_doc_insert - Insert new content relative to a section
457
+ // =========================================================================
458
+ {
459
+ name: "invar_doc_insert",
460
+ label: "Invar Doc Insert",
461
+ description: "Insert new content relative to a section in a markdown document. Use this INSTEAD of Edit()/Write() for section insertion.",
462
+ parameters: Type.Object({
463
+ file: Type.String({
464
+ description: "Path to markdown file",
465
+ }),
466
+ anchor: Type.String({
467
+ description: "Section path for the anchor (slug, fuzzy, index, or line anchor)",
468
+ }),
469
+ content: Type.String({
470
+ description: "Content to insert (include heading if new section)",
471
+ }),
472
+ position: Type.Optional(Type.String({
473
+ description: "Where to insert: 'before', 'after', 'first_child', 'last_child'",
474
+ default: "after",
475
+ enum: ["before", "after", "first_child", "last_child"],
476
+ })),
477
+ }),
478
+ async execute(toolCallId, params, onUpdate, ctx, signal) {
479
+ const cmd = await resolveInvarCommand();
480
+
481
+ if (!isValidPath(params.file) || !isValidPath(params.anchor)) {
482
+ throw new Error("Invalid file or anchor path: contains unsafe characters or path traversal");
483
+ }
484
+
485
+ // Write content to temporary file
486
+ const fs = require("fs");
487
+ const path = require("path");
488
+ const tmpFile = path.join(pi.cwd, `.invar-tmp-${Date.now()}.txt`);
489
+
490
+ try {
491
+ fs.writeFileSync(tmpFile, params.content, "utf-8");
492
+
493
+ const args = [
494
+ ...cmd.args,
495
+ "doc",
496
+ "insert",
497
+ params.file,
498
+ params.anchor,
499
+ "--content",
500
+ tmpFile,
501
+ ];
502
+
503
+ if (params.position && params.position !== "after") {
504
+ args.push("--position", params.position);
505
+ }
506
+
507
+ const result = await pi.exec(cmd.command, args, {
508
+ cwd: pi.cwd,
509
+ signal,
510
+ });
511
+
512
+ if (result.killed) {
513
+ throw new Error("Doc insert command was cancelled");
514
+ }
515
+
516
+ if (result.exitCode !== 0) {
517
+ throw new Error(`Failed to insert content: ${result.stderr}`);
518
+ }
519
+
520
+ return {
521
+ content: [{ type: "text", text: result.stdout || "Content inserted successfully" }],
522
+ details: {
523
+ file: params.file,
524
+ anchor: params.anchor,
525
+ position: params.position || "after",
526
+ },
527
+ };
528
+ } finally {
529
+ // Clean up temp file
530
+ try {
531
+ fs.unlinkSync(tmpFile);
532
+ } catch {
533
+ // Ignore cleanup errors
534
+ }
535
+ }
536
+ },
537
+ },
538
+
539
+ // =========================================================================
540
+ // invar_doc_delete - Delete a section from a document
541
+ // =========================================================================
542
+ {
543
+ name: "invar_doc_delete",
544
+ label: "Invar Doc Delete",
545
+ description: "Delete a section from a markdown document. Use this INSTEAD of Edit()/Write() for section deletion.",
546
+ parameters: Type.Object({
547
+ file: Type.String({
548
+ description: "Path to markdown file",
549
+ }),
550
+ section: Type.String({
551
+ description: "Section path to delete (slug, fuzzy, index, or line anchor)",
552
+ }),
553
+ children: Type.Optional(Type.Boolean({
554
+ description: "Include child sections in deletion",
555
+ default: true,
556
+ })),
557
+ }),
558
+ async execute(toolCallId, params, onUpdate, ctx, signal) {
559
+ const cmd = await resolveInvarCommand();
560
+
561
+ if (!isValidPath(params.file) || !isValidPath(params.section)) {
562
+ throw new Error("Invalid file or section path: contains unsafe characters or path traversal");
563
+ }
564
+
565
+ const args = [...cmd.args, "doc", "delete", params.file, params.section];
566
+
567
+ if (params.children === false) {
568
+ args.push("--no-children");
569
+ }
570
+
571
+ const result = await pi.exec(cmd.command, args, {
572
+ cwd: pi.cwd,
573
+ signal,
574
+ });
575
+
576
+ if (result.killed) {
577
+ throw new Error("Doc delete command was cancelled");
578
+ }
579
+
580
+ if (result.exitCode !== 0) {
581
+ throw new Error(`Failed to delete section: ${result.stderr}`);
582
+ }
583
+
584
+ return {
585
+ content: [{ type: "text", text: result.stdout || "Section deleted successfully" }],
586
+ details: {
587
+ file: params.file,
588
+ section: params.section,
589
+ },
590
+ };
591
+ },
592
+ },
204
593
  ];
205
594
  };
206
595
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: invar-tools
3
- Version: 1.15.0
3
+ Version: 1.15.1
4
4
  Summary: AI-native software engineering tools with design-by-contract verification
5
5
  Project-URL: Homepage, https://github.com/tefx/invar
6
6
  Project-URL: Documentation, https://github.com/tefx/invar#readme
@@ -72,7 +72,7 @@ invar/shell/mcp_config.py,sha256=-hC7Y5BGuVs285b6gBARk7ZyzVxHwPgXSyt_GoN0jfs,458
72
72
  invar/shell/mutation.py,sha256=Lfyk2b8j8-hxAq-iwAgQeOhr7Ci6c5tRF1TXe3CxQCs,8914
73
73
  invar/shell/pattern_integration.py,sha256=pRcjfq3NvMW_tvQCnaXZnD1k5AVEWK8CYOE2jN6VTro,7842
74
74
  invar/shell/pi_hooks.py,sha256=ulZc1sP8mTRJTBsjwFHQzUgg-h8ajRIMp7iF1Y4UUtw,6885
75
- invar/shell/pi_tools.py,sha256=_xTxE3zeEWSUm3IuuMziglkB_nL8NIco7kQ2nZkCMLU,3668
75
+ invar/shell/pi_tools.py,sha256=a3ACDDXykFV8fUB5UpBmgMvppwkmLvT1k_BWm0IY47k,4068
76
76
  invar/shell/property_tests.py,sha256=N9JreyH5PqR89oF5yLcX7ZAV-Koyg5BKo-J05-GUPsA,9109
77
77
  invar/shell/py_refs.py,sha256=Vjz50lmt9prDBcBv4nkkODdiJ7_DKu5zO4UPZBjAfmM,4638
78
78
  invar/shell/skill_manager.py,sha256=Mr7Mh9rxPSKSAOTJCAM5ZHiG5nfUf6KQVCuD4LBNHSI,12440
@@ -143,7 +143,7 @@ invar/templates/onboard/assessment.md.jinja,sha256=EzqF0VUcxJZG2bVJLxTOyQlAERRbh
143
143
  invar/templates/onboard/roadmap.md.jinja,sha256=gmvZk4Hdwe0l3qSFV15QGcsr-OPMhsc6-1K9F2SFSIQ,3939
144
144
  invar/templates/onboard/patterns/python.md,sha256=3wwucAcQz0DlggtpqYo-ZCnmrXgBQ0aBgUHN_EZ1VW0,8681
145
145
  invar/templates/onboard/patterns/typescript.md,sha256=yOVfHtdAdjKkWNh66_dR7z2xEA4sggbIcCKthW-fqac,11983
146
- invar/templates/pi-tools/invar/index.ts,sha256=fhE3aGKk5VYcUdbeVMv-x3tqWHA0eVU641pytY06Bvg,6694
146
+ invar/templates/pi-tools/invar/index.ts,sha256=G3ST0Bd8RWl7LnUGAMJmhIeTvq6L7C18ruVU4bs_S4s,19810
147
147
  invar/templates/protocol/INVAR.md.jinja,sha256=t2ZIQZJvzDTJMrRw_ijUo6ScZmeNK0-nV-H7ztTIyQQ,1464
148
148
  invar/templates/protocol/python/architecture-examples.md,sha256=O96LH9WFpk7G9MrhSbifLS5pyibTIDG-_EGFF7g3V4M,1175
149
149
  invar/templates/protocol/python/contracts-syntax.md,sha256=Q6supTQ3tChVrlN7xhcdb3Q8VGIESxQLA-mQvrNIZmo,1162
@@ -181,10 +181,10 @@ invar/templates/skills/invar-reflect/template.md,sha256=Rr5hvbllvmd8jSLf_0ZjyKt6
181
181
  invar/templates/skills/investigate/SKILL.md.jinja,sha256=cp6TBEixBYh1rLeeHOR1yqEnFqv1NZYePORMnavLkQI,3231
182
182
  invar/templates/skills/propose/SKILL.md.jinja,sha256=6BuKiCqO1AEu3VtzMHy1QWGqr_xqG9eJlhbsKT4jev4,3463
183
183
  invar/templates/skills/review/SKILL.md.jinja,sha256=ET5mbdSe_eKgJbi2LbgFC-z1aviKcHOBw7J5Q28fr4U,14105
184
- invar_tools-1.15.0.dist-info/METADATA,sha256=Xm0Suh3a3jfCfDBqzsuYpuVRwT4dWxRF5-PnZDMgYas,28595
185
- invar_tools-1.15.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
186
- invar_tools-1.15.0.dist-info/entry_points.txt,sha256=RwH_EhqgtFPsnO6RcrwrAb70Zyfb8Mh6uUtztWnUxGk,102
187
- invar_tools-1.15.0.dist-info/licenses/LICENSE,sha256=qeFksp4H4kfTgQxPCIu3OdagXyiZcgBlVfsQ6M5oFyk,10767
188
- invar_tools-1.15.0.dist-info/licenses/LICENSE-GPL,sha256=IvZfC6ZbP7CLjytoHVzvpDZpD-Z3R_qa1GdMdWlWQ6Q,35157
189
- invar_tools-1.15.0.dist-info/licenses/NOTICE,sha256=joEyMyFhFY8Vd8tTJ-a3SirI0m2Sd0WjzqYt3sdcglc,2561
190
- invar_tools-1.15.0.dist-info/RECORD,,
184
+ invar_tools-1.15.1.dist-info/METADATA,sha256=2Pgh_tDChmFZRZpdBlLTf4JrjsX4bFFKq8XznWFTuH4,28595
185
+ invar_tools-1.15.1.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
186
+ invar_tools-1.15.1.dist-info/entry_points.txt,sha256=RwH_EhqgtFPsnO6RcrwrAb70Zyfb8Mh6uUtztWnUxGk,102
187
+ invar_tools-1.15.1.dist-info/licenses/LICENSE,sha256=qeFksp4H4kfTgQxPCIu3OdagXyiZcgBlVfsQ6M5oFyk,10767
188
+ invar_tools-1.15.1.dist-info/licenses/LICENSE-GPL,sha256=IvZfC6ZbP7CLjytoHVzvpDZpD-Z3R_qa1GdMdWlWQ6Q,35157
189
+ invar_tools-1.15.1.dist-info/licenses/NOTICE,sha256=joEyMyFhFY8Vd8tTJ-a3SirI0m2Sd0WjzqYt3sdcglc,2561
190
+ invar_tools-1.15.1.dist-info/RECORD,,