externalcli

package
v0.116.3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 9, 2026 License: MIT Imports: 8 Imported by: 0

Documentation

Overview

Package externalcli implements docs/multi-device-storage.md §5.5.1's continuity layer for path-hashed external-CLI transcript stores (claude, codex).

Forward (`--migrate-external-cli`): when v0 → v1 changes the agent working directory from <v0>/agents/<id> to <v1>/agents/<id>, the external CLIs would otherwise lose their prior chat history. We create a symlink (or, on Windows, a junction) at the v1-hash directory pointing at the v0-hash directory so each CLI's internal path-hash lookup still resolves to the same on-disk state.

Reverse (`--rollback-external-cli`): every forward operation is recorded into a manifest under the v1 dir. Rollback walks the manifest in reverse and undoes each entry. Importantly the v0-hash target is NEVER touched — symlink removal uses os.Remove (not os.RemoveAll) so the underlying transcript store stays intact for an operator who is rolling back to v0.

All forward operations are best-effort: any individual failure is logged as a warning and the migration continues. The v1 install continues to work; the affected agent simply starts with a fresh chat session.

Index

Constants

View Source
const ManifestFileName = "external_cli_migration.json"

ManifestFileName is the on-disk record of forward operations, written under the v1 dir. Its presence is the trigger that `kojo --rollback-external-cli` looks for.

Variables

This section is empty.

Functions

func Rollback

func Rollback(v1Dir string) ([]string, error)

Rollback walks the manifest in reverse and undoes each operation. Errors are returned as warnings — rollback is best-effort by design; an entry the operator already cleaned up by hand should not block the remaining entries.

func SaveManifest

func SaveManifest(v1Dir string, m *Manifest) error

SaveManifest writes the manifest atomically. An empty Ops slice still writes the file so a subsequent rollback knows the forward pass ran (vs "we forgot to record anything").

Types

type CLISpec

type CLISpec struct {
	Name         string
	ProjectsRoot string
	Hash         Hasher
}

CLISpec describes one path-hash CLI's transcript layout for the forward pass. ProjectsRoot is the directory under which each CLI keeps a per-path subdir (e.g. ~/.claude/projects/).

type Hasher

type Hasher func(absDir string) string

Hasher is the path-hash function used by claude / codex. The forward pass reuses internal/agent's claudeEncodePath; tests can substitute a stub.

type Manifest

type Manifest struct {
	Version int  `json:"version"`
	Ops     []Op `json:"ops"`
}

Manifest is the JSON file written under the v1 dir.

func ApplyForward

func ApplyForward(v1Dir string, ops []Op) (*Manifest, []string)

ApplyForward executes the planned operations and records each successful one into the returned Manifest. Failures are turned into warnings (returned as the second value); a partial manifest is still returned so rollback can undo the operations that did succeed.

func LoadManifest

func LoadManifest(v1Dir string) (*Manifest, error)

LoadManifest reads the manifest from v1Dir. Returns (nil, nil) when the file is absent — rollback then has nothing to undo.

type Op

type Op struct {
	Kind   OpKind `json:"kind"`
	Path   string `json:"path"`
	Target string `json:"target,omitempty"`
	// AgentID is informational — included to help an operator who is
	// auditing the manifest understand why each entry exists.
	AgentID string `json:"agent_id,omitempty"`
}

Op is one reversible forward action recorded for rollback.

func PlanSymlinks(specs []CLISpec, agents []PlanInput) []Op

PlanSymlinks builds the list of OpSymlink entries for the given CLIs and agents. Skips any (cli, agent) pair where:

  • the v0 hash directory does not exist (CLI never ran for that agent in v0; nothing to alias)
  • the v1 hash directory already exists (collision; do not clobber)

PlanSymlinks does NOT touch the filesystem; it only computes what Apply would do. Used by tests to assert plan correctness.

type OpKind

type OpKind string

OpKind enumerates reversible operations we know how to perform.

const (
	// OpSymlink: a path-hash directory symlink was created. Path =
	// the new (v1) symlink. Target = the v0 hash directory it points
	// at. Reverse with os.Remove(Path) (do not touch Target).
	OpSymlink OpKind = "symlink"
)

type PlanInput

type PlanInput struct {
	AgentID string
	V0Dir   string // <v0>/agents/<id>
	V1Dir   string // <v1>/agents/<id>
}

PlanInput is the per-agent data the planner needs to decide which operations to record. Caller (cmd/kojo/migrate.go) converts the migration's agent list into a slice of these.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL