invar-tools 1.15.0__py3-none-any.whl → 1.15.2__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()
@@ -7,16 +7,24 @@
7
7
 
8
8
  import { Type } from "@sinclair/typebox";
9
9
  import type { CustomToolFactory } from "@mariozechner/pi-coding-agent";
10
+ import * as fs from "fs";
11
+ import * as path from "path";
10
12
 
11
13
  const factory: CustomToolFactory = (pi) => {
12
- // Helper to check if invar is available
13
- async function checkInvarInstalled(): Promise<boolean> {
14
+ // Helper to resolve invar command (with uvx fallback)
15
+ async function resolveInvarCommand(): Promise<{ command: string; args: string[] }> {
16
+ // Try direct invar command first
14
17
  try {
15
18
  const result = await pi.exec("which", ["invar"]);
16
- return result.exitCode === 0;
19
+ if (result.exitCode === 0) {
20
+ return { command: "invar", args: [] };
21
+ }
17
22
  } catch {
18
- return false;
23
+ // Fall through to uvx
19
24
  }
25
+
26
+ // Fallback to uvx invar-tools
27
+ return { command: "uvx", args: ["invar-tools"] };
20
28
  }
21
29
 
22
30
  // Helper to validate path/target parameters (defense-in-depth)
@@ -58,12 +66,8 @@ const factory: CustomToolFactory = (pi) => {
58
66
  })),
59
67
  }),
60
68
  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"];
69
+ const cmd = await resolveInvarCommand();
70
+ const args = [...cmd.args, "guard"];
67
71
 
68
72
  // Default is --changed (check modified files)
69
73
  if (params.changed === false) {
@@ -80,7 +84,7 @@ const factory: CustomToolFactory = (pi) => {
80
84
  args.push("--strict");
81
85
  }
82
86
 
83
- const result = await pi.exec("invar", args, { cwd: pi.cwd, signal });
87
+ const result = await pi.exec(cmd.command, args, { cwd: pi.cwd, signal });
84
88
 
85
89
  if (result.killed) {
86
90
  throw new Error("Guard verification was cancelled");
@@ -111,16 +115,13 @@ const factory: CustomToolFactory = (pi) => {
111
115
  }),
112
116
  }),
113
117
  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
- }
118
+ const cmd = await resolveInvarCommand();
118
119
 
119
120
  if (!isValidPath(params.target)) {
120
121
  throw new Error("Invalid target path: contains unsafe characters or path traversal");
121
122
  }
122
123
 
123
- const result = await pi.exec("invar", ["sig", params.target], {
124
+ const result = await pi.exec(cmd.command, [...cmd.args, "sig", params.target], {
124
125
  cwd: pi.cwd,
125
126
  signal,
126
127
  });
@@ -160,16 +161,13 @@ const factory: CustomToolFactory = (pi) => {
160
161
  })),
161
162
  }),
162
163
  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
- }
164
+ const cmd = await resolveInvarCommand();
167
165
 
168
166
  if (params.path && params.path !== "." && !isValidPath(params.path)) {
169
167
  throw new Error("Invalid path: contains unsafe characters or path traversal");
170
168
  }
171
169
 
172
- const args = ["map"];
170
+ const args = [...cmd.args, "map"];
173
171
 
174
172
  if (params.path && params.path !== ".") {
175
173
  args.push(params.path);
@@ -179,7 +177,7 @@ const factory: CustomToolFactory = (pi) => {
179
177
  args.push("--top", params.top.toString());
180
178
  }
181
179
 
182
- const result = await pi.exec("invar", args, {
180
+ const result = await pi.exec(cmd.command, args, {
183
181
  cwd: pi.cwd,
184
182
  signal,
185
183
  });
@@ -201,6 +199,395 @@ const factory: CustomToolFactory = (pi) => {
201
199
  };
202
200
  },
203
201
  },
202
+
203
+ // =========================================================================
204
+ // invar_doc_toc - Extract document structure (Table of Contents)
205
+ // =========================================================================
206
+ {
207
+ name: "invar_doc_toc",
208
+ label: "Invar Doc TOC",
209
+ 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.",
210
+ parameters: Type.Object({
211
+ file: Type.String({
212
+ description: "Path to markdown file",
213
+ }),
214
+ depth: Type.Optional(Type.Number({
215
+ description: "Maximum heading depth to include (1-6)",
216
+ default: 6,
217
+ minimum: 1,
218
+ maximum: 6,
219
+ })),
220
+ }),
221
+ async execute(toolCallId, params, onUpdate, ctx, signal) {
222
+ const cmd = await resolveInvarCommand();
223
+
224
+ if (!isValidPath(params.file)) {
225
+ throw new Error("Invalid file path: contains unsafe characters or path traversal");
226
+ }
227
+
228
+ const args = [...cmd.args, "doc", "toc", params.file];
229
+
230
+ if (params.depth && params.depth !== 6) {
231
+ args.push("--depth", params.depth.toString());
232
+ }
233
+
234
+ const result = await pi.exec(cmd.command, args, {
235
+ cwd: pi.cwd,
236
+ signal,
237
+ });
238
+
239
+ if (result.killed) {
240
+ throw new Error("Doc toc command was cancelled");
241
+ }
242
+
243
+ if (result.exitCode !== 0) {
244
+ throw new Error(`Failed to extract TOC: ${result.stderr}`);
245
+ }
246
+
247
+ return {
248
+ content: [{ type: "text", text: result.stdout }],
249
+ details: {
250
+ file: params.file,
251
+ depth: params.depth || 6,
252
+ },
253
+ };
254
+ },
255
+ },
256
+
257
+ // =========================================================================
258
+ // invar_doc_read - Read a specific section from a document
259
+ // =========================================================================
260
+ {
261
+ name: "invar_doc_read",
262
+ label: "Invar Doc Read",
263
+ 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.",
264
+ parameters: Type.Object({
265
+ file: Type.String({
266
+ description: "Path to markdown file",
267
+ }),
268
+ section: Type.String({
269
+ description: "Section path: slug ('requirements/auth'), fuzzy ('auth'), index ('#0/#1'), or line ('@48')",
270
+ }),
271
+ children: Type.Optional(Type.Boolean({
272
+ description: "Include child sections in output",
273
+ default: true,
274
+ })),
275
+ }),
276
+ async execute(toolCallId, params, onUpdate, ctx, signal) {
277
+ const cmd = await resolveInvarCommand();
278
+
279
+ if (!isValidPath(params.file) || !isValidPath(params.section)) {
280
+ throw new Error("Invalid file or section path: contains unsafe characters or path traversal");
281
+ }
282
+
283
+ const args = [...cmd.args, "doc", "read", params.file, params.section, "--json"];
284
+
285
+ if (params.children === false) {
286
+ args.push("--no-children");
287
+ }
288
+
289
+ const result = await pi.exec(cmd.command, args, {
290
+ cwd: pi.cwd,
291
+ signal,
292
+ });
293
+
294
+ if (result.killed) {
295
+ throw new Error("Doc read command was cancelled");
296
+ }
297
+
298
+ if (result.exitCode !== 0) {
299
+ throw new Error(`Failed to read section: ${result.stderr}`);
300
+ }
301
+
302
+ return {
303
+ content: [{ type: "text", text: result.stdout }],
304
+ details: {
305
+ file: params.file,
306
+ section: params.section,
307
+ },
308
+ };
309
+ },
310
+ },
311
+
312
+ // =========================================================================
313
+ // invar_doc_find - Find sections matching a pattern
314
+ // =========================================================================
315
+ {
316
+ name: "invar_doc_find",
317
+ label: "Invar Doc Find",
318
+ 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.",
319
+ parameters: Type.Object({
320
+ file: Type.String({
321
+ description: "Path to markdown file",
322
+ }),
323
+ pattern: Type.String({
324
+ description: "Title pattern (glob-style, e.g., '*auth*')",
325
+ }),
326
+ content: Type.Optional(Type.String({
327
+ description: "Optional content search pattern",
328
+ })),
329
+ level: Type.Optional(Type.Number({
330
+ description: "Filter by heading level (1-6)",
331
+ minimum: 1,
332
+ maximum: 6,
333
+ })),
334
+ }),
335
+ async execute(toolCallId, params, onUpdate, ctx, signal) {
336
+ const cmd = await resolveInvarCommand();
337
+
338
+ if (!isValidPath(params.file)) {
339
+ throw new Error("Invalid file path: contains unsafe characters or path traversal");
340
+ }
341
+
342
+ const args = [...cmd.args, "doc", "find", params.pattern, params.file, "--json"];
343
+
344
+ if (params.content) {
345
+ args.push("--content", params.content);
346
+ }
347
+
348
+ if (params.level) {
349
+ args.push("--level", params.level.toString());
350
+ }
351
+
352
+ const result = await pi.exec(cmd.command, args, {
353
+ cwd: pi.cwd,
354
+ signal,
355
+ });
356
+
357
+ if (result.killed) {
358
+ throw new Error("Doc find command was cancelled");
359
+ }
360
+
361
+ if (result.exitCode !== 0) {
362
+ throw new Error(`Failed to find sections: ${result.stderr}`);
363
+ }
364
+
365
+ return {
366
+ content: [{ type: "text", text: result.stdout }],
367
+ details: {
368
+ file: params.file,
369
+ pattern: params.pattern,
370
+ },
371
+ };
372
+ },
373
+ },
374
+
375
+ // =========================================================================
376
+ // invar_doc_replace - Replace a section's content
377
+ // =========================================================================
378
+ {
379
+ name: "invar_doc_replace",
380
+ label: "Invar Doc Replace",
381
+ description: "Replace a section's content in a markdown document. Use this INSTEAD of Edit()/Write() for section replacement.",
382
+ parameters: Type.Object({
383
+ file: Type.String({
384
+ description: "Path to markdown file",
385
+ }),
386
+ section: Type.String({
387
+ description: "Section path to replace (slug, fuzzy, index, or line anchor)",
388
+ }),
389
+ content: Type.String({
390
+ description: "New content to replace the section with",
391
+ }),
392
+ keep_heading: Type.Optional(Type.Boolean({
393
+ description: "If true, preserve the original heading line",
394
+ default: true,
395
+ })),
396
+ }),
397
+ async execute(toolCallId, params, onUpdate, ctx, signal) {
398
+ const cmd = await resolveInvarCommand();
399
+
400
+ if (!isValidPath(params.file) || !isValidPath(params.section)) {
401
+ throw new Error("Invalid file or section path: contains unsafe characters or path traversal");
402
+ }
403
+
404
+ // Write content to temporary file to avoid shell injection
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 tmpFile = path.join(pi.cwd, `.invar-tmp-${Date.now()}.txt`);
487
+
488
+ try {
489
+ fs.writeFileSync(tmpFile, params.content, "utf-8");
490
+
491
+ const args = [
492
+ ...cmd.args,
493
+ "doc",
494
+ "insert",
495
+ params.file,
496
+ params.anchor,
497
+ "--content",
498
+ tmpFile,
499
+ ];
500
+
501
+ if (params.position && params.position !== "after") {
502
+ args.push("--position", params.position);
503
+ }
504
+
505
+ const result = await pi.exec(cmd.command, args, {
506
+ cwd: pi.cwd,
507
+ signal,
508
+ });
509
+
510
+ if (result.killed) {
511
+ throw new Error("Doc insert command was cancelled");
512
+ }
513
+
514
+ if (result.exitCode !== 0) {
515
+ throw new Error(`Failed to insert content: ${result.stderr}`);
516
+ }
517
+
518
+ return {
519
+ content: [{ type: "text", text: result.stdout || "Content inserted successfully" }],
520
+ details: {
521
+ file: params.file,
522
+ anchor: params.anchor,
523
+ position: params.position || "after",
524
+ },
525
+ };
526
+ } finally {
527
+ // Clean up temp file
528
+ try {
529
+ fs.unlinkSync(tmpFile);
530
+ } catch {
531
+ // Ignore cleanup errors
532
+ }
533
+ }
534
+ },
535
+ },
536
+
537
+ // =========================================================================
538
+ // invar_doc_delete - Delete a section from a document
539
+ // =========================================================================
540
+ {
541
+ name: "invar_doc_delete",
542
+ label: "Invar Doc Delete",
543
+ description: "Delete a section from a markdown document. Use this INSTEAD of Edit()/Write() for section deletion.",
544
+ parameters: Type.Object({
545
+ file: Type.String({
546
+ description: "Path to markdown file",
547
+ }),
548
+ section: Type.String({
549
+ description: "Section path to delete (slug, fuzzy, index, or line anchor)",
550
+ }),
551
+ children: Type.Optional(Type.Boolean({
552
+ description: "Include child sections in deletion",
553
+ default: true,
554
+ })),
555
+ }),
556
+ async execute(toolCallId, params, onUpdate, ctx, signal) {
557
+ const cmd = await resolveInvarCommand();
558
+
559
+ if (!isValidPath(params.file) || !isValidPath(params.section)) {
560
+ throw new Error("Invalid file or section path: contains unsafe characters or path traversal");
561
+ }
562
+
563
+ const args = [...cmd.args, "doc", "delete", params.file, params.section];
564
+
565
+ if (params.children === false) {
566
+ args.push("--no-children");
567
+ }
568
+
569
+ const result = await pi.exec(cmd.command, args, {
570
+ cwd: pi.cwd,
571
+ signal,
572
+ });
573
+
574
+ if (result.killed) {
575
+ throw new Error("Doc delete command was cancelled");
576
+ }
577
+
578
+ if (result.exitCode !== 0) {
579
+ throw new Error(`Failed to delete section: ${result.stderr}`);
580
+ }
581
+
582
+ return {
583
+ content: [{ type: "text", text: result.stdout || "Section deleted successfully" }],
584
+ details: {
585
+ file: params.file,
586
+ section: params.section,
587
+ },
588
+ };
589
+ },
590
+ },
204
591
  ];
205
592
  };
206
593
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: invar-tools
3
- Version: 1.15.0
3
+ Version: 1.15.2
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=tl2SZsbfYcTq7Zpk5T9gFxRtCE_PaFYVWi_GNHiaKzY,19722
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.2.dist-info/METADATA,sha256=hv_0ypXK9ZqotnGr7Ndo191bIKmZw-bA4sGeXpTgmwI,28595
185
+ invar_tools-1.15.2.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
186
+ invar_tools-1.15.2.dist-info/entry_points.txt,sha256=RwH_EhqgtFPsnO6RcrwrAb70Zyfb8Mh6uUtztWnUxGk,102
187
+ invar_tools-1.15.2.dist-info/licenses/LICENSE,sha256=qeFksp4H4kfTgQxPCIu3OdagXyiZcgBlVfsQ6M5oFyk,10767
188
+ invar_tools-1.15.2.dist-info/licenses/LICENSE-GPL,sha256=IvZfC6ZbP7CLjytoHVzvpDZpD-Z3R_qa1GdMdWlWQ6Q,35157
189
+ invar_tools-1.15.2.dist-info/licenses/NOTICE,sha256=joEyMyFhFY8Vd8tTJ-a3SirI0m2Sd0WjzqYt3sdcglc,2561
190
+ invar_tools-1.15.2.dist-info/RECORD,,