npcpy 1.2.19__py3-none-any.whl → 1.2.21__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.
@@ -3,162 +3,169 @@ try:
3
3
  import torch
4
4
  import torch.nn as nn
5
5
  from transformers import AutoTokenizer, AutoModelForSequenceClassification, Trainer, TrainingArguments
6
- except:
7
- pass
8
- import json
9
- from typing import List, Dict, Tuple
10
- import random
11
6
 
12
- class MemoryDataset(Dataset):
13
- def __init__(self, examples: List[Dict], tokenizer, max_length=512):
14
- self.examples = examples
15
- self.tokenizer = tokenizer
16
- self.max_length = max_length
17
-
18
- def __len__(self):
19
- return len(self.examples)
20
-
21
- def __getitem__(self, idx):
22
- example = self.examples[idx]
23
-
24
-
25
- text = f"Memory: {example['memory']}\nContext: {example.get('context', '')}"
26
-
27
- encoding = self.tokenizer(
28
- text,
29
- truncation=True,
30
- padding='max_length',
31
- max_length=self.max_length,
32
- return_tensors='pt'
33
- )
34
-
35
- return {
36
- 'input_ids': encoding['input_ids'].flatten(),
37
- 'attention_mask': encoding['attention_mask'].flatten(),
38
- 'labels': torch.tensor(example['label'], dtype=torch.long)
39
- }
7
+ import json
8
+ from typing import List, Dict, Tuple
9
+ import random
40
10
 
41
- class MemoryTrainer:
42
- def __init__(self, model_name="google/gemma-2b", device="cpu"):
43
- self.device = device
44
- self.tokenizer = AutoTokenizer.from_pretrained(model_name)
45
- if self.tokenizer.pad_token is None:
46
- self.tokenizer.pad_token = self.tokenizer.eos_token
47
-
48
-
49
- self.model = AutoModelForSequenceClassification.from_pretrained(
50
- model_name,
51
- num_labels=3
52
- ).to(device)
53
-
54
- def prepare_training_data(self, approved_memories: List[Dict],
55
- rejected_memories: List[Dict]) -> List[Dict]:
56
- """Prepare training data from memory examples"""
57
- examples = []
58
-
59
-
60
- for memory in approved_memories:
61
- examples.append({
62
- "memory": memory.get("final_memory") or memory.get("initial_memory"),
63
- "context": memory.get("context", ""),
64
- "label": 1
65
- })
66
-
67
-
68
- for memory in rejected_memories:
69
- examples.append({
70
- "memory": memory.get("initial_memory"),
71
- "context": memory.get("context", ""),
72
- "label": 0
73
- })
74
-
75
-
76
- edited_examples = []
77
- for memory in approved_memories[:len(rejected_memories)//2]:
78
- if memory.get("final_memory") and memory.get("initial_memory"):
79
-
80
- edited_examples.append({
11
+ class MemoryDataset(Dataset):
12
+ def __init__(self, examples: List[Dict], tokenizer, max_length=512):
13
+ self.examples = examples
14
+ self.tokenizer = tokenizer
15
+ self.max_length = max_length
16
+
17
+ def __len__(self):
18
+ return len(self.examples)
19
+
20
+ def __getitem__(self, idx):
21
+ example = self.examples[idx]
22
+
23
+
24
+ text = f"Memory: {example['memory']}\nContext: {example.get('context', '')}"
25
+
26
+ encoding = self.tokenizer(
27
+ text,
28
+ truncation=True,
29
+ padding='max_length',
30
+ max_length=self.max_length,
31
+ return_tensors='pt'
32
+ )
33
+
34
+ return {
35
+ 'input_ids': encoding['input_ids'].flatten(),
36
+ 'attention_mask': encoding['attention_mask'].flatten(),
37
+ 'labels': torch.tensor(example['label'], dtype=torch.long)
38
+ }
39
+
40
+ class MemoryTrainer:
41
+ def __init__(self, model_name="google/gemma-2b", device="cpu"):
42
+ self.device = device
43
+ self.tokenizer = AutoTokenizer.from_pretrained(model_name)
44
+ if self.tokenizer.pad_token is None:
45
+ self.tokenizer.pad_token = self.tokenizer.eos_token
46
+
47
+
48
+ self.model = AutoModelForSequenceClassification.from_pretrained(
49
+ model_name,
50
+ num_labels=3
51
+ ).to(device)
52
+
53
+ def prepare_training_data(self, approved_memories: List[Dict],
54
+ rejected_memories: List[Dict]) -> List[Dict]:
55
+ """Prepare training data from memory examples"""
56
+ examples = []
57
+
58
+
59
+ for memory in approved_memories:
60
+ examples.append({
61
+ "memory": memory.get("final_memory") or memory.get("initial_memory"),
62
+ "context": memory.get("context", ""),
63
+ "label": 1
64
+ })
65
+
66
+
67
+ for memory in rejected_memories:
68
+ examples.append({
81
69
  "memory": memory.get("initial_memory"),
82
70
  "context": memory.get("context", ""),
83
- "label": 2
71
+ "label": 0
84
72
  })
85
-
86
- examples.extend(edited_examples)
87
- random.shuffle(examples)
88
- return examples
73
+
74
+
75
+ edited_examples = []
76
+ for memory in approved_memories[:len(rejected_memories)//2]:
77
+ if memory.get("final_memory") and memory.get("initial_memory"):
78
+
79
+ edited_examples.append({
80
+ "memory": memory.get("initial_memory"),
81
+ "context": memory.get("context", ""),
82
+ "label": 2
83
+ })
84
+
85
+ examples.extend(edited_examples)
86
+ random.shuffle(examples)
87
+ return examples
89
88
 
90
- def train(self, approved_memories: List[Dict], rejected_memories: List[Dict],
91
- output_dir: str = "./memory_model", epochs: int = 3):
92
- """Train the memory classification model"""
93
-
94
- if len(approved_memories) < 10 or len(rejected_memories) < 10:
95
- print("Not enough training data. Need at least 10 approved and 10 rejected memories.")
96
- return False
97
-
98
- training_data = self.prepare_training_data(approved_memories, rejected_memories)
99
-
100
-
101
- split_idx = int(0.8 * len(training_data))
102
- train_data = training_data[:split_idx]
103
- val_data = training_data[split_idx:]
104
-
105
- train_dataset = MemoryDataset(train_data, self.tokenizer)
106
- val_dataset = MemoryDataset(val_data, self.tokenizer)
107
-
108
- training_args = TrainingArguments(
109
- output_dir=output_dir,
110
- num_train_epochs=epochs,
111
- per_device_train_batch_size=4,
112
- per_device_eval_batch_size=4,
113
- warmup_steps=100,
114
- weight_decay=0.01,
115
- logging_dir='./logs',
116
- evaluation_strategy="epoch",
117
- save_strategy="epoch",
118
- load_best_model_at_end=True,
119
- )
120
-
121
- trainer = Trainer(
122
- model=self.model,
123
- args=training_args,
124
- train_dataset=train_dataset,
125
- eval_dataset=val_dataset,
126
- )
127
-
128
- trainer.train()
129
- trainer.save_model()
130
- self.tokenizer.save_pretrained(output_dir)
131
-
132
- print(f"Model trained and saved to {output_dir}")
133
- return True
89
+ def train(self, approved_memories: List[Dict], rejected_memories: List[Dict],
90
+ output_dir: str = "./memory_model", epochs: int = 3):
91
+ """Train the memory classification model"""
92
+
93
+ if len(approved_memories) < 10 or len(rejected_memories) < 10:
94
+ print("Not enough training data. Need at least 10 approved and 10 rejected memories.")
95
+ return False
96
+
97
+ training_data = self.prepare_training_data(approved_memories, rejected_memories)
98
+
99
+
100
+ split_idx = int(0.8 * len(training_data))
101
+ train_data = training_data[:split_idx]
102
+ val_data = training_data[split_idx:]
103
+
104
+ train_dataset = MemoryDataset(train_data, self.tokenizer)
105
+ val_dataset = MemoryDataset(val_data, self.tokenizer)
106
+
107
+ training_args = TrainingArguments(
108
+ output_dir=output_dir,
109
+ num_train_epochs=epochs,
110
+ per_device_train_batch_size=4,
111
+ per_device_eval_batch_size=4,
112
+ warmup_steps=100,
113
+ weight_decay=0.01,
114
+ logging_dir='./logs',
115
+ evaluation_strategy="epoch",
116
+ save_strategy="epoch",
117
+ load_best_model_at_end=True,
118
+ )
119
+
120
+ trainer = Trainer(
121
+ model=self.model,
122
+ args=training_args,
123
+ train_dataset=train_dataset,
124
+ eval_dataset=val_dataset,
125
+ )
126
+
127
+ trainer.train()
128
+ trainer.save_model()
129
+ self.tokenizer.save_pretrained(output_dir)
130
+
131
+ print(f"Model trained and saved to {output_dir}")
132
+ return True
134
133
 
135
- def predict_memory_action(self, memory_content: str, context: str = "") -> Tuple[str, float]:
136
- """Predict what action to take on a memory"""
137
- text = f"Memory: {memory_content}\nContext: {context}"
138
-
139
- encoding = self.tokenizer(
140
- text,
141
- truncation=True,
142
- padding=True,
143
- max_length=512,
144
- return_tensors='pt'
145
- ).to(self.device)
146
-
147
- with torch.no_grad():
148
- outputs = self.model(**encoding)
149
- probabilities = torch.softmax(outputs.logits, dim=-1)
150
- predicted_class = torch.argmax(probabilities, dim=-1).item()
151
- confidence = probabilities[0][predicted_class].item()
152
-
153
- actions = {0: "model-rejected", 1: "model-approved", 2: "needs-editing"}
154
- return actions[predicted_class], confidence
134
+ def predict_memory_action(self, memory_content: str, context: str = "") -> Tuple[str, float]:
135
+ """Predict what action to take on a memory"""
136
+ text = f"Memory: {memory_content}\nContext: {context}"
137
+
138
+ encoding = self.tokenizer(
139
+ text,
140
+ truncation=True,
141
+ padding=True,
142
+ max_length=512,
143
+ return_tensors='pt'
144
+ ).to(self.device)
145
+
146
+ with torch.no_grad():
147
+ outputs = self.model(**encoding)
148
+ probabilities = torch.softmax(outputs.logits, dim=-1)
149
+ predicted_class = torch.argmax(probabilities, dim=-1).item()
150
+ confidence = probabilities[0][predicted_class].item()
151
+
152
+ actions = {0: "model-rejected", 1: "model-approved", 2: "needs-editing"}
153
+ return actions[predicted_class], confidence
155
154
 
156
- def auto_approve_memory(self, memory_content: str, context: str = "",
157
- confidence_threshold: float = 0.8) -> Dict:
158
- """Auto-approve memory if confidence is high enough"""
159
- action, confidence = self.predict_memory_action(memory_content, context)
160
-
161
- if confidence >= confidence_threshold:
162
- return {"action": action, "confidence": confidence, "auto_processed": True}
163
- else:
164
- return {"action": "pending_approval", "confidence": confidence, "auto_processed": False}
155
+ def auto_approve_memory(self, memory_content: str, context: str = "",
156
+ confidence_threshold: float = 0.8) -> Dict:
157
+ """Auto-approve memory if confidence is high enough"""
158
+ action, confidence = self.predict_memory_action(memory_content, context)
159
+
160
+ if confidence >= confidence_threshold:
161
+ return {"action": action, "confidence": confidence, "auto_processed": True}
162
+ else:
163
+ return {"action": "pending_approval", "confidence": confidence, "auto_processed": False}
164
+ except:
165
+ Dataset = None
166
+ nn = None
167
+ Trainer = None
168
+ TrainingArguments = None
169
+
170
+ MemoryDataset = None
171
+ MemoryTrainer = None
npcpy/npc_compiler.py CHANGED
@@ -1908,7 +1908,8 @@ class Team:
1908
1908
  "dataframes": {},
1909
1909
  "memories": {},
1910
1910
  "execution_history": [],
1911
- "npc_messages": {}
1911
+ "npc_messages": {},
1912
+ "context":''
1912
1913
  }
1913
1914
 
1914
1915
  if team_path:
@@ -2017,11 +2018,12 @@ class Team:
2017
2018
  self.databases = ctx_data['databases']
2018
2019
  else:
2019
2020
  self.databases = []
2020
- if 'context' in ctx_data:
2021
- self.context = ctx_data['context']
2022
- else:
2023
- self.context = ''
2024
-
2021
+
2022
+ base_context = ctx_data.get('context', '')
2023
+ self.shared_context['context'] = base_context
2024
+ if 'file_patterns' in ctx_data:
2025
+ file_cache = self._parse_file_patterns(ctx_data['file_patterns'])
2026
+ self.shared_context['files'] = file_cache
2025
2027
  if 'preferences' in ctx_data:
2026
2028
  self.preferences = ctx_data['preferences']
2027
2029
  else:
@@ -2031,7 +2033,7 @@ class Team:
2031
2033
  else:
2032
2034
  self.forenpc = self.npcs[list(self.npcs.keys())[0]] if self.npcs else None
2033
2035
  for key, item in ctx_data.items():
2034
- if key not in ['name', 'mcp_servers', 'databases', 'context']:
2036
+ if key not in ['name', 'mcp_servers', 'databases', 'context', 'file_patterns']:
2035
2037
  self.shared_context[key] = item
2036
2038
  return ctx_data
2037
2039
  return {}
@@ -2288,6 +2290,85 @@ class Team:
2288
2290
  team.save(team_dir)
2289
2291
 
2290
2292
  return True
2293
+ def _parse_file_patterns(self, patterns_config):
2294
+ """Parse file patterns configuration and load matching files into KV cache"""
2295
+ if not patterns_config:
2296
+ return {}
2297
+
2298
+ file_cache = {}
2299
+
2300
+ for pattern_entry in patterns_config:
2301
+ if isinstance(pattern_entry, str):
2302
+ pattern_entry = {"pattern": pattern_entry}
2303
+
2304
+ pattern = pattern_entry.get("pattern", "")
2305
+ recursive = pattern_entry.get("recursive", False)
2306
+ base_path = pattern_entry.get("base_path", ".")
2307
+
2308
+ if not pattern:
2309
+ continue
2310
+
2311
+ base_path = os.path.expanduser(base_path)
2312
+ if not os.path.isabs(base_path):
2313
+ base_path = os.path.join(self.team_path or os.getcwd(), base_path)
2314
+
2315
+ matching_files = self._find_matching_files(pattern, base_path, recursive)
2316
+
2317
+ for file_path in matching_files:
2318
+ file_content = self._load_file_content(file_path)
2319
+ if file_content:
2320
+ relative_path = os.path.relpath(file_path, base_path)
2321
+ file_cache[relative_path] = file_content
2322
+
2323
+ return file_cache
2324
+
2325
+ def _find_matching_files(self, pattern, base_path, recursive=False):
2326
+ """Find files matching the given pattern"""
2327
+ matching_files = []
2328
+
2329
+ if not os.path.exists(base_path):
2330
+ return matching_files
2331
+
2332
+ if recursive:
2333
+ for root, dirs, files in os.walk(base_path):
2334
+ for filename in files:
2335
+ if fnmatch.fnmatch(filename, pattern):
2336
+ matching_files.append(os.path.join(root, filename))
2337
+ else:
2338
+ try:
2339
+ for item in os.listdir(base_path):
2340
+ item_path = os.path.join(base_path, item)
2341
+ if os.path.isfile(item_path) and fnmatch.fnmatch(item, pattern):
2342
+ matching_files.append(item_path)
2343
+ except PermissionError:
2344
+ print(f"Permission denied accessing {base_path}")
2345
+
2346
+ return matching_files
2347
+
2348
+ def _load_file_content(self, file_path):
2349
+ """Load content from a file with error handling"""
2350
+ try:
2351
+ with open(file_path, 'r', encoding='utf-8') as f:
2352
+ return f.read()
2353
+ except Exception as e:
2354
+ print(f"Error reading {file_path}: {e}")
2355
+ return None
2356
+
2357
+
2358
+ def _format_parsed_files_context(self, parsed_files):
2359
+ """Format parsed files into context string"""
2360
+ if not parsed_files:
2361
+ return ""
2362
+
2363
+ context_parts = ["Additional context from files:"]
2364
+
2365
+ for file_path, content in parsed_files.items():
2366
+ context_parts.append(f"\n--- {file_path} ---")
2367
+ context_parts.append(content)
2368
+ context_parts.append("")
2369
+
2370
+ return "\n".join(context_parts)
2371
+
2291
2372
  class Pipeline:
2292
2373
  def __init__(self, pipeline_data=None, pipeline_path=None, npc_team=None):
2293
2374
  """Initialize a pipeline from data or file path"""
npcpy/serve.py CHANGED
@@ -443,7 +443,7 @@ def get_global_settings():
443
443
  "embedding_model": "nomic-embed-text",
444
444
  "embedding_provider": "ollama",
445
445
  "search_provider": "perplexity",
446
- "NPCSH_LICENSE_KEY": "",
446
+ "NPC_STUDIO_LICENSE_KEY": "",
447
447
  "default_folder": os.path.expanduser("~/.npcsh/"),
448
448
  }
449
449
  global_vars = {}
@@ -479,7 +479,7 @@ def get_global_settings():
479
479
  "NPCSH_EMBEDDING_MODEL": "embedding_model",
480
480
  "NPCSH_EMBEDDING_PROVIDER": "embedding_provider",
481
481
  "NPCSH_SEARCH_PROVIDER": "search_provider",
482
- "NPCSH_LICENSE_KEY": "NPCSH_LICENSE_KEY",
482
+ "NPC_STUDIO_LICENSE_KEY": "NPC_STUDIO_LICENSE_KEY",
483
483
  "NPCSH_STREAM_OUTPUT": "NPCSH_STREAM_OUTPUT",
484
484
  "NPC_STUDIO_DEFAULT_FOLDER": "default_folder",
485
485
  }
@@ -521,7 +521,7 @@ def save_global_settings():
521
521
  "embedding_model": "NPCSH_EMBEDDING_MODEL",
522
522
  "embedding_provider": "NPCSH_EMBEDDING_PROVIDER",
523
523
  "search_provider": "NPCSH_SEARCH_PROVIDER",
524
- "NPCSH_LICENSE_KEY": "NPCSH_LICENSE_KEY",
524
+ "NPC_STUDIO_LICENSE_KEY": "NPC_STUDIO_LICENSE_KEY",
525
525
  "NPCSH_STREAM_OUTPUT": "NPCSH_STREAM_OUTPUT",
526
526
  "default_folder": "NPC_STUDIO_DEFAULT_FOLDER",
527
527
  }
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: npcpy
3
- Version: 1.2.19
3
+ Version: 1.2.21
4
4
  Summary: npcpy is the premier open-source library for integrating LLMs and Agents into python systems.
5
5
  Home-page: https://github.com/NPC-Worldwide/npcpy
6
6
  Author: Christopher Agostino
@@ -55,6 +55,7 @@ Requires-Dist: kuzu; extra == "local"
55
55
  Requires-Dist: chromadb; extra == "local"
56
56
  Requires-Dist: diffusers; extra == "local"
57
57
  Requires-Dist: torch; extra == "local"
58
+ Requires-Dist: datasets; extra == "local"
58
59
  Provides-Extra: yap
59
60
  Requires-Dist: pyaudio; extra == "yap"
60
61
  Requires-Dist: gtts; extra == "yap"
@@ -74,6 +75,7 @@ Requires-Dist: kuzu; extra == "all"
74
75
  Requires-Dist: chromadb; extra == "all"
75
76
  Requires-Dist: diffusers; extra == "all"
76
77
  Requires-Dist: torch; extra == "all"
78
+ Requires-Dist: datasets; extra == "all"
77
79
  Requires-Dist: pyaudio; extra == "all"
78
80
  Requires-Dist: gtts; extra == "all"
79
81
  Requires-Dist: playsound==1.2.2; extra == "all"
@@ -93,19 +95,15 @@ Dynamic: requires-python
93
95
  Dynamic: summary
94
96
 
95
97
  <p align="center">
96
- <img src="https://raw.githubusercontent.com/cagostino/npcpy/main/npcpy.png" alt="npcpy logo of a solarpunk sign">
98
+ <a href= "https://github.com/cagostino/npcpy/blob/main/docs/npcpy.md">
99
+ <img src="https://raw.githubusercontent.com/cagostino/npcpy/main/npcpy/npc-python.png" alt="npc-python logo" width=250></a>
97
100
  </p>
98
101
 
99
-
100
102
  # npcpy
101
103
 
102
104
  Welcome to `npcpy`, the core library of the NPC Toolkit that supercharges natural language processing pipelines and agent tooling. `npcpy` is a flexible framework for building state-of-the-art applications and conducting novel research with LLMs.
103
105
 
104
106
 
105
- <p align="center">
106
- <a href= "https://github.com/cagostino/npcpy/blob/main/docs/npcpy.md">
107
- <img src="https://raw.githubusercontent.com/cagostino/npcpy/main/npcpy/npc-python.png" alt="npc-python logo" width=250></a>
108
- </p>
109
107
 
110
108
 
111
109
  Here is an example for getting responses for a particular agent:
@@ -1,10 +1,10 @@
1
1
  npcpy/__init__.py,sha256=9imxFtK74_6Rw9rz0kyMnZYl_voPb569tkTlYLt0Urg,131
2
2
  npcpy/llm_funcs.py,sha256=tvcZuQEcIUJClwEJQXBF6ArEVjSuXt1jAcZOcnYWsVQ,85101
3
3
  npcpy/main.py,sha256=RWoRIj6VQLxKdOKvdVyaq2kwG35oRpeXPvp1CAAoG-w,81
4
- npcpy/npc_compiler.py,sha256=BpNlrjwkxhERTrFeFtvv9CUqzULoD2JQuEwRtqwQHLY,92107
4
+ npcpy/npc_compiler.py,sha256=10vu-9WUmlVzaFM_hMJH28iNS1IJXQP3Rb5RT1rZmpA,95326
5
5
  npcpy/npc_sysenv.py,sha256=lPYlKM_TeR4l4-Jcgiqq3CCge8b2oFHdfISD4L_G7eo,30308
6
6
  npcpy/npcs.py,sha256=eExuVsbTfrRobTRRptRpDm46jCLWUgbvy4_U7IUQo-c,744
7
- npcpy/serve.py,sha256=RPYT3ZMu-OmO6dg3Ss04wZQNlZQive5cm2UPUbtToV0,100271
7
+ npcpy/serve.py,sha256=O1dxISi0nQ6jsSOSxBXsULgkltnIcyBS6Z0AjfWmuXA,100296
8
8
  npcpy/tools.py,sha256=A5_oVmZkzGnI3BI-NmneuxeXQq-r29PbpAZP4nV4jrc,5303
9
9
  npcpy/data/__init__.py,sha256=1tcoChR-Hjn905JDLqaW9ElRmcISCTJdE7BGXPlym2Q,642
10
10
  npcpy/data/audio.py,sha256=goon4HfsYgx0bI-n1lhkrzWPrJoejJlycXcB0P62pyk,11280
@@ -17,7 +17,7 @@ npcpy/data/web.py,sha256=ARGoVKUlQmaiX0zJbSvvFmRCwOv_Z7Pcan9c5GxYObQ,5117
17
17
  npcpy/ft/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
18
18
  npcpy/ft/diff.py,sha256=R3Qo6v0-6M1iI0wiXhUzyuYI2ja0q_0i9bE0z3coxzU,28
19
19
  npcpy/ft/ge.py,sha256=my5LtGyVTT40V0i1h9FR-tFFA1FHSga-PeCCgUX1UUI,61
20
- npcpy/ft/memory_trainer.py,sha256=410BtNj308c7V45E809ILbDjCnVRy7n0mdIp2DKOCNY,5904
20
+ npcpy/ft/memory_trainer.py,sha256=QZPznxEEwXbOGroHdMUMa5xpqlNwgV6nqOazI2xgrnQ,6635
21
21
  npcpy/ft/rl.py,sha256=l3RUkEJe4b2yB6pildveu2LJymtNq0F17COwf_CCq3U,34
22
22
  npcpy/ft/sft.py,sha256=i4ENygRPArbLWN4XZZuBnPWaehs8M-J68JB_mewGJHI,62
23
23
  npcpy/gen/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -41,8 +41,8 @@ npcpy/work/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
41
41
  npcpy/work/desktop.py,sha256=F3I8mUtJp6LAkXodsh8hGZIncoads6c_2Utty-0EdDA,2986
42
42
  npcpy/work/plan.py,sha256=QyUwg8vElWiHuoS-xK4jXTxxHvkMD3VkaCEsCmrEPQk,8300
43
43
  npcpy/work/trigger.py,sha256=P1Y8u1wQRsS2WACims_2IdkBEar-iBQix-2TDWoW0OM,9948
44
- npcpy-1.2.19.dist-info/licenses/LICENSE,sha256=j0YPvce7Ng9e32zYOu0EmXjXeJ0Nwawd0RA3uSGGH4E,1070
45
- npcpy-1.2.19.dist-info/METADATA,sha256=dnJPjMvml-0yrEJXUt9g-Rf-UT9mIXuQR3HJZzYgm18,26084
46
- npcpy-1.2.19.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
47
- npcpy-1.2.19.dist-info/top_level.txt,sha256=g1pbSvrOOncB74Bg5-J0Olg4V0A5VzDw-Xz5YObq8BU,6
48
- npcpy-1.2.19.dist-info/RECORD,,
44
+ npcpy-1.2.21.dist-info/licenses/LICENSE,sha256=j0YPvce7Ng9e32zYOu0EmXjXeJ0Nwawd0RA3uSGGH4E,1070
45
+ npcpy-1.2.21.dist-info/METADATA,sha256=UoIjUU728VK_7CUbvzVqNnXkK8AjPEPwagfXBIdO1ko,26025
46
+ npcpy-1.2.21.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
47
+ npcpy-1.2.21.dist-info/top_level.txt,sha256=g1pbSvrOOncB74Bg5-J0Olg4V0A5VzDw-Xz5YObq8BU,6
48
+ npcpy-1.2.21.dist-info/RECORD,,
File without changes