Skip to content
Merged
21 changes: 21 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
name: ci

on:
push:
branches: [ main, sourceos/rev2-1-facade-seed, scaffold3 ]
pull_request:

jobs:
go:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: '1.22'
- name: Download modules
run: go mod tidy
- name: Test
run: go test ./...
- name: Vet
run: go vet ./...
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/bin/
/dist/
/.cache/
*.log
*.out
coverage.out
.DS_Store
14 changes: 14 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Contributing

This repository is a façade-only CLI in phase 1.

Please:
- keep changes small and reviewable
- prefer deterministic command semantics
- keep bootstrap logic delegated to `sourceos-bootstrap`
- preserve explicit docs for boundaries and non-goals

Do not:
- invent boot or enrollment semantics here
- add hidden side effects to wrapper commands
- auto-enable project MCP servers in untrusted workspaces
9 changes: 9 additions & 0 deletions SECURITY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Security Policy

This repository must not contain:
- enrollment tokens
- private signing keys
- token-door secrets
- hidden bootstrap business logic

Phase 1 rule: keep sensitive semantics in their owning repositories and expose only façade wrappers here.
7 changes: 7 additions & 0 deletions cmd/prophet/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package main

import "github.com/socioprophet/prophet-cli/internal/cmd"

func main() {
cmd.Execute()
}
22 changes: 22 additions & 0 deletions docs/AGENT_HYBRID_MODEL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Agent hybrid model

The repository follows a hybrid model with three surfaces:

1. deterministic CLI
2. agent assist
3. agent execute

## Deterministic CLI
The deterministic CLI is the canonical, documented surface.

## Agent assist
Read-mostly planning and explanation over deterministic tools.

## Agent execute
Approval-gated execution that still resolves to deterministic tools.

## Guardrails
- no bootstrap business logic in this repo
- no silent plugin installation
- no automatic project MCP loading in untrusted workspaces
- no competing command language that bypasses deterministic verbs
18 changes: 18 additions & 0 deletions docs/BOOTSTRAP_DELEGATION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Bootstrap delegation

`prophet-cli` is a façade. Bootstrap actions delegate to the installed `sourceos-bootstrap` engine.

## Delegated commands
- `prophet bootstrap doctor`
- `prophet bootstrap login`
- `prophet bootstrap build`
- `prophet bootstrap fetch`
- `prophet bootstrap write`
- `prophet bootstrap info`
- `prophet bootstrap validate <kind> <path>`
- `prophet bootstrap verify <kind> <path>`

## Rules
- do not implement bootstrap business logic here
- do not claim ownership of ReleaseSet, BootReleaseSet, ConfigSource, or TokenDoor semantics here
- wrapper commands should remain transparent about delegation
25 changes: 25 additions & 0 deletions docs/COMMANDS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Commands

## Deterministic façade commands

### Bootstrap
- `prophet bootstrap doctor`
- `prophet bootstrap login`
- `prophet bootstrap build`
- `prophet bootstrap fetch`
- `prophet bootstrap write`
- `prophet bootstrap info`
- `prophet bootstrap validate <kind> <path>`
- `prophet bootstrap verify <kind> <path>`

### Workflow
- `prophet a2a run`

## Hybrid overlay placeholders
- `prophet ask`
- `prophet plan`
- `prophet agent`
- `prophet mcp`

## Notes
These commands are scaffolds in phase 1. The deterministic verbs and delegation boundaries are more important than implementing broad business logic too early.
5 changes: 5 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module github.com/socioprophet/prophet-cli

go 1.22

require github.com/spf13/cobra v1.8.1
19 changes: 19 additions & 0 deletions internal/a2a/workflow_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package a2a

import "testing"

func TestDefaultWorkflowHasExpectedPhases(t *testing.T) {
wf := Default("demo/repo", "DEMO", false)

Check failure on line 6 in internal/a2a/workflow_test.go

View workflow job for this annotation

GitHub Actions / go

undefined: Default
if wf.Repo != "demo/repo" {
t.Fatalf("unexpected repo: %s", wf.Repo)
}
if len(wf.Phases) != 6 {
t.Fatalf("expected 6 phases, got %d", len(wf.Phases))
}
if wf.Phases[0].Name != "propose" {
t.Fatalf("unexpected first phase: %s", wf.Phases[0].Name)
}
if wf.Phases[len(wf.Phases)-1].Name != "done" {
t.Fatalf("unexpected last phase: %s", wf.Phases[len(wf.Phases)-1].Name)
}
}
28 changes: 28 additions & 0 deletions internal/cmd/bootstrap.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package cmd

import "github.com/spf13/cobra"

func newBootstrapCmd() *cobra.Command {
cmd := &cobra.Command{Use: "bootstrap", Short: "Bootstrap facade commands"}
cmd.AddCommand(
newBootstrapLeaf("doctor", "diagnose host prerequisites"),
newBootstrapLeaf("login", "prepare authenticated bootstrap session"),
newBootstrapLeaf("build", "submit or describe a bootstrap build request"),
newBootstrapLeaf("fetch", "fetch release artifacts"),
newBootstrapLeaf("write", "prepare install or recovery media"),
newBootstrapLeaf("info", "show bootstrap engine information"),
&cobra.Command{Use: "validate <kind> <path>", Short: "Validate a frozen object through the bootstrap engine", Args: cobra.ExactArgs(2), RunE: func(cmd *cobra.Command, args []string) error {
return emit(map[string]any{"command": "prophet bootstrap validate", "kind": args[0], "path": args[1], "delegated_to": "sourceos-bootstrap", "status": "scaffold"})
}},
&cobra.Command{Use: "verify <kind> <path>", Short: "Verify a frozen object or artifact through the bootstrap engine", Args: cobra.ExactArgs(2), RunE: func(cmd *cobra.Command, args []string) error {
return emit(map[string]any{"command": "prophet bootstrap verify", "kind": args[0], "path": args[1], "delegated_to": "sourceos-bootstrap", "status": "scaffold"})
}},
)
return cmd
}

func newBootstrapLeaf(name, summary string) *cobra.Command {
return &cobra.Command{Use: name, Short: summary, RunE: func(cmd *cobra.Command, args []string) error {
return emit(map[string]any{"command": "prophet bootstrap " + name, "summary": summary, "delegated_to": "sourceos-bootstrap", "status": "scaffold"})
}}
}
72 changes: 72 additions & 0 deletions internal/cmd/root.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package cmd

import (
"encoding/json"
"fmt"
"os"
"strings"

"github.com/spf13/cobra"
)

type GlobalFlags struct {
Profile string
Space string
Output string
Query string
Quiet bool
Debug bool
NoPager bool
}

var flags GlobalFlags

func Execute() {
if err := NewRootCommand().Execute(); err != nil {
os.Exit(1)
}
}

func NewRootCommand() *cobra.Command {
root := &cobra.Command{Use: "prophet", Short: "Prophet facade CLI", SilenceUsage: true}
root.PersistentFlags().StringVar(&flags.Profile, "profile", "", "named profile")
root.PersistentFlags().StringVar(&flags.Space, "space", "", "execution space")
root.PersistentFlags().StringVarP(&flags.Output, "output", "o", "text", "output format")
root.PersistentFlags().StringVar(&flags.Query, "query", "", "query expression")
root.PersistentFlags().BoolVarP(&flags.Quiet, "quiet", "q", false, "suppress non-essential output")
root.PersistentFlags().BoolVar(&flags.Debug, "debug", false, "enable debug output")
root.PersistentFlags().BoolVar(&flags.NoPager, "no-pager", false, "disable pager")
root.AddCommand(
newBootstrapCmd(),
newA2ACmd(),

Check failure on line 41 in internal/cmd/root.go

View workflow job for this annotation

GitHub Actions / go

undefined: newA2ACmd
newPlaceholderCmd("ask", "Agent assist: explain or inspect without mutating state"),
newPlaceholderCmd("plan", "Agent assist: generate a plan over deterministic tools"),
newPlaceholderCmd("agent", "Agent execute facade"),
newPlaceholderCmd("mcp", "MCP boundary facade"),
)
return root
}

func emit(v any) error {
switch strings.ToLower(flags.Output) {
case "none":
return nil
case "json", "yaml", "table", "tsv":
enc := json.NewEncoder(os.Stdout)
enc.SetIndent("", " ")
return enc.Encode(v)
default:
b, err := json.MarshalIndent(v, "", " ")
if err != nil {
return err
}
fmt.Println(string(b))
return nil
}
}

func newPlaceholderCmd(name, summary string) *cobra.Command {
return &cobra.Command{Use: name, Short: summary, RunE: func(cmd *cobra.Command, args []string) error {
return emit(map[string]any{"command": name, "summary": summary, "status": "scaffold"})
}}
}
25 changes: 25 additions & 0 deletions internal/cmd/root_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package cmd

import "testing"

func TestRootCommandHasExpectedTopLevelCommands(t *testing.T) {
root := NewRootCommand()
want := map[string]bool{
"bootstrap": false,
"a2a": false,
"ask": false,
"plan": false,
"agent": false,
"mcp": false,
}
for _, c := range root.Commands() {
if _, ok := want[c.Name()]; ok {
want[c.Name()] = true
}
}
for name, seen := range want {
if !seen {
t.Fatalf("missing top-level command: %s", name)
}
}
}
Loading