makeless 0.1.1__tar.gz → 0.1.2__tar.gz

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.
Files changed (38) hide show
  1. {makeless-0.1.1 → makeless-0.1.2}/PKG-INFO +1 -1
  2. makeless-0.1.2/main.py +410 -0
  3. {makeless-0.1.1 → makeless-0.1.2}/makeless.egg-info/PKG-INFO +1 -1
  4. {makeless-0.1.1 → makeless-0.1.2}/makeless.egg-info/SOURCES.txt +1 -0
  5. {makeless-0.1.1 → makeless-0.1.2}/pyproject.toml +1 -1
  6. makeless-0.1.2/tools/app_finder.py +104 -0
  7. makeless-0.1.2/tools/database.py +62 -0
  8. {makeless-0.1.1 → makeless-0.1.2}/tools/filesystem.py +38 -14
  9. {makeless-0.1.1 → makeless-0.1.2}/tools/indexer.py +32 -44
  10. {makeless-0.1.1 → makeless-0.1.2}/tools/watcher.py +31 -38
  11. makeless-0.1.1/main.py +0 -337
  12. makeless-0.1.1/tools/app_finder.py +0 -80
  13. {makeless-0.1.1 → makeless-0.1.2}/LICENSE +0 -0
  14. {makeless-0.1.1 → makeless-0.1.2}/README.md +0 -0
  15. {makeless-0.1.1 → makeless-0.1.2}/agent/planner.py +0 -0
  16. {makeless-0.1.1 → makeless-0.1.2}/agent/runtime.py +0 -0
  17. {makeless-0.1.1 → makeless-0.1.2}/agent/state.py +0 -0
  18. {makeless-0.1.1 → makeless-0.1.2}/app/main.py +0 -0
  19. {makeless-0.1.1 → makeless-0.1.2}/execution/executor.py +0 -0
  20. {makeless-0.1.1 → makeless-0.1.2}/makeless.egg-info/dependency_links.txt +0 -0
  21. {makeless-0.1.1 → makeless-0.1.2}/makeless.egg-info/entry_points.txt +0 -0
  22. {makeless-0.1.1 → makeless-0.1.2}/makeless.egg-info/requires.txt +0 -0
  23. {makeless-0.1.1 → makeless-0.1.2}/makeless.egg-info/top_level.txt +0 -0
  24. {makeless-0.1.1 → makeless-0.1.2}/memory/store.py +0 -0
  25. {makeless-0.1.1 → makeless-0.1.2}/observability/logger.py +0 -0
  26. {makeless-0.1.1 → makeless-0.1.2}/requirements.txt +0 -0
  27. {makeless-0.1.1 → makeless-0.1.2}/security/permissions.py +0 -0
  28. {makeless-0.1.1 → makeless-0.1.2}/setup.cfg +0 -0
  29. {makeless-0.1.1 → makeless-0.1.2}/tests/test_state.py +0 -0
  30. {makeless-0.1.1 → makeless-0.1.2}/tools/app_launcher.py +0 -0
  31. {makeless-0.1.1 → makeless-0.1.2}/tools/builtin.py +0 -0
  32. {makeless-0.1.1 → makeless-0.1.2}/tools/launcher.py +0 -0
  33. {makeless-0.1.1 → makeless-0.1.2}/tools/locations.py +0 -0
  34. {makeless-0.1.1 → makeless-0.1.2}/tools/open_router.py +0 -0
  35. {makeless-0.1.1 → makeless-0.1.2}/tools/opener.py +0 -0
  36. {makeless-0.1.1 → makeless-0.1.2}/tools/reader.py +0 -0
  37. {makeless-0.1.1 → makeless-0.1.2}/tools/registry.py +0 -0
  38. {makeless-0.1.1 → makeless-0.1.2}/tools/selector.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: makeless
3
- Version: 0.1.1
3
+ Version: 0.1.2
4
4
  Summary: A local-first computer operator for Windows.
5
5
  Author: Avinash
6
6
  License: MIT License
makeless-0.1.2/main.py ADDED
@@ -0,0 +1,410 @@
1
+ from tools.filesystem import search_files
2
+ from tools.watcher import start_watcher
3
+ from tools.locations import get_system_location
4
+ from tools.reader import read_file
5
+ from tools.selector import select_result
6
+ from tools.open_router import open_item
7
+ from tools.app_finder import find_apps
8
+ from tools.app_launcher import launch_app
9
+ from tools.launcher import search_in_browser
10
+ from tools.indexer import ensure_index
11
+
12
+
13
+ def choose_path(query):
14
+ """
15
+ Search for an item and let the user choose if
16
+ multiple matches are found.
17
+ """
18
+
19
+ results = search_files(
20
+ query,
21
+ max_results=10
22
+ )
23
+
24
+ if not results:
25
+ return None
26
+
27
+ if len(results) == 1:
28
+ return results[0]
29
+
30
+ return select_result(results)
31
+
32
+
33
+ def choose_app(apps):
34
+ """
35
+ Choose the best application automatically.
36
+ """
37
+
38
+ if not apps:
39
+ return None
40
+
41
+ # app_finder removes duplicate application names
42
+ # and returns the best matches first.
43
+ return apps[0]
44
+
45
+ def handle_instruction(instruction):
46
+ instruction = instruction.strip()
47
+ lower_instruction = instruction.casefold()
48
+
49
+ # Read a file.
50
+ if lower_instruction.startswith("read "):
51
+ query = instruction[5:].strip()
52
+
53
+ if not query:
54
+ print(
55
+ "Makeless: Tell me what you want me to read."
56
+ )
57
+ return
58
+
59
+ print(
60
+ f"\nMakeless: Looking for '{query}'..."
61
+ )
62
+
63
+ path = choose_path(query)
64
+
65
+ if not path:
66
+ print(
67
+ "Makeless: I couldn't find that file."
68
+ )
69
+ return
70
+
71
+ success, content = read_file(path)
72
+
73
+ if not success:
74
+ print(
75
+ f"Makeless: {content}"
76
+ )
77
+ return
78
+
79
+ print("\n" + "=" * 50)
80
+ print(f"READING: {path}")
81
+ print("=" * 50 + "\n")
82
+ print(content)
83
+ print("\n" + "=" * 50)
84
+ print("End of file.")
85
+ print("=" * 50)
86
+
87
+ return
88
+
89
+ # Open a browser and search the web.
90
+ if lower_instruction.startswith("open "):
91
+
92
+ search_separators = [
93
+ " and search for ",
94
+ " and search "
95
+ ]
96
+
97
+ separator_found = None
98
+
99
+ for separator in search_separators:
100
+ if separator in lower_instruction:
101
+ separator_found = separator
102
+ break
103
+
104
+ if separator_found:
105
+ split_index = lower_instruction.index(
106
+ separator_found
107
+ )
108
+
109
+ browser_name = instruction[
110
+ 5:split_index
111
+ ].strip()
112
+
113
+ search_query = instruction[
114
+ split_index + len(separator_found):
115
+ ].strip()
116
+
117
+ if not browser_name:
118
+ print(
119
+ "Makeless: Tell me which browser to use."
120
+ )
121
+ return
122
+
123
+ if not search_query:
124
+ print(
125
+ "Makeless: Tell me what you want to search for."
126
+ )
127
+ return
128
+
129
+ print(
130
+ f"\nMakeless: Searching for "
131
+ f"'{search_query}' in "
132
+ f"{browser_name.title()}..."
133
+ )
134
+
135
+ success, message = search_in_browser(
136
+ search_query,
137
+ browser_name
138
+ )
139
+
140
+ print(f"Makeless: {message}")
141
+ return
142
+
143
+ # Find files and folders.
144
+ if lower_instruction.startswith("find "):
145
+ query = instruction[5:].strip()
146
+
147
+ if not query:
148
+ print(
149
+ "Makeless: Tell me what you want to find."
150
+ )
151
+ return
152
+
153
+ print(
154
+ f"\nMakeless: Searching for '{query}'...\n"
155
+ )
156
+
157
+ results = search_files(query)
158
+
159
+ if not results:
160
+ print(
161
+ "Makeless: Nothing found."
162
+ )
163
+ return
164
+
165
+ print(
166
+ f"Makeless: Found {len(results)} result(s)."
167
+ )
168
+
169
+ for index, result in enumerate(
170
+ results,
171
+ start=1
172
+ ):
173
+ print(
174
+ f"{index}. {result}"
175
+ )
176
+
177
+ return
178
+
179
+ # Open a browser and search the web.
180
+ separator = " and search for "
181
+
182
+ if (
183
+ lower_instruction.startswith("open ")
184
+ and separator in lower_instruction
185
+ ):
186
+ split_index = lower_instruction.index(
187
+ separator
188
+ )
189
+
190
+ browser_name = instruction[
191
+ 5:split_index
192
+ ].strip()
193
+
194
+ search_query = instruction[
195
+ split_index + len(separator):
196
+ ].strip()
197
+
198
+ if not browser_name:
199
+ print(
200
+ "Makeless: Tell me which browser to use."
201
+ )
202
+ return
203
+
204
+ if not search_query:
205
+ print(
206
+ "Makeless: Tell me what you want to search for."
207
+ )
208
+ return
209
+
210
+ print(
211
+ f"\nMakeless: Searching for "
212
+ f"'{search_query}' in "
213
+ f"{browser_name.title()}..."
214
+ )
215
+
216
+ success, message = search_in_browser(
217
+ search_query,
218
+ browser_name
219
+ )
220
+
221
+ print(f"Makeless: {message}")
222
+ return
223
+
224
+ # Open something in VS Code.
225
+ if (
226
+ lower_instruction.startswith("open ")
227
+ and (
228
+ lower_instruction.endswith(" in vs code")
229
+ or lower_instruction.endswith(" in vscode")
230
+ )
231
+ ):
232
+ if lower_instruction.endswith(
233
+ " in vs code"
234
+ ):
235
+ query = instruction[5:-11].strip()
236
+ else:
237
+ query = instruction[5:-10].strip()
238
+
239
+ if not query:
240
+ print(
241
+ "Makeless: Tell me what you want to open."
242
+ )
243
+ return
244
+
245
+ print(
246
+ f"\nMakeless: Looking for '{query}'..."
247
+ )
248
+
249
+ system_location = get_system_location(
250
+ query
251
+ )
252
+
253
+ if system_location:
254
+ path = str(system_location)
255
+ else:
256
+ path = choose_path(query)
257
+
258
+ if not path:
259
+ print(
260
+ "Makeless: I couldn't find that."
261
+ )
262
+ return
263
+
264
+ success, message = open_item(
265
+ path,
266
+ app="vscode"
267
+ )
268
+
269
+ print(f"Makeless: {message}")
270
+ return
271
+
272
+ # Open an app, file, or folder.
273
+ if lower_instruction.startswith("open "):
274
+ query = instruction[5:].strip()
275
+
276
+ if not query:
277
+ print(
278
+ "Makeless: Tell me what you want to open."
279
+ )
280
+ return
281
+
282
+ print(
283
+ f"\nMakeless: Looking for '{query}'..."
284
+ )
285
+
286
+ # 1. Check installed applications FIRST.
287
+ apps = find_apps(query)
288
+
289
+ if apps:
290
+ app = choose_app(apps)
291
+
292
+ if app:
293
+ success, message = launch_app(
294
+ app["path"]
295
+ )
296
+
297
+ if success:
298
+ print(
299
+ f"Makeless: Launched "
300
+ f"{app['name']}"
301
+ )
302
+ else:
303
+ print(
304
+ f"Makeless: {message}"
305
+ )
306
+
307
+ return
308
+
309
+ # 2. Check known system locations.
310
+ system_location = get_system_location(
311
+ query
312
+ )
313
+
314
+ if system_location:
315
+ path = str(system_location)
316
+
317
+ else:
318
+ # 3. Search indexed files and folders.
319
+ path = choose_path(query)
320
+
321
+ if not path:
322
+ print(
323
+ "Makeless: I couldn't find that."
324
+ )
325
+ return
326
+
327
+ success, message = open_item(path)
328
+
329
+ print(f"Makeless: {message}")
330
+ return
331
+
332
+ print(
333
+ "Makeless: I don't know how to perform "
334
+ "that task yet."
335
+ )
336
+
337
+
338
+ def main():
339
+ print("================================")
340
+ print(" Makeless")
341
+ print("================================")
342
+ print("Starting local computer operator...")
343
+
344
+ try:
345
+ ensure_index()
346
+
347
+ observer = start_watcher()
348
+
349
+ except Exception as error:
350
+ print(
351
+ f"\nMakeless: Startup error: {error}"
352
+ )
353
+ return
354
+
355
+ print("\nReady.")
356
+ print("Tell me what you want to do.")
357
+ print("Type 'exit' to close.\n")
358
+
359
+ try:
360
+ while True:
361
+ try:
362
+ instruction = input("You: ")
363
+
364
+ if (
365
+ instruction.strip().casefold()
366
+ == "exit"
367
+ ):
368
+ print(
369
+ "\nMakeless: Shutting down..."
370
+ )
371
+ break
372
+
373
+ if not instruction.strip():
374
+ continue
375
+
376
+ # Run one command safely.
377
+ # If it fails, Makeless stays alive.
378
+ try:
379
+ handle_instruction(instruction)
380
+
381
+ except Exception as error:
382
+ print(
383
+ "\nMakeless: Something went wrong "
384
+ f"while handling that command: {error}"
385
+ )
386
+ print(
387
+ "Makeless: Try another command."
388
+ )
389
+
390
+ except EOFError:
391
+ print(
392
+ "\nMakeless: Input closed."
393
+ )
394
+ break
395
+
396
+ except KeyboardInterrupt:
397
+ print(
398
+ "\n\nMakeless: Interrupted. "
399
+ "Shutting down..."
400
+ )
401
+
402
+ finally:
403
+ observer.stop()
404
+ observer.join()
405
+
406
+ print("Makeless: Goodbye!")
407
+
408
+
409
+ if __name__ == "__main__":
410
+ main()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: makeless
3
- Version: 0.1.1
3
+ Version: 0.1.2
4
4
  Summary: A local-first computer operator for Windows.
5
5
  Author: Avinash
6
6
  License: MIT License
@@ -21,6 +21,7 @@ tests/test_state.py
21
21
  tools/app_finder.py
22
22
  tools/app_launcher.py
23
23
  tools/builtin.py
24
+ tools/database.py
24
25
  tools/filesystem.py
25
26
  tools/indexer.py
26
27
  tools/launcher.py
@@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta"
5
5
 
6
6
  [project]
7
7
  name = "makeless"
8
- version = "0.1.1"
8
+ version = "0.1.2"
9
9
  description = "A local-first computer operator for Windows."
10
10
  readme = "README.md"
11
11
  requires-python = ">=3.9"
@@ -0,0 +1,104 @@
1
+ import os
2
+
3
+
4
+ APP_ALIASES = {
5
+ "vscode": "visual studio code",
6
+ "vs code": "visual studio code",
7
+ "code": "visual studio code",
8
+ "chrome": "google chrome",
9
+ "brave": "brave",
10
+ "edge": "microsoft edge"
11
+ }
12
+
13
+
14
+ def get_start_menu_locations():
15
+ locations = []
16
+
17
+ program_data = os.environ.get("PROGRAMDATA")
18
+
19
+ if program_data:
20
+ locations.append(
21
+ os.path.join(
22
+ program_data,
23
+ "Microsoft",
24
+ "Windows",
25
+ "Start Menu",
26
+ "Programs"
27
+ )
28
+ )
29
+
30
+ app_data = os.environ.get("APPDATA")
31
+
32
+ if app_data:
33
+ locations.append(
34
+ os.path.join(
35
+ app_data,
36
+ "Microsoft",
37
+ "Windows",
38
+ "Start Menu",
39
+ "Programs"
40
+ )
41
+ )
42
+
43
+ return locations
44
+
45
+
46
+ def find_apps(query, max_results=10):
47
+ """
48
+ Search Windows Start Menu shortcuts for applications.
49
+ """
50
+
51
+ query = query.casefold().strip()
52
+
53
+ if not query:
54
+ return []
55
+
56
+ search_query = APP_ALIASES.get(
57
+ query,
58
+ query
59
+ )
60
+
61
+ results = []
62
+ seen_names = set()
63
+
64
+ for location in get_start_menu_locations():
65
+
66
+ if not os.path.exists(location):
67
+ continue
68
+
69
+ for root, directories, files in os.walk(location):
70
+
71
+ for file_name in files:
72
+
73
+ if not file_name.casefold().endswith(".lnk"):
74
+ continue
75
+
76
+ app_name = os.path.splitext(
77
+ file_name
78
+ )[0]
79
+
80
+ normalized_name = app_name.casefold()
81
+
82
+ if search_query not in normalized_name:
83
+ continue
84
+
85
+ # Avoid duplicate shortcuts for the same app.
86
+ if normalized_name in seen_names:
87
+ continue
88
+
89
+ seen_names.add(normalized_name)
90
+
91
+ results.append(
92
+ {
93
+ "name": app_name,
94
+ "path": os.path.join(
95
+ root,
96
+ file_name
97
+ )
98
+ }
99
+ )
100
+
101
+ if len(results) >= max_results:
102
+ return results
103
+
104
+ return results
@@ -0,0 +1,62 @@
1
+ import os
2
+ import sqlite3
3
+ from pathlib import Path
4
+
5
+
6
+ def get_data_directory():
7
+ """
8
+ Return a writable location for Makeless user data.
9
+ """
10
+
11
+ local_app_data = os.environ.get("LOCALAPPDATA")
12
+
13
+ if local_app_data:
14
+ data_directory = Path(local_app_data) / "Makeless"
15
+ else:
16
+ data_directory = Path.home() / ".makeless"
17
+
18
+ data_directory.mkdir(parents=True, exist_ok=True)
19
+
20
+ return data_directory
21
+
22
+
23
+ DATABASE_PATH = get_data_directory() / "agentcore.db"
24
+
25
+
26
+ def get_database_connection():
27
+ """
28
+ Open the Makeless SQLite database.
29
+ """
30
+
31
+ DATABASE_PATH.parent.mkdir(parents=True, exist_ok=True)
32
+
33
+ return sqlite3.connect(DATABASE_PATH)
34
+
35
+
36
+ def create_database():
37
+ """
38
+ Create the database and required tables.
39
+ """
40
+
41
+ connection = get_database_connection()
42
+ cursor = connection.cursor()
43
+
44
+ cursor.execute(
45
+ """
46
+ CREATE TABLE IF NOT EXISTS file_index (
47
+ path TEXT PRIMARY KEY,
48
+ name TEXT NOT NULL,
49
+ item_type TEXT NOT NULL
50
+ )
51
+ """
52
+ )
53
+
54
+ cursor.execute(
55
+ """
56
+ CREATE INDEX IF NOT EXISTS idx_file_name
57
+ ON file_index(name)
58
+ """
59
+ )
60
+
61
+ connection.commit()
62
+ connection.close()
@@ -3,26 +3,45 @@ import sqlite3
3
3
  from pathlib import Path
4
4
 
5
5
 
6
- APP_DATA_DIR = Path(
7
- os.environ.get("LOCALAPPDATA", Path.home())
8
- ) / "Makeless"
6
+ APP_DATA_DIRECTORY = Path.home() / ".makeless"
7
+ DATABASE_PATH = APP_DATA_DIRECTORY / "agentcore.db"
9
8
 
10
- DATABASE_PATH = APP_DATA_DIR / "makeless.db"
9
+
10
+ def ensure_database_directory():
11
+ """
12
+ Create the Makeless data directory if it does not exist.
13
+ """
14
+
15
+ APP_DATA_DIRECTORY.mkdir(
16
+ parents=True,
17
+ exist_ok=True
18
+ )
11
19
 
12
20
 
13
21
  def get_database_connection():
14
- APP_DATA_DIR.mkdir(parents=True, exist_ok=True)
22
+ """
23
+ Return a connection to the Makeless database.
24
+ """
25
+
26
+ ensure_database_directory()
15
27
 
16
28
  return sqlite3.connect(DATABASE_PATH)
17
29
 
18
30
 
19
31
  def search_files(query, max_results=20):
20
- query = query.casefold()
32
+ """
33
+ Search the local file index.
34
+ """
35
+
36
+ query = query.casefold().strip()
21
37
 
22
- connection = get_database_connection()
23
- cursor = connection.cursor()
38
+ if not query:
39
+ return []
24
40
 
25
41
  try:
42
+ connection = get_database_connection()
43
+ cursor = connection.cursor()
44
+
26
45
  cursor.execute(
27
46
  """
28
47
  SELECT path, name, item_type
@@ -34,13 +53,11 @@ def search_files(query, max_results=20):
34
53
  )
35
54
 
36
55
  rows = cursor.fetchall()
37
-
38
- except sqlite3.OperationalError:
39
- rows = []
40
-
41
- finally:
42
56
  connection.close()
43
57
 
58
+ except sqlite3.Error:
59
+ return []
60
+
44
61
  results = []
45
62
 
46
63
  ignored_paths = [
@@ -70,7 +87,14 @@ def search_files(query, max_results=20):
70
87
 
71
88
 
72
89
  def find_best_match(query):
73
- results = search_files(query, max_results=100)
90
+ """
91
+ Find the best matching file or folder.
92
+ """
93
+
94
+ results = search_files(
95
+ query,
96
+ max_results=100
97
+ )
74
98
 
75
99
  if not results:
76
100
  return None