starbash 0.1.6__py3-none-any.whl → 0.1.9__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.
@@ -10,7 +10,7 @@ author.email = "FIXMESiril?"
10
10
  [[stage]]
11
11
 
12
12
  description = "Generate master bias"
13
- disabled = true # FIXME, debugging later stuff
13
+ disabled = false # FIXME, debugging later stuff
14
14
 
15
15
  # Restrict processing of this stage to only if detected hardware was found for this session
16
16
  # For any camera
@@ -23,9 +23,9 @@ tool = "siril"
23
23
  # input.source = "most-recent" # only look for the most recent set of raws for this particular type
24
24
  input.type = "bias" # look in all raw repos, but look only for bias files
25
25
 
26
- # for early development we have support for simple absolute file paths with globs
27
- input.source = "path"
28
- input.path = "/workspaces/starbash/images/from_astroboy/masters-raw/2025-09-09/BIAS/*.fit*"
26
+ # Look for files in input repos, finding them by using the "relative" tag they contain
27
+ input.source = "repo"
28
+ # input.path = ".../from_astroboy/masters-raw/2025-09-09/BIAS/*.fit*"
29
29
  input.required = true # Is at least one input file required? If true, we will skip running this stage entirely (with a warning)
30
30
 
31
31
  # make the following also work
@@ -34,7 +34,17 @@ input.required = true # Is at least one input file required? If true, we will s
34
34
  #os.makedirs(os.path.dirname(process_dir), exist_ok=True)
35
35
  #frames = glob(f"{masters_raw}/{date}/BIAS/{date}_*.fit*")
36
36
  #siril_run_in_temp_dir(frames, ...
37
- when = "session-config" # run at the start of each session process
37
+ when = "setup.masters" # run when master biases are regenerated
38
+
39
+ # Based on the following definitions in the stage toml file...
40
+ output.dest = "repo" # write to a particular repo
41
+ output.type = "master" # write output to the special masters repo
42
+
43
+ # the following fields will be auto populated in the context before entry:
44
+ # context.output.base_path - the full filepath to write the output file to **excluding the suffix**
45
+ # context.output.full_path - the full filepath to write the output file to (including suffix)
46
+ # (NOT implemented / needed) context.output.root_path - points to the base of the destination repo
47
+ # (NOT implemented / needed) context.output.suffix - the suffix to append to the output file (e.g. .fits or .fit.gz)
38
48
 
39
49
  # The following constants are auto defined before running the tool
40
50
  # context.process_dir (points to the session specific semi-persistent local dir for that sessions written/read data files)
@@ -42,8 +52,15 @@ when = "session-config" # run at the start of each session process
42
52
  # context.temp_dir (points to a temporary directory this tool can use for writing)
43
53
 
44
54
  # Everything in the constants dict will be predefined as named variables for use by the script
45
- context.date = "2025-09-09" # FIXME - later find auto latest date with bias frames
46
- context.output = "{masters}/biases/{date}_stacked.fits" # if the output already exists processing will be skipped
55
+ # context.date = "2025-09-0.9" # FIXME - later find auto latest date with bias frames
56
+
57
+ # output file will be place in the masters repo
58
+ # if the output already exists processing will be skipped
59
+ #
60
+ # with a path like "{instrument}/{date}/{imagetyp}/{sessionconfig}.fits"
61
+ # this path comes from master_repo.relative
62
+ # context.output = "{output.root}/{instrument}/{date}/{imagetyp}/{sessionconfig}.fits"
63
+
47
64
 
48
65
  script = '''
49
66
  # Convert Bias Frames to .fit files
@@ -51,5 +68,5 @@ script = '''
51
68
  cd {process_dir}
52
69
 
53
70
  # Stack Bias Frames to bias_stacked.fit
54
- stack bias rej 3 3 -nonorm -out={output}
71
+ stack bias rej 3 3 -nonorm -out={output["base_path"]}
55
72
  '''
@@ -21,6 +21,11 @@ dir = "osc_single_duo"
21
21
 
22
22
  # processing stages, currently all declared here, but possibly in the future they could be added by user/other toml files
23
23
 
24
+ # Not included in standard list - for now we run manually
25
+ #[[stages]]
26
+ #name = "setup-masters" # for flat processing, master generation etc
27
+ #priority = 5
28
+
24
29
  [[stages]]
25
30
  name = "session-config" # for flat processing, master generation etc
26
31
  priority = 10
starbash/selection.py CHANGED
@@ -2,11 +2,57 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- import json
6
5
  import logging
7
- from pathlib import Path
8
- from typing import Any, Optional
9
- from datetime import datetime
6
+ from typing import Any, Optional, TYPE_CHECKING
7
+ from repo import Repo
8
+
9
+
10
+ def where_tuple(conditions: dict[str, Any] | None) -> tuple[str, list[Any]]:
11
+ """Search for sessions matching the given conditions.
12
+
13
+ Args:
14
+ conditions: Dictionary of session key-value pairs to match, or None for all.
15
+ Special keys:
16
+ - 'date_start': Filter sessions starting on or after this date
17
+ - 'date_end': Filter sessions starting on or before this date
18
+
19
+ Returns:
20
+ Tuple of (WHERE clause string, list of parameters)
21
+ """
22
+ if conditions is None:
23
+ conditions = {}
24
+
25
+ # Build WHERE clause dynamically based on conditions
26
+ where_clauses = []
27
+ params = []
28
+
29
+ # Extract date range conditions
30
+ date_start = conditions.get("date_start")
31
+ date_end = conditions.get("date_end")
32
+
33
+ # Add date range filters to WHERE clause
34
+ if date_start:
35
+ where_clauses.append("start >= ?")
36
+ params.append(date_start)
37
+
38
+ if date_end:
39
+ where_clauses.append("start <= ?")
40
+ params.append(date_end)
41
+
42
+ # Add standard conditions to WHERE clause
43
+ for key, value in conditions.items():
44
+ if key not in ("date_start", "date_end") and value is not None:
45
+ column_name = key
46
+ where_clauses.append(f"{column_name} = ?")
47
+ params.append(value)
48
+
49
+ # Build the query
50
+ query = ""
51
+
52
+ if where_clauses:
53
+ query += " WHERE " + " AND ".join(where_clauses)
54
+
55
+ return (query, params)
10
56
 
11
57
 
12
58
  class Selection:
@@ -19,16 +65,17 @@ class Selection:
19
65
  - Image types
20
66
  - Telescope names
21
67
 
22
- The selection state is saved to disk and can be used to build database queries.
68
+ The selection state is saved to the user config repo TOML file and can be
69
+ used to build database queries.
23
70
  """
24
71
 
25
- def __init__(self, state_file: Path):
26
- """Initialize the Selection with a state file path.
72
+ def __init__(self, user_repo: "Repo"):
73
+ """Initialize the Selection with the user config repository.
27
74
 
28
75
  Args:
29
- state_file: Path to the JSON file where selection state is persisted
76
+ user_repo: The Repo object for user preferences where selection state is persisted
30
77
  """
31
- self.state_file = state_file
78
+ self.user_repo = user_repo
32
79
  self.targets: list[str] = []
33
80
  self.date_start: Optional[str] = None
34
81
  self.date_end: Optional[str] = None
@@ -40,39 +87,59 @@ class Selection:
40
87
  self._load()
41
88
 
42
89
  def _load(self) -> None:
43
- """Load selection state from disk."""
44
- if self.state_file.exists():
45
- try:
46
- with open(self.state_file, "r") as f:
47
- data = json.load(f)
48
- self.targets = data.get("targets", [])
49
- self.date_start = data.get("date_start")
50
- self.date_end = data.get("date_end")
51
- self.filters = data.get("filters", [])
52
- self.image_types = data.get("image_types", [])
53
- self.telescopes = data.get("telescopes", [])
54
- logging.debug(f"Loaded selection state from {self.state_file}")
55
- except Exception as e:
56
- logging.warning(f"Failed to load selection state: {e}")
90
+ """Load selection state from user config repo."""
91
+ try:
92
+ # Load with type-safe defaults
93
+ targets = self.user_repo.get("selection.targets", [])
94
+ self.targets = targets if isinstance(targets, list) else []
95
+
96
+ self.date_start = self.user_repo.get("selection.date_start")
97
+ self.date_end = self.user_repo.get("selection.date_end")
98
+
99
+ filters = self.user_repo.get("selection.filters", [])
100
+ self.filters = filters if isinstance(filters, list) else []
101
+
102
+ image_types = self.user_repo.get("selection.image_types", [])
103
+ self.image_types = image_types if isinstance(image_types, list) else []
104
+
105
+ telescopes = self.user_repo.get("selection.telescopes", [])
106
+ self.telescopes = telescopes if isinstance(telescopes, list) else []
107
+
108
+ logging.debug(f"Loaded selection state from {self.user_repo.url}")
109
+ except Exception as e:
110
+ logging.warning(f"Failed to load selection state: {e}")
57
111
 
58
112
  def _save(self) -> None:
59
- """Save selection state to disk."""
113
+ """Save selection state to user config repo."""
60
114
  try:
61
- # Ensure parent directory exists
62
- self.state_file.parent.mkdir(parents=True, exist_ok=True)
63
-
64
- data = {
65
- "targets": self.targets,
66
- "date_start": self.date_start,
67
- "date_end": self.date_end,
68
- "filters": self.filters,
69
- "image_types": self.image_types,
70
- "telescopes": self.telescopes,
71
- }
72
-
73
- with open(self.state_file, "w") as f:
74
- json.dump(data, f, indent=2)
75
- logging.debug(f"Saved selection state to {self.state_file}")
115
+ self.user_repo.set("selection.targets", self.targets)
116
+
117
+ # Handle date fields - set if not None, delete if None (to clear them)
118
+ if self.date_start is not None:
119
+ self.user_repo.set("selection.date_start", self.date_start)
120
+ else:
121
+ # Delete the key if it exists
122
+ if "selection" in self.user_repo.config:
123
+ sel_section = self.user_repo.config["selection"]
124
+ if isinstance(sel_section, dict) and "date_start" in sel_section:
125
+ del sel_section["date_start"] # type: ignore
126
+
127
+ if self.date_end is not None:
128
+ self.user_repo.set("selection.date_end", self.date_end)
129
+ else:
130
+ # Delete the key if it exists
131
+ if "selection" in self.user_repo.config:
132
+ sel_section = self.user_repo.config["selection"]
133
+ if isinstance(sel_section, dict) and "date_end" in sel_section:
134
+ del sel_section["date_end"] # type: ignore
135
+
136
+ self.user_repo.set("selection.filters", self.filters)
137
+ self.user_repo.set("selection.image_types", self.image_types)
138
+ self.user_repo.set("selection.telescopes", self.telescopes)
139
+
140
+ # Write the updated config to disk
141
+ self.user_repo.write_config()
142
+ logging.debug(f"Saved selection state to {self.user_repo.url}")
76
143
  except Exception as e:
77
144
  logging.error(f"Failed to save selection state: {e}")
78
145
 
@@ -174,14 +241,11 @@ class Selection:
174
241
  and not self.telescopes
175
242
  )
176
243
 
177
- def get_query_conditions(self) -> dict[str, Any]:
244
+ def get_query_conditions(self) -> tuple[str, list[Any]]:
178
245
  """Build query conditions based on the current selection.
179
246
 
180
247
  Returns:
181
- Dictionary of query conditions that can be used with Database methods.
182
- Special keys:
183
- - 'date_start': ISO date string for start of range
184
- - 'date_end': ISO date string for end of range
248
+ A tuple of SQL (WHERE clause string, list of parameters)
185
249
  """
186
250
  conditions = {}
187
251
 
@@ -212,7 +276,7 @@ class Selection:
212
276
  if self.date_end:
213
277
  conditions["date_end"] = self.date_end
214
278
 
215
- return conditions
279
+ return where_tuple(conditions)
216
280
 
217
281
  def summary(self) -> dict[str, Any]:
218
282
  """Get a summary of the current selection state.
@@ -0,0 +1,13 @@
1
+ # This is a master repository for (Starbash)[{PROJECT_URL}].
2
+ #
3
+ # This file marks the root directory of a set of auto matinained astrophotography
4
+ # 'master' files, such as master darks, flats or biases.
5
+ #
6
+ # You generally don't need to edit this file directly - it was auto generated when you ran
7
+ # "sb repo add --master {REPO_PATH}".
8
+ #
9
+
10
+ [repo]
11
+ kind = "master"
12
+
13
+ relative = "{DEFAULT_RELATIVE}"
@@ -1,4 +1,4 @@
1
- # This is your Starbash user configuration file. It can be used to provide default settings
1
+ # This is your (Starbash)[{PROJECT_URL}] user configuration file. It can be used to provide default settings
2
2
  # to workflows.
3
3
 
4
4
  # (or wherever is appropriate for the user's platform).
starbash/toml.py ADDED
@@ -0,0 +1,29 @@
1
+ import tomlkit
2
+ from tomlkit.toml_file import TOMLFile
3
+ from pathlib import Path
4
+ from importlib import resources
5
+ from typing import Any
6
+
7
+ from starbash import url
8
+
9
+ def toml_from_template(template_name: str, dest_path: Path, overrides: dict[str, Any] = {}) -> tomlkit.TOMLDocument:
10
+ """Load a TOML document from a template file.
11
+ expand {vars} in the template using the `overrides` dictionary.
12
+ """
13
+
14
+ tomlstr = (
15
+ resources.files("starbash")
16
+ .joinpath(f"templates/{template_name}.toml")
17
+ .read_text()
18
+ )
19
+
20
+ # add default vars always available
21
+ vars = {
22
+ "PROJECT_URL": url.project
23
+ }
24
+ vars.update(overrides)
25
+ tomlstr = tomlstr.format(**vars)
26
+
27
+ toml = tomlkit.parse(tomlstr)
28
+ TOMLFile(dest_path).write(toml)
29
+ return toml
starbash/tool.py CHANGED
@@ -44,13 +44,68 @@ def expand_context(s: str, context: dict) -> str:
44
44
 
45
45
  # throw an error if any remaining unexpanded variables remain unexpanded
46
46
  unexpanded_vars = re.findall(r"\{([^{}]+)\}", expanded)
47
+
48
+ # Remove duplicates
49
+ unexpanded_vars = list(dict.fromkeys(unexpanded_vars))
47
50
  if unexpanded_vars:
48
51
  raise KeyError("Missing context variable(s): " + ", ".join(unexpanded_vars))
49
52
 
50
53
  return expanded
51
54
 
52
55
 
53
- def make_safe_globals(context: dict = {}) -> dict:
56
+ def expand_context_unsafe(s: str, context: dict) -> str:
57
+ """Expand a string with Python expressions in curly braces using RestrictedPython.
58
+
59
+ Context variables are directly available in expressions without a prefix.
60
+
61
+ Supports expressions like:
62
+ - "foo {1 + 2}" -> "foo 3"
63
+ - "bar {name}" -> "bar <value of context['name']>"
64
+ - "path {instrument}/{date}/file.fits" -> "path MyScope/2025-01-01/file.fits"
65
+ - "sum {x + y}" -> "sum <value of context['x'] + context['y']>"
66
+
67
+ Args:
68
+ s: String with Python expressions in curly braces
69
+ context: Dictionary of variables available directly in expressions
70
+
71
+ Returns:
72
+ String with all expressions evaluated and substituted
73
+
74
+ Raises:
75
+ ValueError: If any expression cannot be evaluated (syntax errors, missing variables, etc.)
76
+
77
+ Note: Uses RestrictedPython for safety, but still has security implications.
78
+ This is a more powerful but less safe alternative to expand_context().
79
+ """
80
+ # Find all expressions in curly braces
81
+ pattern = r"\{([^{}]+)\}"
82
+
83
+ def eval_expression(match):
84
+ """Evaluate a single expression and return its string representation."""
85
+ expr = match.group(1).strip()
86
+
87
+ try:
88
+ # Compile the expression with RestrictedPython
89
+ byte_code = RestrictedPython.compile_restricted(
90
+ expr, filename="<template expression>", mode="eval"
91
+ )
92
+
93
+ # Evaluate with safe globals and the context
94
+ result = eval(byte_code, make_safe_globals(context), None)
95
+ return str(result)
96
+
97
+ except Exception as e:
98
+ raise ValueError(f"Failed to evaluate expression '{expr}'") from e
99
+
100
+ # Replace all expressions
101
+ expanded = re.sub(pattern, eval_expression, s)
102
+
103
+ logger.debug(f"Unsafe expanded '{s}' into '{expanded}'")
104
+
105
+ return expanded
106
+
107
+
108
+ def make_safe_globals(extra_globals: dict = {}) -> dict:
54
109
  """Generate a set of RestrictedPython globals for AstoGlue exec/eval usage"""
55
110
  # Define the global and local namespaces for the restricted execution.
56
111
  # FIXME - this is still unsafe, policies need to be added to limit import/getattr etc...
@@ -88,9 +143,9 @@ def make_safe_globals(context: dict = {}) -> dict:
88
143
  "__name__": "__starbash_script__",
89
144
  "__metaclass__": type,
90
145
  # Extra globals auto imported into the scripts context
91
- "context": context,
92
146
  "logger": logging.getLogger("script"), # Allow logging within the script
93
147
  }
148
+ execution_globals.update(extra_globals)
94
149
  return execution_globals
95
150
 
96
151
 
@@ -128,13 +183,26 @@ def tool_run(cmd: str, cwd: str, commands: str | None = None) -> None:
128
183
  logger.debug(f"Tool output:\n{result.stdout.strip()}")
129
184
 
130
185
 
131
- # siril_path = "/home/kevinh/packages/Siril-1.4.0~beta3-x86_64.AppImage"
132
- siril_path = "org.siril.Siril" # flatpak
186
+ def executable_path(commands: list[str], name: str) -> str:
187
+ """Find the correct executable path to run for the given tool"""
188
+ for cmd in commands:
189
+ if shutil.which(cmd):
190
+ return cmd
191
+ raise FileNotFoundError(f"{name} not found, you probably need to install it.")
133
192
 
134
193
 
135
194
  def siril_run(temp_dir: str, commands: str, input_files: list[str] = []) -> None:
136
195
  """Executes Siril with a script of commands in a given working directory."""
137
196
 
197
+ # siril_path = "/home/kevinh/packages/Siril-1.4.0~beta3-x86_64.AppImage"
198
+ # Possible siril commands, with preferred option first
199
+ siril_commands = ["org.siril.Siril", "siril-cli", "siril"]
200
+ siril_path = executable_path(siril_commands, "Siril")
201
+ if siril_path == "org.siril.Siril":
202
+ # The executable is inside a flatpak, so run the lighter/faster/no-gui required exe
203
+ # from inside the flatpak
204
+ siril_path = "flatpak run --command=siril-cli org.siril.Siril"
205
+
138
206
  # Create symbolic links for all input files in the temp directory
139
207
  for f in input_files:
140
208
  os.symlink(
@@ -163,8 +231,11 @@ def siril_run(temp_dir: str, commands: str, input_files: list[str] = []) -> None
163
231
  def graxpert_run(cwd: str, arguments: str) -> None:
164
232
  """Executes Graxpert with the specified command line arguments"""
165
233
 
234
+ graxpert_commands = ["graxpert"]
235
+ graxpert_path = executable_path(graxpert_commands, "Graxpert")
236
+
166
237
  # Arguments look similar to: graxpert -cmd background-extraction -output /tmp/testout tests/test_images/real_crummy.fits
167
- cmd = f"graxpert {arguments}"
238
+ cmd = f"{graxpert_path} {arguments}"
168
239
 
169
240
  tool_run(cmd, cwd)
170
241
 
@@ -207,7 +278,7 @@ class SirilTool(Tool):
207
278
 
208
279
  # Iteratively expand the command string to handle nested placeholders.
209
280
  # The loop continues until the string no longer changes.
210
- expanded = expand_context(commands, context)
281
+ expanded = expand_context_unsafe(commands, context)
211
282
 
212
283
  input_files = context.get("input_files", [])
213
284
 
@@ -245,16 +316,17 @@ class PythonTool(Tool):
245
316
  )
246
317
  # No locals yet
247
318
  execution_locals = None
248
- exec(byte_code, make_safe_globals(context), execution_locals)
319
+ globals = {"context": context}
320
+ exec(byte_code, make_safe_globals(globals), execution_locals)
249
321
  except SyntaxError as e:
250
- logger.error(f"Syntax error in python script: {e}")
251
- raise
322
+ raise # Just rethrow - no need to rewrap
252
323
  except Exception as e:
253
- logger.error(f"Error during python script execution: {e}")
254
- raise
324
+ raise ValueError(f"Error during python script execution") from e
255
325
  finally:
256
326
  os.chdir(original_cwd)
257
327
 
258
328
 
259
329
  # A dictionary mapping tool names to their respective tool instances.
260
- tools = {tool.name: tool for tool in [SirilTool(), GraxpertTool(), PythonTool()]}
330
+ tools: dict[str, Tool] = {
331
+ tool.name: tool for tool in [SirilTool(), GraxpertTool(), PythonTool()]
332
+ }
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: starbash
3
- Version: 0.1.6
3
+ Version: 0.1.9
4
4
  Summary: A tool for automating/standardizing/sharing astrophotography workflows.
5
5
  License-File: LICENSE
6
6
  Author: Kevin Hester
@@ -20,22 +20,33 @@ Requires-Dist: tomlkit (>=0.13.3,<0.14.0)
20
20
  Requires-Dist: typer (>=0.20.0,<0.21.0)
21
21
  Description-Content-Type: text/markdown
22
22
 
23
- # Starbash
23
+ # [Starbash](https://github.com/geeksville/starbash)
24
+
25
+ <img src="https://raw.githubusercontent.com/geeksville/starbash/refs/heads/main/img/icon.png" alt="Starbash: Astrophotography workflows simplified" width="30%" align="right" style="margin-bottom: 20px;">
24
26
 
25
27
  [![PyPI - Version](https://img.shields.io/pypi/v/starbash)](https://pypi.org/project/starbash/)
26
28
  [![GitHub branch check runs](https://img.shields.io/github/check-runs/geeksville/starbash/main)](https://github.com/geeksville/starbash/actions)
27
29
  [![codecov](https://codecov.io/github/geeksville/starbash/graph/badge.svg?token=47RE10I7O1)](https://codecov.io/github/geeksville/starbash)
28
30
 
29
- ![app icon](https://raw.githubusercontent.com/geeksville/starbash/refs/heads/main/img/icon.png "Starbash: Astrophotography workflows simplified")
30
-
31
31
  A tool for automating/standardizing/sharing astrophotography workflows.
32
32
 
33
+ * Automatic - with sensible defaults, that you can change as needed.
34
+ * Easy - provides a 'seestar like' starting-point for autoprocessing all your sessions (by default).
35
+ * Fast - even with large image repositories. Automatic master bias and flat generation and reasonable defaults
36
+ * Sharable - you can share/use recipes for image preprocessing flows.
37
+
38
+ (This project is currently 'alpha' and missing recipes for some workflows, but adding new recipes is easy and we're happy to help. Please file a github issue if your images are not auto-processed and we'll work out a fix.)
39
+
40
+ <br clear="right">
41
+
33
42
  # Current status
34
43
 
35
44
  Not quite ready 😊. But making good progress.
36
45
 
37
46
  See the current [TODO](TODO.md) file for work items. I'll be looking for pre-alpha testers/feedback soon.
38
47
 
48
+ ![Sample session movie](https://raw.githubusercontent.com/geeksville/starbash/refs/heads/main/doc/vhs/sample-session.gif)
49
+
39
50
  ## Current features
40
51
 
41
52
  * Automatically recognizes and auto-parses the default NINA, Asiair and Seestar raw file repos (adding support for other layouts is easy)
@@ -56,15 +67,20 @@ See the current [TODO](TODO.md) file for work items. I'll be looking for pre-al
56
67
 
57
68
  Currently the easiest way to install this command-line based tool is to install is via [pipx](https://pipx.pypa.io/stable/). If you don't already have pipx and you have python installed, you can auto install it by running "pip install --user pipx." If you don't have python installed see the pipx link for pipx installers for any OS.
58
69
 
59
- Once pipx is installed just run:
70
+ Once pipx is installed just run the following **two** commands (the sb --install-completion will make TAB auto-complete automatically complete sb options (for most platforms)). Installing auto-complete is **highly** recommended because it makes entering starbash commands fast - by pressing the TAB key:
60
71
 
61
72
  ```
62
- pipx install starbash
73
+ pipx install starbash
63
74
  installed package starbash 0.1.3, installed using Python 3.12.3
64
75
  These apps are now globally available
65
76
  - sb
66
77
  - starbash
67
78
  done! ✨ 🌟 ✨
79
+
80
+ ➜ sb --install-completion
81
+ bash completion installed in /home/.../sb.sh
82
+ Completion will take effect once you restart the terminal
83
+
68
84
  ```
69
85
 
70
86
  FIXME - add getting started instructions (possibly with a screenshare video)
@@ -72,10 +88,10 @@ FIXME - add getting started instructions (possibly with a screenshare video)
72
88
  ## Supported commands
73
89
 
74
90
  ### Repository Management
75
- - `sb repo [--verbose]` - List installed repos (use `-v` for details)
76
- - `sb repo add <filepath|URL>` - Add a repository
77
- - `sb repo remove <REPONUM>` - Remove the indicated repo from the repo list
78
- - `sb repo reindex [--force] [REPONUM]` - Reindex the specified repo (or all repos if none specified)
91
+ - `sb repo list [--verbose]` - List installed repos (use `-v` for details)
92
+ - `sb repo add [--master] <filepath|URL>` - Add a repository, optionally specifying the type
93
+ - `sb repo remove <REPOURL>` - Remove the indicated repo from the repo list
94
+ - `sb repo reindex [--force] <REPOURL>` - Reindex the specified repo (or all repos if none specified)
79
95
 
80
96
  ### User Preferences
81
97
  - `sb user name "Your Name"` - Set name for attribution in generated images
@@ -92,18 +108,18 @@ FIXME - add getting started instructions (possibly with a screenshare video)
92
108
  - `sb select date <after|before|between> <DATE> [DATE]` - Limit to sessions in the specified date range
93
109
  - `sb select export SESSIONNUM DESTDIR` - Export the images for indicated session number into the specified directory (or current directory if not specified). If possible symbolic links are used, if not the files are copied.
94
110
 
95
- ## Not yet supported commands
96
-
97
- ### Setup & Configuration
111
+ ### Selection information
98
112
  - `sb info` - Show user preferences location and other app info
99
113
  - `sb info target` - List targets (filtered based on the current selection)
100
114
  - `sb info telescope` - List instruments (filtered based on the current selection)
101
115
  - `sb info filter` - List all filters found in current selection
102
116
 
117
+ ## Not yet supported commands
118
+
103
119
  ### Export & Processing
104
- - `sb process siril` - Generate Siril directory tree and run Siril GUI
105
- - `sb process auto` - Automatic processing
106
- - `sb process masters` - Generate master flats, darks, and biases from available raw frames
120
+ - `sb process siril [--run] SESSIONNUM DESTDIR` - Generate Siril directory tree and optionally run Siril GUI
121
+ - `sb process auto [SESSIONNUM]` - Automatic processing. If session # is specified process only that session, otherwise all selected sessions will be processed
122
+ - `sb process masters` - Generate master flats, darks, and biases from available raw frames in the current selection
107
123
 
108
124
  ## Supported tools (now)
109
125
 
@@ -122,3 +138,8 @@ We try to make this project useful and friendly. If you find problems please fi
122
138
  We accept pull-requests and enjoy discussing possible new development directions via github issues. If you might want to work on this, just describe what your interests are and we can talk about how to get it merged.
123
139
 
124
140
  Project members can access crash reports [here](https://geeksville.sentry.io/insights/projects/starbash/?project=4510264204132352).
141
+
142
+ ## License
143
+
144
+ Copyright 2025 Kevin Hester, kevinh@geeksville.com.
145
+ Licensed under the (GPL v3)[LICENSE]
@@ -0,0 +1,37 @@
1
+ repo/__init__.py,sha256=St2udJPFek-KwjagkH23h8kBYJHyIXrXjQJpL2Vjz_s,229
2
+ repo/manager.py,sha256=4QEt4oRzlTcf75Q7gALBFhtZS_JXOJF6apnSYozRC5c,4812
3
+ repo/repo.py,sha256=7EgzEDzTDuluGFbGHLz_-tracze4jPuzerXJpjUaDKU,10680
4
+ starbash/__init__.py,sha256=0kEchneSvvuKV334VwULs3UZSIJ9Edsfvawuq39LJmo,1146
5
+ starbash/analytics.py,sha256=XYe7PN-pymXmmv8x1C2mUV_wecTnY9gHjczUv3fjSo8,4346
6
+ starbash/app.py,sha256=BnuPzM3aBROveMbx0SFkfDMm_ItLJUstEmEWspJOKs0,34168
7
+ starbash/commands/__init__.py,sha256=sapJ5w5PFGUfmXtUA1bGIFTr0_gf91Gw6rgCcmzmjUw,706
8
+ starbash/commands/info.py,sha256=PNiCdvKocQCAeIMFKYtMsx6PmGnTcxsto8yA3kNvnxo,4131
9
+ starbash/commands/process.py,sha256=c5fsLX0sZRzC_8baT2aZYOpqN57jttL5YEvSJH_U3LU,5236
10
+ starbash/commands/repo.py,sha256=lelqdo8t6sGvXw9V6ax77P6l7U6z-N4nlc_6RzuOaWQ,7623
11
+ starbash/commands/select.py,sha256=_D4FNVnqIYUCWcLYSIYaHDCMccQBPJ3mAVHgqk6Nkr8,14493
12
+ starbash/commands/user.py,sha256=MIJFhhWokoaBiZ6rKqQaSVxSvO_glwG5dTGhaZy-6PA,4249
13
+ starbash/database.py,sha256=Rno00XGukunS5bTT5SqrEQudUhgXcP0jyvJK1hy_cU0,23058
14
+ starbash/defaults/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
15
+ starbash/defaults/starbash.toml,sha256=tDF3YFs7JrkZhF2Z1jHQLHR3-4iKSAU7PAeVG9SafZ4,2246
16
+ starbash/main.py,sha256=DdeV_w4P__q0sTcGjGUm5hBqIDurq8DUKoDQQaawsfA,1600
17
+ starbash/paths.py,sha256=BKKnSXt3tOh16o7ljDcQLtWKIiepEmud9JFtzRwDHtg,1317
18
+ starbash/recipes/README.md,sha256=OC0CUAL7xhxujOtEg97vrdNLZmGPLusBeOnwTo5rKN8,317
19
+ starbash/recipes/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
20
+ starbash/recipes/master_bias/starbash.toml,sha256=M06qprFiAhdRubRudHEusrdzNzm7aaqaisjXO7r1tlg,2985
21
+ starbash/recipes/master_flat/starbash.toml,sha256=HWZXjXbTiExsC6o5QNr1-5x37PBRo7XNwBEto8Ea-LE,1430
22
+ starbash/recipes/osc_dual_duo/starbash.py,sha256=2cI4L6-d50-mL4hY0Oe6ad1v7g18D7ud0Hn0AlHZDOk,5788
23
+ starbash/recipes/osc_dual_duo/starbash.toml,sha256=xh3zEqdotRZr7eUcqel-xnQcWH8z-PUFRuWQfLp3F_E,3195
24
+ starbash/recipes/osc_single_duo/starbash.toml,sha256=kB-FMek0Z-8vieUfxKqmZMFNnZsSKdufSkLSgH03CkY,2117
25
+ starbash/recipes/starbash.toml,sha256=etkMf9iQbEXIKp8GkB05m4jQlnxTqTsDT8MtSJaBSR8,1173
26
+ starbash/selection.py,sha256=vsup4XDsch0Z6aGLO9pgcGGgku0xCWfUgk49lRWr9_g,10826
27
+ starbash/templates/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
28
+ starbash/templates/repo/master.toml,sha256=c53CXMdIPlw9E50LRabgHfyn2qOS0dhz7nmXBx0ETQA,392
29
+ starbash/templates/userconfig.toml,sha256=4bpUQ5WlA2Ebbl6c5QxmS2-SXV4IxmN3-H9IghepQEQ,1459
30
+ starbash/toml.py,sha256=aNuCqsQ33HYDpWgr_9vpDpd80h2vOU2-kBQmkUqri7c,783
31
+ starbash/tool.py,sha256=mBRvSwr2Z96YSBwBgYNrQqj83La97yJkBvZhKWvDqfM,11533
32
+ starbash/url.py,sha256=lorxQJ27jSfzsKCb0QvpcvLiPZG55Dkd_c1JPFbni4I,402
33
+ starbash-0.1.9.dist-info/METADATA,sha256=IjkplSkTM32SsUOEA1V0791t6S-EDSqiEeglAiB53IM,7716
34
+ starbash-0.1.9.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
35
+ starbash-0.1.9.dist-info/entry_points.txt,sha256=REQyWs8e5TJsNK7JVVWowKVoytMmKlUwuFHLTmSX4hc,67
36
+ starbash-0.1.9.dist-info/licenses/LICENSE,sha256=ixuiBLtpoK3iv89l7ylKkg9rs2GzF9ukPH7ynZYzK5s,35148
37
+ starbash-0.1.9.dist-info/RECORD,,