@agentikos/omega-os 0.1.0 → 0.2.0

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 (73) hide show
  1. package/README.md +25 -13
  2. package/bootstrap/lib/steps.sh +214 -9
  3. package/bootstrap/manifest.example.yaml +6 -1
  4. package/docs/COMPLETION-PLAN.md +48 -0
  5. package/omega/Agentik_Engine/README.md +25 -10
  6. package/omega/Agentik_Engine/omega_engine/__init__.py +66 -2
  7. package/omega/Agentik_Engine/omega_engine/account.py +505 -0
  8. package/omega/Agentik_Engine/omega_engine/autonomous.py +538 -0
  9. package/omega/Agentik_Engine/omega_engine/cli.py +467 -29
  10. package/omega/Agentik_Engine/omega_engine/daemons/__init__.py +14 -0
  11. package/omega/Agentik_Engine/omega_engine/daemons/autonomous.py +56 -0
  12. package/omega/Agentik_Engine/omega_engine/daemons/engine.py +187 -0
  13. package/omega/Agentik_Engine/omega_engine/daemons/telegram.py +231 -0
  14. package/omega/Agentik_Engine/omega_engine/educators/__init__.py +51 -0
  15. package/omega/Agentik_Engine/omega_engine/educators/artifact.py +65 -0
  16. package/omega/Agentik_Engine/omega_engine/educators/automation.py +76 -0
  17. package/omega/Agentik_Engine/omega_engine/educators/base.py +327 -0
  18. package/omega/Agentik_Engine/omega_engine/educators/claudecode.py +71 -0
  19. package/omega/Agentik_Engine/omega_engine/educators/connection.py +75 -0
  20. package/omega/Agentik_Engine/omega_engine/educators/coworker.py +68 -0
  21. package/omega/Agentik_Engine/omega_engine/educators/loop.py +82 -0
  22. package/omega/Agentik_Engine/omega_engine/educators/prompt.py +68 -0
  23. package/omega/Agentik_Engine/omega_engine/educators/skill.py +69 -0
  24. package/omega/Agentik_Engine/omega_engine/executor.py +46 -6
  25. package/omega/Agentik_Engine/omega_engine/mission.py +13 -1
  26. package/omega/Agentik_Engine/omega_engine/provider.py +247 -1
  27. package/omega/Agentik_Engine/omega_engine/rag/__init__.py +21 -0
  28. package/omega/Agentik_Engine/omega_engine/rag/agentic.py +83 -0
  29. package/omega/Agentik_Engine/omega_engine/rag/base.py +42 -0
  30. package/omega/Agentik_Engine/omega_engine/rag/corrective.py +119 -0
  31. package/omega/Agentik_Engine/omega_engine/rag/graph.py +169 -0
  32. package/omega/Agentik_Engine/omega_engine/rag/hybrid.py +205 -0
  33. package/omega/Agentik_Engine/omega_engine/rag/multimodal.py +136 -0
  34. package/omega/Agentik_Engine/omega_engine/rag/router.py +110 -0
  35. package/omega/Agentik_Engine/omega_engine/reducer.py +21 -3
  36. package/omega/Agentik_Engine/omega_engine/store.py +65 -5
  37. package/omega/Agentik_Engine/omega_engine/sync.py +304 -0
  38. package/omega/Agentik_Engine/omega_engine/tools.py +272 -0
  39. package/omega/Agentik_Engine/pyproject.toml +1 -1
  40. package/omega/Agentik_Engine/tests/test_account.py +333 -0
  41. package/omega/Agentik_Engine/tests/test_autonomous.py +361 -0
  42. package/omega/Agentik_Engine/tests/test_educators.py +233 -0
  43. package/omega/Agentik_Engine/tests/test_rag.py +287 -0
  44. package/omega/Agentik_Engine/tests/test_snapshot_partial.py +172 -0
  45. package/omega/Agentik_Engine/tests/test_tools_and_sync.py +312 -0
  46. package/omega/Agentik_SSOT/skills/rag-route.md +73 -0
  47. package/package.json +1 -1
  48. package/omega/Agentik_Engine/omega_engine/__pycache__/__init__.cpython-313.pyc +0 -0
  49. package/omega/Agentik_Engine/omega_engine/__pycache__/audit.cpython-313.pyc +0 -0
  50. package/omega/Agentik_Engine/omega_engine/__pycache__/audit_arsenal.cpython-313.pyc +0 -0
  51. package/omega/Agentik_Engine/omega_engine/__pycache__/barrier.cpython-313.pyc +0 -0
  52. package/omega/Agentik_Engine/omega_engine/__pycache__/bus.cpython-313.pyc +0 -0
  53. package/omega/Agentik_Engine/omega_engine/__pycache__/cli.cpython-313.pyc +0 -0
  54. package/omega/Agentik_Engine/omega_engine/__pycache__/events.cpython-313.pyc +0 -0
  55. package/omega/Agentik_Engine/omega_engine/__pycache__/executor.cpython-313.pyc +0 -0
  56. package/omega/Agentik_Engine/omega_engine/__pycache__/mission.cpython-313.pyc +0 -0
  57. package/omega/Agentik_Engine/omega_engine/__pycache__/progress.cpython-313.pyc +0 -0
  58. package/omega/Agentik_Engine/omega_engine/__pycache__/project.cpython-313.pyc +0 -0
  59. package/omega/Agentik_Engine/omega_engine/__pycache__/provider.cpython-313.pyc +0 -0
  60. package/omega/Agentik_Engine/omega_engine/__pycache__/reducer.cpython-313.pyc +0 -0
  61. package/omega/Agentik_Engine/omega_engine/__pycache__/report.cpython-313.pyc +0 -0
  62. package/omega/Agentik_Engine/omega_engine/__pycache__/router.cpython-313.pyc +0 -0
  63. package/omega/Agentik_Engine/omega_engine/__pycache__/store.cpython-313.pyc +0 -0
  64. package/omega/Agentik_Engine/omega_engine/__pycache__/supervisor.cpython-313.pyc +0 -0
  65. package/omega/Agentik_Engine/omega_engine/__pycache__/task.cpython-313.pyc +0 -0
  66. package/omega/Agentik_Engine/omega_engine/__pycache__/telegram.cpython-313.pyc +0 -0
  67. package/omega/Agentik_Engine/tests/__pycache__/test_audit_arsenal.cpython-313.pyc +0 -0
  68. package/omega/Agentik_Engine/tests/__pycache__/test_executor.cpython-313.pyc +0 -0
  69. package/omega/Agentik_Engine/tests/__pycache__/test_mission.cpython-313.pyc +0 -0
  70. package/omega/Agentik_Engine/tests/__pycache__/test_progress.cpython-313.pyc +0 -0
  71. package/omega/Agentik_Engine/tests/__pycache__/test_project.cpython-313.pyc +0 -0
  72. package/omega/Agentik_Engine/tests/__pycache__/test_reducer.cpython-313.pyc +0 -0
  73. package/omega/Agentik_Engine/tests/__pycache__/test_report.cpython-313.pyc +0 -0
@@ -1,7 +1,9 @@
1
1
  """The `omega` command-line interface.
2
2
 
3
3
  A thin entry point. The installer's doctor step calls `omega doctor`; operators
4
- use `omega status` to see every task's derived state.
4
+ use `omega status` to see every task's derived state. `omega account ...` and
5
+ `omega billing` give visibility into the Claude Code Max account pool — see
6
+ `docs/ACCOUNT-AND-BILLING.md` for the design.
5
7
  """
6
8
  from __future__ import annotations
7
9
 
@@ -62,43 +64,237 @@ def cmd_status(_args: argparse.Namespace) -> int:
62
64
  return 0
63
65
 
64
66
 
65
- def cmd_account(_args: argparse.Namespace) -> int:
66
- """Show the Claude Code Max account pool and the selection strategy.
67
+ # ──── account subcommands ─────────────────────────────────────────────────
67
68
 
68
- Omega OS runs one engine, not N tmux sessions — so an account is not
69
- "switched" globally. The Claude provider holds a POOL and distributes agent
70
- calls across accounts. Add one with `omega account login`.
71
- See docs/ACCOUNT-AND-BILLING.md.
69
+
70
+ def _print_pool(pool) -> None:
71
+ print(f"Claude Max account pool selection: {pool.selection}")
72
+ if not pool.accounts:
73
+ print(" (empty)")
74
+ print(" add an account: omega account login")
75
+ return
76
+ for a in pool.accounts:
77
+ used = f"{a.tokens_used:>9,d} tok" if a.tokens_used else " -"
78
+ print(f" [{a.status:<9}] {a.id:<16} {used} {a.label}")
79
+ print(" add an account: omega account login")
80
+
81
+
82
+ def cmd_account_list(_args: argparse.Namespace) -> int:
83
+ """Show the Claude Code Max account pool and the selection strategy."""
84
+ from omega_engine.account import AccountPool
85
+
86
+ pool = AccountPool.load(_omega_home())
87
+ _print_pool(pool)
88
+ return 0
89
+
90
+
91
+ def cmd_account_login(args: argparse.Namespace) -> int:
92
+ """Add an account: run the OAuth flow, store the token in the vault,
93
+ and add a pool entry.
94
+
95
+ Strategy: try the RFC 8628 device-code flow first; if it's unavailable
96
+ (Anthropic does not currently publish those endpoints), fall back to a
97
+ manual paste flow — the user logs in via browser elsewhere, copies the
98
+ token, and pastes it. Either way ends with the token in the vault and
99
+ a new account in `accounts.yaml`.
72
100
  """
73
- cfg = _omega_home() / "Agentik_Providers" / "claude" / "accounts.yaml"
74
- if not cfg.exists():
75
- cfg = cfg.with_name("accounts.example.yaml")
76
- if not cfg.exists():
77
- print("no Claude Max account pool configured — see docs/ACCOUNT-AND-BILLING.md")
78
- return 0
101
+ from omega_engine.account import (
102
+ AccountPool,
103
+ ClaudeAccount,
104
+ claude_device_code_flow,
105
+ claude_oauth_login_url,
106
+ write_token,
107
+ )
108
+
109
+ account_id = (args.id or "").strip()
110
+ if not account_id:
111
+ account_id = input("account id (e.g. max-primary): ").strip()
112
+ if not account_id:
113
+ print("account id is required — aborting")
114
+ return 2
115
+ label = (args.label or "").strip() or f"Claude Max account ({account_id})"
116
+ secret_ref = f"CLAUDE_OAUTH_{account_id}"
117
+
118
+ token: str | None = None
119
+ if not args.manual:
120
+ print("attempting OAuth device-code flow...")
121
+ try:
122
+ result = claude_device_code_flow()
123
+ instruction = result.get("raw", {}).get("_human_instruction") \
124
+ or result.get("_human_instruction") or ""
125
+ if instruction:
126
+ print(instruction)
127
+ token = result.get("access_token")
128
+ except RuntimeError as exc:
129
+ print(f"device-code flow unavailable: {exc}")
130
+ print("falling back to manual paste...")
131
+
132
+ if not token:
133
+ # manual fallback — this is the path that always works
134
+ print()
135
+ print("MANUAL LOGIN")
136
+ print(f" 1. open in a browser: {claude_oauth_login_url()}")
137
+ print(" 2. complete login on the Claude account you want to add")
138
+ print(" 3. copy the OAuth token from the dashboard or developer panel")
139
+ print()
140
+ try:
141
+ token = input("paste the OAuth token here: ").strip()
142
+ except EOFError:
143
+ token = ""
144
+ if not token:
145
+ print("no token provided — aborting (vault not modified)")
146
+ return 2
147
+
148
+ home = _omega_home()
149
+ path = write_token(home, secret_ref, token)
150
+ pool = AccountPool.load(home)
151
+ pool.add(ClaudeAccount(
152
+ id=account_id, label=label, secret_ref=secret_ref,
153
+ weight=1, status="active",
154
+ ))
155
+ cfg = pool.save(home)
156
+ print(f" vault: {path} (chmod 600)")
157
+ print(f" pool: {cfg}")
158
+ _print_pool(pool)
159
+ return 0
160
+
161
+
162
+ def cmd_account_use(args: argparse.Namespace) -> int:
163
+ """Set an account's status: active | resting | disabled."""
164
+ from omega_engine.account import AccountPool
165
+
166
+ home = _omega_home()
167
+ pool = AccountPool.load(home)
168
+ if pool.get(args.id) is None:
169
+ print(f"no account '{args.id}' in the pool")
170
+ return 2
79
171
  try:
80
- import yaml
81
- except ImportError:
82
- print(f"account pool config: {cfg} (install pyyaml to render it)")
83
- return 0
84
- data = yaml.safe_load(cfg.read_text()) or {}
85
- pool = data.get("pool", [])
86
- print(f"Claude Max account pool — selection: {data.get('selection', 'least-used')}")
87
- for a in pool or [{"status": "(empty)", "id": "", "label": ""}]:
88
- print(f" [{str(a.get('status', '?')):<9}] {str(a.get('id', '')):<16} {a.get('label', '')}")
89
- print(" add an account: omega account login (docs/ACCOUNT-AND-BILLING.md)")
172
+ pool.set_status(args.id, args.status)
173
+ except ValueError as exc:
174
+ print(str(exc))
175
+ return 2
176
+ pool.save(home)
177
+ _print_pool(pool)
90
178
  return 0
91
179
 
92
180
 
181
+ def cmd_account_pool(args: argparse.Namespace) -> int:
182
+ """Edit pool-wide knobs: selection strategy and per-account weights.
183
+
184
+ Non-interactive flags drive scripted edits; with no flags, it prints the
185
+ current pool for inspection.
186
+ """
187
+ from omega_engine.account import AccountPool
188
+
189
+ home = _omega_home()
190
+ pool = AccountPool.load(home)
191
+ changed = False
192
+ if args.selection:
193
+ if args.selection not in ("round-robin", "least-used", "by-quota"):
194
+ print(
195
+ f"invalid selection '{args.selection}' — "
196
+ "must be round-robin|least-used|by-quota"
197
+ )
198
+ return 2
199
+ pool.selection = args.selection
200
+ changed = True
201
+ if args.weight:
202
+ # each --weight ID=N — parse and apply
203
+ for spec in args.weight:
204
+ if "=" not in spec:
205
+ print(f"weight spec '{spec}' must be of the form id=N")
206
+ return 2
207
+ ident, _, val = spec.partition("=")
208
+ ident, val = ident.strip(), val.strip()
209
+ acc = pool.get(ident)
210
+ if acc is None:
211
+ print(f"no account '{ident}' — skipping")
212
+ continue
213
+ try:
214
+ acc.weight = max(int(val), 0)
215
+ except ValueError:
216
+ print(f"weight for '{ident}' must be an integer, got {val!r}")
217
+ return 2
218
+ changed = True
219
+ if changed:
220
+ pool.save(home)
221
+ _print_pool(pool)
222
+ return 0
223
+
224
+
225
+ def cmd_account(args: argparse.Namespace) -> int:
226
+ """Account dispatcher — keeps `omega account` (no subcommand) working as a
227
+ list view, matches the original surface area."""
228
+ sub = getattr(args, "account_cmd", None) or "list"
229
+ fns = {
230
+ "list": cmd_account_list,
231
+ "login": cmd_account_login,
232
+ "use": cmd_account_use,
233
+ "pool": cmd_account_pool,
234
+ }
235
+ if sub not in fns:
236
+ print(f"unknown account subcommand: {sub}")
237
+ return 2
238
+ return fns[sub](args)
239
+
240
+
241
+ # ──── billing ─────────────────────────────────────────────────────────────
242
+
243
+
93
244
  def cmd_billing(_args: argparse.Namespace) -> int:
94
245
  """Show usage and cost per Claude Max account.
95
246
 
96
- Per-account billing reads token usage recorded on the event log against the
97
- provider cost model so you see which account is near its weekly limit.
247
+ Sources: token usage on `task.*` events (live aggregation via
248
+ `BillingAggregator`) plus the cached counter in `accounts.yaml`. The event
249
+ log is the source of truth; the cached counter is the convenience view.
98
250
  """
251
+ from omega_engine.account import AccountPool, BillingAggregator
252
+ from omega_engine.store import SQLiteStore
253
+
254
+ home = _omega_home()
255
+ pool = AccountPool.load(home)
99
256
  print("omega billing — usage per Claude Max account")
100
- print(" source: token usage on task.* events + the provider cost model")
101
- print(" build-out: live per-account aggregation — see docs/ACCOUNT-AND-BILLING.md")
257
+ print()
258
+ print(f" pool selection: {pool.selection}")
259
+ print(f" accounts in pool: {len(pool.accounts)}")
260
+ print()
261
+
262
+ db = home / "Agentik_Runtime" / "eventlog" / "omega.db"
263
+ if not db.exists():
264
+ print(" no event store yet — nothing to aggregate")
265
+ return 0
266
+ store = SQLiteStore(db)
267
+ totals = BillingAggregator.from_event_log(store)
268
+ if not totals:
269
+ print(" no per-account usage recorded in the event log yet")
270
+ print(" (events emit `usage.account_id` once the Claude provider "
271
+ "is wired)")
272
+ store.close()
273
+ return 0
274
+
275
+ pool_ids = {a.id for a in pool.accounts}
276
+ header = f" {'account':<16} {'calls':>7} {'input_tok':>12} {'output_tok':>12} {'total_tok':>12}"
277
+ print(header)
278
+ print(f" {'-' * 64}")
279
+ grand_in = grand_out = grand_calls = 0
280
+ for account_id, t in sorted(totals.items()):
281
+ marker = "" if account_id in pool_ids else " (unknown)"
282
+ total_tok = t["input_tokens"] + t["output_tokens"]
283
+ print(
284
+ f" {account_id:<16} {t['total_calls']:>7,d} "
285
+ f"{t['input_tokens']:>12,d} {t['output_tokens']:>12,d} "
286
+ f"{total_tok:>12,d}{marker}"
287
+ )
288
+ grand_in += t["input_tokens"]
289
+ grand_out += t["output_tokens"]
290
+ grand_calls += t["total_calls"]
291
+ print(f" {'-' * 64}")
292
+ print(
293
+ f" {'TOTAL':<16} {grand_calls:>7,d} "
294
+ f"{grand_in:>12,d} {grand_out:>12,d} "
295
+ f"{grand_in + grand_out:>12,d}"
296
+ )
297
+ store.close()
102
298
  return 0
103
299
 
104
300
 
@@ -134,13 +330,227 @@ def cmd_project(args: argparse.Namespace) -> int:
134
330
  return 0
135
331
 
136
332
 
137
- def main(argv: list[str] | None = None) -> int:
333
+ # --------------------------------------------------------------------------
334
+ # `omega tool ...` — the registry of third-party tools (incl. installed MCPs).
335
+ # --------------------------------------------------------------------------
336
+
337
+
338
+ def _tool_install_npm(name: str, package: str, home: Path) -> dict[str, str]:
339
+ """Install one npm package into Agentik_Tools/<name>/ and return locator info."""
340
+ import shutil as _sh
341
+ import subprocess
342
+
343
+ tool_dir = home / "Agentik_Tools" / name
344
+ tool_dir.mkdir(parents=True, exist_ok=True)
345
+ npm = _sh.which("npm")
346
+ if npm is None:
347
+ raise RuntimeError("npm not found on PATH — install Node.js first")
348
+ subprocess.run(
349
+ [npm, "install", "-g", "--prefix", str(tool_dir), package],
350
+ check=True, capture_output=True, text=True, timeout=600,
351
+ )
352
+ bin_dir = tool_dir / "bin"
353
+ invoke = ""
354
+ if bin_dir.is_dir():
355
+ bins = sorted(p for p in bin_dir.iterdir() if p.is_file())
356
+ if bins:
357
+ invoke = str(bins[0].relative_to(home))
358
+ return {"path": f"Agentik_Tools/{name}/", "invoke": invoke}
359
+
360
+
361
+ def cmd_tool_list(_args: argparse.Namespace) -> int:
362
+ from omega_engine.tools import ToolRegistry
363
+ reg = ToolRegistry.load(_omega_home())
364
+ tools = reg.list()
365
+ if not tools:
366
+ print("(no tools installed)")
367
+ return 0
368
+ print(f"{len(tools)} tool(s) installed:")
369
+ for t in tools:
370
+ ver = f"@{t.version}" if t.version else ""
371
+ print(f" {t.name}{ver} source={t.source or '-'} invoke={t.invoke or '-'}")
372
+ return 0
373
+
374
+
375
+ def cmd_tool_install(args: argparse.Namespace) -> int:
376
+ """Install a tool — either an MCP id from the catalog OR an npm package."""
377
+ home = _omega_home()
378
+ name = args.name
379
+ from omega_engine.tools import (
380
+ CatalogError,
381
+ Tool,
382
+ ToolRegistry,
383
+ install_from_catalog,
384
+ load_catalog,
385
+ )
386
+
387
+ if args.npm is None:
388
+ # Default path: treat <name> as an MCP catalog id.
389
+ try:
390
+ catalog = load_catalog(home)
391
+ tool = install_from_catalog(home, name, catalog=catalog)
392
+ print(f"installed {tool.name} (source={tool.source})")
393
+ return 0
394
+ except CatalogError as exc:
395
+ print(f"error: {exc}")
396
+ return 1
397
+ except Exception as exc: # noqa: BLE001
398
+ print(f"error: {exc}")
399
+ return 1
400
+
401
+ # --npm given: install a bare npm package under Agentik_Tools/<name>/.
402
+ try:
403
+ loc = _tool_install_npm(name, args.npm, home)
404
+ except Exception as exc: # noqa: BLE001
405
+ print(f"error: {exc}")
406
+ return 1
407
+ reg = ToolRegistry.load(home)
408
+ reg.install(Tool(name=name, path=loc["path"], invoke=loc["invoke"],
409
+ source=f"npm:{args.npm}"))
410
+ reg.save(home)
411
+ print(f"installed {name} (npm:{args.npm})")
412
+ return 0
413
+
414
+
415
+ def cmd_tool_remove(args: argparse.Namespace) -> int:
416
+ import shutil as _sh
417
+ from omega_engine.tools import ToolRegistry
418
+ home = _omega_home()
419
+ reg = ToolRegistry.load(home)
420
+ tool = reg.get(args.name)
421
+ if tool is None:
422
+ print(f"not installed: {args.name}")
423
+ return 1
424
+ tool_dir = home / tool.path
425
+ if tool_dir.exists():
426
+ _sh.rmtree(tool_dir, ignore_errors=True)
427
+ reg.remove(args.name)
428
+ reg.save(home)
429
+ print(f"removed {args.name}")
430
+ return 0
431
+
432
+
433
+ def cmd_tool_update(args: argparse.Namespace) -> int:
434
+ """Re-install the tool from its recorded source."""
435
+ from omega_engine.tools import (
436
+ CatalogError,
437
+ Tool,
438
+ ToolRegistry,
439
+ install_from_catalog,
440
+ load_catalog,
441
+ )
442
+ home = _omega_home()
443
+ reg = ToolRegistry.load(home)
444
+ tool = reg.get(args.name)
445
+ if tool is None:
446
+ print(f"not installed: {args.name}")
447
+ return 1
448
+ if tool.source.startswith("mcp:") or tool.source.startswith("catalog:"):
449
+ try:
450
+ catalog = load_catalog(home)
451
+ install_from_catalog(home, args.name, catalog=catalog, registry=reg)
452
+ print(f"updated {args.name}")
453
+ return 0
454
+ except CatalogError as exc:
455
+ print(f"error: {exc}")
456
+ return 1
457
+ if tool.source.startswith("npm:"):
458
+ pkg = tool.source.split(":", 1)[1]
459
+ try:
460
+ loc = _tool_install_npm(args.name, pkg, home)
461
+ except Exception as exc: # noqa: BLE001
462
+ print(f"error: {exc}")
463
+ return 1
464
+ reg.install(Tool(name=args.name, path=loc["path"], invoke=loc["invoke"],
465
+ source=tool.source))
466
+ reg.save(home)
467
+ print(f"updated {args.name}")
468
+ return 0
469
+ print(f"cannot update — unknown source: {tool.source}")
470
+ return 1
471
+
472
+
473
+ def cmd_tool(args: argparse.Namespace) -> int:
474
+ """`omega tool` dispatcher."""
475
+ sub = getattr(args, "tool_cmd", None) or "list"
476
+ fns = {
477
+ "list": cmd_tool_list,
478
+ "install": cmd_tool_install,
479
+ "remove": cmd_tool_remove,
480
+ "update": cmd_tool_update,
481
+ }
482
+ if sub not in fns:
483
+ print(f"unknown tool subcommand: {sub}")
484
+ return 2
485
+ return fns[sub](args)
486
+
487
+
488
+ def cmd_sync(_args: argparse.Namespace) -> int:
489
+ """Project the SSOT into every active provider's native shape."""
490
+ from omega_engine.sync import SyncEngine
491
+ outcomes = SyncEngine().sync_all(_omega_home())
492
+ for o in outcomes:
493
+ if o.get("projected"):
494
+ print(
495
+ f" [ok] {o['provider']}: skills={o['skills_written']} "
496
+ f"commands={o['commands_written']} "
497
+ f"agents={o.get('agents_written', 0)} "
498
+ f"mcp={o['mcp_servers']} hooks={len(o.get('hooks', []))}"
499
+ )
500
+ else:
501
+ print(f" [-] {o['provider']}: {o.get('reason', 'skipped')}")
502
+ return 0
503
+
504
+
505
+ def cmd_daemon(args: argparse.Namespace) -> int:
506
+ """Run one of the 24/7 service daemons.
507
+
508
+ ``omega daemon engine`` — the event-store keeper + local HTTP API.
509
+ ``omega daemon telegram`` — long-polls Telegram, routes inbound messages.
510
+ ``omega daemon autonomous`` — runs the autonomous-agent supervisor.
511
+
512
+ The installer's systemd user units invoke these.
513
+ """
514
+ name = args.name
515
+ if name == "engine":
516
+ from omega_engine.daemons import engine as d
517
+ return d.main()
518
+ if name == "telegram":
519
+ from omega_engine.daemons import telegram as d
520
+ return d.main()
521
+ if name == "autonomous":
522
+ from omega_engine.daemons import autonomous as d
523
+ return d.main()
524
+ print(f"unknown daemon: {name!r} — expected engine|telegram|autonomous")
525
+ return 2
526
+
527
+
528
+ def _build_parser() -> argparse.ArgumentParser:
138
529
  parser = argparse.ArgumentParser(prog="omega", description="Omega OS control CLI")
139
530
  sub = parser.add_subparsers(dest="cmd", required=True)
140
531
  sub.add_parser("version", help="print the engine version").set_defaults(fn=cmd_version)
141
532
  sub.add_parser("doctor", help="validate the deployment").set_defaults(fn=cmd_doctor)
142
533
  sub.add_parser("status", help="show all tasks and their derived state").set_defaults(fn=cmd_status)
143
- sub.add_parser("account", help="show the Claude Max account pool").set_defaults(fn=cmd_account)
534
+
535
+ p_acc = sub.add_parser("account", help="manage the Claude Max account pool")
536
+ p_acc.set_defaults(fn=cmd_account)
537
+ acc_sub = p_acc.add_subparsers(dest="account_cmd")
538
+ acc_sub.add_parser("list", help="show the pool (default)")
539
+ p_login = acc_sub.add_parser("login", help="add an account via OAuth (or manual paste)")
540
+ p_login.add_argument("--id", help="account id (e.g. max-primary)", default=None)
541
+ p_login.add_argument("--label", help="human label", default=None)
542
+ p_login.add_argument("--manual", action="store_true",
543
+ help="skip the device-code attempt; paste a token directly")
544
+ p_use = acc_sub.add_parser("use", help="set an account's status")
545
+ p_use.add_argument("id", help="account id")
546
+ p_use.add_argument("status", choices=["active", "resting", "disabled"])
547
+ p_pool = acc_sub.add_parser("pool", help="edit weights and selection strategy")
548
+ p_pool.add_argument("--selection", choices=["round-robin", "least-used", "by-quota"],
549
+ help="set the pool-wide selection strategy")
550
+ p_pool.add_argument("--weight", action="append", default=[],
551
+ metavar="ID=N",
552
+ help="set an account weight; repeatable")
553
+
144
554
  sub.add_parser("billing", help="show usage/cost per Claude Max account").set_defaults(fn=cmd_billing)
145
555
  p_run = sub.add_parser("run", help="run a mission end-to-end")
146
556
  p_run.add_argument("intent", help="the mission, in natural language")
@@ -150,6 +560,34 @@ def main(argv: list[str] | None = None) -> int:
150
560
  p_proj.add_argument("--no-telegram", action="store_true",
151
561
  help="skip Telegram topic creation")
152
562
  p_proj.set_defaults(fn=cmd_project)
563
+
564
+ p_dae = sub.add_parser("daemon", help="run a 24/7 service daemon")
565
+ p_dae.add_argument("name", choices=["engine", "telegram", "autonomous"],
566
+ help="which daemon to run")
567
+ p_dae.set_defaults(fn=cmd_daemon)
568
+
569
+ p_tool = sub.add_parser("tool", help="manage installed tools (incl. MCP servers)")
570
+ p_tool.set_defaults(fn=cmd_tool)
571
+ tool_sub = p_tool.add_subparsers(dest="tool_cmd")
572
+ tool_sub.add_parser("list", help="list installed tools (default)")
573
+ p_tinst = tool_sub.add_parser("install", help="install from the MCP catalog (default) or via --npm")
574
+ p_tinst.add_argument("name", help="tool name (an MCP catalog id, or a free name when --npm is given)")
575
+ p_tinst.add_argument("--npm", default=None, metavar="PACKAGE",
576
+ help="install a bare npm package under Agentik_Tools/<name>/")
577
+ p_trm = tool_sub.add_parser("remove", help="remove a tool + unregister")
578
+ p_trm.add_argument("name", help="tool name")
579
+ p_tup = tool_sub.add_parser("update", help="re-install a tool from its recorded source")
580
+ p_tup.add_argument("name", help="tool name")
581
+
582
+ sub.add_parser(
583
+ "sync",
584
+ help="project Agentik_SSOT/ into each provider's native shape",
585
+ ).set_defaults(fn=cmd_sync)
586
+ return parser
587
+
588
+
589
+ def main(argv: list[str] | None = None) -> int:
590
+ parser = _build_parser()
153
591
  args = parser.parse_args(argv)
154
592
  return args.fn(args)
155
593
 
@@ -0,0 +1,14 @@
1
+ """24/7 service layer for Omega OS.
2
+
3
+ Three long-running daemons backed by systemd user units:
4
+
5
+ * :mod:`omega_engine.daemons.engine` — the event-store keeper + heartbeat
6
+ * :mod:`omega_engine.daemons.telegram` — long-polls Telegram, routes inbound
7
+ messages to either the autonomous supervisor or :func:`run_mission`
8
+ * :mod:`omega_engine.daemons.autonomous` — runs the autonomous-agent
9
+ supervisor
10
+
11
+ Each daemon exposes a ``main()`` entry point invoked by ``omega daemon <name>``.
12
+ """
13
+
14
+ __all__ = ["engine", "telegram", "autonomous"]
@@ -0,0 +1,56 @@
1
+ """The autonomous-agent daemon.
2
+
3
+ Loads every charter under
4
+ ``$OMEGA_HOME/Agentik_Orchestration/autonomous/``, builds an
5
+ :class:`omega_engine.autonomous.AutonomousSupervisor`, and runs it until
6
+ SIGTERM/SIGINT.
7
+
8
+ This daemon owns the cron + event triggers. Channel triggers are routed in
9
+ from the Telegram daemon; webhook triggers are awakened by whatever HTTP shim
10
+ your operator put in front (the supervisor exposes ``wake_webhook`` for them).
11
+ """
12
+ from __future__ import annotations
13
+
14
+ import logging
15
+ import os
16
+
17
+ from omega_engine.autonomous import build_supervisor_from_home
18
+
19
+ logger = logging.getLogger("omega.daemon.autonomous")
20
+
21
+
22
+ def main() -> int:
23
+ logging.basicConfig(
24
+ level=os.environ.get("OMEGA_LOG_LEVEL", "INFO"),
25
+ format="%(asctime)s %(name)s %(levelname)s %(message)s",
26
+ )
27
+ # Optional outbound Telegram bridge — used by the supervisor to deliver
28
+ # progress + reports for missions it spawns. Best-effort: missing vault
29
+ # token means "no Telegram", not "abort".
30
+ telegram = None
31
+ try:
32
+ from omega_engine.telegram import TelegramBridge
33
+ telegram = TelegramBridge.from_vault()
34
+ except Exception as exc: # noqa: BLE001
35
+ logger.warning("autonomous daemon: no outbound Telegram (%s)", exc)
36
+
37
+ supervisor = build_supervisor_from_home(telegram=telegram)
38
+ charters = supervisor.charters()
39
+ if not charters:
40
+ logger.warning(
41
+ "autonomous daemon: no charters found — idling. Drop a YAML in "
42
+ "Agentik_Orchestration/autonomous/ and restart."
43
+ )
44
+ else:
45
+ for c in charters:
46
+ logger.info("autonomous: loaded charter %s (%s, trigger=%s)",
47
+ c.id, c.role, c.trigger_type)
48
+
49
+ # blocks until SIGTERM / SIGINT (the supervisor wires both)
50
+ supervisor.run()
51
+ logger.info("autonomous daemon stopped")
52
+ return 0
53
+
54
+
55
+ if __name__ == "__main__": # pragma: no cover
56
+ raise SystemExit(main())