From fe7b7b184c76ed9e1aca0663d4afb10412997d7f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 30 Apr 2026 13:17:43 +0000 Subject: [PATCH 1/4] Initial plan From faea18c3278742672093a5e93764ea96698402d7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 30 Apr 2026 13:23:50 +0000 Subject: [PATCH 2/4] feat: scaffold read-only sourceosctl CLI (issue: scaffold read-only sourceosctl CLI) Agent-Logs-Url: https://github.com/SourceOS-Linux/sourceos-devtools/sessions/9276cc7b-6239-4371-bdd4-49e6044305f6 Co-authored-by: mdheller <21163552+mdheller@users.noreply.github.com> --- Makefile | 22 +-- README.md | 46 ++++- bin/sourceosctl | 11 ++ fixtures/sample_nlboot_evidence.json | 17 ++ fixtures/sample_release.json | 14 ++ scripts/validate_scaffold.py | 21 +++ sourceosctl/__init__.py | 3 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 271 bytes sourceosctl/__pycache__/cli.cpython-312.pyc | Bin 0 -> 5748 bytes sourceosctl/cli.py | 125 ++++++++++++++ sourceosctl/commands/__init__.py | 1 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 226 bytes .../__pycache__/agents.cpython-312.pyc | Bin 0 -> 1365 bytes .../commands/__pycache__/ai.cpython-312.pyc | Bin 0 -> 957 bytes .../__pycache__/doctor.cpython-312.pyc | Bin 0 -> 1624 bytes .../__pycache__/fingerprint.cpython-312.pyc | Bin 0 -> 2256 bytes .../__pycache__/nlboot.cpython-312.pyc | Bin 0 -> 1970 bytes .../__pycache__/profiles.cpython-312.pyc | Bin 0 -> 889 bytes .../__pycache__/release.cpython-312.pyc | Bin 0 -> 2448 bytes sourceosctl/commands/agents.py | 31 ++++ sourceosctl/commands/ai.py | 15 ++ sourceosctl/commands/doctor.py | 34 ++++ sourceosctl/commands/fingerprint.py | 36 ++++ sourceosctl/commands/nlboot.py | 37 ++++ sourceosctl/commands/profiles.py | 15 ++ sourceosctl/commands/release.py | 41 +++++ tests/__init__.py | 1 + tests/__pycache__/test_cli.cpython-312.pyc | Bin 0 -> 10901 bytes tests/test_cli.py | 161 ++++++++++++++++++ 29 files changed, 614 insertions(+), 17 deletions(-) create mode 100755 bin/sourceosctl create mode 100644 fixtures/sample_nlboot_evidence.json create mode 100644 fixtures/sample_release.json create mode 100644 scripts/validate_scaffold.py create mode 100644 sourceosctl/__init__.py create mode 100644 sourceosctl/__pycache__/__init__.cpython-312.pyc create mode 100644 sourceosctl/__pycache__/cli.cpython-312.pyc create mode 100644 sourceosctl/cli.py create mode 100644 sourceosctl/commands/__init__.py create mode 100644 sourceosctl/commands/__pycache__/__init__.cpython-312.pyc create mode 100644 sourceosctl/commands/__pycache__/agents.cpython-312.pyc create mode 100644 sourceosctl/commands/__pycache__/ai.cpython-312.pyc create mode 100644 sourceosctl/commands/__pycache__/doctor.cpython-312.pyc create mode 100644 sourceosctl/commands/__pycache__/fingerprint.cpython-312.pyc create mode 100644 sourceosctl/commands/__pycache__/nlboot.cpython-312.pyc create mode 100644 sourceosctl/commands/__pycache__/profiles.cpython-312.pyc create mode 100644 sourceosctl/commands/__pycache__/release.cpython-312.pyc create mode 100644 sourceosctl/commands/agents.py create mode 100644 sourceosctl/commands/ai.py create mode 100644 sourceosctl/commands/doctor.py create mode 100644 sourceosctl/commands/fingerprint.py create mode 100644 sourceosctl/commands/nlboot.py create mode 100644 sourceosctl/commands/profiles.py create mode 100644 sourceosctl/commands/release.py create mode 100644 tests/__init__.py create mode 100644 tests/__pycache__/test_cli.cpython-312.pyc create mode 100644 tests/test_cli.py diff --git a/Makefile b/Makefile index cb1e5df..4046d81 100644 --- a/Makefile +++ b/Makefile @@ -1,22 +1,12 @@ -.PHONY: validate +.PHONY: validate test -validate: +validate: test @test -f README.md @test -f AGENTS.md @test -f .github/copilot-instructions.md @test -f docs/DEVTOOLS_SCOPE.md @test -f repo.maturity.yaml - @python3 - <<'PY' -import pathlib -for path in [ - 'README.md', - 'AGENTS.md', - '.github/copilot-instructions.md', - 'docs/DEVTOOLS_SCOPE.md', - 'repo.maturity.yaml', -]: - text = pathlib.Path(path).read_text() - if not text.strip(): - raise SystemExit(f'{path} is empty') -print('OK: sourceos-devtools validation') -PY + @python3 scripts/validate_scaffold.py + +test: + @python3 -m unittest discover -s tests -v diff --git a/README.md b/README.md index febc4e8..3a4f54a 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,45 @@ It should not contain: - SourceOS image build state; - secrets, tokens, credentials, private keys, or device-specific enrollment secrets. +## sourceosctl CLI + +`sourceosctl` is the read-only/dry-run CLI surface for SourceOS developer and AI operator workflows. + +### Usage + +```text +sourceosctl [--version] [] [options] +``` + +### Commands + +| Command | Description | +| --- | --- | +| `sourceosctl doctor` | Run environment health checks (read-only) | +| `sourceosctl profiles list` | List available SourceOS profiles (read-only) | +| `sourceosctl nlboot evidence inspect ` | Inspect a NLBoot evidence JSON file (read-only) | +| `sourceosctl release inspect ` | Inspect a release artifact JSON file (read-only) | +| `sourceosctl fingerprint collect --dry-run` | Print environment fingerprint fields (dry-run only) | +| `sourceosctl ai labs list` | List available AI labs (read-only) | +| `sourceosctl agents sandbox plan --dry-run` | Print agent sandbox plan (dry-run only) | + +### Running from the repo + +```bash +python3 bin/sourceosctl --help +python3 bin/sourceosctl doctor +python3 bin/sourceosctl profiles list +python3 bin/sourceosctl nlboot evidence inspect fixtures/sample_nlboot_evidence.json +python3 bin/sourceosctl release inspect fixtures/sample_release.json +python3 bin/sourceosctl fingerprint collect --dry-run +python3 bin/sourceosctl ai labs list +python3 bin/sourceosctl agents sandbox plan --dry-run +``` + +### Design constraints + +All commands in the current surface are **read-only or dry-run**. No mutating command is implemented. Commands that would mutate host state are explicitly rejected at runtime. + ## First milestone M1 is repo maturity and install surface definition: @@ -62,4 +101,9 @@ M1 is repo maturity and install surface definition: make validate ``` -The initial validation target checks repository metadata and JSON/YAML syntax where present. Implementation-specific validation should be added with each tool surface. +The validation target runs the unit test suite and checks repository metadata. All 21 tests must pass. + +```bash +make test # run tests only +``` + diff --git a/bin/sourceosctl b/bin/sourceosctl new file mode 100755 index 0000000..f0cad7f --- /dev/null +++ b/bin/sourceosctl @@ -0,0 +1,11 @@ +#!/usr/bin/env python3 +"""sourceosctl entry-point script.""" +import sys +import os + +# Allow running directly from the repo root without installing. +sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) + +from sourceosctl.cli import main + +sys.exit(main()) diff --git a/fixtures/sample_nlboot_evidence.json b/fixtures/sample_nlboot_evidence.json new file mode 100644 index 0000000..1f20678 --- /dev/null +++ b/fixtures/sample_nlboot_evidence.json @@ -0,0 +1,17 @@ +{ + "schemaVersion": "nlboot-evidence.v1", + "kind": "NLBootEvidence", + "timestamp": "2025-01-01T00:00:00Z", + "target": { + "host": "example-host", + "arch": "x86_64", + "uefi": true + }, + "boot": { + "loader": "systemd-boot", + "kernel": "6.6.0-sourceos", + "cmdline": "root=/dev/sda1 ro quiet" + }, + "status": "verified", + "signature": "stub-signature-not-real" +} diff --git a/fixtures/sample_release.json b/fixtures/sample_release.json new file mode 100644 index 0000000..fa07eb7 --- /dev/null +++ b/fixtures/sample_release.json @@ -0,0 +1,14 @@ +{ + "schemaVersion": "sourceos-release.v1", + "name": "sourceos-devtools", + "version": "0.1.0", + "channel": "stable", + "artifacts": [ + "sourceosctl-0.1.0-linux-x86_64.tar.gz", + "sourceosctl-0.1.0-darwin-arm64.tar.gz" + ], + "metadata": { + "gitRef": "refs/heads/main", + "builtAt": "2025-01-01T00:00:00Z" + } +} diff --git a/scripts/validate_scaffold.py b/scripts/validate_scaffold.py new file mode 100644 index 0000000..047e882 --- /dev/null +++ b/scripts/validate_scaffold.py @@ -0,0 +1,21 @@ +"""Validation script for sourceos-devtools repository scaffold.""" + +import pathlib +import sys + +REQUIRED = [ + "README.md", + "AGENTS.md", + ".github/copilot-instructions.md", + "docs/DEVTOOLS_SCOPE.md", + "repo.maturity.yaml", +] + +for path in REQUIRED: + p = pathlib.Path(path) + if not p.exists(): + raise SystemExit(f"MISSING: {path}") + if not p.read_text().strip(): + raise SystemExit(f"EMPTY: {path}") + +print("OK: sourceos-devtools validation") diff --git a/sourceosctl/__init__.py b/sourceosctl/__init__.py new file mode 100644 index 0000000..1f61660 --- /dev/null +++ b/sourceosctl/__init__.py @@ -0,0 +1,3 @@ +"""sourceosctl - SourceOS Developer and AI Operator CLI.""" + +__version__ = "0.1.0" diff --git a/sourceosctl/__pycache__/__init__.cpython-312.pyc b/sourceosctl/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6a87b4f6afcb27632e5fea408797df356e9bc6d2 GIT binary patch literal 271 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 argparse.ArgumentParser: + """Build and return the argument parser.""" + parser = argparse.ArgumentParser( + prog="sourceosctl", + description="SourceOS developer and AI operator CLI (read-only / dry-run surface)", + ) + parser.add_argument( + "--version", action="version", version=f"sourceosctl {__version__}" + ) + + sub = parser.add_subparsers(dest="command", metavar="") + sub.required = True + + # --- doctor --- + doctor_p = sub.add_parser("doctor", help="Run environment health checks") + doctor_p.set_defaults(func=doctor.run) + + # --- profiles --- + profiles_p = sub.add_parser("profiles", help="Profile management") + profiles_sub = profiles_p.add_subparsers(dest="profiles_command", metavar="") + profiles_sub.required = True + profiles_list_p = profiles_sub.add_parser("list", help="List available profiles") + profiles_list_p.set_defaults(func=profiles.list_profiles) + + # --- nlboot --- + nlboot_p = sub.add_parser("nlboot", help="NLBoot operator helpers") + nlboot_sub = nlboot_p.add_subparsers(dest="nlboot_command", metavar="") + nlboot_sub.required = True + nlboot_evidence_p = nlboot_sub.add_parser("evidence", help="NLBoot evidence helpers") + nlboot_evidence_sub = nlboot_evidence_p.add_subparsers( + dest="nlboot_evidence_command", metavar="" + ) + nlboot_evidence_sub.required = True + nlboot_inspect_p = nlboot_evidence_sub.add_parser( + "inspect", help="Inspect a NLBoot evidence file" + ) + nlboot_inspect_p.add_argument("path", help="Path to NLBoot evidence JSON file") + nlboot_inspect_p.set_defaults(func=nlboot.inspect_evidence) + + # --- release --- + release_p = sub.add_parser("release", help="Release artifact inspection") + release_sub = release_p.add_subparsers(dest="release_command", metavar="") + release_sub.required = True + release_inspect_p = release_sub.add_parser("inspect", help="Inspect a release artifact") + release_inspect_p.add_argument("path", help="Path to release artifact JSON file") + release_inspect_p.set_defaults(func=release.inspect) + + # --- fingerprint --- + fingerprint_p = sub.add_parser("fingerprint", help="Environment fingerprint utilities") + fingerprint_sub = fingerprint_p.add_subparsers( + dest="fingerprint_command", metavar="" + ) + fingerprint_sub.required = True + fingerprint_collect_p = fingerprint_sub.add_parser( + "collect", help="Collect environment fingerprint (dry-run only)" + ) + fingerprint_collect_p.add_argument( + "--dry-run", + action="store_true", + default=True, + dest="dry_run", + help="Print what would be collected without writing to disk (default: True)", + ) + fingerprint_collect_p.set_defaults(func=fingerprint.collect) + + # --- ai --- + ai_p = sub.add_parser("ai", help="AI operator utilities") + ai_sub = ai_p.add_subparsers(dest="ai_command", metavar="") + ai_sub.required = True + ai_labs_p = ai_sub.add_parser("labs", help="AI lab helpers") + ai_labs_sub = ai_labs_p.add_subparsers(dest="ai_labs_command", metavar="") + ai_labs_sub.required = True + ai_labs_list_p = ai_labs_sub.add_parser("list", help="List available AI labs") + ai_labs_list_p.set_defaults(func=ai.list_labs) + + # --- agents --- + agents_p = sub.add_parser("agents", help="Agent sandbox helpers") + agents_sub = agents_p.add_subparsers(dest="agents_command", metavar="") + agents_sub.required = True + agents_sandbox_p = agents_sub.add_parser("sandbox", help="Agent sandbox management") + agents_sandbox_sub = agents_sandbox_p.add_subparsers( + dest="agents_sandbox_command", metavar="" + ) + agents_sandbox_sub.required = True + agents_sandbox_plan_p = agents_sandbox_sub.add_parser( + "plan", help="Plan agent sandbox (dry-run only)" + ) + agents_sandbox_plan_p.add_argument( + "--dry-run", + action="store_true", + default=True, + dest="dry_run", + help="Print plan without executing (default: True)", + ) + agents_sandbox_plan_p.set_defaults(func=agents.sandbox_plan) + + return parser + + +def main(argv=None) -> int: + """Main entry point. Returns exit code.""" + parser = build_parser() + args = parser.parse_args(argv) + return args.func(args) or 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/sourceosctl/commands/__init__.py b/sourceosctl/commands/__init__.py new file mode 100644 index 0000000..001eca1 --- /dev/null +++ b/sourceosctl/commands/__init__.py @@ -0,0 +1 @@ +"""sourceosctl command modules.""" diff --git a/sourceosctl/commands/__pycache__/__init__.cpython-312.pyc b/sourceosctl/commands/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d929cf41993a231f60267bd9f5898225bc87fb46 GIT binary patch literal 226 zcmX@j%ge<81ZTrOXDS2f#~=<2FhUuhIe?7m3@Hpz43&(UOjR<)`K3k4srkjpB{>So z`MJ4?c_|9H`6;D2sl|GJnvA#D&u`pB6drrMn~k$+lL7|2`4Y3uumx2B-Eldw>GUlp|u~aO`AHb*0X8-YiH9L+Fm2S z81z}96@(&=gM_X@xd(;vmbeK6##1Jh|G+?B_3HWR>ppzSl;At8x*Aagi1ZM8Qp6)& zTe5znv>d{+(%F`mci!;fy#WiiK?}j#eHkPgT_#!vAv#}{ETCH&7yzh>`|+fKg1a5&^NfsQ{Dj2^ll$<*&Tu!~1chCsCw4NC1l#^_bF_7=}(o zri+GTI+ICgRdkr8jvvxRPJYE{pXkZit&*YxbB05`s4IyeVcHpK3Axc2OQ-A4&Ot@E zMTYe$yT(C34o zO$}Aiz>dPa3b*^&#^>*L*FU^`)w2sHkbPA+rAuQC@MNPi5HaguB@!k(yFzYvMy)MW zo3b7A=+yscLd`qX4?uNB8T;w5XjQ>>P1X_{g`B}pZQ*pAJa$RznZ4D!&;M}caLzkw z&fjZYJ6e2pf9cz$zw5PG_qa(G7XNZ)e|Nn@=hdT$u(0%qSoIg~IB2qG@2&1#ytDmB zbLqH-%JTYahtA5qxz-7KDGSr>QW18$(){;i3#X!n1uDb&iO5BxJ&V(m<~y;V8E2Q| nbGS9^sS7wEux0&8R_+t`k<&P1-*%7BEn4$;UwTY%DeeCO%!P)k literal 0 HcmV?d00001 diff --git a/sourceosctl/commands/__pycache__/ai.cpython-312.pyc b/sourceosctl/commands/__pycache__/ai.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4d28a54eb70b507f947a5b19dac78a602c7355b8 GIT binary patch literal 957 zcmaJ<&r4K69G~~z>~D9K=pr4Q6l0gzT~IJm5tK**Gt25=$ntzM>d@Oa+su0#`$+K6 zp+g5d8CYO9ql-uXhb~Vju!o1BE?q*hlc&D3xfTQs%y+(@?`P)oo$q(%eLkN>FuvbE z=G;W+iw2#MJavvoz}Z9;Eg))8lUfwNvS@-PX{zm|!OLiG#ab|EmYK^Y%{|2nR;#O& z5BqIS98s^^9vvlLxSX8g6!d}2_m7ifyNFiikTwowXD z8c;@~E%ZA1NALLnMy+fq7xvF^<&(3<>bHQpRfXJPHZ2R!ZB@eF$xh!pG%2b%<-$CwFq1S&^MTSU2beK?O%9_3quKG{J@!jg_&r(&JR~oH2<3f!!okDg) z4&87y3uYaC#hEBO_yJ!!?C$-6jKpo@nRS@XZ7%Q6rTO{+KKJNO$046Y!jr`Dz#H3X(|&T1wjZZjVPooazSS08ONKfcg*Z= zQd?^^NJs<;33FLh3D$o^oQ=_9jM3;|SJn}aF;9JVxOH8>1Ajkm7LEhjpTto61z9EH-00kuGc_@A& zW~4HokSa-tb2cVw4i}4>3DYWZOcj}PI>BZUnYLO$nqe2zTmj`~t>k0R)g+Y(vK?aP zxRz&61leSffxRIX&n++ws+`Nsv?Dwe8<4a8h{255w6(7@f%aWfUu&5lf1GJh$O~&N zW)2+TMYO$$8e^tWJAc=d2aNyVvco=VCgN*{5=y?;Du0Am$>j;}@+Jpmmxlp|YmfAQ z*p&lz;Rtrc6&SnVvBO|HGZd<#%e@9ZI0wM{>?>5Is$31YGGP1?~veRx{t z+w8HnFCFvw59PPV)>@swfpSlxw{q@nqO+dURLf!QNXjbC|3@<>)pw8v&tlUkC#hU2 z+XZYAk7$Z2B}23Gm=vint=pBLiETBH9TQGc!PIYzQy$M!ab6=P701Wke5=B%YK0#? zzt1s|RxACX=6gu1nqjB}edOo_!B+RHCC#!BteiOjVH2tB@f0yZ{{rVV-9RwGQ8CYy z$6mW8LR8jDC1gS>SY?X_<`A)TY*NW88M;kmhM_V$u~Cu9f@A9jmF5e2u0W-dR~lxu zAtEFZq@qTqEh;x3CRJFv*^*`#sKWAWYBI|q>*Z@|MMR}J&2SJ^{G>c8&2}uydnT$s z_NP(>Ttq4M8!!<`&0{i~vamyP2wP_%nzJ!BtfyFVwvlT6ORQA0mdR3?3XG5RF-HoU z)-$Xa3t%T4T^QdHL!Vyy_)_h{?RW27U%vipJh>51ZpH`JyEo#4>r?eudP{tHy9G+$ zzH#T~^3BZ`2G(D!$40iq3%^Huzi>ZumtNnD_AN~KxZ%5n<(K z;iA0V(OtWGuj9Gx-V=-CzbWAwUpv3lS39$GdC6YNufDqKtWMV>$@@xbH^|8T>;y`L z!RnErEoFE&1Ukb{0w5TvM@D}f{3-oI`o1!`9qrm<^#KW)2C`XJy==D86QqY3Nf%qF zz?z^@$Zr7Y_pB$`vagI5F?0;{3OU8LUaMJgw(JTV$2|Z;k3jSx2tNd&UHJqTt{r;< Im}p%88>*a^U;qFB literal 0 HcmV?d00001 diff --git a/sourceosctl/commands/__pycache__/fingerprint.cpython-312.pyc b/sourceosctl/commands/__pycache__/fingerprint.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0b91fcb5bb50c1700da1cf074d9121dcb03d0efc GIT binary patch literal 2256 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 literal 0 HcmV?d00001 diff --git a/sourceosctl/commands/__pycache__/release.cpython-312.pyc b/sourceosctl/commands/__pycache__/release.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b1c4dc34872b77922d218b7a6c0abb82c7a3d670 GIT binary patch literal 2448 zcma)7T}&KR6u$Gff3yFTg4MP|OM#WLNE@3IOV~leXuVEOCKt}crVQCE>zT;>^ua%uE0YbODKy=p@xetx~#+UZqSIBQcQe zp(XYb+r_2n2$yL>FbPEs3URf+UsR-4!4`-}mwUvxF35^D0OPu>D$xfdQm5%aP){Kx zPFaBoA?Y#rxBL;_CxBkmCG7Nwm9-3zz(s6aH>_{6ltCq&8?^x3uteHU-EC_nYq_({eZ$Iv0%^t#^nk5P1Z}QFarBDP$>!|n#Vl!qPE^_25|x~i%Z`?1 zH?eV-*>IcKc*<-XWfk*l72A1GpiJjASkjGu)cFib3o+{O3b9N8f!G&u8 zyoGz3b!J_LQwj_MM2+$K)3&AI(x0(;yBp|*vP^@9^D2-+*O^kThU-6KZna{%vz{_f zVZ)6*afSz1z?Qs?RkL91^tz08JdsgDnV+?cIH@90W80OewB-^oTfwd1J|~<0={A?f*~vc_2Bm%)3mQS))VL5Ey38 zgCQ!iHBAxq@q@#M@~XPQFWn5O%bSwP$o-c56HtU zTFJ_wn3N^q_^H=AF?a|Wx>e{jeOkO1_KRmB(lC-_+-)hPPf>@In8_+)KQx_#mL`1C zLL?i2T7^tl5Y~MztjWlSLUFChEH{=E=g1%eCCEiK0vj1x5LT$x3`DnXh-+1LL8H9=8eEQGHVe8S+SQk}RdjHaUqZPL-_OlnXg z$b&VL z6*xSAVwr6gQb*g8YFtcev1n1Z2j`%XG5ZZX9sxhq$8NRUWM+0SDK(eCa8q=ET&~ypUhRG&0RWZ4wDoUE^ zMtE|%bvUe>0Wt&aFs@4Q6*595a}nw$C+o0ZGd;3~6V^o~4oy~)uA%etME$X7lz~ML!xjILs`!tz8m^L{)b17(2eTY0OjmI0lUV9;e_omb84~pOu znFsAtJh&s^J|7-$8*R(8Q|?LkEWc}{eSr^*9~wP0_Tr+;R|wY6u+xJDfAot}Uv_=o zHSapS5C|@@EEie~RE%dvGx^%7=wx(u+q1KQ#>?!I2l&I|=SI)v)aiF-efvj_5pM@a z59W^LPt5WS*gMwuj`rr#(~YzK=t##R>n?bA)!3)v*`|{F_Y$dpOKT9Hz6oTT int: + """Print an agent sandbox plan without executing it. + + Only --dry-run is supported; real execution is not implemented. + """ + if not getattr(args, "dry_run", True): + print( + "error: only --dry-run is supported; sandbox execution is not implemented", + file=sys.stderr, + ) + return 1 + + print("agents sandbox plan --dry-run") + print("Planned steps (not executed):") + for step in _STUB_PLAN: + print(f" {step}") + print("\n(dry-run: no sandbox created)") + return 0 diff --git a/sourceosctl/commands/ai.py b/sourceosctl/commands/ai.py new file mode 100644 index 0000000..00fa2d4 --- /dev/null +++ b/sourceosctl/commands/ai.py @@ -0,0 +1,15 @@ +"""ai command: AI operator utilities.""" + +_STUB_LABS = [ + {"name": "local-inference", "status": "available", "description": "Local model inference lab"}, + {"name": "model-router", "status": "stub", "description": "Governed model routing lab (client stub)"}, + {"name": "guardrail-fabric", "status": "stub", "description": "Guardrail policy inspection lab (client stub)"}, +] + + +def list_labs(args) -> int: + """List available AI labs. Read-only.""" + print("Available AI labs (stub):") + for lab in _STUB_LABS: + print(f" {lab['name']:<22} [{lab['status']:<9}] {lab['description']}") + return 0 diff --git a/sourceosctl/commands/doctor.py b/sourceosctl/commands/doctor.py new file mode 100644 index 0000000..70b0ae3 --- /dev/null +++ b/sourceosctl/commands/doctor.py @@ -0,0 +1,34 @@ +"""doctor command: run environment health checks.""" + +import platform +import shutil +import sys + + +def run(args) -> int: + """Print a summary of environment health. Read-only.""" + checks = [] + + checks.append(("python", sys.version.split()[0], True)) + checks.append(("platform", platform.system(), True)) + + git_path = shutil.which("git") + checks.append(("git", git_path if git_path else "not found", git_path is not None)) + + nix_path = shutil.which("nix") + checks.append(("nix", nix_path if nix_path else "not found", False)) + + all_ok = True + for name, value, required in checks: + status = "ok" if value and value != "not found" else ("warn" if not required else "FAIL") + if status == "FAIL": + all_ok = False + print(f" {status:<6} {name}: {value}") + + if all_ok: + print("\ndoctor: all required checks passed") + else: + print("\ndoctor: one or more required checks failed", file=sys.stderr) + return 1 + + return 0 diff --git a/sourceosctl/commands/fingerprint.py b/sourceosctl/commands/fingerprint.py new file mode 100644 index 0000000..760d7d5 --- /dev/null +++ b/sourceosctl/commands/fingerprint.py @@ -0,0 +1,36 @@ +"""fingerprint command: environment fingerprint utilities.""" + +import platform +import shutil +import sys + + +_FIELDS = [ + ("os", lambda: platform.system()), + ("os_version", lambda: platform.version()), + ("machine", lambda: platform.machine()), + ("python", lambda: sys.version.split()[0]), + ("git", lambda: shutil.which("git") or "not found"), + ("nix", lambda: shutil.which("nix") or "not found"), +] + + +def collect(args) -> int: + """Collect environment fingerprint. + + With --dry-run (default and only mode) prints what would be collected + without writing anything to disk. + """ + if not getattr(args, "dry_run", True): + print( + "error: only --dry-run is supported; mutating collect is not implemented", + file=sys.stderr, + ) + return 1 + + print("fingerprint collect --dry-run") + print("Fields that would be collected:") + for name, getter in _FIELDS: + print(f" {name}: {getter()}") + print("\n(dry-run: no data written)") + return 0 diff --git a/sourceosctl/commands/nlboot.py b/sourceosctl/commands/nlboot.py new file mode 100644 index 0000000..093c44a --- /dev/null +++ b/sourceosctl/commands/nlboot.py @@ -0,0 +1,37 @@ +"""nlboot command: NLBoot evidence inspection helpers.""" + +import json +import pathlib +import sys + + +def inspect_evidence(args) -> int: + """Inspect a NLBoot evidence file. Read-only.""" + path = pathlib.Path(args.path) + if not path.exists(): + print(f"error: file not found: {path}", file=sys.stderr) + return 1 + + try: + data = json.loads(path.read_text()) + except json.JSONDecodeError as exc: + print(f"error: invalid JSON in {path}: {exc}", file=sys.stderr) + return 1 + + schema = data.get("schemaVersion", "") + kind = data.get("kind", "") + print(f"NLBoot evidence: {path}") + print(f" schemaVersion : {schema}") + print(f" kind : {kind}") + + for key, value in data.items(): + if key in ("schemaVersion", "kind"): + continue + if isinstance(value, dict): + print(f" {key}:") + for k, v in value.items(): + print(f" {k}: {v}") + else: + print(f" {key}: {value}") + + return 0 diff --git a/sourceosctl/commands/profiles.py b/sourceosctl/commands/profiles.py new file mode 100644 index 0000000..9dfab88 --- /dev/null +++ b/sourceosctl/commands/profiles.py @@ -0,0 +1,15 @@ +"""profiles command: list available SourceOS profiles.""" + +_STUB_PROFILES = [ + {"name": "developer", "description": "Standard developer workstation profile"}, + {"name": "operator", "description": "AI operator / model-router profile"}, + {"name": "minimal", "description": "Minimal read-only inspection profile"}, +] + + +def list_profiles(args) -> int: + """List stub profiles. Read-only.""" + print("Available profiles (stub):") + for profile in _STUB_PROFILES: + print(f" {profile['name']:<16} {profile['description']}") + return 0 diff --git a/sourceosctl/commands/release.py b/sourceosctl/commands/release.py new file mode 100644 index 0000000..b7b36d2 --- /dev/null +++ b/sourceosctl/commands/release.py @@ -0,0 +1,41 @@ +"""release command: release artifact inspection.""" + +import json +import pathlib +import sys + + +def inspect(args) -> int: + """Inspect a release artifact. Read-only.""" + path = pathlib.Path(args.path) + if not path.exists(): + print(f"error: file not found: {path}", file=sys.stderr) + return 1 + + try: + data = json.loads(path.read_text()) + except json.JSONDecodeError as exc: + print(f"error: invalid JSON in {path}: {exc}", file=sys.stderr) + return 1 + + schema = data.get("schemaVersion", "") + name = data.get("name", "") + version = data.get("version", "") + print(f"Release artifact: {path}") + print(f" schemaVersion : {schema}") + print(f" name : {name}") + print(f" version : {version}") + + for key, value in data.items(): + if key in ("schemaVersion", "name", "version"): + continue + if isinstance(value, dict): + print(f" {key}:") + for k, v in value.items(): + print(f" {k}: {v}") + elif isinstance(value, list): + print(f" {key}: {', '.join(str(v) for v in value)}") + else: + print(f" {key}: {value}") + + return 0 diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..fccd2af --- /dev/null +++ b/tests/__init__.py @@ -0,0 +1 @@ +"""Tests for sourceosctl CLI.""" diff --git a/tests/__pycache__/test_cli.cpython-312.pyc b/tests/__pycache__/test_cli.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..25bb38ee457597c3441e087943cd8b0afd14bad2 GIT binary patch literal 10901 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 Date: Thu, 30 Apr 2026 13:24:08 +0000 Subject: [PATCH 3/4] chore: add .gitignore for Python bytecode; remove committed pycache Agent-Logs-Url: https://github.com/SourceOS-Linux/sourceos-devtools/sessions/9276cc7b-6239-4371-bdd4-49e6044305f6 Co-authored-by: mdheller <21163552+mdheller@users.noreply.github.com> --- .gitignore | 9 +++++++++ .../__pycache__/__init__.cpython-312.pyc | Bin 271 -> 0 bytes sourceosctl/__pycache__/cli.cpython-312.pyc | Bin 5748 -> 0 bytes .../__pycache__/__init__.cpython-312.pyc | Bin 226 -> 0 bytes .../commands/__pycache__/agents.cpython-312.pyc | Bin 1365 -> 0 bytes .../commands/__pycache__/ai.cpython-312.pyc | Bin 957 -> 0 bytes .../commands/__pycache__/doctor.cpython-312.pyc | Bin 1624 -> 0 bytes .../__pycache__/fingerprint.cpython-312.pyc | Bin 2256 -> 0 bytes .../commands/__pycache__/nlboot.cpython-312.pyc | Bin 1970 -> 0 bytes .../__pycache__/profiles.cpython-312.pyc | Bin 889 -> 0 bytes .../__pycache__/release.cpython-312.pyc | Bin 2448 -> 0 bytes tests/__pycache__/test_cli.cpython-312.pyc | Bin 10901 -> 0 bytes 12 files changed, 9 insertions(+) create mode 100644 .gitignore delete mode 100644 sourceosctl/__pycache__/__init__.cpython-312.pyc delete mode 100644 sourceosctl/__pycache__/cli.cpython-312.pyc delete mode 100644 sourceosctl/commands/__pycache__/__init__.cpython-312.pyc delete mode 100644 sourceosctl/commands/__pycache__/agents.cpython-312.pyc delete mode 100644 sourceosctl/commands/__pycache__/ai.cpython-312.pyc delete mode 100644 sourceosctl/commands/__pycache__/doctor.cpython-312.pyc delete mode 100644 sourceosctl/commands/__pycache__/fingerprint.cpython-312.pyc delete mode 100644 sourceosctl/commands/__pycache__/nlboot.cpython-312.pyc delete mode 100644 sourceosctl/commands/__pycache__/profiles.cpython-312.pyc delete mode 100644 sourceosctl/commands/__pycache__/release.cpython-312.pyc delete mode 100644 tests/__pycache__/test_cli.cpython-312.pyc diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2dc685a --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +# Python bytecode +__pycache__/ +*.py[cod] +*.pyo + +# Distribution / packaging +*.egg-info/ +dist/ +build/ diff --git a/sourceosctl/__pycache__/__init__.cpython-312.pyc b/sourceosctl/__pycache__/__init__.cpython-312.pyc deleted file mode 100644 index 6a87b4f6afcb27632e5fea408797df356e9bc6d2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 271 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 diff --git a/sourceosctl/commands/__pycache__/ai.cpython-312.pyc b/sourceosctl/commands/__pycache__/ai.cpython-312.pyc deleted file mode 100644 index 4d28a54eb70b507f947a5b19dac78a602c7355b8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 957 zcmaJ<&r4K69G~~z>~D9K=pr4Q6l0gzT~IJm5tK**Gt25=$ntzM>d@Oa+su0#`$+K6 zp+g5d8CYO9ql-uXhb~Vju!o1BE?q*hlc&D3xfTQs%y+(@?`P)oo$q(%eLkN>FuvbE z=G;W+iw2#MJavvoz}Z9;Eg))8lUfwNvS@-PX{zm|!OLiG#ab|EmYK^Y%{|2nR;#O& z5BqIS98s^^9vvlLxSX8g6!d}2_m7ifyNFiikTwowXD z8c;@~E%ZA1NALLnMy+fq7xvF^<&(3<>bHQpRfXJPHZ2R!ZB@eF$xh!pG%2b%<-$CwFq1S&^MTSU2beK?O%9_3quKG{J@!jg_&r(&JR~oH2<3f!!okDg) z4&87y3uYaC#hEBO_yJ!!?C$-6jKpo@nRS@XZ7%Q6rTO{+KKJNO$046Y!jr`Dz#H3X(|&T1wjZZjVPooazSS08ONKfcg*Z= zQd?^^NJs<;33FLh3D$o^oQ=_9jM3;|SJn}aF;9JVxOH8>1Ajkm7LEhjpTto61z9EH-00kuGc_@A& zW~4HokSa-tb2cVw4i}4>3DYWZOcj}PI>BZUnYLO$nqe2zTmj`~t>k0R)g+Y(vK?aP zxRz&61leSffxRIX&n++ws+`Nsv?Dwe8<4a8h{255w6(7@f%aWfUu&5lf1GJh$O~&N zW)2+TMYO$$8e^tWJAc=d2aNyVvco=VCgN*{5=y?;Du0Am$>j;}@+Jpmmxlp|YmfAQ z*p&lz;Rtrc6&SnVvBO|HGZd<#%e@9ZI0wM{>?>5Is$31YGGP1?~veRx{t z+w8HnFCFvw59PPV)>@swfpSlxw{q@nqO+dURLf!QNXjbC|3@<>)pw8v&tlUkC#hU2 z+XZYAk7$Z2B}23Gm=vint=pBLiETBH9TQGc!PIYzQy$M!ab6=P701Wke5=B%YK0#? zzt1s|RxACX=6gu1nqjB}edOo_!B+RHCC#!BteiOjVH2tB@f0yZ{{rVV-9RwGQ8CYy z$6mW8LR8jDC1gS>SY?X_<`A)TY*NW88M;kmhM_V$u~Cu9f@A9jmF5e2u0W-dR~lxu zAtEFZq@qTqEh;x3CRJFv*^*`#sKWAWYBI|q>*Z@|MMR}J&2SJ^{G>c8&2}uydnT$s z_NP(>Ttq4M8!!<`&0{i~vamyP2wP_%nzJ!BtfyFVwvlT6ORQA0mdR3?3XG5RF-HoU z)-$Xa3t%T4T^QdHL!Vyy_)_h{?RW27U%vipJh>51ZpH`JyEo#4>r?eudP{tHy9G+$ zzH#T~^3BZ`2G(D!$40iq3%^Huzi>ZumtNnD_AN~KxZ%5n<(K z;iA0V(OtWGuj9Gx-V=-CzbWAwUpv3lS39$GdC6YNufDqKtWMV>$@@xbH^|8T>;y`L z!RnErEoFE&1Ukb{0w5TvM@D}f{3-oI`o1!`9qrm<^#KW)2C`XJy==D86QqY3Nf%qF zz?z^@$Zr7Y_pB$`vagI5F?0;{3OU8LUaMJgw(JTV$2|Z;k3jSx2tNd&UHJqTt{r;< Im}p%88>*a^U;qFB diff --git a/sourceosctl/commands/__pycache__/fingerprint.cpython-312.pyc b/sourceosctl/commands/__pycache__/fingerprint.cpython-312.pyc deleted file mode 100644 index 0b91fcb5bb50c1700da1cf074d9121dcb03d0efc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2256 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 diff --git a/sourceosctl/commands/__pycache__/release.cpython-312.pyc b/sourceosctl/commands/__pycache__/release.cpython-312.pyc deleted file mode 100644 index b1c4dc34872b77922d218b7a6c0abb82c7a3d670..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2448 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 Date: Thu, 30 Apr 2026 13:32:10 +0000 Subject: [PATCH 4/4] chore: extend .gitignore; trigger CI rerun after workflow approval Agent-Logs-Url: https://github.com/SourceOS-Linux/sourceos-devtools/sessions/ff4ad036-69d4-4e8e-9f15-d3b5085618d1 Co-authored-by: mdheller <21163552+mdheller@users.noreply.github.com> --- .gitignore | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.gitignore b/.gitignore index 2dc685a..d9a6198 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,7 @@ __pycache__/ *.egg-info/ dist/ build/ + +# Temporary files +*.tmp +.DS_Store