Documentation
¶
Overview ¶
auth.go holds the shared login/logout helpers used by both the CLI (`jcode login` / `jcode logout`) and the web API (POST /api/cloud/login, /api/cloud/logout) so the two entry points can never drift apart.
capabilities.go builds the device-capabilities mirror (M12): the compose facets the local control plane can offer a remotely-started session — projects (from the session index), models (from config + the model registry, mirroring GET /api/models), the supported reasoning-effort levels, and the available slash commands (from GET /api/slash-commands). The connector reports it as the top-level `capabilities` field of every sessions upsert; the orchestrator stores it in devices.capabilities.
connector.go implements the jcloud relay connector: the local jcode poses as an always-on runner toward the cloud, over purely outbound connections (long poll + POSTs). It forwards downlink commands to the local web control plane (127.0.0.1) and pumps local WS events uplink. Every failure is logged as a warning — the connector must never affect the local web server.
Package cloud implements the jcloud (device relay) client side: device-code login (RFC 8628), device registration, and the on-disk device credentials. See cloud/docs/17-jcode-device-relay.md §3 for the interface contract.
crypto.go implements the M5 E2E encryption layer (定稿契约,勿改 wire 格式):
- CEK: one 32-byte AES-256-GCM key per account, stored base64 in ~/.jcode/cloud.json (`cek`, generation in `key_gen`). Lazily generated on first need (connector start / `jcode cloud` commands), atomically written back, cached in-process.
- Envelope: {"enc":"aes-256-gcm","key_gen":N,"nonce":"<b64 12B>","ct":"<b64>"}. Sealed fields: uplink events/ephemeral payload, sessions upsert meta, ack result; downlink command payload. Grey rule: a JSON object payload with a string `enc` field is treated as an envelope and decrypted; anything else is read as plaintext (M3/M4 compatibility).
- Pairing wrap (ECIES/P-256): ephemeral P-256 key pair → ECDH(ephemeral, requester pubkey) → HKDF-SHA256(shared, salt=nil, info="jcode-device-cek") → 32B wrap key → AES-256-GCM over {"cek":"<b64>","key_gen":N}.
- Recovery: the CEK doubles as 256 bits of BIP39 entropy → 24-word phrase.
events.go is the connector's uplink event path: it subscribes to the local web server's WS event stream (/api/ws), classifies each event as durable or ephemeral, assigns per-session seq numbers, and uploads — durable events in batches (persisted by the orchestrator for offline replay), ephemeral events fire-and-forget (SSE fanout only).
inbox.go lands non-image chat attachments (M12) at the fixed per-session location ~/.jcode/inbox/<session_id>/<filename>. The message text then references them ("[附件] name → path") so the agent can read them with its file tools. Attachment bytes travel inside the E2E envelope, so they are only ever plaintext on-device.
pairing_inbox.go handles pairing.request downlink commands (M11-W1): a request carrying an offer_id comes from a QR-code claim and is approved automatically (scan-to-pair); any other request is parked in an in-memory pending inbox until the user approves or denies it from the web UI (the CLI `jcode cloud approve|deny` path keeps working against the orchestrator directly). The inbox lives on the Connector because pairings only arrive through its poll loop; the Supervisor delegates the web endpoints to it.
pairings.go implements the device-side pairing approval endpoints (M5 task book, device-token authenticated, under /internal/v1/device):
GET /internal/v1/device/pairings?status=pending → {pairings:[...]}
GET /internal/v1/device/pairings/{id} → pairing (incl. pubkey)
POST /internal/v1/device/pairings/{id}/respond → {approve, wrap?}
The pairing requester (console browser / mobile) generated a P-256 key pair and sent its SPKI DER base64 as `pubkey`; on approval the device wraps the CEK for that key (see crypto.go WrapCEK) and returns it via respond.
relay.go implements the jcode ↔ orchestrator device-relay protocol (cloud/docs/17-jcode-device-relay.md §4): long-poll command delivery, command acks, heartbeats, session-index upserts and event uploads.
Payload fields are opaque to the server: since M5 they are E2E envelopes (see crypto.go) whenever the device's CEK cipher is active, plaintext during the pre-CEK grey period. Sealing/opening happens in the Connector (sealUplink / openDownlink), not in these transport helpers.
sessions.go mirrors the local session index (~/.jcode/sessions/session.json) to the cloud: one full upsert at startup, then an upsert whenever the index file changes (detected by mtime polling — deliberately no fsnotify). The upsert response carries each session's last_seq, which seeds the event pump's per-session seq allocator (续号).
supervisor.go owns the cloud relay connector lifecycle so the relay can be stopped/started without a process restart (the settings auto_connect toggle hot-applies), and exposes the live Status served at GET /api/cloud/status.
sync_store.go persists the M19 per-session cloud-sync opt-in at ~/.jcode/cloud-sessions.json (0600): a flat {session_id: true|false} map. A session WITHOUT an entry is "unset" and never syncs (default OFF) — the global cloud.sync_default config is consulted only once, when the web layer stamps a brand-new session, so flipping the default never retroactively changes existing sessions.
The web API and the relay connector hold separate SyncStore instances on the same file; both transparently re-read it when its mtime/size changes, so a toggle via the API takes effect in the connector without any explicit wiring (event filtering on the next event, metadata upsert on the next session-sync tick).
Index ¶
- Constants
- Variables
- func CEKFromPhrase(phrase string) ([]byte, error)
- func CEKToPhrase(cek []byte) (string, error)
- func CredentialsPath() (string, error)
- func DeleteCredentials() error
- func FingerprintHash(source string) string
- func Forget(ctx context.Context, warnf func(format string, args ...any)) error
- func GenerateCEK() ([]byte, error)
- func GenerateIdentityKeyPair() (publicKey, privateKey string, err error)
- func HardwareFingerprintSource() string
- func IsEnvelope(raw json.RawMessage) bool
- func Logout(ctx context.Context, warnf func(format string, args ...any)) error
- func ResetCEKCache()
- func ResolveFingerprintSource() (string, error)
- func SaveCredentials(creds *Credentials) error
- func ShouldConnect(autoConnect bool, creds *Credentials) bool
- func SyncStorePath() (string, error)
- func UnwrapCEK(wrap *CEKWrap, requesterPriv *ecdh.PrivateKey) (cek []byte, keyGen int, err error)
- func UpdateConfigCloud(url string, enabled bool) error
- func ValidateCloudURL(raw string) (string, error)
- type APIError
- type Backoff
- type CEKWrap
- type CapabilityModel
- type CapabilityProject
- type CapabilitySlashCommand
- type Client
- func (c *Client) AckCommand(ctx context.Context, token, id string, ack CommandAck) error
- func (c *Client) GetPairing(ctx context.Context, token, id string) (*Pairing, error)
- func (c *Client) Heartbeat(ctx context.Context, token string) error
- func (c *Client) ListPairings(ctx context.Context, token, status string) ([]Pairing, error)
- func (c *Client) PollCommands(ctx context.Context, token string, wait time.Duration) (cmds []DeviceCommand, ok bool, err error)
- func (c *Client) PollDeviceToken(ctx context.Context, deviceCode, fingerprint string) (*DeviceTokenResponse, error)
- func (c *Client) PollForToken(ctx context.Context, deviceCode, fingerprint string, ...) (*DeviceTokenResponse, error)
- func (c *Client) RegisterDevice(ctx context.Context, token string, req RegisterDeviceRequest) error
- func (c *Client) RequestDeviceCode(ctx context.Context, clientName string) (*DeviceCodeResponse, error)
- func (c *Client) RespondPairing(ctx context.Context, token, id string, approve bool, wrap *CEKWrap) error
- func (c *Client) RevokeDevice(ctx context.Context, token string) error
- func (c *Client) SendEphemeral(ctx context.Context, token, sid, kind string, payload json.RawMessage) error
- func (c *Client) UploadEvents(ctx context.Context, token, sid string, events []EventUpload) (*EventsUploadResponse, error)
- func (c *Client) UpsertSessions(ctx context.Context, token string, sessions []SessionUpsert, ...) (*SessionsUpsertResponse, error)
- type CommandAck
- type Connector
- func (c *Connector) ApprovePairing(ctx context.Context, id string) error
- func (c *Connector) DenyPairing(ctx context.Context, id string) error
- func (c *Connector) LastPaired() (PairedInfo, bool)
- func (c *Connector) PendingPairings() []PendingPairing
- func (c *Connector) Run(ctx context.Context)
- func (c *Connector) Status() (state string, lastErr string)
- type ConnectorConfig
- type Credentials
- type DeviceCapabilities
- type DeviceCodeResponse
- type DeviceCommand
- type DeviceTokenResponse
- type Envelope
- type EnvelopeCipher
- type EventUpload
- type EventsUploadResponse
- type PairedInfo
- type Pairing
- type PendingPairing
- type RegisterDeviceRequest
- type SessionSeqInfo
- type SessionUpsert
- type SessionsUpsertResponse
- type Status
- type Supervisor
- func (s *Supervisor) ApprovePairing(ctx context.Context, id string) error
- func (s *Supervisor) BuildConnector(creds *Credentials) *Connector
- func (s *Supervisor) DenyPairing(ctx context.Context, id string) error
- func (s *Supervisor) LastPaired() (PairedInfo, bool)
- func (s *Supervisor) PendingPairings() []PendingPairing
- func (s *Supervisor) SetAutoConnect(enabled bool) error
- func (s *Supervisor) Start(ctx context.Context)
- func (s *Supervisor) Status() Status
- func (s *Supervisor) SyncCredentials()
- type SyncStore
- func (s *SyncStore) Delete(sessionID string) error
- func (s *SyncStore) Enabled(sessionID string) bool
- func (s *SyncStore) Has(sessionID string) bool
- func (s *SyncStore) Path() string
- func (s *SyncStore) Set(sessionID string, enabled bool) error
- func (s *SyncStore) SetIfAbsent(sessionID string, enabled bool) (bool, error)
- func (s *SyncStore) Snapshot() map[string]bool
Constants ¶
const ( StateOffline = "offline" StateConnecting = "connecting" StateOnline = "online" StateError = "error" )
Connector states surfaced via Status (supervisor → GET /api/cloud/status).
const DefaultCloudURL = "https://cloud.j-code.net"
DefaultCloudURL is the public jcloud orchestrator address.
Variables ¶
var ( // ErrAuthorizationDenied is returned when the user denies the user_code on // the verification page. ErrAuthorizationDenied = errors.New("authorization denied by user") // ErrDeviceCodeExpired is returned when the device_code expires before the // user authorizes, or when the overall expiry deadline passes while polling. ErrDeviceCodeExpired = errors.New("device code expired") )
Sentinel errors for terminal device-token polling outcomes.
var ErrUnknownPairing = errors.New("no such pending pairing")
ErrUnknownPairing is returned by ApprovePairing/DenyPairing when the id is not in the connector's pending inbox (already handled, or never received).
Functions ¶
func CEKFromPhrase ¶
CEKFromPhrase decodes a 24-word BIP39 recovery phrase back into the CEK.
func CEKToPhrase ¶
CEKToPhrase encodes a 32-byte CEK as its 24-word BIP39 recovery phrase.
func CredentialsPath ¶
CredentialsPath returns the full path to the credentials file (~/.jcode/cloud.json).
func DeleteCredentials ¶
func DeleteCredentials() error
DeleteCredentials forgets the complete local device identity, including secrets in the system keyring. A missing file/key is not an error.
func FingerprintHash ¶
FingerprintHash maps a fingerprint source to the ONLY form that ever leaves the machine: sha256 hex with a domain separator. The raw hardware id is never sent to the orchestrator.
func Forget ¶
Forget signs out and removes the full local identity. This is intentionally separate from Logout because forgetting keys requires other clients to pair with the device again.
func GenerateCEK ¶
GenerateCEK returns a fresh random 32-byte CEK.
func GenerateIdentityKeyPair ¶
GenerateIdentityKeyPair creates a fresh X25519 device identity key pair and returns both keys base64-encoded (standard encoding, raw 32-byte keys).
func HardwareFingerprintSource ¶
func HardwareFingerprintSource() string
HardwareFingerprintSource returns the OS-level machine id ("" when unavailable) — exported for callers (login --status, the connector) that must NOT generate a fresh fallback random on the spot.
func IsEnvelope ¶
func IsEnvelope(raw json.RawMessage) bool
IsEnvelope applies the grey-scale detection rule: raw is a JSON object with a string `enc` field.
func Logout ¶
Logout signs the device out of jcloud while preserving its encryption identity. Only the revocable access token is cleared; the device key pair, CEK and fingerprint remain in the system keyring so the next login can resume without breaking paired browsers/mobile clients.
func ResetCEKCache ¶
func ResetCEKCache()
ResetCEKCache clears the in-process CEK cache. Called on logout and by tests that swap the credentials file.
func ResolveFingerprintSource ¶
ResolveFingerprintSource returns the stable fingerprint source for this machine: the persisted cloud.json value if present (stable by definition), else the OS machine id, else a freshly generated fallback ("fallback:<hostname>:<random16B>").
The fallback is stable only once persisted: the caller MUST write the returned source into Credentials.Fingerprint when it saves the credentials file (both login paths do). Saving a hardware-derived source too is deliberate: it pins the identity against a later ioreg/machine-id failure.
func SaveCredentials ¶
func SaveCredentials(creds *Credentials) error
SaveCredentials writes credentials atomically to ~/.jcode/cloud.json with owner-only permissions (file 0600, directory 0700), mirroring the pattern used by config.saveConfig.
func ShouldConnect ¶
func ShouldConnect(autoConnect bool, creds *Credentials) bool
ShouldConnect reports whether the relay connector should start: the device must be logged in (creds present with a token) and auto_connect must not be explicitly disabled. Kept pure so the startup gate is unit-testable.
func SyncStorePath ¶
SyncStorePath returns the default store path (~/.jcode/cloud-sessions.json).
func UnwrapCEK ¶
UnwrapCEK reverses WrapCEK with the requester's P-256 private key. The device side never calls this in production — it exists for the requester side and for round-trip tests.
func UpdateConfigCloud ¶
UpdateConfigCloud sets config.cloud while preserving the stored url (when the url argument is empty, i.e. logout) and the user's auto_connect/e2ee preferences. Login/logout must not require a fully configured provider set, so a LoadConfig failure falls back to a best-effort raw read of the file (unknown fields may be dropped in that case).
func ValidateCloudURL ¶
ValidateCloudURL normalizes a --cloud flag value and enforces the scheme policy from docs/17 §3.3: https everywhere, except that localhost / 127.0.0.1 / [::1] (any port) may use http for development. The returned URL has no trailing slash.
Types ¶
type APIError ¶
type APIError struct {
StatusCode int `json:"-"`
Code string `json:"code"`
Message string `json:"message"`
}
APIError is the orchestrator's error envelope {error:{code,message}}.
type Backoff ¶
type Backoff struct {
Min time.Duration
Max time.Duration
// contains filtered or unexported fields
}
Backoff is a thread-safe exponential backoff with a cap, used by every connector retry loop (register, poll, WS reconnect). It is injectable so tests can shrink the delays.
func NewBackoff ¶
NewBackoff returns a Backoff starting at min and capped at max.
type CEKWrap ¶
type CEKWrap struct {
EphemeralPubKey string `json:"ephemeral_pubkey"` // base64 SPKI DER, P-256
Nonce string `json:"nonce"` // base64, 12 bytes
CT string `json:"ct"` // base64 ciphertext
}
CEKWrap is the ECIES-wrapped CEK sent back to an approved pairing requester.
type CapabilityModel ¶
type CapabilityModel struct {
Provider string `json:"provider"`
ID string `json:"id"`
Label string `json:"label"`
}
CapabilityModel is one selectable model of a configured provider.
type CapabilityProject ¶
CapabilityProject is one known project directory.
type CapabilitySlashCommand ¶
type CapabilitySlashCommand struct {
Slash string `json:"slash"`
Description string `json:"description"`
Type string `json:"type"` // "skill" | "flow"
}
CapabilitySlashCommand mirrors one item of the web control plane's GET /api/slash-commands response (skill- and workflow-provided commands).
type Client ¶
Client talks to the jcloud orchestrator's device-auth and internal device endpoints.
func NewClient ¶
NewClient returns a Client for baseURL (already validated). A nil HTTPClient means a default one with a 30s timeout.
func (*Client) AckCommand ¶
AckCommand reports a command's execution result: POST /internal/v1/device/commands/{id}/ack.
func (*Client) GetPairing ¶
GetPairing fetches one pairing request (including the requester pubkey): GET /internal/v1/device/pairings/{id}.
func (*Client) Heartbeat ¶
Heartbeat keeps the device marked online: POST /internal/v1/device/heartbeat (empty body).
func (*Client) ListPairings ¶
ListPairings queries pairing requests, optionally filtered by status (empty = all): GET /internal/v1/device/pairings[?status=pending].
func (*Client) PollCommands ¶
func (c *Client) PollCommands(ctx context.Context, token string, wait time.Duration) (cmds []DeviceCommand, ok bool, err error)
PollCommands long-polls the downlink queue: GET /internal/v1/device/poll?wait=<wait>. ok is false on a 204 (no commands), in which case the caller should poll again immediately.
func (*Client) PollDeviceToken ¶
func (c *Client) PollDeviceToken(ctx context.Context, deviceCode, fingerprint string) (*DeviceTokenResponse, error)
PollDeviceToken makes a single token poll attempt: POST /auth/device/token. fingerprint is the sha256 machine-fingerprint hash (M16; "" for none) — the orchestrator uses it to dedup a re-login onto the existing devices row. Pending authorization surfaces as an *APIError with Code "authorization_pending" (or "slow_down"); terminal failures use "access_denied" / "expired_token".
func (*Client) PollForToken ¶
func (c *Client) PollForToken(ctx context.Context, deviceCode, fingerprint string, interval, expiresIn time.Duration) (*DeviceTokenResponse, error)
PollForToken polls POST /auth/device/token every interval until the user authorizes, the deadline (now+expiresIn) passes, or ctx is cancelled. Zero interval/expiresIn fall back to RFC 8628 defaults. fingerprint rides every poll (M16, see PollDeviceToken).
func (*Client) RegisterDevice ¶
RegisterDevice registers (or refreshes) this device with the orchestrator: POST /internal/v1/device/register (Bearer device token).
func (*Client) RequestDeviceCode ¶
func (c *Client) RequestDeviceCode(ctx context.Context, clientName string) (*DeviceCodeResponse, error)
RequestDeviceCode starts the device authorization flow (RFC 8628 §3.1): POST {url}/auth/device/code.
func (*Client) RespondPairing ¶
func (c *Client) RespondPairing(ctx context.Context, token, id string, approve bool, wrap *CEKWrap) error
RespondPairing approves or denies a pairing request: POST /internal/v1/device/pairings/{id}/respond. wrap carries the ECIES- wrapped CEK and is required on approval, nil on denial.
func (*Client) RevokeDevice ¶
RevokeDevice asks the orchestrator to revoke this device's token: POST /internal/v1/device/revoke. A 404 (endpoint not deployed yet on the server side) is treated as success — local cleanup proceeds regardless.
func (*Client) SendEphemeral ¶
func (c *Client) SendEphemeral(ctx context.Context, token, sid, kind string, payload json.RawMessage) error
SendEphemeral forwards one ephemeral (token-level) event: POST /internal/v1/device/sessions/{sid}/ephemeral. Fire-and-forget — failures are dropped by the caller, never retried.
func (*Client) UploadEvents ¶
func (c *Client) UploadEvents(ctx context.Context, token, sid string, events []EventUpload) (*EventsUploadResponse, error)
UploadEvents appends a batch of durable events: POST /internal/v1/device/sessions/{sid}/events. Idempotent — the server skips (sid, seq) pairs it already holds and reports them as conflicted.
func (*Client) UpsertSessions ¶
func (c *Client) UpsertSessions(ctx context.Context, token string, sessions []SessionUpsert, capabilities json.RawMessage) (*SessionsUpsertResponse, error)
UpsertSessions mirrors the local session index to the cloud: POST /internal/v1/device/sessions. The response carries each session's last_seq for event-pump seq resumption. capabilities is the M12 device-capabilities mirror (DeviceCapabilities JSON, sealed when the CEK cipher is active), stored by the orchestrator in devices.capabilities; nil omits the field.
type CommandAck ¶
type CommandAck struct {
Status string `json:"status"` // "ok" | "error"
Result any `json:"result,omitempty"`
}
CommandAck is the uplink execution receipt for one command.
type Connector ¶
type Connector struct {
// contains filtered or unexported fields
}
Connector is the cloud relay client. Construct with NewConnector, then Run.
func NewConnector ¶
func NewConnector(cfg ConnectorConfig) *Connector
NewConnector builds a Connector from cfg.
func (*Connector) ApprovePairing ¶
ApprovePairing wraps the CEK for a pending requester and responds to the orchestrator (web endpoint path; manual approval).
func (*Connector) DenyPairing ¶
DenyPairing responds denial to the orchestrator for a pending request.
func (*Connector) LastPaired ¶
func (c *Connector) LastPaired() (PairedInfo, bool)
LastPaired reports the most recent approval, ok=false when none happened since the connector started.
func (*Connector) PendingPairings ¶
func (c *Connector) PendingPairings() []PendingPairing
PendingPairings snapshots the pending inbox (oldest first).
type ConnectorConfig ¶
type ConnectorConfig struct {
CloudURL string // orchestrator base URL (config.cloud.url, else credentials)
Credentials *Credentials // device identity from ~/.jcode/cloud.json
LocalBase string // local web control plane, e.g. http://127.0.0.1:8080
LocalToken string // web auth token (empty when the server doesn't require auth)
Version string // jcode version reported at register
// Optional knobs (tests inject these; zero values use defaults).
HTTPClient *http.Client
Hostname string
HeartbeatInterval time.Duration
PollWait time.Duration
IndexPollInterval time.Duration
BatchWindow time.Duration
BatchMax int
Backoff *Backoff
// IndexPathFn / ListSessionsFn default to the real session index; tests
// override them to point at a temp dir.
IndexPathFn func() (string, error)
ListSessionsFn func() (map[string][]session.SessionMeta, error)
// InboxDir is the root under which chat attachments land
// (<InboxDir>/<session_id>/<filename>). Empty → ~/.jcode/inbox.
InboxDir string
// ModelCapabilitiesFn overrides the model/effort facet of the capabilities
// mirror (default: config + model registry); tests inject a fixed list.
ModelCapabilitiesFn func() ([]CapabilityModel, []string, error)
// SlashCommandsFn overrides the slash-commands facet of the capabilities
// mirror (default: GET /api/slash-commands on the local control plane);
// tests inject a fixed list.
SlashCommandsFn func() ([]CapabilitySlashCommand, error)
// Cipher, when non-nil, seals uplink payloads (events/ephemeral payload,
// sessions meta, ack result) and opens downlink command payloads. Nil
// means plaintext uplink (pre-CEK grey period; see crypto.go). Run lazily
// initializes it from ~/.jcode/cloud.json when left nil; tests inject one
// explicitly (or set CipherDisabled to force the plaintext path).
Cipher *EnvelopeCipher
CipherDisabled bool
// SyncStore is the M19 per-session cloud-sync gate (~/.jcode/cloud-sessions.json):
// only sessions with an explicit opt-in are upserted / have their events
// uploaded. Nil lazily loads the default path on first use; a load failure
// fails CLOSED (nothing syncs). Tests inject a temp-file store.
SyncStore *SyncStore
}
ConnectorConfig carries everything the connector needs. The zero durations fall back to production defaults; tests inject short ones.
type Credentials ¶
type Credentials struct {
CloudURL string `json:"cloud_url"`
DeviceID string `json:"device_id"`
DeviceToken string `json:"device_token"`
DeviceName string `json:"device_name"`
// PublicKey / PrivateKey are the X25519 device identity key pair, base64
// (standard encoding) encoded raw keys. The public key is registered with
// the orchestrator for later E2E key exchange (CEK wrapping).
PublicKey string `json:"public_key"`
PrivateKey string `json:"private_key"`
// KeyGen is the current CEK generation this device holds.
KeyGen int `json:"key_gen"`
// CEK is the account-level AES-256-GCM content encryption key, base64.
// Empty means "not initialized yet" (pre-M5 credentials file); it is
// lazily generated on first need (see crypto.go EnsureCEK).
CEK string `json:"cek,omitempty"`
// Fingerprint is the stable machine fingerprint SOURCE (M16): the OS
// machine id, or a "fallback:<hostname>:<random>" string generated once.
// It never leaves the machine — only its sha256 (FingerprintHash) is sent
// to the orchestrator for login dedup. Empty on pre-M16 files; resolved
// on the fly then (see ResolveFingerprintSource).
Fingerprint string `json:"fingerprint,omitempty"`
}
Credentials is the device identity. Non-sensitive metadata is persisted at ~/.jcode/cloud.json; DeviceToken, PrivateKey and CEK live in the operating system keyring. A missing metadata file means "not configured".
func LoadCredentials ¶
func LoadCredentials() (*Credentials, error)
LoadCredentials reads ~/.jcode/cloud.json. A missing file is not an error: it returns (nil, nil) to mean "not logged in".
type DeviceCapabilities ¶
type DeviceCapabilities struct {
Projects []CapabilityProject `json:"projects"`
Models []CapabilityModel `json:"models"`
CurrentModel *CapabilityModel `json:"current_model,omitempty"`
Efforts []string `json:"efforts"`
SlashCommands []CapabilitySlashCommand `json:"slash_commands"`
}
DeviceCapabilities is the capabilities payload stored by the orchestrator and consumed by the console/mobile compose UI.
type DeviceCodeResponse ¶
type DeviceCodeResponse struct {
DeviceCode string `json:"device_code"`
UserCode string `json:"user_code"`
VerificationURI string `json:"verification_uri"`
ExpiresIn int `json:"expires_in"` // seconds
Interval int `json:"interval"` // seconds
}
DeviceCodeResponse is the answer of POST /auth/device/code.
type DeviceCommand ¶
type DeviceCommand struct {
ID string `json:"id"`
Kind string `json:"kind"` // chat.send | chat.stop | approval.respond | …
SessionID string `json:"session_id,omitempty"`
Payload json.RawMessage `json:"payload,omitempty"`
}
DeviceCommand is one downlink command delivered by the poll endpoint.
type DeviceTokenResponse ¶
type DeviceTokenResponse struct {
AccessToken string `json:"access_token"`
TokenType string `json:"token_type"`
DeviceID string `json:"device_id"`
// Deduped is true when the orchestrator recognized the machine fingerprint
// and reused the existing devices row (M16) instead of minting a new one.
Deduped bool `json:"deduped"`
}
DeviceTokenResponse is the success answer of POST /auth/device/token.
type Envelope ¶
type Envelope struct {
Enc string `json:"enc"`
KeyGen int `json:"key_gen"`
Nonce string `json:"nonce"` // base64, 12 bytes
CT string `json:"ct"` // base64 ciphertext
}
Envelope is the sealed-payload wire format.
type EnvelopeCipher ¶
type EnvelopeCipher struct {
// contains filtered or unexported fields
}
EnvelopeCipher seals/opens payloads with one CEK generation.
func EnsureCEK ¶
func EnsureCEK() (*EnvelopeCipher, error)
EnsureCEK returns the account CEK cipher, lazily generating and persisting the CEK on first use (atomic write-back to ~/.jcode/cloud.json). The cipher is cached in-process afterwards. Requires the device to be logged in.
func NewEnvelopeCipher ¶
func NewEnvelopeCipher(cek []byte, keyGen int) (*EnvelopeCipher, error)
NewEnvelopeCipher builds a cipher from a raw 32-byte CEK and its generation.
func RecoverCEK ¶
func RecoverCEK(phrase string) (*EnvelopeCipher, error)
RecoverCEK rebuilds the CEK from a 24-word recovery phrase and persists it (keeping the stored key_gen, default 1), overwriting any existing CEK. The caller is responsible for warning and confirmation before calling.
func RotateCEK ¶
func RotateCEK() (*EnvelopeCipher, error)
RotateCEK generates a new CEK at generation key_gen+1, persists it, and updates the in-process cache. Already-paired clients keep the old CEK and must re-pair to read new content.
func (*EnvelopeCipher) CEK ¶
func (e *EnvelopeCipher) CEK() []byte
CEK returns a copy of the raw CEK bytes.
func (*EnvelopeCipher) KeyGen ¶
func (e *EnvelopeCipher) KeyGen() int
KeyGen reports the CEK generation this cipher seals with.
func (*EnvelopeCipher) Open ¶
func (e *EnvelopeCipher) Open(raw json.RawMessage) ([]byte, error)
Open decrypts an envelope payload. The caller must have applied IsEnvelope first (or use OpenMaybe); passing plaintext here is an error.
func (*EnvelopeCipher) OpenMaybe ¶
func (e *EnvelopeCipher) OpenMaybe(raw json.RawMessage) ([]byte, bool, error)
OpenMaybe decrypts raw when it is an envelope, otherwise returns it unchanged (plaintext grey-scale passthrough). The boolean reports whether decryption happened.
func (*EnvelopeCipher) Seal ¶
func (e *EnvelopeCipher) Seal(plaintext []byte) (json.RawMessage, error)
Seal encrypts plaintext into the envelope wire format (as JSON).
type EventUpload ¶
type EventUpload struct {
Seq int64 `json:"seq"`
Kind string `json:"kind"`
Payload json.RawMessage `json:"payload"`
}
EventUpload is one durable event in a batch upload.
type EventsUploadResponse ¶
type EventsUploadResponse struct {
Accepted []int64 `json:"accepted"`
Conflicted []int64 `json:"conflicted"`
MaxSeq int64 `json:"max_seq"`
}
EventsUploadResponse is the answer of POST /internal/v1/device/sessions/{sid}/events. Conflicted lists seqs the server already had (skipped); MaxSeq is the server's high-water mark for the session and is used to resync the local allocator.
type PairedInfo ¶
type PairedInfo struct {
PairingID string `json:"pairing_id"`
Label string `json:"label"`
Auto bool `json:"auto"`
PairedAt time.Time `json:"paired_at"`
}
PairedInfo records the most recent approval so the web UI can notify ("device X paired via QR code"). Auto is true for offer-based (QR scan) approvals, false for manual approvals.
type Pairing ¶
type Pairing struct {
ID string `json:"id"`
Label string `json:"label"`
PubKey string `json:"pubkey"` // requester P-256 public key, base64 SPKI DER
Status string `json:"status"` // "pending" | "approved" | "denied"
CreatedAt string `json:"created_at"`
}
Pairing is one pairing request as seen by the device.
type PendingPairing ¶
type PendingPairing struct {
PairingID string `json:"pairing_id"`
Label string `json:"label"`
ReceivedAt time.Time `json:"received_at"`
PubKey string `json:"-"`
}
PendingPairing is one pairing.request parked for user approval. PubKey is kept in memory only (needed to wrap the CEK on approve) and never serialized.
type RegisterDeviceRequest ¶
type RegisterDeviceRequest struct {
Name string `json:"name"`
Hostname string `json:"hostname"`
JcodeVersion string `json:"jcode_version"`
PubKey string `json:"pubkey"` // X25519 public key, base64
// Platform is how this jcode instance was launched ("desktop" | "cli");
// see detectPlatform in connector.go.
Platform string `json:"platform,omitempty"`
// E2EE reports the connector's ACTUAL encryption state (M13): true only
// when the CEK cipher is active and cloud.e2ee did not disable it. The
// orchestrator gates plaintext downlink on it (docs/17 §6.7).
E2EE bool `json:"e2ee,omitempty"`
// Fingerprint is the sha256 of the machine fingerprint (M16): it backfills
// rows minted without one so a later login dedups onto this device.
Fingerprint string `json:"fingerprint,omitempty"`
}
RegisterDeviceRequest is the body of POST /internal/v1/device/register.
type SessionSeqInfo ¶
SessionSeqInfo is the server's per-session high-water mark, returned by the sessions upsert so the event pump can resume seq numbering after a restart.
type SessionUpsert ¶
type SessionUpsert struct {
SessionID string `json:"session_id"`
Status string `json:"status"` // "running" | "idle"
Meta json.RawMessage `json:"meta"` // SessionMeta JSON, as-is
}
SessionUpsert mirrors one local session's index entry to the cloud.
type SessionsUpsertResponse ¶
type SessionsUpsertResponse struct {
Sessions []SessionSeqInfo `json:"sessions"`
}
SessionsUpsertResponse is the answer of POST /internal/v1/device/sessions.
type Status ¶
type Status struct {
LoggedIn bool `json:"logged_in"`
AutoConnect bool `json:"auto_connect"`
State string `json:"state"` // "offline" | "connecting" | "online" | "error"
DeviceName string `json:"device_name"`
CloudURL string `json:"cloud_url"`
Error string `json:"error,omitempty"`
}
Status is the JSON snapshot of the cloud relay served by the web API.
type Supervisor ¶
type Supervisor struct {
// Version is the jcode version reported at device register. It lives in
// the command package (import cycle), so callers set it before Start.
Version string
// contains filtered or unexported fields
}
Supervisor owns the relay Connector lifecycle: Start launches the connector when the device is logged in with cloud.auto_connect enabled, SetAutoConnect persists and hot-applies the toggle, and Status reports the live snapshot. Like the connector itself it is strictly best-effort — failures are logged warnings, never errors that could affect the web server. All methods are safe for concurrent use.
func NewSupervisor ¶
func NewSupervisor(cfg *config.Config, port int, webToken string) *Supervisor
NewSupervisor returns a Supervisor bound to cfg (mutated live by SetAutoConnect), the local web control plane port and its auth token.
func (*Supervisor) ApprovePairing ¶
func (s *Supervisor) ApprovePairing(ctx context.Context, id string) error
ApprovePairing approves a pending pairing through the live connector.
func (*Supervisor) BuildConnector ¶
func (s *Supervisor) BuildConnector(creds *Credentials) *Connector
BuildConnector is the pure start decision + construction behind Start: nil when the connector should not run (not logged in, or auto_connect explicitly disabled). Exported so the command package reuses one code path.
func (*Supervisor) DenyPairing ¶
func (s *Supervisor) DenyPairing(ctx context.Context, id string) error
DenyPairing denies a pending pairing through the live connector.
func (*Supervisor) LastPaired ¶
func (s *Supervisor) LastPaired() (PairedInfo, bool)
LastPaired reports the live connector's most recent pairing approval.
func (*Supervisor) PendingPairings ¶
func (s *Supervisor) PendingPairings() []PendingPairing
PendingPairings returns the live connector's pending pairing inbox, empty when the connector is not running.
func (*Supervisor) SetAutoConnect ¶
func (s *Supervisor) SetAutoConnect(enabled bool) error
SetAutoConnect persists cloud.auto_connect (preserving the stored Enabled/URL/E2EE fields, same read/modify/write pattern as `jcode login`) and hot-applies it: enabling starts the connector when credentials are valid, disabling stops it. On a SaveConfig failure the in-memory config is rolled back and the error returned.
func (*Supervisor) Start ¶
func (s *Supervisor) Start(ctx context.Context)
Start applies the current config/credentials: when the device is logged in and auto_connect is enabled the connector is launched under a child of ctx (web server shutdown tears it down); otherwise the supervisor stays idle.
func (*Supervisor) Status ¶
func (s *Supervisor) Status() Status
Status snapshots the relay state. Credentials are re-read from disk so a login/logout from another process is reflected without a restart.
func (*Supervisor) SyncCredentials ¶
func (s *Supervisor) SyncCredentials()
SyncCredentials re-reads the on-disk credentials after a login/logout (web API or another process) and reconciles the connector: logged out (or auto_connect off) stops it; fresh or replaced credentials (re)start it. Like everything cloud-side it is best-effort — a credentials read failure is logged and leaves the current state untouched.
type SyncStore ¶
type SyncStore struct {
// contains filtered or unexported fields
}
SyncStore is the per-session cloud-sync state backed by one JSON file. All methods are safe for concurrent use.
func LoadSyncStore ¶
LoadSyncStore opens the store at path. A missing file is not an error — it means "no session opted in yet". A parse/IO error IS returned: callers decide whether to fail closed (connector: nothing syncs) or surface it (web API: 500).
func (*SyncStore) Delete ¶
Delete forgets the local per-session preference after the session itself is deleted. The connector observes the file change and sends a replacement snapshot that removes the corresponding cloud mirror.
func (*SyncStore) Enabled ¶
Enabled reports whether the session has an explicit sync opt-in. Unset sessions report false (default OFF).
func (*SyncStore) Path ¶
Path returns the store's backing file path (the connector's session-sync loop watches it for mtime changes).
func (*SyncStore) Set ¶
Set records the session's sync state and persists the store atomically (0600), overwriting any existing entry.
func (*SyncStore) SetIfAbsent ¶
SetIfAbsent records the session's sync state only when no explicit entry exists yet (session-creation stamping must never clobber a user toggle). It reports whether an entry was written.