@cotal-ai/connector-opencode 0.11.6 → 0.12.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.
- package/dist/index.js +4 -4
- package/dist/plugin.bundle.js +181 -136
- package/dist/tools.d.ts +9 -3
- package/dist/tools.d.ts.map +1 -1
- package/package.json +5 -8
package/dist/plugin.bundle.js
CHANGED
|
@@ -2995,9 +2995,9 @@ var require_nkeys = __commonJS({
|
|
|
2995
2995
|
exports.createCluster = createCluster;
|
|
2996
2996
|
exports.createServer = createServer2;
|
|
2997
2997
|
exports.createCurve = createCurve;
|
|
2998
|
-
exports.fromPublic =
|
|
2998
|
+
exports.fromPublic = fromPublic3;
|
|
2999
2999
|
exports.fromCurveSeed = fromCurveSeed;
|
|
3000
|
-
exports.fromSeed =
|
|
3000
|
+
exports.fromSeed = fromSeed4;
|
|
3001
3001
|
var kp_1 = require_kp();
|
|
3002
3002
|
var public_1 = require_public();
|
|
3003
3003
|
var codec_1 = require_codec();
|
|
@@ -3027,7 +3027,7 @@ var require_nkeys = __commonJS({
|
|
|
3027
3027
|
function createCurve() {
|
|
3028
3028
|
return createPair(Prefix2.Curve);
|
|
3029
3029
|
}
|
|
3030
|
-
function
|
|
3030
|
+
function fromPublic3(src) {
|
|
3031
3031
|
const ba = new TextEncoder().encode(src);
|
|
3032
3032
|
const raw = codec_1.Codec._decode(ba);
|
|
3033
3033
|
const prefix = Prefixes.parsePrefix(raw[0]);
|
|
@@ -3046,7 +3046,7 @@ var require_nkeys = __commonJS({
|
|
|
3046
3046
|
}
|
|
3047
3047
|
return new curve_1.CurveKP(sd.buf);
|
|
3048
3048
|
}
|
|
3049
|
-
function
|
|
3049
|
+
function fromSeed4(src) {
|
|
3050
3050
|
const sd = codec_1.Codec.decodeSeed(src);
|
|
3051
3051
|
if (sd.prefix === Prefix2.Curve) {
|
|
3052
3052
|
return fromCurveSeed(src);
|
|
@@ -3139,12 +3139,12 @@ var require_util = __commonJS({
|
|
|
3139
3139
|
"use strict";
|
|
3140
3140
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3141
3141
|
exports.encode = encode3;
|
|
3142
|
-
exports.decode =
|
|
3142
|
+
exports.decode = decode5;
|
|
3143
3143
|
exports.dump = dump;
|
|
3144
3144
|
function encode3(bytes) {
|
|
3145
3145
|
return btoa(String.fromCharCode(...bytes));
|
|
3146
3146
|
}
|
|
3147
|
-
function
|
|
3147
|
+
function decode5(b64str) {
|
|
3148
3148
|
const bin = atob(b64str);
|
|
3149
3149
|
const bytes = new Uint8Array(bin.length);
|
|
3150
3150
|
for (let i = 0; i < bin.length; i++) {
|
|
@@ -3372,7 +3372,7 @@ var require_encoders = __commonJS({
|
|
|
3372
3372
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3373
3373
|
exports.TD = exports.TE = exports.Empty = void 0;
|
|
3374
3374
|
exports.encode = encode3;
|
|
3375
|
-
exports.decode =
|
|
3375
|
+
exports.decode = decode5;
|
|
3376
3376
|
exports.Empty = new Uint8Array(0);
|
|
3377
3377
|
exports.TE = new TextEncoder();
|
|
3378
3378
|
exports.TD = new TextDecoder();
|
|
@@ -3402,7 +3402,7 @@ var require_encoders = __commonJS({
|
|
|
3402
3402
|
}
|
|
3403
3403
|
return concat(...bufs);
|
|
3404
3404
|
}
|
|
3405
|
-
function
|
|
3405
|
+
function decode5(a) {
|
|
3406
3406
|
if (!a || a.length === 0) {
|
|
3407
3407
|
return "";
|
|
3408
3408
|
}
|
|
@@ -3608,7 +3608,7 @@ var require_util2 = __commonJS({
|
|
|
3608
3608
|
exports.collect = collect;
|
|
3609
3609
|
exports.jitter = jitter;
|
|
3610
3610
|
exports.backoff = backoff;
|
|
3611
|
-
exports.nanos =
|
|
3611
|
+
exports.nanos = nanos5;
|
|
3612
3612
|
exports.millis = millis;
|
|
3613
3613
|
exports.randomToken = randomToken;
|
|
3614
3614
|
var encoders_1 = require_encoders();
|
|
@@ -3793,7 +3793,7 @@ var require_util2 = __commonJS({
|
|
|
3793
3793
|
}
|
|
3794
3794
|
};
|
|
3795
3795
|
}
|
|
3796
|
-
function
|
|
3796
|
+
function nanos5(millis2) {
|
|
3797
3797
|
return millis2 * 1e6;
|
|
3798
3798
|
}
|
|
3799
3799
|
function millis(ns) {
|
|
@@ -24008,25 +24008,90 @@ var Algorithms;
|
|
|
24008
24008
|
|
|
24009
24009
|
// ../../packages/core/dist/provision.js
|
|
24010
24010
|
var import_nkeys2 = __toESM(require_mod(), 1);
|
|
24011
|
-
var FIVE_MINUTES = 5 * 60;
|
|
24012
|
-
var STANDING_RENEWABLE_TTL_SEC = 24 * 60 * 60;
|
|
24013
|
-
var ROTATION_RENEWED_TTL_SEC = 30 * 24 * 60 * 60;
|
|
24014
|
-
var BASE_LIMITS = {
|
|
24015
|
-
subs: -1,
|
|
24016
|
-
conn: -1,
|
|
24017
|
-
leaf: -1,
|
|
24018
|
-
imports: -1,
|
|
24019
|
-
exports: -1,
|
|
24020
|
-
data: -1,
|
|
24021
|
-
payload: -1,
|
|
24022
|
-
wildcards: true
|
|
24023
|
-
};
|
|
24024
|
-
var DATA_LIMITS = { ...BASE_LIMITS, mem_storage: -1, disk_storage: -1 };
|
|
24025
|
-
var SYS_LIMITS = { ...BASE_LIMITS, mem_storage: 0, disk_storage: 0 };
|
|
24026
24011
|
|
|
24027
|
-
// ../../packages/core/dist/
|
|
24012
|
+
// ../../packages/core/dist/backup.js
|
|
24013
|
+
var import_jetstream3 = __toESM(require_mod4(), 1);
|
|
24014
|
+
var import_transport_node3 = __toESM(require_transport_node(), 1);
|
|
24015
|
+
|
|
24016
|
+
// ../../packages/core/dist/backup-config.js
|
|
24028
24017
|
var import_jetstream = __toESM(require_mod4(), 1);
|
|
24029
24018
|
var import_transport_node = __toESM(require_transport_node(), 1);
|
|
24019
|
+
var DEFAULT_DUPLICATE_WINDOW = (0, import_transport_node.nanos)(2 * 60 * 1e3);
|
|
24020
|
+
var BACKUP_MAX_MSGS_PER_SUBJECT = 1e3;
|
|
24021
|
+
var BACKUP_PLANE3_DEDUP_WINDOW_MS = 2 * 60 * 60 * 1e3;
|
|
24022
|
+
var PLANE3_DUPLICATE_WINDOW = (0, import_transport_node.nanos)(BACKUP_PLANE3_DEDUP_WINDOW_MS);
|
|
24023
|
+
function baseConfig(name, subjects) {
|
|
24024
|
+
return {
|
|
24025
|
+
name,
|
|
24026
|
+
subjects,
|
|
24027
|
+
retention: import_jetstream.RetentionPolicy.Limits,
|
|
24028
|
+
storage: import_jetstream.StorageType.File,
|
|
24029
|
+
max_consumers: -1,
|
|
24030
|
+
max_msgs: -1,
|
|
24031
|
+
max_bytes: -1,
|
|
24032
|
+
max_age: 0,
|
|
24033
|
+
max_msgs_per_subject: -1,
|
|
24034
|
+
max_msg_size: -1,
|
|
24035
|
+
discard: import_jetstream.DiscardPolicy.Old,
|
|
24036
|
+
num_replicas: 1,
|
|
24037
|
+
no_ack: false,
|
|
24038
|
+
duplicate_window: DEFAULT_DUPLICATE_WINDOW,
|
|
24039
|
+
compression: import_jetstream.StoreCompression.None,
|
|
24040
|
+
allow_direct: false,
|
|
24041
|
+
mirror_direct: false,
|
|
24042
|
+
discard_new_per_subject: false,
|
|
24043
|
+
allow_rollup_hdrs: false,
|
|
24044
|
+
deny_delete: false,
|
|
24045
|
+
deny_purge: false,
|
|
24046
|
+
sealed: false,
|
|
24047
|
+
consumer_limits: {}
|
|
24048
|
+
};
|
|
24049
|
+
}
|
|
24050
|
+
function canonicalBackupStreamConfig(space, stream) {
|
|
24051
|
+
const p = spacePrefix(space);
|
|
24052
|
+
if (stream === chatStream(space)) {
|
|
24053
|
+
return {
|
|
24054
|
+
...baseConfig(stream, [`${p}.chat.>`]),
|
|
24055
|
+
max_msgs_per_subject: BACKUP_MAX_MSGS_PER_SUBJECT,
|
|
24056
|
+
allow_direct: true
|
|
24057
|
+
};
|
|
24058
|
+
}
|
|
24059
|
+
if (stream === dmStream(space))
|
|
24060
|
+
return baseConfig(stream, [`${p}.inst.>`]);
|
|
24061
|
+
if (stream === taskStream(space)) {
|
|
24062
|
+
return { ...baseConfig(stream, [`${p}.svc.>`]), retention: import_jetstream.RetentionPolicy.Workqueue };
|
|
24063
|
+
}
|
|
24064
|
+
if (stream === inboxStream(space)) {
|
|
24065
|
+
return {
|
|
24066
|
+
...baseConfig(stream, [`${p}.dinbox.>`]),
|
|
24067
|
+
max_msgs_per_subject: BACKUP_MAX_MSGS_PER_SUBJECT,
|
|
24068
|
+
duplicate_window: PLANE3_DUPLICATE_WINDOW
|
|
24069
|
+
};
|
|
24070
|
+
}
|
|
24071
|
+
if (stream === dlvStream(space)) {
|
|
24072
|
+
return {
|
|
24073
|
+
...baseConfig(stream, [`${p}.dlv.>`]),
|
|
24074
|
+
max_msgs_per_subject: BACKUP_MAX_MSGS_PER_SUBJECT,
|
|
24075
|
+
duplicate_window: PLANE3_DUPLICATE_WINDOW
|
|
24076
|
+
};
|
|
24077
|
+
}
|
|
24078
|
+
for (const bucket of [channelBucket(space), aclBucket(space), membersBucket(space)]) {
|
|
24079
|
+
if (stream === `KV_${bucket}`) {
|
|
24080
|
+
return {
|
|
24081
|
+
...baseConfig(stream, [`$KV.${bucket}.>`]),
|
|
24082
|
+
max_msgs_per_subject: 1,
|
|
24083
|
+
discard: import_jetstream.DiscardPolicy.New,
|
|
24084
|
+
allow_direct: true,
|
|
24085
|
+
allow_rollup_hdrs: true
|
|
24086
|
+
};
|
|
24087
|
+
}
|
|
24088
|
+
}
|
|
24089
|
+
throw new Error(`stream ${JSON.stringify(stream)} is not a backed-up stream for space ${JSON.stringify(space)}`);
|
|
24090
|
+
}
|
|
24091
|
+
|
|
24092
|
+
// ../../packages/core/dist/streams.js
|
|
24093
|
+
var import_jetstream2 = __toESM(require_mod4(), 1);
|
|
24094
|
+
var import_transport_node2 = __toESM(require_transport_node(), 1);
|
|
24030
24095
|
var import_kv2 = __toESM(require_mod6(), 1);
|
|
24031
24096
|
|
|
24032
24097
|
// ../../packages/core/dist/acls.js
|
|
@@ -24077,69 +24142,25 @@ async function commitAcl(kv, owner, allowSubscribe) {
|
|
|
24077
24142
|
}
|
|
24078
24143
|
|
|
24079
24144
|
// ../../packages/core/dist/streams.js
|
|
24080
|
-
var MAX_MSGS_PER_SUBJECT =
|
|
24081
|
-
var PLANE3_DEDUP_WINDOW_MS = 2 * 60 * 60 * 1e3;
|
|
24145
|
+
var MAX_MSGS_PER_SUBJECT = BACKUP_MAX_MSGS_PER_SUBJECT;
|
|
24082
24146
|
var DINBOX_MAX_ACK_PENDING = 1e3;
|
|
24083
24147
|
var MANAGER_LEASE_TTL_MS = 1e4;
|
|
24084
24148
|
var MEMBERSHIP_MAX_BYTES = 64 * 1024 * 1024;
|
|
24085
24149
|
async function createSpaceStreams(jsm, space) {
|
|
24086
|
-
const
|
|
24087
|
-
|
|
24088
|
-
name: chatStream(space),
|
|
24089
|
-
subjects: [`${p}.chat.>`],
|
|
24090
|
-
retention: import_jetstream.RetentionPolicy.Limits,
|
|
24091
|
-
storage: import_jetstream.StorageType.File,
|
|
24092
|
-
max_msgs_per_subject: MAX_MSGS_PER_SUBJECT,
|
|
24093
|
-
// capped per-channel backlog (buffer + history)
|
|
24094
|
-
discard: import_jetstream.DiscardPolicy.Old,
|
|
24095
|
-
// Direct Get API stays enabled on CHAT (harmless: agents hold no DIRECT.GET grant). Per-channel
|
|
24096
|
-
// history reads no longer use it — they go through contained single-filter ephemeral consumers
|
|
24097
|
-
// (endpoint `collectHistory`) so the read ACL bounds them. NEVER set on DM/TASK: direct-get
|
|
24098
|
-
// would bypass the consumer-create deny that is DM's confidentiality boundary.
|
|
24099
|
-
allow_direct: true
|
|
24100
|
-
});
|
|
24101
|
-
await jsm.streams.add({
|
|
24102
|
-
name: dmStream(space),
|
|
24103
|
-
subjects: [`${p}.inst.>`],
|
|
24104
|
-
retention: import_jetstream.RetentionPolicy.Limits,
|
|
24105
|
-
storage: import_jetstream.StorageType.File
|
|
24106
|
-
});
|
|
24107
|
-
await jsm.streams.add({
|
|
24108
|
-
name: taskStream(space),
|
|
24109
|
-
subjects: [`${p}.svc.>`],
|
|
24110
|
-
retention: import_jetstream.RetentionPolicy.Workqueue,
|
|
24111
|
-
storage: import_jetstream.StorageType.File
|
|
24112
|
-
});
|
|
24113
|
-
await jsm.streams.add({
|
|
24114
|
-
name: inboxStream(space),
|
|
24115
|
-
subjects: [`${p}.dinbox.>`],
|
|
24116
|
-
retention: import_jetstream.RetentionPolicy.Limits,
|
|
24117
|
-
storage: import_jetstream.StorageType.File,
|
|
24118
|
-
max_msgs_per_subject: MAX_MSGS_PER_SUBJECT,
|
|
24119
|
-
discard: import_jetstream.DiscardPolicy.Old,
|
|
24120
|
-
duplicate_window: (0, import_transport_node.nanos)(PLANE3_DEDUP_WINDOW_MS)
|
|
24121
|
-
});
|
|
24122
|
-
await jsm.streams.add({
|
|
24123
|
-
name: dlvStream(space),
|
|
24124
|
-
subjects: [`${p}.dlv.>`],
|
|
24125
|
-
retention: import_jetstream.RetentionPolicy.Limits,
|
|
24126
|
-
storage: import_jetstream.StorageType.File,
|
|
24127
|
-
max_msgs_per_subject: MAX_MSGS_PER_SUBJECT,
|
|
24128
|
-
discard: import_jetstream.DiscardPolicy.Old,
|
|
24129
|
-
duplicate_window: (0, import_transport_node.nanos)(PLANE3_DEDUP_WINDOW_MS)
|
|
24130
|
-
});
|
|
24150
|
+
for (const stream of [chatStream(space), dmStream(space), taskStream(space), inboxStream(space), dlvStream(space)])
|
|
24151
|
+
await jsm.streams.add(canonicalBackupStreamConfig(space, stream));
|
|
24131
24152
|
}
|
|
24132
24153
|
function dmDurableConfig(space, owner, actor, opts = {}) {
|
|
24133
24154
|
const cfg = {
|
|
24134
24155
|
durable_name: dmDurable(owner, actor),
|
|
24135
24156
|
filter_subject: unicastRecvFilter(space, owner, actor),
|
|
24136
24157
|
// inst.<owner>.<actor>.> — every DM to me
|
|
24137
|
-
ack_policy:
|
|
24138
|
-
ack_wait: (0,
|
|
24139
|
-
deliver_policy:
|
|
24158
|
+
ack_policy: import_jetstream2.AckPolicy.Explicit,
|
|
24159
|
+
ack_wait: (0, import_transport_node2.nanos)(opts.ackWaitMs ?? 6e4),
|
|
24160
|
+
deliver_policy: import_jetstream2.DeliverPolicy.All
|
|
24140
24161
|
};
|
|
24141
24162
|
if (opts.inactiveThresholdMs)
|
|
24142
|
-
cfg.inactive_threshold = (0,
|
|
24163
|
+
cfg.inactive_threshold = (0, import_transport_node2.nanos)(opts.inactiveThresholdMs);
|
|
24143
24164
|
return cfg;
|
|
24144
24165
|
}
|
|
24145
24166
|
function taskDurableConfig(space, role, opts = {}) {
|
|
@@ -24147,17 +24168,17 @@ function taskDurableConfig(space, role, opts = {}) {
|
|
|
24147
24168
|
durable_name: taskDurable(role),
|
|
24148
24169
|
filter_subject: anycastServeFilter(space, role),
|
|
24149
24170
|
// svc.<role>.> — every anycast to the role
|
|
24150
|
-
ack_policy:
|
|
24151
|
-
ack_wait: (0,
|
|
24171
|
+
ack_policy: import_jetstream2.AckPolicy.Explicit,
|
|
24172
|
+
ack_wait: (0, import_transport_node2.nanos)(opts.ackWaitMs ?? 6e4)
|
|
24152
24173
|
};
|
|
24153
24174
|
}
|
|
24154
24175
|
function inboxReaderConfig(space, opts = {}) {
|
|
24155
24176
|
return {
|
|
24156
24177
|
durable_name: readerDurable(opts.shard, opts.shards),
|
|
24157
24178
|
filter_subject: `${spacePrefix(space)}.dinbox.>`,
|
|
24158
|
-
ack_policy:
|
|
24159
|
-
ack_wait: (0,
|
|
24160
|
-
deliver_policy:
|
|
24179
|
+
ack_policy: import_jetstream2.AckPolicy.Explicit,
|
|
24180
|
+
ack_wait: (0, import_transport_node2.nanos)(opts.ackWaitMs ?? 6e4),
|
|
24181
|
+
deliver_policy: import_jetstream2.DeliverPolicy.All,
|
|
24161
24182
|
max_ack_pending: DINBOX_MAX_ACK_PENDING
|
|
24162
24183
|
};
|
|
24163
24184
|
}
|
|
@@ -24165,27 +24186,50 @@ function dlvDurableConfig(space, owner, actor, opts = {}) {
|
|
|
24165
24186
|
const cfg = {
|
|
24166
24187
|
durable_name: dlvDurable(owner, actor),
|
|
24167
24188
|
filter_subject: dlvSubject(space, owner, actor),
|
|
24168
|
-
ack_policy:
|
|
24169
|
-
ack_wait: (0,
|
|
24170
|
-
deliver_policy:
|
|
24189
|
+
ack_policy: import_jetstream2.AckPolicy.Explicit,
|
|
24190
|
+
ack_wait: (0, import_transport_node2.nanos)(opts.ackWaitMs ?? 6e4),
|
|
24191
|
+
deliver_policy: import_jetstream2.DeliverPolicy.All
|
|
24171
24192
|
};
|
|
24172
24193
|
if (opts.inactiveThresholdMs)
|
|
24173
|
-
cfg.inactive_threshold = (0,
|
|
24194
|
+
cfg.inactive_threshold = (0, import_transport_node2.nanos)(opts.inactiveThresholdMs);
|
|
24174
24195
|
return cfg;
|
|
24175
24196
|
}
|
|
24176
24197
|
function fanoutDurableConfig(space, opts = {}) {
|
|
24177
24198
|
return {
|
|
24178
24199
|
durable_name: fanoutDurable(opts.shard, opts.shards),
|
|
24179
24200
|
filter_subject: chatWildcard(space),
|
|
24180
|
-
ack_policy:
|
|
24181
|
-
ack_wait: (0,
|
|
24182
|
-
deliver_policy:
|
|
24201
|
+
ack_policy: import_jetstream2.AckPolicy.Explicit,
|
|
24202
|
+
ack_wait: (0, import_transport_node2.nanos)(opts.ackWaitMs ?? 6e4),
|
|
24203
|
+
deliver_policy: import_jetstream2.DeliverPolicy.New
|
|
24183
24204
|
};
|
|
24184
24205
|
}
|
|
24185
24206
|
|
|
24207
|
+
// ../../packages/core/dist/backup.js
|
|
24208
|
+
var DEFAULT_TRANSFER_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
24209
|
+
|
|
24210
|
+
// ../../packages/core/dist/provision.js
|
|
24211
|
+
var FIVE_MINUTES = 5 * 60;
|
|
24212
|
+
var STANDING_RENEWABLE_TTL_SEC = 24 * 60 * 60;
|
|
24213
|
+
var ROTATION_RENEWED_TTL_SEC = 30 * 24 * 60 * 60;
|
|
24214
|
+
var BASE_LIMITS = {
|
|
24215
|
+
subs: -1,
|
|
24216
|
+
conn: -1,
|
|
24217
|
+
leaf: -1,
|
|
24218
|
+
imports: -1,
|
|
24219
|
+
exports: -1,
|
|
24220
|
+
data: -1,
|
|
24221
|
+
payload: -1,
|
|
24222
|
+
wildcards: true
|
|
24223
|
+
};
|
|
24224
|
+
var DATA_LIMITS = { ...BASE_LIMITS, mem_storage: -1, disk_storage: -1 };
|
|
24225
|
+
var SYS_LIMITS = { ...BASE_LIMITS, mem_storage: 0, disk_storage: 0 };
|
|
24226
|
+
|
|
24227
|
+
// ../../packages/core/dist/space-auth.js
|
|
24228
|
+
var import_nkeys3 = __toESM(require_mod(), 1);
|
|
24229
|
+
|
|
24186
24230
|
// ../../packages/core/dist/channels.js
|
|
24187
24231
|
var import_kv3 = __toESM(require_mod6(), 1);
|
|
24188
|
-
var
|
|
24232
|
+
var import_transport_node4 = __toESM(require_transport_node(), 1);
|
|
24189
24233
|
function parseDuration(s) {
|
|
24190
24234
|
const m = /^(\d+)(s|m|h|d)$/.exec(s.trim());
|
|
24191
24235
|
if (!m)
|
|
@@ -24209,12 +24253,12 @@ async function openChannelRegistry(nc, space, opts = {}) {
|
|
|
24209
24253
|
return opts.create ? kvm.create(channelBucket(space)) : kvm.open(channelBucket(space));
|
|
24210
24254
|
}
|
|
24211
24255
|
async function readChannelConfig(kv, channel) {
|
|
24212
|
-
return
|
|
24256
|
+
return decode2(kv, channel);
|
|
24213
24257
|
}
|
|
24214
24258
|
async function readChannelDefaults(kv) {
|
|
24215
|
-
return
|
|
24259
|
+
return decode2(kv, CHANNEL_DEFAULTS_KEY);
|
|
24216
24260
|
}
|
|
24217
|
-
async function
|
|
24261
|
+
async function decode2(kv, key) {
|
|
24218
24262
|
const e = await kv.get(key);
|
|
24219
24263
|
if (!e || e.operation === "DEL" || e.operation === "PURGE")
|
|
24220
24264
|
return void 0;
|
|
@@ -24334,15 +24378,15 @@ function durableEligible(rec, seq) {
|
|
|
24334
24378
|
}
|
|
24335
24379
|
|
|
24336
24380
|
// ../../packages/core/dist/membership-feed.js
|
|
24337
|
-
var
|
|
24381
|
+
var import_transport_node5 = __toESM(require_transport_node(), 1);
|
|
24338
24382
|
var import_kv5 = __toESM(require_mod6(), 1);
|
|
24339
24383
|
|
|
24340
24384
|
// ../../packages/core/dist/evict.js
|
|
24341
|
-
var
|
|
24385
|
+
var import_transport_node6 = __toESM(require_transport_node(), 1);
|
|
24342
24386
|
|
|
24343
24387
|
// ../../packages/core/dist/lease.js
|
|
24344
24388
|
var import_kv6 = __toESM(require_mod6(), 1);
|
|
24345
|
-
var
|
|
24389
|
+
var import_transport_node7 = __toESM(require_transport_node(), 1);
|
|
24346
24390
|
async function openDeliveryRegistry(nc, space) {
|
|
24347
24391
|
return new import_kv6.Kvm(nc).open(deliveryBucket(space));
|
|
24348
24392
|
}
|
|
@@ -24468,8 +24512,8 @@ var isWin = process.platform === "win32";
|
|
|
24468
24512
|
// ../../packages/core/dist/endpoint.js
|
|
24469
24513
|
import { EventEmitter } from "node:events";
|
|
24470
24514
|
import { randomUUID } from "node:crypto";
|
|
24471
|
-
var
|
|
24472
|
-
var
|
|
24515
|
+
var import_transport_node8 = __toESM(require_transport_node(), 1);
|
|
24516
|
+
var import_jetstream4 = __toESM(require_mod4(), 1);
|
|
24473
24517
|
var import_kv7 = __toESM(require_mod6(), 1);
|
|
24474
24518
|
var DEFAULT_SERVER = "nats://127.0.0.1:4222";
|
|
24475
24519
|
var PLANE3_FRAME_HEADER = "Cotal-Delivery-Frame";
|
|
@@ -24796,7 +24840,7 @@ var CotalEndpoint = class _CotalEndpoint extends EventEmitter {
|
|
|
24796
24840
|
if (stale)
|
|
24797
24841
|
await this.refreshCreds(!this.currentCreds);
|
|
24798
24842
|
}
|
|
24799
|
-
this.nc = await (0,
|
|
24843
|
+
this.nc = await (0, import_transport_node8.connect)({
|
|
24800
24844
|
servers: this.servers,
|
|
24801
24845
|
// In USER MODE the connection `name` carries the client-chosen inbox nonce (= connId) the callout
|
|
24802
24846
|
// scopes `_INBOX_<connId>.>` on (see EndpointOptions.bearer); otherwise it's the display handle.
|
|
@@ -24816,7 +24860,7 @@ var CotalEndpoint = class _CotalEndpoint extends EventEmitter {
|
|
|
24816
24860
|
...authOpts({ token: this.token, user: this.user, pass: this.pass, creds: this.credsSource ? () => this.currentCreds : this.currentCreds, bearer: this.userMode ? () => this.currentBearer : void 0, sentinelCreds: this.sentinelCreds, tls: this.tls })
|
|
24817
24861
|
});
|
|
24818
24862
|
this.watchStatus();
|
|
24819
|
-
this.js = (0,
|
|
24863
|
+
this.js = (0, import_jetstream4.jetstream)(this.nc);
|
|
24820
24864
|
if (this.doWatch || this.doRegister) {
|
|
24821
24865
|
const kvm = new import_kv7.Kvm(this.nc);
|
|
24822
24866
|
this.kv = this.authed ? await kvm.open(presenceBucket(this.space)) : await kvm.create(presenceBucket(this.space), { ttl: this.ttlMs });
|
|
@@ -24838,7 +24882,7 @@ var CotalEndpoint = class _CotalEndpoint extends EventEmitter {
|
|
|
24838
24882
|
}, this.heartbeatMs);
|
|
24839
24883
|
}
|
|
24840
24884
|
if (this.doConsume) {
|
|
24841
|
-
this.jsm = await (0,
|
|
24885
|
+
this.jsm = await (0, import_jetstream4.jetstreamManager)(this.nc);
|
|
24842
24886
|
if (!this.authed)
|
|
24843
24887
|
await this.ensureStreams();
|
|
24844
24888
|
await this.startConsumers();
|
|
@@ -25367,7 +25411,7 @@ var CotalEndpoint = class _CotalEndpoint extends EventEmitter {
|
|
|
25367
25411
|
async listChannels() {
|
|
25368
25412
|
if (!this.nc)
|
|
25369
25413
|
throw new Error(this.notLiveMsg());
|
|
25370
|
-
const mgr = await (0,
|
|
25414
|
+
const mgr = await (0, import_jetstream4.jetstreamManager)(this.nc);
|
|
25371
25415
|
const counts = /* @__PURE__ */ new Map();
|
|
25372
25416
|
try {
|
|
25373
25417
|
const info = await mgr.streams.info(chatStream(this.space), { subjects_filter: ">" });
|
|
@@ -25484,7 +25528,7 @@ var CotalEndpoint = class _CotalEndpoint extends EventEmitter {
|
|
|
25484
25528
|
async streamHistory(stream, subject, limit) {
|
|
25485
25529
|
if (!this.nc)
|
|
25486
25530
|
throw new Error("endpoint not started");
|
|
25487
|
-
const js = (0,
|
|
25531
|
+
const js = (0, import_jetstream4.jetstream)(this.nc);
|
|
25488
25532
|
const msgs = [];
|
|
25489
25533
|
try {
|
|
25490
25534
|
const consumer = await js.consumers.get(stream, { filter_subjects: [subject] });
|
|
@@ -25517,7 +25561,7 @@ var CotalEndpoint = class _CotalEndpoint extends EventEmitter {
|
|
|
25517
25561
|
for await (const s of this.nc.status()) {
|
|
25518
25562
|
if (s.type !== "error")
|
|
25519
25563
|
continue;
|
|
25520
|
-
if (s.error instanceof
|
|
25564
|
+
if (s.error instanceof import_transport_node8.PermissionViolationError && this.confirmingChatSubs.has(s.error.subject))
|
|
25521
25565
|
continue;
|
|
25522
25566
|
this.emit("error", describeStatusError(s.error));
|
|
25523
25567
|
}
|
|
@@ -25861,10 +25905,10 @@ var CotalEndpoint = class _CotalEndpoint extends EventEmitter {
|
|
|
25861
25905
|
await this.jsm.consumers.add(chatStream(this.space), {
|
|
25862
25906
|
name,
|
|
25863
25907
|
filter_subject: subject,
|
|
25864
|
-
ack_policy:
|
|
25908
|
+
ack_policy: import_jetstream4.AckPolicy.None,
|
|
25865
25909
|
mem_storage: true,
|
|
25866
|
-
inactive_threshold: (0,
|
|
25867
|
-
deliver_policy:
|
|
25910
|
+
inactive_threshold: (0, import_transport_node8.nanos)(3e4),
|
|
25911
|
+
deliver_policy: import_jetstream4.DeliverPolicy.StartSequence,
|
|
25868
25912
|
opt_start_seq: fromSeqExcl + 1
|
|
25869
25913
|
});
|
|
25870
25914
|
let copied = 0;
|
|
@@ -26217,7 +26261,7 @@ var CotalEndpoint = class _CotalEndpoint extends EventEmitter {
|
|
|
26217
26261
|
channel: entry.channel,
|
|
26218
26262
|
msg: authenticatedChannelMessage(entry.msg, entry.channel)
|
|
26219
26263
|
};
|
|
26220
|
-
const frameHeaders = (0,
|
|
26264
|
+
const frameHeaders = (0, import_transport_node8.headers)();
|
|
26221
26265
|
frameHeaders.set(PLANE3_FRAME_HEADER, "1");
|
|
26222
26266
|
await this.js.publish(dlvSubject(this.space, pr.owner, pr.actor), JSON.stringify(frame), {
|
|
26223
26267
|
msgID: `${entry.msg.id}:${owner}:${entry.generation}`,
|
|
@@ -26342,7 +26386,7 @@ var CotalEndpoint = class _CotalEndpoint extends EventEmitter {
|
|
|
26342
26386
|
* distinct from a responder that errored. nats.js surfaces it as NoRespondersError, or a RequestError
|
|
26343
26387
|
* whose `isNoResponders()` is true. */
|
|
26344
26388
|
isNoResponders(e) {
|
|
26345
|
-
return e instanceof
|
|
26389
|
+
return e instanceof import_transport_node8.NoRespondersError || e instanceof import_transport_node8.RequestError && e.isNoResponders();
|
|
26346
26390
|
}
|
|
26347
26391
|
/** Agent-side: this session's CURRENT durable memberships (channel + join generation) from the
|
|
26348
26392
|
* manager — the agent holds no read on the privileged members KV. `undefined` ⇒ NO control responder
|
|
@@ -26434,7 +26478,7 @@ var CotalEndpoint = class _CotalEndpoint extends EventEmitter {
|
|
|
26434
26478
|
async manager() {
|
|
26435
26479
|
if (!this.nc)
|
|
26436
26480
|
throw new Error("endpoint not started");
|
|
26437
|
-
this.jsm ??= await (0,
|
|
26481
|
+
this.jsm ??= await (0, import_jetstream4.jetstreamManager)(this.nc);
|
|
26438
26482
|
return this.jsm;
|
|
26439
26483
|
}
|
|
26440
26484
|
/** Bind this endpoint's durable consumers: DM inbox, chat, and (if a role) the task queue. */
|
|
@@ -26682,10 +26726,10 @@ var CotalEndpoint = class _CotalEndpoint extends EventEmitter {
|
|
|
26682
26726
|
await this.jsm.consumers.add(stream, {
|
|
26683
26727
|
name,
|
|
26684
26728
|
filter_subject: subject,
|
|
26685
|
-
ack_policy:
|
|
26729
|
+
ack_policy: import_jetstream4.AckPolicy.None,
|
|
26686
26730
|
mem_storage: true,
|
|
26687
|
-
inactive_threshold: (0,
|
|
26688
|
-
..."time" in start ? { deliver_policy:
|
|
26731
|
+
inactive_threshold: (0, import_transport_node8.nanos)(3e4),
|
|
26732
|
+
..."time" in start ? { deliver_policy: import_jetstream4.DeliverPolicy.StartTime, opt_start_time: start.time.toISOString() } : { deliver_policy: import_jetstream4.DeliverPolicy.StartSequence, opt_start_seq: start.seq }
|
|
26689
26733
|
});
|
|
26690
26734
|
try {
|
|
26691
26735
|
const consumer = await this.js.consumers.get(stream, name);
|
|
@@ -27040,7 +27084,7 @@ function authOpts(a) {
|
|
|
27040
27084
|
if (!a.sentinelCreds)
|
|
27041
27085
|
throw new Error("user-mode bearer requires sentinelCreds");
|
|
27042
27086
|
return {
|
|
27043
|
-
authenticator: [(0,
|
|
27087
|
+
authenticator: [(0, import_transport_node8.credsAuthenticator)(new TextEncoder().encode(a.sentinelCreds)), (0, import_transport_node8.tokenAuthenticator)(a.bearer)],
|
|
27044
27088
|
tls
|
|
27045
27089
|
};
|
|
27046
27090
|
}
|
|
@@ -27048,7 +27092,7 @@ function authOpts(a) {
|
|
|
27048
27092
|
if (a.token || a.user || a.pass)
|
|
27049
27093
|
throw new Error("creds are mutually exclusive with token/user/pass auth");
|
|
27050
27094
|
const creds = a.creds;
|
|
27051
|
-
const authenticator = typeof creds === "function" ? (nonce) => (0,
|
|
27095
|
+
const authenticator = typeof creds === "function" ? (nonce) => (0, import_transport_node8.credsAuthenticator)(new TextEncoder().encode(creds()))(nonce) : (0, import_transport_node8.credsAuthenticator)(new TextEncoder().encode(creds));
|
|
27052
27096
|
return { authenticator, tls };
|
|
27053
27097
|
}
|
|
27054
27098
|
return { token: a.token, user: a.user, pass: a.pass, tls };
|
|
@@ -27091,22 +27135,22 @@ function decodeBearerPrincipal(bearer) {
|
|
|
27091
27135
|
return { owner, actor };
|
|
27092
27136
|
}
|
|
27093
27137
|
function describeStatusError(err2) {
|
|
27094
|
-
if (err2 instanceof
|
|
27138
|
+
if (err2 instanceof import_transport_node8.PermissionViolationError) {
|
|
27095
27139
|
return new Error(`NATS permission denied: cannot ${err2.operation} "${err2.subject}" - check this endpoint's ACLs (a denied peer looks "absent" rather than blocked)`, { cause: err2 });
|
|
27096
27140
|
}
|
|
27097
27141
|
return err2;
|
|
27098
27142
|
}
|
|
27099
27143
|
function isPermissionDenied(e) {
|
|
27100
|
-
if (e instanceof
|
|
27144
|
+
if (e instanceof import_transport_node8.PermissionViolationError)
|
|
27101
27145
|
return true;
|
|
27102
|
-
if (e?.cause instanceof
|
|
27146
|
+
if (e?.cause instanceof import_transport_node8.PermissionViolationError)
|
|
27103
27147
|
return true;
|
|
27104
27148
|
return /permissions?\s+violation/i.test(String(e?.message ?? ""));
|
|
27105
27149
|
}
|
|
27106
27150
|
|
|
27107
27151
|
// ../../packages/core/dist/spaces.js
|
|
27108
|
-
var
|
|
27109
|
-
var
|
|
27152
|
+
var import_transport_node9 = __toESM(require_transport_node(), 1);
|
|
27153
|
+
var import_jetstream5 = __toESM(require_mod4(), 1);
|
|
27110
27154
|
var import_kv8 = __toESM(require_mod6(), 1);
|
|
27111
27155
|
|
|
27112
27156
|
// ../../packages/core/dist/registry.js
|
|
@@ -27470,8 +27514,10 @@ var MeshAgent = class extends EventEmitter2 {
|
|
|
27470
27514
|
}
|
|
27471
27515
|
const existing = this.inbox.find((p) => p.item.id === m.id);
|
|
27472
27516
|
if (existing) {
|
|
27473
|
-
if (delivery.durable)
|
|
27517
|
+
if (delivery.durable) {
|
|
27474
27518
|
existing.ack = delivery.ack;
|
|
27519
|
+
this.emit("incoming", existing.item);
|
|
27520
|
+
}
|
|
27475
27521
|
return;
|
|
27476
27522
|
}
|
|
27477
27523
|
if (!meta3)
|
|
@@ -28129,7 +28175,7 @@ __export(external_exports, {
|
|
|
28129
28175
|
cuid2: () => cuid22,
|
|
28130
28176
|
custom: () => custom,
|
|
28131
28177
|
date: () => date3,
|
|
28132
|
-
decode: () =>
|
|
28178
|
+
decode: () => decode4,
|
|
28133
28179
|
decodeAsync: () => decodeAsync2,
|
|
28134
28180
|
describe: () => describe2,
|
|
28135
28181
|
discriminatedUnion: () => discriminatedUnion,
|
|
@@ -28508,7 +28554,7 @@ __export(core_exports2, {
|
|
|
28508
28554
|
config: () => config,
|
|
28509
28555
|
createStandardJSONSchemaMethod: () => createStandardJSONSchemaMethod,
|
|
28510
28556
|
createToJSONSchemaMethod: () => createToJSONSchemaMethod,
|
|
28511
|
-
decode: () =>
|
|
28557
|
+
decode: () => decode3,
|
|
28512
28558
|
decodeAsync: () => decodeAsync,
|
|
28513
28559
|
describe: () => describe,
|
|
28514
28560
|
encode: () => encode,
|
|
@@ -29515,7 +29561,7 @@ var encode = /* @__PURE__ */ _encode($ZodRealError);
|
|
|
29515
29561
|
var _decode = (_Err) => (schema, value, _ctx) => {
|
|
29516
29562
|
return _parse(_Err)(schema, value, _ctx);
|
|
29517
29563
|
};
|
|
29518
|
-
var
|
|
29564
|
+
var decode3 = /* @__PURE__ */ _decode($ZodRealError);
|
|
29519
29565
|
var _encodeAsync = (_Err) => async (schema, value, _ctx) => {
|
|
29520
29566
|
const ctx = _ctx ? { ..._ctx, direction: "backward" } : { direction: "backward" };
|
|
29521
29567
|
return _parseAsync(_Err)(schema, value, ctx);
|
|
@@ -40704,7 +40750,7 @@ var parseAsync2 = /* @__PURE__ */ _parseAsync(ZodRealError);
|
|
|
40704
40750
|
var safeParse2 = /* @__PURE__ */ _safeParse(ZodRealError);
|
|
40705
40751
|
var safeParseAsync2 = /* @__PURE__ */ _safeParseAsync(ZodRealError);
|
|
40706
40752
|
var encode2 = /* @__PURE__ */ _encode(ZodRealError);
|
|
40707
|
-
var
|
|
40753
|
+
var decode4 = /* @__PURE__ */ _decode(ZodRealError);
|
|
40708
40754
|
var encodeAsync2 = /* @__PURE__ */ _encodeAsync(ZodRealError);
|
|
40709
40755
|
var decodeAsync2 = /* @__PURE__ */ _decodeAsync(ZodRealError);
|
|
40710
40756
|
var safeEncode2 = /* @__PURE__ */ _safeEncode(ZodRealError);
|
|
@@ -40768,7 +40814,7 @@ var ZodType = /* @__PURE__ */ $constructor("ZodType", (inst, def) => {
|
|
|
40768
40814
|
inst.safeParseAsync = async (data, params) => safeParseAsync2(inst, data, params);
|
|
40769
40815
|
inst.spa = inst.safeParseAsync;
|
|
40770
40816
|
inst.encode = (data, params) => encode2(inst, data, params);
|
|
40771
|
-
inst.decode = (data, params) =>
|
|
40817
|
+
inst.decode = (data, params) => decode4(inst, data, params);
|
|
40772
40818
|
inst.encodeAsync = async (data, params) => encodeAsync2(inst, data, params);
|
|
40773
40819
|
inst.decodeAsync = async (data, params) => decodeAsync2(inst, data, params);
|
|
40774
40820
|
inst.safeEncode = (data, params) => safeEncode2(inst, data, params);
|
|
@@ -42538,7 +42584,7 @@ config(en_default());
|
|
|
42538
42584
|
|
|
42539
42585
|
// ../connector-core/dist/docs-bundle.generated.js
|
|
42540
42586
|
var DOCS_BUNDLE = {
|
|
42541
|
-
"version": "0.
|
|
42587
|
+
"version": "0.12.0",
|
|
42542
42588
|
"generatedFrom": "docs/*.md + SPEC.md + spec/cotal.schema.json",
|
|
42543
42589
|
"pages": [
|
|
42544
42590
|
{
|
|
@@ -42616,7 +42662,7 @@ var DOCS_BUNDLE = {
|
|
|
42616
42662
|
"title": "`cotal` CLI reference",
|
|
42617
42663
|
"kind": "Reference: describes the TypeScript reference implementation (the `cotal` CLI), not the wire contract.",
|
|
42618
42664
|
"summary": "cotal is the operator command line for the reference implementation: bring a mesh up, mint identities, launch agents, watch what they do, and tear it all down.",
|
|
42619
|
-
"body": "# `cotal` CLI reference\n\n> **Reference**: describes the TypeScript reference implementation (the `cotal` CLI), not the wire contract. \xB7 **For:** operators \xB7 **Wire contract:** [SPEC](../SPEC.md)\n\n`cotal` is the operator command line for the reference implementation: bring a mesh up, mint\nidentities, launch agents, watch what they do, and tear it all down. It is a thin client over the\nwire contract: the normative subjects and schemas live in the [SPEC](../SPEC.md); this page is\nlookup material for the commands, not a walkthrough; if you are new, start with\n[Getting started](getting-started.md).\n\n## Running it\n\n```bash\nnpm install -g cotal-ai # puts `cotal` on your PATH (needs Node 20+)\ncotal --help # every command, grouped\ncotal <command> --help # one command's flags and usage\n```\n\n`npx cotal-ai <command>` runs it without a global install; in a dev clone, `pnpm cotal <command>`\nruns it through `tsx` with no build step. Bare `cotal` prints help. Every command generates its own\n`--help`, usage, and shell completion from its declared flags.\n\nCommands come from the surfaces the binary composes: the base mesh CLI, the manager\n(`supervise`), and the delivery daemon (`deliver`), plus any operator-installed extensions.\n`cotal ext add <npm-package>` installs any registry providers a package contributes: commands,\nruntimes, and local process lifecycle descriptors. The `web` dashboard and optional manager\nruntimes ship this way.\n\n## Commands\n\n| Area | Command | Purpose |\n|---|---|---|\n| Set up & lifecycle | [`setup`](#setup) | Guided, configure-only setup (installs, seeds personas; launches nothing) |\n| Set up & lifecycle | [`up`](#up) | Start a local mesh (nats-server + JetStream), or boot a whole manifest with `-f` |\n| Set up & lifecycle | [`down`](#down) | Stop the whole stack, selected registered components, or a manifest deploy |\n| Set up & lifecycle | [`clean`](#clean) | Configurable cleanup: purge history (live), or wipe the local store / identity (stopped) |\n| Set up & lifecycle | [`meshes`](#meshes-use-status) | List the running meshes on this machine |\n| Set up & lifecycle | [`use`](#meshes-use-status) | Set the default mesh a bare `cotal spawn` joins |\n| Set up & lifecycle | [`status`](#meshes-use-status) | Read-only diagnostics for setup, processes, and the selected mesh |\n| Agents & personas | [`spawn`](#spawn) | Launch an agent from a persona (foreground, or `--detach` via the manager) |\n| Agents & personas | [`models`](#models) | List connector model catalogs and variants from the manager |\n| Agents & personas | [`ps`](#ps-stop-attach) | List managed agents and their mesh status |\n| Agents & personas | [`stop`](#ps-stop-attach) | Ask the manager to stop a managed agent |\n| Agents & personas | [`attach`](#ps-stop-attach) | Stream and drive a managed agent's terminal (pty runtime) |\n| Agents & personas | [`personas`](#personas) | List, show, edit, create, or remove local personas |\n| Agents & personas | [`supervise`](#supervise) | Run a manager daemon (the agent supervisor / control plane) |\n| Agents & personas | [`runtimes`](#runtimes) | List the agent runtimes the manager can spawn through and whether each is reachable |\n| Messaging & watching | [`endpoints`](#endpoints) | List every endpoint in the live presence roster, including infrastructure |\n| Messaging & watching | [`send`](#send) | Send one message, then exit: DM a peer, post a channel, or ask a role |\n| Messaging & watching | [`channels`](#channels) | Inspect or set the channel registry (replay, description, instructions) |\n| Messaging & watching | [`history`](#history) | Clear retained message history |\n| Messaging & watching | [`console`](#console) | Live protocol view for a space (TUI, or `--plain` line stream) |\n| Messaging & watching | [`web`](#web) | Browser dashboard (installed as the `@cotal-ai/web` extension) |\n| Auth & meshes | [`mint`](#mint) | Mint a creds file for a space (static auth mode) |\n| Auth & meshes | [`login`](#login-logout) | Sign in to a per-user-auth mesh's IdP (once per machine) |\n| Auth & meshes | [`logout`](#login-logout) | Revoke the IdP session and clear the cached login |\n| Auth & meshes | [`actor`](#actor) | Manage a user-auth space's actor ledger (grant / revoke / list) |\n| Auth & meshes | [`doctor`](#doctor) | Credential-health diagnosis and repair (`doctor auth`) |\n| Auth & meshes | [`join`](#join) | Join a space as your own presence (interactive) |\n| Manifest | [`topology`](#manifest-deploys) | Validate and view a mesh manifest's access graph (read-only) |\n| Extensions & misc | [`ext`](#ext) | Install / remove operator CLI extensions |\n| Extensions & misc | [`completion`](#completion) | Print or install shell completion |\n| Extensions & misc | [`feedback`](#feedback) | Send feedback to the Cotal developers |\n| Extensions & misc | [`deliver`](#server-daemons) | Run the server-side Plane-3 delivery daemon |\n| Extensions & misc | [`feedback-intake`](#server-daemons) | Run a self-hosted feedback intake server |\n\nThe manifest modes of `up`, `spawn`, and `down` (`-f <cotal.yaml>`) plus `topology` are covered\ntogether under [Manifest deploys](#manifest-deploys).\n\n## setup\n\n```bash\ncotal setup [--full] [--demo] [--yes]\n```\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--full` | off | Redo the full guided flow (implies `--demo`) |\n| `--demo` | off | Also seed the guided expert team (`david`, `sven`, `me`) |\n| `--yes`, `-y` | off | Non-interactive accept-all (for agents / CI) |\n\nGuided setup is **configure-only**: it checks prerequisites, installs the Claude Code plugin, and\nseeds persona files, and it launches nothing (no mesh, no web, no manager). First run gets the\nnarrated flow; later runs print a status card. By default it seeds one `default` persona; the\n`david`/`sven`/`me` team is opt-in via `--demo`. See [Getting started](getting-started.md) and, for\nmaintainers, [setup internals](setup-internals.md).\n\n## up\n\n```bash\ncotal up [--detach] [--open] [--space <s>] [--server <url>] [--channels <path>] [--runtime <name>]\ncotal up -f <cotal.yaml> [--dry-run] [--runtime <name>]\n```\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--server <url>` | auto (free local port) | Listen URL override |\n| `--host <host>` | \u2014 | Bind host override |\n| `--space <s>` | the folder's name | Space name |\n| `--store-dir <dir>` | \u2014 | JetStream store directory |\n| `--channels <path>` | `.cotal/channels.json` if present | Channel-registry seed file (JSON). An explicit path that is missing is an error |\n| `--open` | off (auth) | Unauthenticated dev mesh: no JWT, no ACLs |\n| `--user-auth` | off | Per-user auth: people `cotal login`; connects are authorized against the actor ledger |\n| `--idp <url>` | \u2014 | With `--user-auth`: the IdP auth base URL to pin on first enable |\n| `--detach` | off | Run in the background (stop with `cotal down`) |\n| `--file <cotal.yaml>`, `-f` | \u2014 | Launch a whole mesh from a manifest |\n| `--dry-run` | off | With `-f`: print the plan, mutate nothing |\n| `--runtime <name>` | `pty` (or the manifest's, with `-f`) | Agent runtime for the mesh manager (`pty` built in; others are installed extensions, explicit-only). Resolved + probed before the broker starts; an uninstalled/unreachable runtime fails loud. With `-f`, overrides the manifest's runtime |\n\n`cotal up` boots a local nats-server with JetStream and, in auth mode (the default), JWT auth and\nper-agent ACLs; `--detach` records the mesh so `cotal spawn` from any directory can find it. With no\n`--server`, it auto-selects a free port if the default address is taken; an explicit `--server`\nstays fail-loud on collision. `--detach` also brings up the control plane (delivery daemon in auth\nmode, then the manager). The `-f` form is a [manifest deploy](#manifest-deploys); see\n[Run a mesh](run-a-mesh.md).\n\n`--user-auth --idp <url>` starts the space's auth service alongside the broker (the NATS\nauth callout plus the loopback token exchange); it is torn down with `cotal down`, and a\nre-run of `cotal up` heals a dead service on a running broker. `--user-auth` and `--open`\ncontradict each other and are refused loudly; a running broker cannot change auth mode\nwithout a `cotal down` first. See [identity & auth](identity-and-auth.md).\n\n## down\n\n```bash\ncotal down\ncotal down manager [delivery auth web nats ...]\ncotal down -f <cotal.yaml> | --run <id> [--dry-run]\n```\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--file <cotal.yaml>`, `-f` | \u2014 | Tear down this manifest's deploy |\n| `--run <id>` | \u2014 | Tear down one `spawn -f` run by id |\n| `--dry-run` | off | Print the manifest teardown or selected components, mutate nothing |\n\nBare `cotal down` stops the whole local stack in dependency order. Positional component names stop\nonly those self-registered local processes; for example, `cotal down manager` leaves delivery and\nthe broker running, and `cotal down web` is available when the web extension is installed. The\n`-f` / `--run` forms tear down a [manifest deploy](#manifest-deploys) without stopping the whole mesh\nand cannot be combined with component names. Stopping `nats` alone is refused while an unselected\nregistered daemon is still live; include those components or use bare `cotal down`.\n\n`down` never deletes on-disk state; that is [`clean`](#clean).\n\n## clean\n\n```bash\ncotal clean <history|store|all> --force\n```\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--space <s>` / `--server <url>` / `--creds <path>` | resolved mesh | `history`: target mesh |\n| `--dms` | off | `history`: also clear DM history |\n| `--store-dir <dir>` | `.cotal/nats` | `store`/`all`: JetStream store directory |\n| `--force` | \u2014 | Required: destructive, no prompting |\n\nOne configurable cleanup verb; every target requires `--force`.\n\n- `history` purges the retained message backlog on the **running** broker (channels, plus DMs\n with `--dms`). The same operation as [`history clear`](#history), which stays as an alias.\n- `store` deletes the **stopped** mesh's JetStream store (`.cotal/nats`): streams, durable\n consumers, and messages. This is the reset for stale on-disk broker state, e.g. durables\n minted by an older, incompatible Cotal generation surviving a `down`/`up` cycle.\n- `all` is `store` plus the space identity (`.cotal/auth`), the local creds and markers tied to\n it, any crash residue a normal `down` would have swept (stale pidfiles, `run/`), and the mesh's\n registry entry; the next `cotal up` mints a fresh identity.\n\n`history` needs the mesh up; `store` and `all` refuse while any recorded mesh process is still\nalive (run `cotal down` first). Personas (`.cotal/agents`) and logs are never touched. A custom\nstore location is not recorded anywhere, so `--store-dir` must repeat whatever the mesh was\nlaunched with.\n\n## meshes, use, status\n\n```bash\ncotal meshes\ncotal use <space>\ncotal status [--space <s>] [--server <url>]\n```\n\n`meshes` lists the running meshes on this machine; a `*` marks the `current` default a bare\n`cotal spawn` joins. `use <space>` sets that default when several are running. `status` is a\nread-only report across four sections: machine prerequisites, this folder's `.cotal/`, the\nrecorded meshes, and a live snapshot of the selected mesh (roster, channels, membership feed).\n`status` takes only `--space` / `--server` to pick the mesh to inspect; it starts nothing.\n\n## spawn\n\n```bash\ncotal spawn [<persona>] [--detach] [--name <n>] [--agent <a>] [--model <m>] [--variant <v>] [--prompt <text>] [--cwd <dir>]\ncotal spawn -f <cotal.yaml> [--dry-run]\n```\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--space <s>` | resolved mesh | Target space |\n| `--server <url>` | registry entry | Broker URL override |\n| `--creds <path>` | \u2014 | Control-caller creds for an off-registry manager (`--detach` only) |\n| `--name <n>` | persona's `name:` | Presence-name override (does not choose the persona) |\n| `--config <persona-or-path>` | \u2014 | Persona catalog name or file path; wins over the positional |\n| `--agent <a>` | `COTAL_DEFAULT_AGENT`, else `claude` | Connector type (`claude`, `opencode`, `hermes`, \u2026) |\n| `--role <r>` | persona's `role:` | Role override |\n| `--model <m>` | persona's `model:` | Model override |\n| `--variant <v>` | persona's `variant:` | Model variant override (connector-defined; e.g. OpenCode reasoning tiers) |\n| `--cwd <dir>` | this cwd | Working directory to root the agent at |\n| `--prompt <text>` | \u2014 | Initial prompt auto-submitted at start |\n| `--resume <id>` | \u2014 | Fork an existing session id into the mesh (claude only) |\n| `--transcript` / `--no-transcript` | off | Mirror the session transcript to `tr-<name>` |\n| `--share-tools <sel>` | none | Share named operator MCP servers with the agent |\n| `--subscribe <a,b>` | persona's | Channel read-set override |\n| `--allow-subscribe <a,b>` | = subscribe | Read-ACL override |\n| `--allow-publish <a,b>` | deny | Post-ACL override |\n| `--detach`, `-d` | off | Launch via the manager into a detached PTY (reattach with `cotal attach`) |\n| `--file <cotal.yaml>`, `-f` | \u2014 | Deploy a manifest onto the running mesh |\n| `--dry-run` | off | With `-f`: print the plan, mutate nothing |\n| `--allow-stale <a,b>` | \u2014 | With `-f`: waive named stale agents (apply-only) |\n| `--runtime <name>` | manifest's | With `-f`: override the manifest's runtime |\n\nThe persona (`--config` > positional > `COTAL_DEFAULT_PERSONA` > `default`) is loaded from the\ntarget mesh's `.cotal/agents/`; the launch flags override the file. Foreground runs the agent\nattached to your terminal; `--detach` hands the launch to the running manager. `--detach` is the\nonly mode that registers a durable delivery membership; a foreground spawn reads live only. See\n[Connect Claude Code](connect-claude.md) and [Agent files](agent-files.md); `-f` is a\n[manifest deploy](#manifest-deploys). (`cotal start` was merged into `cotal spawn --detach`.)\n\n## models\n\n```bash\ncotal models [--agent <connector>] [--refresh]\n```\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--space <s>` / `--server <url>` / `--creds <path>` | resolved mesh | Which manager to reach |\n| `--agent <connector>` | all registered connectors | Connector whose catalog to list |\n| `--refresh` | off | Ask the connector to refresh its provider cache |\n\nAsks the running manager for each connector's model catalog (model ids plus their variants)\nfor connectors that expose one (OpenCode today; a connector without a catalog says so). Pick a\nresult with `cotal spawn --model <provider/model> --variant <v>`.\n\n## endpoints\n\n```bash\ncotal endpoints [--space <s>] [--server <url>] [--creds <path>]\n```\n\nLists the mesh presence roster: agents, the manager, and any other protocol endpoint, with each\nendpoint's role, kind, status, and current activity. Unlike `ps`, this is a read-only presence view;\nit is not limited to child processes owned by the manager.\n\n## ps, stop, attach\n\n```bash\ncotal ps [--space <s>]\ncotal stop --name <n> [--space <s>]\ncotal attach --name <n> [--space <s>]\n```\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--space <s>` / `--server <url>` / `--creds <path>` | resolved mesh | Which manager to reach |\n| `--name <n>` | \u2014 | Managed agent to stop / attach (required) |\n\nThese are operator clients over the running manager's control plane. `ps` lists managed agents with\ntheir mesh status (`starting\u2026` / `working` / `waiting` / `offline`); on a user-auth mesh it also\nrenders each managed agent's last credential-refresh outcome, fail-closed. `attach` streams and\ndrives an agent's terminal on the `pty` runtime; detach with the escape key (Ctrl-] by default; see\n[`COTAL_DETACH_KEY`](config.md)). `stop` and `attach` need a running manager to talk to. On a\nstatic mesh they are cross-agent admin operations. On a user-auth mesh, your own agents (any agent\nunder your owner) need only the `spawn` scope; another owner's agent needs `admin` on your ledger\nrow ([identity & auth](identity-and-auth.md)). Launch detached agents with\n[`spawn --detach`](#spawn).\n\n## personas\n\n```bash\ncotal personas list [-v] [--running]\ncotal personas show <name>\ncotal personas edit <name>\ncotal personas new <name> (--prompt <t> | --from <f>) [--role <r>] [--model <m>]\ncotal personas rm <name> --force\n```\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--space <s>` / `--server <url>` / `--creds <path>` | resolved mesh | Which mesh's persona catalog |\n| `--role <r>` | \u2014 | `new`: the persona's role |\n| `--model <m>` | \u2014 | `new`: the persona's model |\n| `--prompt <t>` | \u2014 | `new`: the persona's prompt text |\n| `--from <f>` | \u2014 | `new`: seed the prompt from a file |\n| `--verbose`, `-v` | off | `list`: include role / model / description |\n| `--running` | off | `list`: mark personas live on the mesh |\n| `--force` | \u2014 | `rm`: required, delete without prompting |\n\nPersonas are the local agent files under `.cotal/agents/` that `cotal spawn` launches. See\n[Agent files](agent-files.md) for the file format.\n\n## supervise\n\n```bash\ncotal supervise [--runtime <name>] [--space <s>] [--server <url>] [--spawn <names>]\n```\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--space <s>` | this folder's auth space | Space to supervise |\n| `--server <url>` | the local mesh | Broker URL |\n| `--runtime <name>` | `pty` | Agent runtime (`pty` built in; extension runtimes are explicit-only) |\n| `--console-port <n>` | \u2014 | Protocol-console port |\n| `--roster <file>` | \u2014 | Declarative roster to boot at startup |\n| `--launch <spec>` | \u2014 | Resolved manifest launch spec (from `up -f` / `spawn -f`) |\n| `--spawn <names>` | \u2014 | Comma-separated personas to pre-spawn at startup |\n\nThe manager is the agent supervisor and control plane: it answers `spawn --detach`, `stop`, `ps`,\n`attach`, and the `cotal_*` manager tools. `cotal up --detach` starts one for you; run `supervise`\ndirectly to recover a dead manager or drive a custom runtime. Default runtime is `pty`; install an\noptional provider first (`cotal ext add @cotal-ai/orca`, `@cotal-ai/tmux`, or `@cotal-ai/cmux`) and\nselect it explicitly. A missing provider or app fails loudly; there is no fallback. See [Deploy](deploy.md).\n\n## runtimes\n\n```bash\ncotal runtimes\n```\n\nLists every agent runtime the manager can spawn through: the built-in `pty`, the official providers\n(`orca`, `tmux`, `cmux`), and any custom provider installed via `cotal ext add`. Each installed\nprovider is probed so you can see what is actually reachable on this machine before selecting it:\n\n```\npty built in\norca installed \xB7 reachable @cotal-ai/orca\ntmux available \xB7 cotal ext add @cotal-ai/tmux\ncmux available \xB7 cotal ext add @cotal-ai/cmux\n```\n\n`installed \xB7 reachable` / `unreachable` is the provider's own `available()` probe; `available` means\nit is a known runtime you can add with the shown command. Selecting an unknown or uninstalled runtime\nvia `up`/`spawn --runtime <name>` fails loud and, for a known one, points at the exact `cotal ext add`\npackage \u2014 there is no silent fallback to `pty`.\n\n## send\n\n```bash\ncotal send dm <agent> \"<text>\" [--space <s>] [--server <url>] [--creds <path>]\ncotal send msg <channel> \"<text>\"\ncotal send ask <role> \"<text>\"\n```\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--space <s>` / `--server <url>` / `--creds <path>` | resolved mesh | Which mesh, and (off-registry) which credential |\n\nOne-shot messaging: connect, send a single direct message (`dm`), channel post (`msg`), or role\nask/anycast (`ask`), then exit. For a running conversation, agents use the mesh tools instead\n([MCP tools](mcp-tools.md)).\n\n## channels\n\n```bash\ncotal channels list\ncotal channels set <name> [--replay | --no-replay] [--window <n>] [--desc <s>] [--instructions <s>]\ncotal channels default --replay | --no-replay\n```\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--space <s>` / `--server <url>` / `--creds <path>` | resolved mesh | Target mesh |\n| `--replay` / `--no-replay` | \u2014 | `set`/`default`: replay history to new joiners, or not |\n| `--window <n>` | \u2014 | `set`: replay window size |\n| `--desc <s>` | \u2014 | `set`: one-line channel description |\n| `--instructions <s>` | \u2014 | `set`: instructions shown to joiners |\n\nInspects and edits the channel registry: replay policy, description, and joiner instructions. ACL\nsemantics (who may read or post) are set at mint / provision time, not here; see\n[Channels and permissions](channels-and-permissions.md). On a user-auth mesh, `list` rides your\nown login as is; `set` and `default` edit the registry over a short-lived channel-writer view,\nwhich needs ledger scope `admin` ([Identity & auth](identity-and-auth.md)).\n\n## history\n\n```bash\ncotal history clear --force [--dms] [--space <s>]\n```\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--space <s>` / `--server <url>` / `--creds <path>` | resolved mesh | Target mesh |\n| `--dms` | off | Also clear DM history |\n| `--force` | \u2014 | Required: clear without prompting |\n\nPurges retained channel history; `--dms` extends it to direct-message history. An alias of\n[`clean history`](#clean). On a user-auth mesh the purge rides a short-lived purger view over\nyour login, which needs ledger scope `admin` ([Identity & auth](identity-and-auth.md)).\n\n## console\n\n```bash\ncotal console [--plain] [--space <s>]\n```\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--space <s>` / `--server <url>` / `--creds <path>` | resolved mesh | Space to watch |\n| `--plain` | off | Line stream instead of the TUI |\n\nA live protocol view for a space: a lazygit-style TUI, or a plain line stream on `--plain`. On a\nuser-auth mesh it rides the read-only admin view over your login, which needs ledger scope\n`admin`. See [Watch a mesh](watch-a-mesh.md).\n\n## web\n\n```bash\ncotal ext add @cotal-ai/web # install once\ncotal web [--detach] [--port <n>] [--no-open] [--space <s>]\n```\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--space <s>` / `--server <url>` / `--creds <path>` | resolved mesh | Space to serve |\n| `--port <n>` | `7799` | HTTP port |\n| `--detach` | off | Run in the background; stop with `cotal down web` or bare `cotal down` |\n| `--no-open` | off | Don't open the browser |\n\nThe browser observability dashboard: presence, channels, and a live feed. It is **not** part of\n`cotal up`: it ships as the `@cotal-ai/web` extension (`cotal setup` installs it automatically; otherwise\n`cotal ext add @cotal-ai/web`). It self-registers `cotal web` into this surface and serves\n`http://cotal.localhost:7799` (loopback; `*.localhost` resolves in Chrome/Firefox/Edge; Safari may\nneed `http://127.0.0.1:7799`). On a user-auth mesh the dashboard rides the read-only admin view\nover your login, and a channel purge asks for its own channel-purger view per click; both need\nledger scope `admin`. Detached mode re-execs the current Cotal installation, writes diagnostics to\nthe mesh root's `.cotal/web.log`, and reports success only after the HTTP server answers. It requires\na recorded mesh root, but can be launched from any directory once `cotal up` has recorded the mesh.\nSee [Watch a mesh](watch-a-mesh.md).\n\n## mint\n\n```bash\ncotal mint <name> [--profile <agent|observer|admin>] [--out <path>] [--signer]\n```\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--profile <agent\\|observer\\|admin>` | `agent` | Credential profile |\n| `--out <path>` | `.cotal/auth/creds/<name>.creds` | Output path |\n| `--signer` | off | Emit a stripped account-signing file instead |\n| `--force` | off | With `--signer`: overwrite an existing file |\n| `--allow-subscribe <a,b>` | profile default | Read-ACL override |\n| `--allow-publish <a,b>` | profile default | Post-ACL override |\n\nMints a NATS creds file for a space in **static** auth mode, scoped to a profile and (optionally)\nexplicit read/post ACLs. `--signer` emits an account-signing file for delegating minting to another\nhost. A per-user-auth space refuses `mint`: agents there join under a logged-in user\n([`login`](#login-logout) + [`actor grant`](#actor)), never via a handed-out creds file. See\n[Identity and auth](identity-and-auth.md).\n\n## login, logout\n\n```bash\ncotal login --idp <auth base URL> [--client-id <id>]\ncotal logout --idp <auth base URL>\n```\n\nSigns you in to a per-user-auth mesh's IdP (device code flow) and caches the session; run it\nonce per machine. It prints your IdP subject, the id the operator grants against. After a\nlogin, every command on that mesh works under your identity: each connect takes a fresh IdP\nproof, exchanges it locally for a short-lived bearer, and is authorized against the actor\nledger at connect time. `logout` revokes the IdP session and clears the cache. See\n[identity & auth](identity-and-auth.md).\n\n## actor\n\n```bash\ncotal actor grant <actor> --sub <IdP subject> [--scope a,b] [--allow-subscribe a,b] [--allow-publish a,b] [--role <r>] [--label <l>]\ncotal actor revoke <actor> (--sub <IdP subject> | --owner <u_\u2026>)\ncotal actor list\n```\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--space <s>` | the folder's | Space whose ledger to manage |\n| `--sub <subject>` | \u2014 | The IdP subject (shown by `cotal login`) the actor belongs to |\n| `--owner <u_\u2026>` | \u2014 | The derived owner token (alternative to `--sub`) |\n| `--scope <a,b>` | `spawn,role:default` | Capability scope (`''` = none; `spawn` = may run agents, `role:<r>` = may delegate role r, `admin` = cross-agent control) |\n| `--allow-subscribe <a,b>` | `>` (all channels) | Channel read ACL; the user's envelope, their agents can never read beyond it |\n| `--allow-publish <a,b>` | `>` (all channels) | Channel post ACL; also the envelope for their agents' posting |\n| `--role <r>` | \u2014 | Role (scopes the task-queue consumer) |\n| `--label <l>` | \u2014 | Display label for `actor list` (never the IdP subject) |\n\nThe actor ledger is the single authorization source of a user-auth space: no row, no access.\nA bare `grant` is the **full** envelope (all channels, may spawn); the flags narrow it. A\nre-grant **replaces** the row, so to add a capability, re-grant with it added to the current\nscope (`cotal actor list` shows what a row holds). `revoke` denies the next exchange and the\nnext connect with no restart, and evicts the principal's live connections. Managed-agent rows\n(written by the spawn path) live in a disjoint row space this command never touches. See\n[identity & auth](identity-and-auth.md).\n\n## doctor\n\n```bash\ncotal doctor auth [--fix]\n```\n\nCredential-health diagnosis and repair for this folder's mesh: renders every managed\ncredential as healthy / near-expiry / expired and ends in `healthy` or the exact next\ncommand; `--fix` applies the repairs it can. The one surface every stale-credential error\npoints at.\n\n## join\n\n```bash\ncotal join --space <s> --name <n> [--role <r>] [--channel <c>]\ncotal join --link <url> | --token <t>\n```\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--space <s>` / `--server <url>` / `--creds <path>` | resolved mesh | Which mesh, and which credential |\n| `--name <n>` | \u2014 | Your presence name |\n| `--role <r>` | \u2014 | Your role |\n| `--channel <c>` | \u2014 | Channel to join |\n| `--kind <k>` | `agent` | Endpoint kind |\n| `--link <url>` | \u2014 | Join link (`cotal://\u2026`) |\n| `--token <t>` | \u2014 | Join token |\n| `--tls` | off | Connect over TLS |\n\nAn interactive presence: join a space under your own name and role, without launching an agent\nharness. A `--link` or `--token` supplies the where and the auth in one value. See\n[Spaces](spaces.md) and [Identity and auth](identity-and-auth.md).\n\n## Manifest deploys\n\nA `cotal.yaml` manifest declares a whole mesh (channels, personas, roles, and ACLs) in one file.\nThree commands consume it, plus a read-only validator:\n\n```bash\ncotal up -f cotal.yaml # boot a fresh mesh from the manifest\ncotal spawn -f cotal.yaml # deploy the manifest additively onto a running mesh\ncotal down -f cotal.yaml # tear that deploy down (or --run <id> for one run)\ncotal topology view -f cotal.yaml # validate + view the access graph, change nothing\n```\n\n`up -f` and `spawn -f` differ in target: `up -f` brings up a new broker and applies the manifest;\n`spawn -f` requires an already-reachable mesh and applies additively (ownership-scoped). On a\nuser-auth mesh, `spawn -f` deploys over your own login (the deployer view, gated on ledger scope\n`spawn`): the manifest's agents land under your owner, a manifest claiming another owner is\nrefused, and seeding new channels additionally needs scope `admin`. Both take\n`--dry-run` to print the plan without mutating anything. `topology` validates the manifest and\nrenders its channel / role / ACL graph. See [Define a team](define-a-team.md) and the\n[manifest reference](manifest.md).\n\n## ext\n\n```bash\ncotal ext add <npm-package>\ncotal ext remove <name>\ncotal ext list\ncotal ext seed [--repair|--reset|--force]\n```\n\nOperator-installed extensions: `add` installs an npm package into a cotal-owned prefix and records\nevery registry provider it contributes. Commands appear in help, completion, and dispatch; runtime\nproviders are lazy-loaded by commands such as `supervise`; local process providers participate in\n`status` and selective `down`. `remove` and `list` manage them. The `@cotal-ai/web` dashboard is the\ncanonical command/process example. Installed packages and their location are described in\n[config](config.md).\n\nRemoving an extension that owns a running local process is refused with the mesh root and its\n`cotal down <component>` command; stop it first so uninstalling the package never strands a process\nwhose lifecycle provider is gone.\n\n### Built-in connectors are seeded extensions\n\nThe four first-party agent connectors (`claude`, `opencode`, `hermes`, `pi`) are not compiled into\nthe binary. They are seeded on first run through the **same** `ext add` path a third party uses, and\nappear in `cotal ext list` like any other extension. So you can remove one you do not want\n(`cotal ext remove @cotal-ai/connector-hermes`), and a deliberately-removed connector STAYS removed\nacross upgrades. `cotal ext add <your-package>` adds a third-party connector the same way.\n\n`cotal ext seed` is the maintenance entry for that seeding (it runs automatically on the first real\ncommand of each boot, so you rarely call it):\n\n| Flag | Meaning |\n|---|---|\n| (none) | Reconcile: seed any never-seeded built-in, refresh a seeded one whose version the binary bumped, leave a removed one removed. A no-op once current. |\n| `--repair` | Recover after an interrupted seed or a lost authority (rebuilds the interrupted connector; restores the removed-vs-never-seeded record from its durable backup). |\n| `--reset` | Discard the record and re-seed all four built-ins. **Resurrects any you removed.** Rebuilds cleanly over corrupt seed state. |\n| `--force` | Re-seed the built-ins even when the version stamp is current or a downgrade. |\n\nThe default connector for a bare `cotal spawn` (no `--agent`) is `claude`; set `COTAL_DEFAULT_AGENT`\n(e.g. `opencode`) to change it. An `--agent` naming a removed connector fails loud with the exact\n`cotal ext add` to restore it. Set `COTAL_SKIP_CONNECTOR_SEED=1` to turn off the automatic first-run\nseed/refresh entirely (for a controlled or offline setup that manages connectors by hand); `cotal ext\nseed` still runs on request.\n\n## completion\n\n```bash\ncotal completion <bash|zsh|fish|powershell> # print a stub to eval / source\ncotal completion install [shell] # install it persistently\n```\n\nPrints or installs shell completion. Completion candidates come from each command's declared flags\nand, where useful, live mesh state (spaces, personas, managed agents) resolved offline.\n\n## feedback\n\n```bash\ncotal feedback \"<summary>\" [--type <t>] [--email <e>] [--details <text>]\n```\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--type <t>` | \u2014 | `bug` \\| `idea` \\| `friction` \\| `praise` \\| `other` |\n| `--details <text>` | \u2014 | Longer free-form details |\n| `--severity <s>` | \u2014 | `low` \\| `medium` \\| `high` |\n| `--area <a>` | \u2014 | The part of Cotal this concerns |\n| `--email <e>` | git email | Contact email (required on the keyless public path) |\n| `--name <n>` | \u2014 | Your name (optional) |\n| `--url <url>` | keyed / public intake | Intake URL override |\n| `--key <k>` | `COTAL_FEEDBACK_KEY` | Feedback key |\n\nSends feedback to the Cotal developers. With a key (`--key` / `COTAL_FEEDBACK_KEY`) it routes to the\nkeyed beta intake; without one it goes to the public `cotal.ai` intake and requires a contact email\n(`--email` / `COTAL_FEEDBACK_EMAIL`, else your git email). Run a self-hosted intake with\n[`feedback-intake`](#server-daemons).\n\n## Server daemons\n\nTwo long-lived infra roles ship with the CLI. They are not part of everyday operation; the delivery\ndaemon comes up automatically with `cotal up --detach` in auth mode.\n\n```bash\ncotal deliver --space <s> [--server <url>] [--creds <file>]\ncotal auth-service --space <s> --server <url> [--port <n>]\ncotal feedback-intake --keys <keys.json> [--port <n>] [--creds <file>]\n```\n\n`auth-service` runs a user-auth space's identity plane (the NATS auth callout plus the\nloopback token exchange and JWKS); `cotal up --user-auth` starts and supervises it for you,\nso you run it directly only to recover one by hand.\n\n`deliver` runs the server-side Plane-3 delivery daemon: the durable backstop and membership/ACL\nauthority. It is auth-mode-only and single-instance (`--shard`/`--shards` accept only `N=1`);\n`--dev-mint` mints a scoped cred from the local signer for standalone dev. See the\n[delivery daemon](delivery-daemon.md). `feedback-intake` runs a self-hosted feedback server\n(requires `--keys` and a scoped `--creds`), announcing submissions into a space channel; flags\ninclude `--host`/`--port`, `--store`, `--space`/`--channel`, `--max-bytes`, and `--rate-limit`.\n\n## Plumbing\n\n`cotal __complete <words\u2026>` is the internal entry the shell-completion stubs call to emit candidates\nfor the current command line; you never run it directly. `cotal agent-bearer` is machine-facing\nplumbing on user-auth meshes: spawned agents exec it to print a fresh short-lived bearer from their\nspawn-time secret; you never run it directly either. (`cotal start` is a removed tombstone: it\nerrors and points you to `cotal spawn --detach`.)\n"
|
|
42665
|
+
"body": "# `cotal` CLI reference\n\n> **Reference**: describes the TypeScript reference implementation (the `cotal` CLI), not the wire contract. \xB7 **For:** operators \xB7 **Wire contract:** [SPEC](../SPEC.md)\n\n`cotal` is the operator command line for the reference implementation: bring a mesh up, mint\nidentities, launch agents, watch what they do, and tear it all down. It is a thin client over the\nwire contract: the normative subjects and schemas live in the [SPEC](../SPEC.md); this page is\nlookup material for the commands, not a walkthrough; if you are new, start with\n[Getting started](getting-started.md).\n\n## Running it\n\n```bash\nnpm install -g cotal-ai # puts `cotal` on your PATH (needs Node 20+)\ncotal --help # every command, grouped\ncotal <command> --help # one command's flags and usage\n```\n\n`npx cotal-ai <command>` runs it without a global install; in a dev clone, `pnpm cotal <command>`\nruns it through `tsx` with no build step. Bare `cotal` prints help. Every command generates its own\n`--help`, usage, and shell completion from its declared flags.\n\nCommands come from the surfaces the binary composes: the base mesh CLI, the manager\n(`supervise`), and the delivery daemon (`deliver`), plus any operator-installed extensions.\n`cotal ext add <npm-package>` installs any registry providers a package contributes: commands,\nruntimes, and local process lifecycle descriptors. The `web` dashboard and optional manager\nruntimes ship this way.\n\n## Commands\n\n| Area | Command | Purpose |\n|---|---|---|\n| Set up & lifecycle | [`setup`](#setup) | Guided, configure-only setup (installs, seeds personas; launches nothing) |\n| Set up & lifecycle | [`up`](#up) | Start a local mesh (nats-server + JetStream), or boot a whole manifest with `-f` |\n| Set up & lifecycle | [`down`](#down) | Stop the whole stack, selected registered components, or a manifest deploy |\n| Set up & lifecycle | [`backup`](#backup-and-restore) | Create an offline full-space or registry-only artifact from a preserved cut |\n| Set up & lifecycle | [`clean`](#clean) | Configurable cleanup: purge history (live), or wipe the local store / identity (stopped) |\n| Set up & lifecycle | [`meshes`](#meshes-use-status) | List the running meshes on this machine |\n| Set up & lifecycle | [`use`](#meshes-use-status) | Set the default mesh a bare `cotal spawn` joins |\n| Set up & lifecycle | [`status`](#meshes-use-status) | Read-only diagnostics for setup, processes, and the selected mesh |\n| Agents & personas | [`spawn`](#spawn) | Launch an agent from a persona (foreground, or `--detach` via the manager) |\n| Agents & personas | [`models`](#models) | List connector model catalogs and variants from the manager |\n| Agents & personas | [`ps`](#ps-stop-attach) | List managed agents and their mesh status |\n| Agents & personas | [`stop`](#ps-stop-attach) | Ask the manager to stop a managed agent |\n| Agents & personas | [`attach`](#ps-stop-attach) | Stream and drive a managed agent's terminal (pty runtime) |\n| Agents & personas | [`personas`](#personas) | List, show, edit, create, or remove local personas |\n| Agents & personas | [`supervise`](#supervise) | Run a manager daemon (the agent supervisor / control plane) |\n| Agents & personas | [`runtimes`](#runtimes) | List the agent runtimes the manager can spawn through and whether each is reachable |\n| Messaging & watching | [`endpoints`](#endpoints) | List every endpoint in the live presence roster, including infrastructure |\n| Messaging & watching | [`send`](#send) | Send one message, then exit: DM a peer, post a channel, or ask a role |\n| Messaging & watching | [`channels`](#channels) | Inspect or set the channel registry |\n| Messaging & watching | [`history`](#history) | Clear retained message history |\n| Messaging & watching | [`console`](#console) | Live protocol view for a space (TUI, or `--plain` line stream) |\n| Messaging & watching | [`web`](#web) | Browser dashboard (installed as the `@cotal-ai/web` extension) |\n| Auth & meshes | [`mint`](#mint) | Mint a creds file for a space (static auth mode) |\n| Auth & meshes | [`login`](#login-logout) | Sign in to a per-user-auth mesh's IdP (once per machine) |\n| Auth & meshes | [`logout`](#login-logout) | Revoke the IdP session and clear the cached login |\n| Auth & meshes | [`actor`](#actor) | Manage a user-auth space's actor ledger (grant / revoke / list) |\n| Auth & meshes | [`doctor`](#doctor) | Credential-health diagnosis and repair (`doctor auth`) |\n| Auth & meshes | [`join`](#join) | Join a space as your own presence (interactive) |\n| Manifest | [`topology`](#manifest-deploys) | Validate and view a mesh manifest's access graph (read-only) |\n| Extensions & misc | [`ext`](#ext) | Install / remove operator CLI extensions |\n| Extensions & misc | [`completion`](#completion) | Print or install shell completion |\n| Extensions & misc | [`feedback`](#feedback) | Send feedback to the Cotal developers |\n| Extensions & misc | [`deliver`](#server-daemons) | Run the server-side Plane-3 delivery daemon |\n| Extensions & misc | [`feedback-intake`](#server-daemons) | Run a self-hosted feedback intake server |\n\nThe manifest modes of `up`, `spawn`, and `down` (`-f <cotal.yaml>`) plus `topology` are covered\ntogether under [Manifest deploys](#manifest-deploys).\n\n## setup\n\n```bash\ncotal setup [--full] [--demo] [--yes]\n```\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--full` | off | Redo the full guided flow (implies `--demo`) |\n| `--demo` | off | Also seed the guided expert team (`david`, `sven`, `me`) |\n| `--yes`, `-y` | off | Non-interactive accept-all (for agents / CI) |\n\nGuided setup is **configure-only**: it checks prerequisites, installs the Claude Code plugin, and\nseeds persona files, and it launches nothing (no mesh, no web, no manager). First run gets the\nnarrated flow; later runs print a status card. By default it seeds one `default` persona; the\n`david`/`sven`/`me` team is opt-in via `--demo`. See [Getting started](getting-started.md) and, for\nmaintainers, [setup internals](setup-internals.md).\n\n## up\n\n```bash\ncotal up [--detach] [--open] [--space <s>] [--server <url>] [--channels <path>] [--runtime <name>]\ncotal up --restore <dir> [--restore-only registry] [--accept-missing-source]\ncotal up -f <cotal.yaml> [--dry-run] [--runtime <name>]\n```\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--server <url>` | auto (free local port) | Listen URL override |\n| `--host <host>` | \u2014 | Bind host override |\n| `--space <s>` | the folder's name | Space name |\n| `--store-dir <dir>` | \u2014 | JetStream store directory |\n| `--channels <path>` | `.cotal/channels.json` if present | Channel-registry seed file (JSON). An explicit path that is missing is an error |\n| `--restore <dir>` | \u2014 | Restore a completed offline backup before exposing the normal listener |\n| `--restore-only registry` | artifact selection | Restore only the registry component |\n| `--accept-missing-source` | off | Explicit disaster consent when the inode-bound preserved source is absent |\n| `--open` | off (auth) | Unauthenticated dev mesh: no JWT, no ACLs |\n| `--user-auth` | off | Per-user auth: people `cotal login`; connects are authorized against the actor ledger |\n| `--idp <url>` | \u2014 | With `--user-auth`: the IdP auth base URL to pin on first enable |\n| `--detach` | off | Run in the background (stop with `cotal down`) |\n| `--file <cotal.yaml>`, `-f` | \u2014 | Launch a whole mesh from a manifest |\n| `--dry-run` | off | With `-f`: print the plan, mutate nothing |\n| `--runtime <name>` | `pty` (or the manifest's, with `-f`) | Agent runtime for the mesh manager (`pty` built in; others are installed extensions, explicit-only). Resolved + probed before the broker starts; an uninstalled/unreachable runtime fails loud. With `-f`, overrides the manifest's runtime |\n\n`cotal up` boots a local nats-server with JetStream and, in auth mode (the default), JWT auth and\nper-agent ACLs; `--detach` records the mesh so `cotal spawn` from any directory can find it. With no\n`--server`, it auto-selects a free port if the default address is taken; an explicit `--server`\nstays fail-loud on collision. `--detach` also brings up the control plane (delivery daemon in auth\nmode, then the manager). The `-f` form is a [manifest deploy](#manifest-deploys); see\n[Run a mesh](run-a-mesh.md).\n\n`--user-auth --idp <url>` starts the space's auth service alongside the broker (the NATS\nauth callout plus the loopback token exchange); it is torn down with `cotal down`, and a\nre-run of `cotal up` heals a dead service on a running broker. `--user-auth` and `--open`\ncontradict each other and are refused loudly; a running broker cannot change auth mode\nwithout a `cotal down` first. See [identity & auth](identity-and-auth.md).\n\n## down\n\n```bash\ncotal down\ncotal down --preserve-state [--store-dir <dir>]\ncotal down manager [delivery auth web nats ...]\ncotal down -f <cotal.yaml> | --run <id> [--dry-run]\n```\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--file <cotal.yaml>`, `-f` | \u2014 | Tear down this manifest's deploy |\n| `--run <id>` | \u2014 | Tear down one `spawn -f` run by id |\n| `--dry-run` | off | Print the manifest teardown or selected components, mutate nothing |\n| `--preserve-state` | off | Bare whole stack only: fence the manager, retain principals and durable state, stop and prove the stack down, then publish `ready` |\n| `--store-dir <dir>` | `.cotal/nats` | With `--preserve-state`: the actual store path (required for a custom store) |\n\nBare `cotal down` stops the whole local stack in dependency order. Positional component names stop\nonly those self-registered local processes; for example, `cotal down manager` leaves delivery and\nthe broker running, and `cotal down web` is available when the web extension is installed. The\n`-f` / `--run` forms tear down a [manifest deploy](#manifest-deploys) without stopping the whole mesh\nand cannot be combined with component names. Stopping `nats` alone is refused while an unselected\nregistered daemon is still live; include those components or use bare `cotal down`.\n\nNormal `down` remains destructive at the logical identity/durable layer. `--preserve-state` is a\ndifferent maintenance transition: it suppresses leave/deprovision cleanup, persists the manager's\nsame-principal resume inventory, stops the entire stack without removing run/auth artifacts, and\npublishes a stable inode-bound cut only after every recorded process is proven stopped and the exact\nrecorded NATS endpoint is unreachable. A missing or stale broker pidfile never counts as stopped. The\nattempt is bound durably before the manager is fenced, the resume document and attempt-bound\n`cut-intent` are fsynced before manager commit, and the manager's commitment itself is journaled\n(`cut-committed`) before any process stops. A retry after a crash at any of those boundaries reuses\nthe exact recorded attempt and finishes the remaining stop and endpoint proofs idempotently, without\nneeding the (by then intentionally dead) manager. A partial cut never publishes `ready`. It cannot\nbe combined with component names, manifest teardown, or `--dry-run`.\n\n## clean\n\n```bash\ncotal clean <history|store|all> --force\ncotal clean restore-attempt --attempt <id> --force\ncotal clean restore-fallback --attempt <id> --force\n```\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--space <s>` / `--server <url>` / `--creds <path>` | resolved mesh | `history`: target mesh |\n| `--dms` | off | `history`: also clear DM history |\n| `--store-dir <dir>` | `.cotal/nats` | `store`/`all`: JetStream store directory |\n| `--force` | \u2014 | Required: destructive, no prompting |\n| `--attempt <id>` | \u2014 | `restore-attempt`: exact stale pre-commit attempt; `restore-fallback`: matching healthy committed restore |\n\nOne configurable cleanup verb; every target requires `--force`.\n\n- `history` purges the retained message backlog on the **running** broker (channels, plus DMs\n with `--dms`). The same operation as [`history clear`](#history), which stays as an alias.\n- `store` deletes the **stopped** mesh's JetStream store (`.cotal/nats`): streams, durable\n consumers, and messages. This is the reset for stale on-disk broker state, e.g. durables\n minted by an older, incompatible Cotal generation surviving a `down`/`up` cycle.\n- `all` is `store` plus the space identity (`.cotal/auth`), the local creds and markers tied to\n it, any crash residue a normal `down` would have swept (stale pidfiles, `run/`), and the mesh's\n registry entry; the next `cotal up` mints a fresh identity.\n\n`history` needs the mesh up; `store` and `all` refuse while any recorded mesh process is still\nalive or any same-root recorded broker endpoint remains reachable (run `cotal down` first). Personas\n(`.cotal/agents`) and logs are never touched. A custom\nstore location is not recorded anywhere, so `--store-dir` must repeat whatever the mesh was\nlaunched with. Custom cleanup targets must contain either the Cotal store-generation marker or a\nreal `jetstream/` store directory; filesystem roots, project roots, and Cotal auth/maintenance trees\nare always refused.\n\n`store` and `all` also refuse every maintenance journal state. After a healthy committed restore,\n`restore-fallback` is the only supported way to remove the recorded unchanged old-store inode; it\nnever deletes the active target, requires both the exact attempt id and `--force`, and retires the\ncompleted restore journal so a later `down --preserve-state` can start a new backup cycle.\n\n## backup and restore\n\n```bash\ncotal down --preserve-state [--store-dir <dir>]\ncotal backup create <dir> [--only full|registry] [--store-dir <dir>]\ncotal up --restore <dir> [--restore-only registry] [--accept-missing-source]\n```\n\nBackup is offline-only. It requires the stable `ready` record from `down --preserve-state`, an exact\nstore match, no live recorded process, and an unreachable exact endpoint from the recorded cut.\nThat endpoint is probed immediately before cloning, so a live broker with a missing or stale pidfile\nis still refused. It claims the cut, reflink/copies the stopped source to a\nprivate attempt clone, and opens only that clone on a random loopback bootstrap broker with an\nindependent parent/deadline watchdog. It validates the canonical stream and pull-consumer inventory,\nwrites native snapshots with consumers excluded, and stores conservative contiguous ACK-floor\ncheckpoints separately. The original store is never opened by the backup broker, and the stack is\nnot restarted implicitly. Artifact destinations must not overlap the preserved source or maintenance\nattempt tree. Restore artifacts and targets likewise cannot nest inside or contain each other, the\npreserved source, or the maintenance attempt tree.\n\n`full` is the default and indivisible: channel registry, CHAT/DM/TASK/INBOX/DLV, ACL, MEMBERS, and\nvalidated durable checkpoints. `registry` is the sole partial artifact. Presence, derived membership\nfeed, leases, native ephemeral/history consumers, credentials, keys, tokens, owner secrets, and actor\nledger files are excluded. Artifacts are exclusively created `0700`; snapshot/checkpoint files and\nthe manifest are `0600`; `manifest.json` is written last with exact sizes and SHA-256 values. The\ndirectory is trusted operator input: hashes detect corruption, not malicious rewriting.\n\nRestore validates and stages the exact allowlisted artifact bytes before moving or creating a store.\nIt requires the same space and existing trust state. The whole pre-commit window holds a journaled\nliveness claim (coordinator, watchdogs, brokers, absolute deadline): ordinary `up` and a repeated\n`up --restore` refuse while the claim is live, and a stale attempt is recovered only after the\ndeadline has elapsed and every recorded owner is proven dead \u2014 automatically by a retried\n`up --restore`, or explicitly with `cotal clean restore-attempt --attempt <id> --force`. Nothing\never rolls back a live attempt. A registry-only artifact restores as registry-only whether or not\n`--restore-only registry` is passed; omitted infrastructure is always created and the exact\npost-restore stream inventory is asserted before commit intent. Ordinary `up` from a preserved cut\nresumes only the exact recorded source store and runtime; a contradicting `--store-dir` or\n`--runtime` fails in preflight. Authenticated restores validate the complete\nspace trust bundle before staging, including nkeys, seed matches, JWTs, signers, and space binding;\nfull restores commit to the validated operator, system-account, data-account, and active-signer root\nchain in addition to the static/user authority fingerprint. The composed commitment is revalidated\nimmediately before store mutation and never includes secret seeds. Restore never creates fresh auth.\nSame-path restores atomically retain the old\nsource at the journaled fallback path; alternate targets retain it in place; a missing canonical\nsource needs explicit `--accept-missing-source`. Quarantine and target restores use current canonical\nconfigs on isolated random-loopback brokers, never expose native snapshot consumers, and publish a\ncommit-intent immediately before the normal listener starts. Archive bytes never instantiate the real\ntarget: after quarantine validation, every stream is re-snapshotted from the validated quarantine\nstate into attempt-owned sanitized files, and the target is restored solely from those. Before that boundary, failure rolls back\nthe attempt-owned target; after it, ambiguity preserves both stores and records forward-repair\nrecourse. The cooperative maintenance lock excludes Cotal commands, not arbitrary raw NATS processes.\n\nBootstrap brokers in every auth mode \u2014 including open \u2014 mount the store under a local account with\nrandom operation-specific logins only, each carrying the exact per-phase subject permission matrix;\nnormal static credentials and user-auth sentinel/bearer connections are rejected, and no auth\nservice or callout starts. Open mode differs only in its account label, never in authority. Inventory, each stream snapshot,\nrestore initiation, exact upload id, validation, and each checkpoint recreation use separate exact\nauthorities. Every checkpoint carries the source stream's message/first/last sequence state and must\nmatch its snapshot record before mutation; core then derives and validates the only allowed start\npolicy. TASK is not a CLI exception: the same core checkpoint API recreates its canonical `DeliverAll`\nWorkQueue durable because acknowledged tasks are absent from retention and NATS forbids a\nstart-sequence policy there. Registry-only restore creates every omitted canonical stream and transient\nbucket on the isolated target before the normal listener is exposed. It deliberately does not resume\nretained agents or recreate their DM/DLV/TASK/ACL state; their identity material stays retained and\nstopped rather than being reprovisioned into a partial restore.\n\nAfter listener readiness, the manager starts attempt-bound, validates retained credentials/tokens\nwithout granting or reprovisioning, and resumes the exact persisted principals under cleanup\nsuppression. Registry-only restore uses the same flow with an empty agent set. `commitResume` is an\nidempotent validation barrier only: success must be `awaitingFinalize` with an attempt-bound 64-hex\ncommit token and does not release suppression. Under the workspace lock, the CLI first fsyncs that\nexact evidence as `manager-committed` (restore) or `resume-committed` (ordinary resume), then calls\ntoken-bound `finalizeResume`; only an `active` response for the exact token releases suppression. The\nCLI records the same token in finalization evidence before a restore becomes `active`, or before an\nordinary resume retires and consumes the marker. Re-entry from either committed state skips the prior\nidempotent activation/commit phases, retries finalization with the durable token, and finishes the\nworkspace transition. Failure before finalization preserves the committed state and cleanup\nsuppression; it is not rewritten through a degraded transition. Re-entry between any two earlier\nboundaries reuses the same attempt and may retry the idempotent phases without deleting retained state. A missing or\nchanged per-agent dependency is a named fail-closed result; the journal becomes degraded and remains\navailable for forward repair. A retry from `resume-intent`,\n`resume-active`, or `resume-degraded` reuses the same attempt and inventory after the prior listener is\nproven stopped. Every normal restore listener has an unguessable attempt-bound NATS server name. The\nCLI fsyncs its exact name/nonce, canonical endpoint, process owner, and generation-bound target identity\nimmediately after spawn. Re-entry accepts a surviving listener only when its INFO server name, live PID\nrecord, endpoint, and target identity all match that proof; degraded restore repair then moves through\nthe guarded workspace transition only after manager commit. If an uncommitted bound owner is provably\ndead, recovery retires that exact proof under the maintenance lock and binds a fresh listener for the\nsame attempt, endpoint, and target with a new nonce and server name. A live foreign/mismatched listener\nor ambiguous owner is preserved and refused, never adopted by reachability alone. A reconstructed\ncommit/degraded attempt without either the exact bound proof or a durable dead-listener replacement\nrecord fails closed even when the recorded port is free. A later ordinary startup may pass an `active`\nrestore only when its details prove manager commit and its exact recorded listener is dead.\n\n## meshes, use, status\n\n```bash\ncotal meshes\ncotal use <space>\ncotal status [--space <s>] [--server <url>]\n```\n\n`meshes` lists the running meshes on this machine; a `*` marks the `current` default a bare\n`cotal spawn` joins. `use <space>` sets that default when several are running. `status` is a\nread-only report across four sections: machine prerequisites, this folder's `.cotal/`, the\nrecorded meshes, and a live snapshot of the selected mesh (roster, channels, membership feed).\n`status` takes only `--space` / `--server` to pick the mesh to inspect; it starts nothing.\n\n## spawn\n\n```bash\ncotal spawn [<persona>] [--detach] [--name <n>] [--agent <a>] [--model <m>] [--variant <v>] [--prompt <text>] [--cwd <dir>]\ncotal spawn -f <cotal.yaml> [--dry-run]\n```\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--space <s>` | resolved mesh | Target space |\n| `--server <url>` | registry entry | Broker URL override |\n| `--creds <path>` | \u2014 | Control-caller creds for an off-registry manager (`--detach` only) |\n| `--name <n>` | persona's `name:` | Presence-name override (does not choose the persona) |\n| `--config <persona-or-path>` | \u2014 | Persona catalog name or file path; wins over the positional |\n| `--agent <a>` | `COTAL_DEFAULT_AGENT`, else `claude` | Connector type (`claude`, `opencode`, `hermes`, \u2026) |\n| `--role <r>` | persona's `role:` | Role override |\n| `--model <m>` | persona's `model:` | Model override |\n| `--variant <v>` | persona's `variant:` | Model variant override (connector-defined; e.g. OpenCode reasoning tiers) |\n| `--cwd <dir>` | this cwd | Working directory to root the agent at |\n| `--prompt <text>` | \u2014 | Initial prompt auto-submitted at start |\n| `--resume <id>` | \u2014 | Fork an existing session id into the mesh (claude only) |\n| `--transcript` / `--no-transcript` | off | Mirror the session transcript to `tr-<name>` |\n| `--share-tools <sel>` | none | Share named operator MCP servers with the agent |\n| `--subscribe <a,b>` | persona's | Channel read-set override |\n| `--allow-subscribe <a,b>` | = subscribe | Read-ACL override |\n| `--allow-publish <a,b>` | deny | Post-ACL override |\n| `--detach`, `-d` | off | Launch via the manager into a detached PTY (reattach with `cotal attach`) |\n| `--file <cotal.yaml>`, `-f` | \u2014 | Deploy a manifest onto the running mesh |\n| `--dry-run` | off | With `-f`: print the plan, mutate nothing |\n| `--allow-stale <a,b>` | \u2014 | With `-f`: waive named stale agents (apply-only) |\n| `--runtime <name>` | manifest's | With `-f`: override the manifest's runtime |\n\nThe persona (`--config` > positional > `COTAL_DEFAULT_PERSONA` > `default`) is loaded from the\ntarget mesh's `.cotal/agents/`; the launch flags override the file. Foreground runs the agent\nattached to your terminal; `--detach` hands the launch to the running manager. Both modes get the\ndurable backstop on a mesh that runs the delivery daemon; `--live-only` skips it for a foreground\nspawn (messages posted while it is disconnected are then not replayed). A foreground exit retires\nthe agent's creds and broker footprint, like a manager despawn. See\n[Connect Claude Code](connect-claude.md) and [Agent files](agent-files.md); `-f` is a\n[manifest deploy](#manifest-deploys). (`cotal start` was merged into `cotal spawn --detach`.)\n\n## models\n\n```bash\ncotal models [--agent <connector>] [--refresh]\n```\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--space <s>` / `--server <url>` / `--creds <path>` | resolved mesh | Which manager to reach |\n| `--agent <connector>` | all registered connectors | Connector whose catalog to list |\n| `--refresh` | off | Ask the connector to refresh its provider cache |\n\nAsks the running manager for each connector's model catalog (model ids plus their variants)\nfor connectors that expose one (OpenCode today; a connector without a catalog says so). Pick a\nresult with `cotal spawn --model <provider/model> --variant <v>`.\n\n## endpoints\n\n```bash\ncotal endpoints [--space <s>] [--server <url>] [--creds <path>]\n```\n\nLists the mesh presence roster: agents, the manager, and any other protocol endpoint, with each\nendpoint's role, kind, status, and current activity. Unlike `ps`, this is a read-only presence view;\nit is not limited to child processes owned by the manager.\n\n## ps, stop, attach\n\n```bash\ncotal ps [--space <s>]\ncotal stop --name <n> [--space <s>]\ncotal attach --name <n> [--space <s>]\n```\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--space <s>` / `--server <url>` / `--creds <path>` | resolved mesh | Which manager to reach |\n| `--name <n>` | \u2014 | Managed agent to stop / attach (required) |\n\nThese are operator clients over the running manager's control plane. `ps` lists managed agents with\ntheir mesh status (`starting\u2026` / `working` / `waiting` / `offline`); on a user-auth mesh it also\nrenders each managed agent's last credential-refresh outcome, fail-closed. `attach` streams and\ndrives an agent's terminal on the `pty` runtime; detach with the escape key (Ctrl-] by default; see\n[`COTAL_DETACH_KEY`](config.md)). `stop` and `attach` need a running manager to talk to. On a\nstatic mesh they are cross-agent admin operations. On a user-auth mesh, your own agents (any agent\nunder your owner) need only the `spawn` scope; another owner's agent needs `admin` on your ledger\nrow ([identity & auth](identity-and-auth.md)). Launch detached agents with\n[`spawn --detach`](#spawn).\n\n## personas\n\n```bash\ncotal personas list [-v] [--running]\ncotal personas show <name>\ncotal personas edit <name>\ncotal personas new <name> (--prompt <t> | --from <f>) [--role <r>] [--model <m>]\ncotal personas rm <name> --force\n```\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--space <s>` / `--server <url>` / `--creds <path>` | resolved mesh | Which mesh's persona catalog |\n| `--role <r>` | \u2014 | `new`: the persona's role |\n| `--model <m>` | \u2014 | `new`: the persona's model |\n| `--prompt <t>` | \u2014 | `new`: the persona's prompt text |\n| `--from <f>` | \u2014 | `new`: seed the prompt from a file |\n| `--verbose`, `-v` | off | `list`: include role / model / description |\n| `--running` | off | `list`: mark personas live on the mesh |\n| `--force` | \u2014 | `rm`: required, delete without prompting |\n\nPersonas are the local agent files under `.cotal/agents/` that `cotal spawn` launches. See\n[Agent files](agent-files.md) for the file format.\n\n## supervise\n\n```bash\ncotal supervise [--runtime <name>] [--space <s>] [--server <url>] [--spawn <names>]\n```\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--space <s>` | this folder's auth space | Space to supervise |\n| `--server <url>` | the local mesh | Broker URL |\n| `--runtime <name>` | `pty` | Agent runtime (`pty` built in; extension runtimes are explicit-only) |\n| `--console-port <n>` | \u2014 | Protocol-console port |\n| `--roster <file>` | \u2014 | Declarative roster to boot at startup |\n| `--launch <spec>` | \u2014 | Resolved manifest launch spec (from `up -f` / `spawn -f`) |\n| `--spawn <names>` | \u2014 | Comma-separated personas to pre-spawn at startup |\n\nThe manager is the agent supervisor and control plane: it answers `spawn --detach`, `stop`, `ps`,\n`attach`, and the `cotal_*` manager tools. `cotal up --detach` starts one for you; run `supervise`\ndirectly to recover a dead manager or drive a custom runtime. Default runtime is `pty`; install an\noptional provider first (`cotal ext add @cotal-ai/orca`, `@cotal-ai/tmux`, or `@cotal-ai/cmux`) and\nselect it explicitly. A missing provider or app fails loudly; there is no fallback. See [Deploy](deploy.md).\n\n## runtimes\n\n```bash\ncotal runtimes\n```\n\nLists every agent runtime the manager can spawn through: the built-in `pty`, the official providers\n(`orca`, `tmux`, `cmux`), and any custom provider installed via `cotal ext add`. Each installed\nprovider is probed so you can see what is actually reachable on this machine before selecting it:\n\n```\npty built in\norca installed \xB7 reachable @cotal-ai/orca\ntmux available \xB7 cotal ext add @cotal-ai/tmux\ncmux available \xB7 cotal ext add @cotal-ai/cmux\n```\n\n`installed \xB7 reachable` / `unreachable` is the provider's own `available()` probe; `available` means\nit is a known runtime you can add with the shown command. Selecting an unknown or uninstalled runtime\nvia `up`/`spawn --runtime <name>` fails loud and, for a known one, points at the exact `cotal ext add`\npackage \u2014 there is no silent fallback to `pty`.\n\n## send\n\n```bash\ncotal send dm <agent> \"<text>\" [--space <s>] [--server <url>] [--creds <path>]\ncotal send msg <channel> \"<text>\"\ncotal send ask <role> \"<text>\"\n```\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--space <s>` / `--server <url>` / `--creds <path>` | resolved mesh | Which mesh, and (off-registry) which credential |\n\nOne-shot messaging: connect, send a single direct message (`dm`), channel post (`msg`), or role\nask/anycast (`ask`), then exit. For a running conversation, agents use the mesh tools instead\n([MCP tools](mcp-tools.md)).\n\n## channels\n\n```bash\ncotal channels list\ncotal channels set <name> [--replay | --no-replay] [--window <n>] [--desc <s>] [--instructions <s>]\ncotal channels default --replay | --no-replay\n```\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--space <s>` / `--server <url>` / `--creds <path>` | resolved mesh | Target mesh |\n| `--replay` / `--no-replay` | \u2014 | `set`/`default`: replay history to new joiners, or not |\n| `--window <n>` | \u2014 | `set`: replay window size |\n| `--desc <s>` | \u2014 | `set`: one-line channel description |\n| `--instructions <s>` | \u2014 | `set`: instructions shown to joiners |\n\nInspects and edits the channel registry: replay policy, description, and joiner instructions. ACL\nsemantics (who may read or post) are set at mint / provision time, not here; see\n[Channels and permissions](channels-and-permissions.md). On a user-auth mesh, `list` rides your\nown login as is; `set` and `default` edit the registry over a short-lived\nchannel-writer view, which needs ledger scope `admin` ([Identity & auth](identity-and-auth.md)).\n\n\n## history\n\n```bash\ncotal history clear --force [--dms] [--space <s>]\n```\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--space <s>` / `--server <url>` / `--creds <path>` | resolved mesh | Target mesh |\n| `--dms` | off | Also clear DM history |\n| `--force` | \u2014 | Required: clear without prompting |\n\nPurges retained channel history; `--dms` extends it to direct-message history. An alias of\n[`clean history`](#clean). On a user-auth mesh the purge rides a short-lived purger view over\nyour login, which needs ledger scope `admin` ([Identity & auth](identity-and-auth.md)).\n\n## console\n\n```bash\ncotal console [--plain] [--space <s>]\n```\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--space <s>` / `--server <url>` / `--creds <path>` | resolved mesh | Space to watch |\n| `--plain` | off | Line stream instead of the TUI |\n\nA live protocol view for a space: a lazygit-style TUI, or a plain line stream on `--plain`. On a\nuser-auth mesh it rides the read-only admin view over your login, which needs ledger scope\n`admin`. See [Watch a mesh](watch-a-mesh.md).\n\n## web\n\n```bash\ncotal ext add @cotal-ai/web # install once\ncotal web [--detach] [--port <n>] [--no-open] [--space <s>]\n```\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--space <s>` / `--server <url>` / `--creds <path>` | resolved mesh | Space to serve |\n| `--port <n>` | `7799` | HTTP port |\n| `--detach` | off | Run in the background; stop with `cotal down web` or bare `cotal down` |\n| `--no-open` | off | Don't open the browser |\n\nThe browser observability dashboard: presence, channels, and a live feed. It is **not** part of\n`cotal up`: it ships as the `@cotal-ai/web` extension (`cotal setup` installs it automatically; otherwise\n`cotal ext add @cotal-ai/web`). It self-registers `cotal web` into this surface and serves\n`http://cotal.localhost:7799` (loopback; `*.localhost` resolves in Chrome/Firefox/Edge; Safari may\nneed `http://127.0.0.1:7799`). On a user-auth mesh the dashboard rides the read-only admin view\nover your login, and a channel purge asks for its own channel-purger view per click; both need\nledger scope `admin`. Detached mode re-execs the current Cotal installation, writes diagnostics to\nthe mesh root's `.cotal/web.log`, and reports success only after the HTTP server answers. It requires\na recorded mesh root, but can be launched from any directory once `cotal up` has recorded the mesh.\nSee [Watch a mesh](watch-a-mesh.md).\n\n## mint\n\n```bash\ncotal mint <name> [--profile <agent|observer|admin>] [--out <path>] [--signer]\n```\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--profile <agent\\|observer\\|admin>` | `agent` | Credential profile |\n| `--out <path>` | `.cotal/auth/creds/<name>.creds` | Output path |\n| `--signer` | off | Emit a stripped account-signing file instead |\n| `--force` | off | With `--signer`: overwrite an existing file |\n| `--allow-subscribe <a,b>` | profile default | Read-ACL override |\n| `--allow-publish <a,b>` | profile default | Post-ACL override |\n\nMints a NATS creds file for a space in **static** auth mode, scoped to a profile and (optionally)\nexplicit read/post ACLs. `--signer` emits an account-signing file for delegating minting to another\nhost. A per-user-auth space refuses `mint`: agents there join under a logged-in user\n([`login`](#login-logout) + [`actor grant`](#actor)), never via a handed-out creds file. See\n[Identity and auth](identity-and-auth.md).\n\n## login, logout\n\n```bash\ncotal login --idp <auth base URL> [--client-id <id>]\ncotal logout --idp <auth base URL>\n```\n\nSigns you in to a per-user-auth mesh's IdP (device code flow) and caches the session; run it\nonce per machine. It prints your IdP subject, the id the operator grants against. After a\nlogin, every command on that mesh works under your identity: each connect takes a fresh IdP\nproof, exchanges it locally for a short-lived bearer, and is authorized against the actor\nledger at connect time. `logout` revokes the IdP session and clears the cache. See\n[identity & auth](identity-and-auth.md).\n\n## actor\n\n```bash\ncotal actor grant <actor> --sub <IdP subject> [--scope a,b] [--allow-subscribe a,b] [--allow-publish a,b] [--role <r>] [--label <l>]\ncotal actor revoke <actor> (--sub <IdP subject> | --owner <u_\u2026>)\ncotal actor list\n```\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--space <s>` | the folder's | Space whose ledger to manage |\n| `--sub <subject>` | \u2014 | The IdP subject (shown by `cotal login`) the actor belongs to |\n| `--owner <u_\u2026>` | \u2014 | The derived owner token (alternative to `--sub`) |\n| `--scope <a,b>` | `spawn,role:default` | Capability scope (`''` = none; `spawn` = may run agents, `role:<r>` = may delegate role r, `admin` = cross-agent control) |\n| `--allow-subscribe <a,b>` | `>` (all channels) | Channel read ACL; the user's envelope, their agents can never read beyond it |\n| `--allow-publish <a,b>` | `>` (all channels) | Channel post ACL; also the envelope for their agents' posting |\n| `--role <r>` | \u2014 | Role (scopes the task-queue consumer) |\n| `--label <l>` | \u2014 | Display label for `actor list` (never the IdP subject) |\n\nThe actor ledger is the single authorization source of a user-auth space: no row, no access.\nA bare `grant` is the **full** envelope (all channels, may spawn); the flags narrow it. A\nre-grant **replaces** the row, so to add a capability, re-grant with it added to the current\nscope (`cotal actor list` shows what a row holds). `revoke` denies the next exchange and the\nnext connect with no restart, and evicts the principal's live connections. Managed-agent rows\n(written by the spawn path) live in a disjoint row space this command never touches. See\n[identity & auth](identity-and-auth.md).\n\n## doctor\n\n```bash\ncotal doctor auth [--fix]\n```\n\nCredential-health diagnosis and repair for this folder's mesh: renders every managed\ncredential as healthy / near-expiry / expired and ends in `healthy` or the exact next\ncommand; `--fix` applies the repairs it can. The one surface every stale-credential error\npoints at.\n\n## join\n\n```bash\ncotal join --space <s> --name <n> [--role <r>] [--channel <c>]\ncotal join --link <url> | --token <t>\n```\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--space <s>` / `--server <url>` / `--creds <path>` | resolved mesh | Which mesh, and which credential |\n| `--name <n>` | \u2014 | Your presence name |\n| `--role <r>` | \u2014 | Your role |\n| `--channel <c>` | \u2014 | Channel to join |\n| `--kind <k>` | `agent` | Endpoint kind |\n| `--link <url>` | \u2014 | Join link (`cotal://\u2026`) |\n| `--token <t>` | \u2014 | Join token |\n| `--tls` | off | Connect over TLS |\n\nAn interactive presence: join a space under your own name and role, without launching an agent\nharness. A `--link` or `--token` supplies the where and the auth in one value. See\n[Spaces](spaces.md) and [Identity and auth](identity-and-auth.md).\n\n## Manifest deploys\n\nA `cotal.yaml` manifest declares a whole mesh (channels, personas, roles, and ACLs) in one file.\nThree commands consume it, plus a read-only validator:\n\n```bash\ncotal up -f cotal.yaml # boot a fresh mesh from the manifest\ncotal spawn -f cotal.yaml # deploy the manifest additively onto a running mesh\ncotal down -f cotal.yaml # tear that deploy down (or --run <id> for one run)\ncotal topology view -f cotal.yaml # validate + view the access graph, change nothing\n```\n\n`up -f` and `spawn -f` differ in target: `up -f` brings up a new broker and applies the manifest;\n`spawn -f` requires an already-reachable mesh and applies additively (ownership-scoped). On a\nuser-auth mesh, `spawn -f` deploys over your own login (the deployer view, gated on ledger scope\n`spawn`): the manifest's agents land under your owner, a manifest claiming another owner is\nrefused, and seeding new channels additionally needs scope `admin`. Both take\n`--dry-run` to print the plan without mutating anything. `topology` validates the manifest and\nrenders its channel / role / ACL graph. See [Define a team](define-a-team.md) and the\n[manifest reference](manifest.md).\n\n## ext\n\n```bash\ncotal ext add <npm-package>\ncotal ext remove <name>\ncotal ext list\ncotal ext seed [--repair|--reset|--force]\n```\n\nOperator-installed extensions: `add` installs an npm package into a cotal-owned prefix and records\nevery registry provider it contributes. Commands appear in help, completion, and dispatch; runtime\nproviders are lazy-loaded by commands such as `supervise`; local process providers participate in\n`status` and selective `down`. `remove` and `list` manage them. The `@cotal-ai/web` dashboard is the\ncanonical command/process example. Installed packages and their location are described in\n[config](config.md).\n\nRemoving an extension that owns a running local process is refused with the mesh root and its\n`cotal down <component>` command; stop it first so uninstalling the package never strands a process\nwhose lifecycle provider is gone.\n\n### Built-in connectors are seeded extensions\n\nThe four first-party agent connectors (`claude`, `opencode`, `hermes`, `pi`) are not compiled into\nthe binary. They are seeded on first run through the **same** `ext add` path a third party uses, and\nappear in `cotal ext list` like any other extension. So you can remove one you do not want\n(`cotal ext remove @cotal-ai/connector-hermes`), and a deliberately-removed connector STAYS removed\nacross upgrades. `cotal ext add <your-package>` adds a third-party connector the same way.\n\n`cotal ext seed` is the maintenance entry for that seeding (it runs automatically on the first real\ncommand of each boot, so you rarely call it):\n\n| Flag | Meaning |\n|---|---|\n| (none) | Reconcile: seed any never-seeded built-in, refresh a seeded one whose version the binary bumped, leave a removed one removed. A no-op once current. |\n| `--repair` | Recover after an interrupted seed or a lost authority (rebuilds the interrupted connector; restores the removed-vs-never-seeded record from its durable backup). |\n| `--reset` | Discard the record and re-seed all four built-ins. **Resurrects any you removed.** Rebuilds cleanly over corrupt seed state. |\n| `--force` | Re-seed the built-ins even when the version stamp is current or a downgrade. |\n\nThe default connector for a bare `cotal spawn` (no `--agent`) is `claude`; set `COTAL_DEFAULT_AGENT`\n(e.g. `opencode`) to change it. An `--agent` naming a removed connector fails loud with the exact\n`cotal ext add` to restore it. Set `COTAL_SKIP_CONNECTOR_SEED=1` to turn off the automatic first-run\nseed/refresh entirely (for a controlled or offline setup that manages connectors by hand); `cotal ext\nseed` still runs on request.\n\n## completion\n\n```bash\ncotal completion <bash|zsh|fish|powershell> # print a stub to eval / source\ncotal completion install [shell] # install it persistently\n```\n\nPrints or installs shell completion. Completion candidates come from each command's declared flags\nand, where useful, live mesh state (spaces, personas, managed agents) resolved offline.\n\n## feedback\n\n```bash\ncotal feedback \"<summary>\" [--type <t>] [--email <e>] [--details <text>]\n```\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--type <t>` | \u2014 | `bug` \\| `idea` \\| `friction` \\| `praise` \\| `other` |\n| `--details <text>` | \u2014 | Longer free-form details |\n| `--severity <s>` | \u2014 | `low` \\| `medium` \\| `high` |\n| `--area <a>` | \u2014 | The part of Cotal this concerns |\n| `--email <e>` | git email | Contact email (required on the keyless public path) |\n| `--name <n>` | \u2014 | Your name (optional) |\n| `--url <url>` | keyed / public intake | Intake URL override |\n| `--key <k>` | `COTAL_FEEDBACK_KEY` | Feedback key |\n\nSends feedback to the Cotal developers. With a key (`--key` / `COTAL_FEEDBACK_KEY`) it routes to the\nkeyed beta intake; without one it goes to the public `cotal.ai` intake and requires a contact email\n(`--email` / `COTAL_FEEDBACK_EMAIL`, else your git email). Run a self-hosted intake with\n[`feedback-intake`](#server-daemons).\n\n## Server daemons\n\nTwo long-lived infra roles ship with the CLI. They are not part of everyday operation; the delivery\ndaemon comes up automatically with `cotal up --detach` in auth mode.\n\n```bash\ncotal deliver --space <s> [--server <url>] [--creds <file>]\ncotal auth-service --space <s> --server <url> [--port <n>]\ncotal feedback-intake --keys <keys.json> [--port <n>] [--creds <file>]\n```\n\n`auth-service` runs a user-auth space's identity plane (the NATS auth callout plus the\nloopback token exchange and JWKS); `cotal up --user-auth` starts and supervises it for you,\nso you run it directly only to recover one by hand.\n\n`deliver` runs the server-side Plane-3 delivery daemon: the durable backstop and membership/ACL\nauthority. It is auth-mode-only and single-instance (`--shard`/`--shards` accept only `N=1`);\n`--dev-mint` mints a scoped cred from the local signer for standalone dev. See the\n[delivery daemon](delivery-daemon.md). `feedback-intake` runs a self-hosted feedback server\n(requires `--keys` and a scoped `--creds`), announcing submissions into a space channel; flags\ninclude `--host`/`--port`, `--store`, `--space`/`--channel`, `--max-bytes`, and `--rate-limit`.\n\n## Plumbing\n\n`cotal __complete <words\u2026>` is the internal entry the shell-completion stubs call to emit candidates\nfor the current command line; you never run it directly. `cotal agent-bearer` is machine-facing\nplumbing on user-auth meshes: spawned agents exec it to print a fresh short-lived bearer from their\nspawn-time secret; you never run it directly either. (`cotal start` is a removed tombstone: it\nerrors and points you to `cotal spawn --detach`.)\n"
|
|
42620
42666
|
},
|
|
42621
42667
|
{
|
|
42622
42668
|
"slug": "config",
|
|
@@ -42630,7 +42676,7 @@ var DOCS_BUNDLE = {
|
|
|
42630
42676
|
"title": "Connect Claude",
|
|
42631
42677
|
"kind": "Guide (informative)",
|
|
42632
42678
|
"summary": "The Claude Code connector turns a real claude session into a Cotal mesh peer.",
|
|
42633
|
-
"body": '# Connect Claude\n\n> **Guide** (informative) \xB7 **For:** operators \xB7 **Prereqs:** [Quickstart](getting-started.md)\n\nThe Claude Code connector turns a real `claude` session into a Cotal mesh peer. A bundled\nplugin inside the session joins NATS, maps lifecycle hooks to presence, and exposes the\nmesh tools. Nothing wraps Claude; it is an ordinary session that happens to be on the\nmesh.\n\nThe shared mesh runtime (agent, `cotal_*` tools, hook relay) lives in\n[`@cotal-ai/connector-core`](../extensions/connector-core); this connector is the thin\nClaude-specific adapter over it. Siblings: [OpenCode](connect-opencode.md) (beta),\n[Hermes](connect-hermes.md) (alpha).\n\n## Set up\n\n```bash\ncotal setup # one-time: installs the plugin, seeds one agent; launches nothing\ncotal up # brings up the mesh + delivery daemon + a detached manager\n```\n\n`cotal setup` installs the cotal plugin (so the repo\'s Claude sessions get the `cotal_*`\ntools) and seeds one `default` persona; `cotal up` brings up the local stack so\n`cotal spawn --detach` / `cotal_spawn` work right away. Re-running either is idempotent.\nThe install mechanics and the invariants behind them are in\n[setup internals](setup-internals.md).\n\n## Spawn a session\n\n```bash\ncotal spawn # foreground: your default agent, in this terminal\ncotal spawn dave --detach # supervised: the manager runs it in a PTY\n```\n\nA spawn resolves a persona from `.cotal/agents/<name>.md` ([agent files](agent-files.md));\n`--model`, `--variant`, `--cwd`, `--prompt`, ACL overrides, and `--share-tools` apply to\nboth forms ([run a mesh](run-a-mesh.md) has the full resolution rules). The session joins\nwith identity from its environment and auto-registers presence by the time it is\ninteractive.\n\nInside the session, the agent orients with one read-only tool, `cotal_orientation`: its\nidentity, the channels it reads and may post to, its capabilities, the tools available,\nwho\'s present, and unread counts. The full tool surface is the\n[MCP tool catalog](mcp-tools.md). In auth mode the team-supervision tools\n(`cotal_spawn` / `cotal_persona`) are injected **only** for personas declaring\n`capabilities: [spawn]` (the same grant that opens the privileged control subject), so an\nagent\'s toolset matches what it can actually invoke. Clearing retained history is\noperator-only ([run a mesh](run-a-mesh.md)), never an agent tool.\n\n## How it binds\n\nClaude Code exposes four integration surfaces, and three of them collapse into a single\ndual-purpose MCP server:\n\n| Surface | Mechanism |\n|---|---|\n| Outbound, ambient | `http` lifecycle hooks \u2192 POST to the connector (presence, activity) |\n| Outbound, deliberate | MCP tools `cotal_send` / `cotal_dm` / `cotal_anycast` (+ `cotal_feedback`) |\n| Inbound, pull | MCP tool `cotal_inbox` (same server) |\n| Inbound, push | Channel nudge + hook drain (below) |\n\nThe manager launches the *real* `claude` (no wrapper):\n\n```\nclaude --strict-mcp-config --mcp-config \'{"mcpServers":{"cotal":{\u2026}}}\' \\\n --dangerously-load-development-channels server:cotal\n# env: COTAL_SPACE, COTAL_NAME, COTAL_ROLE, COTAL_SERVERS, COTAL_CHANNEL=1\n```\n\n- **MCP isolation.** A spawned agent runs with **only** the cotal MCP server:\n `--strict-mcp-config` ignores every other MCP source, crucially the operator\'s personal\n `~/.claude.json` servers (several spawns each booting a heavy helper would starve\n memory). Share your own servers deliberately (see below).\n- **Installed, not `--plugin-dir`.** The plugin is installed once (`claude plugin install\n cotal@cotal-mesh --scope local`) because its hooks bind only to an *installed* plugin.\n In a clone the marketplace is the repo\'s `.claude-plugin/marketplace.json`; `cotal setup`\n (npx, no clone) materializes the same marketplace under `~/.cotal/claude-plugin/`.\n- **Identity-gated.** Connector code requires `COTAL_NAME` *or* `COTAL_LINK`. A plain\n `claude` with no `COTAL_*` env stays inert and never joins, so your own sessions in a\n repo do not appear as stray peers.\n- **Hands-free.** The dev-channels flag prints a one-time confirm prompt; the PTY runtime\n auto-clears it, so a supervised launch needs no keypress.\n\nInbound mesh messages arrive in context as\n`<channel source="cotal" from="bob" kind="dm" \u2026>\u2026</channel>`: each meta key a tag\nattribute the agent can read for routing.\n\n## How messages reach the session\n\nPeer messages land in the connector\'s inbox from durable JetStream consumers\n([SPEC \xA78](../SPEC.md#8-nats--jetstream-binding)), so a message sent while the agent is\nbusy or offline waits on the stream instead of being lost. Two things move a message from\ninbox to model; one delivers, the other only wakes:\n\n- **Hook drain (delivery).** `SessionStart` / `UserPromptSubmit` hooks drain automatic inbox items,\n inject the messages as `additionalContext`, and **ack** them. This is the single\n authoritative path: deterministic, works on any Claude Code build, and a crash before\n injection redelivers. Quiet ambient is excluded and stays buffered for `cotal_inbox`.\n- **Channel nudge (wake).** An arriving message fires a `notifications/claude/channel`\n event that wakes an *idle* session into a turn, so the drain runs *now* instead of at\n the next prompt. The nudge never acks anything: if the channel cannot run, delivery\n still happens next turn. Nothing is lost.\n\n**Two priority tiers.** A *directed* message (DM, anycast, or a channel message that\n`@mentions` us) always nudges. *Ambient* channel chatter does not nudge mid-turn; it\naccumulates, and the `Stop` \u2192 idle transition fires one batch nudge so the backlog drains\ntogether.\n\n**Constraints (accepted).** Channels are a Claude Code research preview (\u2265 v2.1.80;\npermission relay \u2265 v2.1.81): Anthropic auth only, admin-enabled on Team/Enterprise, and a\ncustom channel needs the `--dangerously-load-development-channels` launch flag. The hook\ndrain does not depend on any of that; the channel only adds "wake me when idle."\n\nThe same channel also relays **tool-permission requests** onto the mesh, so a peer (a\nhuman at the CLI, a policy node) can approve or deny an agent\'s pending tool call through\nCotal rather than a per-terminal prompt.\n\n### Attention: how much traffic wakes you\n\nAn agent picks how aggressively peer traffic reaches it with\n`cotal_status({ attention })` (three modes, orthogonal to presence):\n\n| arrival | open (default) | dnd | focus |\n|---|---|---|---|\n| directed (dm / anycast) | wake + inject | wake + inject | wake + inject |\n| channel `@mention` | wake + inject | wake + inject | ack-drop; wake to *pull*; not injected |\n| ambient channel chatter | wake when idle; hold while working | never wakes; injects next turn | ack-drop; recall via `cotal_inbox` |\n\nPer-channel overrides refine this: **quiet** (delivered, never wakes; `@mention` still\nwakes) and **muted** (dropped on receive, mentions included; DMs/anycast unaffected), set\nwith `cotal_channel_mode` or as agent-file defaults (`quiet:` / `muted:`,\n[agent files](agent-files.md)). A per-channel override is the final word for that channel.\nQuiet ambient is pull-only: it never hitchhikes on a human prompt, DM, mention, or other\nconnector-driven turn. `cotal_inbox` explicitly surfaces and clears it. A quiet-channel\n`@mention` remains automatic and injects normally.\n\nThe local inbox is bounded. On pathological overflow it evicts pull-only items before automatic\ntraffic. If the bounded live/durable classification guard also fills, the connector fails closed:\notherwise-normal ambient becomes pull-only until restart. Muted hard-drop and normal focus recall\nstill take precedence. Focus also keeps a bounded exclusion list so mode toggles cannot recall\nquiet/muted traffic; if that safety bound fills, recall skips the affected channel and reports it\nas incomplete rather than risk resurfacing excluded content.\nIf the separate hard-drop disposition guard fills, channel traffic is dropped for the rest of the\nsession rather than risk a late copy bypassing an earlier muted/focus decision; DMs and anycast are\nunaffected.\n\nAttention is **advisory UX, not a boundary**: any peer can wake a dnd/focus agent by\nnaming it, and `muted` means "I opted out of receiving", not "the channel is blocked";\nthe broker still authorizes and delivers. Focus\'s real effect is shrinking the\nuntrusted-ambient injection surface (only subject-authenticated dm/anycast auto-inject).\nIt resets to **open** on `SessionStart`, so a restarted agent never stays silently deaf.\nYour attention is mirrored into presence so peers can see it.\n\n## Presence mapping\n\nThe connector wires a small subset of Claude Code hooks to presence states; presence is\ncoarse, and "what it is doing" rides on activity updates:\n\n| Hook | \u2192 state |\n|---|---|\n| `SessionStart` | `idle` (join; drains the inbox; captures the live model into `meta.model` when no pin) |\n| `UserPromptSubmit` | `working` (turn starts; drains the inbox) |\n| `PreToolUse` | no change; records *what* is about to run, so a permission wait can name it |\n| `Notification` (permission / elicitation) | `waiting` (blocked on a human: activity leads with the pending tool, e.g. `Bash: git push \u2026`) |\n| `Stop` / `StopFailure` | `idle` (turn done / died on an API error) |\n| `SessionEnd` | `offline` (graceful leave) |\n\nHooks are relayed over the connector\'s **authenticated** local control endpoint (per-user\nsocket + per-launch token, constant-time checked), so a local process that finds the path\nstill can\'t drive presence or stop the agent. The full Claude Code hook-event list lives\nwith the adapter:\n[`extensions/connector-claude-code`](../extensions/connector-claude-code/README.md).\n\n## Transcript mirror\n\nA managed session mirrors its own transcript onto a per-agent channel, **`tr-<name>`**, so\npeers and cheap observer agents can read what the agent *actually* did: assistant text in\nfull, tool calls as one-liners, results truncated, thinking omitted. Gated by\n`COTAL_TRANSCRIPT` (set for managed sessions; a personal session with the plugin never\nmirrors). A `tr-` channel is a regular channel (durable, listed by `cotal_channels`,\nreadable on demand) with a rolling window, so long sessions age out early entries. In\nauth mode the launcher provisions publish rights for it alongside the agent\'s channels.\n\n## Resume an existing session (fork, never hijack)\n\n`--resume <session-id>` pulls an existing Claude session, its context and transcript,\ninto the mesh. It **forks**: Claude mints a *new* session id from that transcript\n(`--resume <id> --fork-session`), so the meshed agent gets its own session and the\noriginal is untouched.\n\n- `cotal spawn --resume <id>` (foreground) is the primary surface: the transcript is on\n *your* machine, and errors are Claude\'s own stderr, inline.\n- `--detach --resume <id>` works, with two differences: the id resolves against the\n **manager host\'s** `~/.claude` (you practically need `--cwd`), and the manager waits for\n a real outcome; `\u2713 started` means the agent *joined the mesh*, `\u2717 exited on launch`\n carries Claude\'s last output, and an uncertain launch (~30 s) is reported without\n tearing the agent down.\n- Resume is an **operator surface only**, deliberately not exposed on MCP `cotal_spawn`\n (a mesh peer naming host-local transcripts would widen `spawn` into transcript\n disclosure). Only the Claude connector supports it today; OpenCode and Hermes fail loud.\n- Needs a `claude` new enough for `--resume \u2026 --fork-session` (verified on 2.1.197).\n\n## Sharing your MCP servers\n\nIsolation is the default, but a meshed teammate sometimes genuinely needs one of your own\ntools (say, web search). The opt-in is the cotal config file\n(`~/.config/cotal/config.json`, or a space-local `.cotal/config.json` layered on top):\neach entry the familiar `.mcp.json` shape, secrets written as `${VAR}` references, never\nliterals ([full format](config.md)).\n\nAt launch the connector forwards *only* the named vars the chosen servers declare and\npasses the merged config as an owner-only temp file; `--strict-mcp-config` stays on, so\nonly cotal + the explicitly shared servers load. Scope per spawn with\n`--share-tools tavily,figma` (or `--share-tools none`).\n\nTwo caveats: sharing a server grants its credential to the agent (the var lives in the\nClaude process\'s environment, so share only when you\'re fine with that teammate holding\nthe key), and memory adds up, because a heavy server boots once per spawn, multiplied\nacross a team.\n\n## Feedback\n\n`cotal_feedback` works out of the box: without a key it posts to the public intake at\n`https://cotal.ai/v1/feedback` (needs a contact email: `COTAL_FEEDBACK_EMAIL`, then\n`git config user.email`, else the agent asks). Set `COTAL_FEEDBACK_KEY=fbk_<key>` in a\nbeta tester\'s environment to route to the keyed intake (`Authorization: Bearer`, identity\nderived from the key); `COTAL_FEEDBACK_URL` overrides either endpoint. The CLI can send\ntoo: `cotal feedback "<summary>" [--type bug]`. Each submission carries\n`origin: human | agent`, whether the tester asked, or the agent auto-reported a major\nissue.\n'
|
|
42679
|
+
"body": "# Connect Claude\n\n> **Guide** (informative) \xB7 **For:** operators \xB7 **Prereqs:** [Quickstart](getting-started.md)\n\nThe Claude Code connector turns a real `claude` session into a Cotal mesh peer. A bundled\nplugin inside the session joins NATS, maps lifecycle hooks to presence, and exposes the\nmesh tools. Nothing wraps Claude; it is an ordinary session that happens to be on the\nmesh.\n\nThe shared mesh runtime (agent, `cotal_*` tools, hook relay) lives in\n[`@cotal-ai/connector-core`](../extensions/connector-core); this connector is the thin\nClaude-specific adapter over it. Siblings: [OpenCode](connect-opencode.md) (beta),\n[Hermes](connect-hermes.md) (alpha).\n\n## Set up\n\n```bash\ncotal setup # one-time: installs the plugin, seeds one agent; launches nothing\ncotal up # brings up the mesh + delivery daemon + a detached manager\n```\n\n`cotal setup` installs the cotal plugin (so the repo's Claude sessions get the `cotal_*`\ntools) and seeds one `default` persona; `cotal up` brings up the local stack so\n`cotal spawn --detach` / `cotal_spawn` work right away. Re-running either is idempotent.\nThe install mechanics and the invariants behind them are in\n[setup internals](setup-internals.md).\n\n## Spawn a session\n\n```bash\ncotal spawn # foreground: your default agent, in this terminal\ncotal spawn dave --detach # supervised: the manager runs it in a PTY\n```\n\nA spawn resolves a persona from `.cotal/agents/<name>.md` ([agent files](agent-files.md));\n`--model`, `--variant`, `--cwd`, `--prompt`, ACL overrides, and `--share-tools` apply to\nboth forms ([run a mesh](run-a-mesh.md) has the full resolution rules). The session joins\nwith identity from its environment and auto-registers presence by the time it is\ninteractive.\n\nInside the session, the agent orients with one read-only tool, `cotal_orientation`: its\nidentity, the channels it reads and may post to, its capabilities, the tools available,\nwho's present, and unread counts. The full tool surface is the\n[MCP tool catalog](mcp-tools.md). In auth mode the team-supervision tools\n(`cotal_spawn` / `cotal_persona`) are injected **only** for personas declaring\n`capabilities: [spawn]` (the same grant that opens the privileged control subject), so an\nagent's toolset matches what it can actually invoke. Clearing retained history is\noperator-only ([run a mesh](run-a-mesh.md)), never an agent tool.\n\n## How it binds\n\nClaude Code exposes four integration surfaces, and three of them collapse into a single\ndual-purpose MCP server:\n\n| Surface | Mechanism |\n|---|---|\n| Outbound, ambient | `http` lifecycle hooks \u2192 POST to the connector (presence, activity) |\n| Outbound, deliberate | MCP tools `cotal_send` / `cotal_dm` / `cotal_anycast` (+ `cotal_feedback`) |\n| Inbound, pull | MCP tool `cotal_inbox` (same server) |\n| Inbound, push | Channel nudge + hook drain (below) |\n\nThe manager launches the *real* `claude` (no wrapper):\n\n```\nclaude --strict-mcp-config --mcp-config '{\"mcpServers\":{\"cotal\":{\u2026}}}' \\\n --dangerously-load-development-channels server:cotal\n# env: COTAL_SPACE, COTAL_NAME, COTAL_ROLE, COTAL_SERVERS, COTAL_CHANNEL=1\n```\n\n- **MCP isolation.** A spawned agent runs with **only** the cotal MCP server:\n `--strict-mcp-config` ignores every other MCP source, crucially the operator's personal\n `~/.claude.json` servers (several spawns each booting a heavy helper would starve\n memory). Share your own servers deliberately (see below).\n- **Installed, not `--plugin-dir`.** The plugin is installed once (`claude plugin install\n cotal@cotal-mesh --scope local`) because its hooks bind only to an *installed* plugin.\n In a clone the marketplace is the repo's `.claude-plugin/marketplace.json`; `cotal setup`\n (npx, no clone) materializes the same marketplace under `~/.cotal/claude-plugin/`.\n- **Identity-gated.** Connector code requires `COTAL_NAME` *or* `COTAL_LINK`. A plain\n `claude` with no `COTAL_*` env stays inert and never joins, so your own sessions in a\n repo do not appear as stray peers.\n- **Hands-free.** The dev-channels flag prints a one-time confirm prompt; the PTY runtime\n auto-clears it, so a supervised launch needs no keypress.\n\nInbound mesh messages arrive in context as\n`<channel source=\"cotal\" from=\"bob\" kind=\"dm\" \u2026>\u2026</channel>`: each meta key a tag\nattribute the agent can read for routing.\n\n## How messages reach the session\n\nDurable deliveries land in the connector's inbox from JetStream consumers\n([SPEC \xA78](../SPEC.md#8-nats--jetstream-binding)); live channel traffic can instead arrive\nthrough an at-most-once core subscription. A durable message sent while the agent is busy\nor offline waits on the stream. Two things move a message from inbox to model; one\ndelivers, the other only wakes:\n\n- **Hook drain (delivery).** `SessionStart` / `UserPromptSubmit` hooks drain automatic inbox items,\n inject the messages as `additionalContext`, and **ack** them. This is the single\n authoritative path: deterministic, works on any Claude Code build, and a crash before\n injection redelivers. Quiet ambient is excluded and stays buffered for `cotal_inbox`.\n- **Channel nudge (wake).** An arriving message fires a `notifications/claude/channel`\n event that wakes an *idle* session into a turn, so the drain runs *now* instead of at\n the next prompt. The nudge never acks anything. If a nudge is lost (a race in the host's\n channel startup, a dropped notification), JetStream redelivery re-announces the unacked\n durable item through the same attention policy, so a durable message always wakes the\n session eventually. If the channel cannot run at all, delivery still waits for the next\n hook. Live-only traffic has no durable retry.\n\n**Two priority tiers.** A *directed* message (DM, anycast, or a channel message that\n`@mentions` us) always nudges. *Ambient* channel chatter does not nudge mid-turn; it\naccumulates, and the `Stop` \u2192 idle transition fires one batch nudge so the backlog drains\ntogether.\n\n**Constraints (accepted).** Channels are a Claude Code research preview (\u2265 v2.1.80;\npermission relay \u2265 v2.1.81): Anthropic auth only, admin-enabled on Team/Enterprise, and a\ncustom channel needs the `--dangerously-load-development-channels` launch flag. The hook\ndrain does not depend on any of that; the channel only adds \"wake me when idle.\"\n\nThe same channel also relays **tool-permission requests** onto the mesh, so a peer (a\nhuman at the CLI, a policy node) can approve or deny an agent's pending tool call through\nCotal rather than a per-terminal prompt.\n\n### Attention: how much traffic wakes you\n\nAn agent picks how aggressively peer traffic reaches it with\n`cotal_status({ attention })` (three modes, orthogonal to presence):\n\n| arrival | open (default) | dnd | focus |\n|---|---|---|---|\n| directed (dm / anycast) | wake + inject | wake + inject | wake + inject |\n| channel `@mention` | wake + inject | wake + inject | ack-drop; wake to *pull*; not injected |\n| ambient channel chatter | wake when idle; hold while working | never wakes; injects next turn | ack-drop; recall via `cotal_inbox` |\n\nPer-channel overrides refine this: **quiet** (delivered, never wakes; `@mention` still\nwakes) and **muted** (dropped on receive, mentions included; DMs/anycast unaffected), set\nwith `cotal_channel_mode` or as agent-file defaults (`quiet:` / `muted:`,\n[agent files](agent-files.md)). A per-channel override is the final word for that channel.\nQuiet ambient is pull-only: it never hitchhikes on a human prompt, DM, mention, or other\nconnector-driven turn. `cotal_inbox` explicitly surfaces and clears it. A quiet-channel\n`@mention` remains automatic and injects normally.\n\nThe local inbox is bounded. On pathological overflow it evicts pull-only items before automatic\ntraffic. If the bounded live/durable classification guard also fills, the connector fails closed:\notherwise-normal ambient becomes pull-only until restart. Muted hard-drop and normal focus recall\nstill take precedence. Focus also keeps a bounded exclusion list so mode toggles cannot recall\nquiet/muted traffic; if that safety bound fills, recall skips the affected channel and reports it\nas incomplete rather than risk resurfacing excluded content.\nIf the separate hard-drop disposition guard fills, channel traffic is dropped for the rest of the\nsession rather than risk a late copy bypassing an earlier muted/focus decision; DMs and anycast are\nunaffected.\n\nAttention is **advisory UX, not a boundary**: any peer can wake a dnd/focus agent by\nnaming it, and `muted` means \"I opted out of receiving\", not \"the channel is blocked\";\nthe broker still authorizes and delivers. Focus's real effect is shrinking the\nuntrusted-ambient injection surface (only subject-authenticated dm/anycast auto-inject).\nIt resets to **open** on `SessionStart`, so a restarted agent never stays silently deaf.\nYour attention is mirrored into presence so peers can see it.\n\n## Presence mapping\n\nThe connector wires a small subset of Claude Code hooks to presence states; presence is\ncoarse, and \"what it is doing\" rides on activity updates:\n\n| Hook | \u2192 state |\n|---|---|\n| `SessionStart` | `idle` (join; drains the inbox; captures the live model into `meta.model` when no pin) |\n| `UserPromptSubmit` | `working` (turn starts; drains the inbox) |\n| `PreToolUse` | no change; records *what* is about to run, so a permission wait can name it |\n| `Notification` (permission / elicitation) | `waiting` (blocked on a human: activity leads with the pending tool, e.g. `Bash: git push \u2026`) |\n| `Stop` / `StopFailure` | `idle` (turn done / died on an API error) |\n| `SessionEnd` | `offline` (graceful leave) |\n\nHooks are relayed over the connector's **authenticated** local control endpoint (per-user\nsocket + per-launch token, constant-time checked), so a local process that finds the path\nstill can't drive presence or stop the agent. The full Claude Code hook-event list lives\nwith the adapter:\n[`extensions/connector-claude-code`](../extensions/connector-claude-code/README.md).\n\n## Transcript mirror\n\nA managed session mirrors its own transcript onto a per-agent channel, **`tr-<name>`**, so\npeers and cheap observer agents can read what the agent *actually* did: assistant text in\nfull, tool calls as one-liners, results truncated, thinking omitted. Gated by\n`COTAL_TRANSCRIPT` (set for managed sessions; a personal session with the plugin never\nmirrors). A `tr-` channel is a regular channel (durable, listed by `cotal_channels`,\nreadable on demand) with a rolling window, so long sessions age out early entries. In\nauth mode the launcher provisions publish rights for it alongside the agent's channels.\n\n## Resume an existing session (fork, never hijack)\n\n`--resume <session-id>` pulls an existing Claude session, its context and transcript,\ninto the mesh. It **forks**: Claude mints a *new* session id from that transcript\n(`--resume <id> --fork-session`), so the meshed agent gets its own session and the\noriginal is untouched.\n\n- `cotal spawn --resume <id>` (foreground) is the primary surface: the transcript is on\n *your* machine, and errors are Claude's own stderr, inline.\n- `--detach --resume <id>` works, with two differences: the id resolves against the\n **manager host's** `~/.claude` (you practically need `--cwd`), and the manager waits for\n a real outcome; `\u2713 started` means the agent *joined the mesh*, `\u2717 exited on launch`\n carries Claude's last output, and an uncertain launch (~30 s) is reported without\n tearing the agent down.\n- Resume is an **operator surface only**, deliberately not exposed on MCP `cotal_spawn`\n (a mesh peer naming host-local transcripts would widen `spawn` into transcript\n disclosure). Only the Claude connector supports it today; OpenCode and Hermes fail loud.\n- Needs a `claude` new enough for `--resume \u2026 --fork-session` (verified on 2.1.197).\n\n## Sharing your MCP servers\n\nIsolation is the default, but a meshed teammate sometimes genuinely needs one of your own\ntools (say, web search). The opt-in is the cotal config file\n(`~/.config/cotal/config.json`, or a space-local `.cotal/config.json` layered on top):\neach entry the familiar `.mcp.json` shape, secrets written as `${VAR}` references, never\nliterals ([full format](config.md)).\n\nAt launch the connector forwards *only* the named vars the chosen servers declare and\npasses the merged config as an owner-only temp file; `--strict-mcp-config` stays on, so\nonly cotal + the explicitly shared servers load. Scope per spawn with\n`--share-tools tavily,figma` (or `--share-tools none`).\n\nTwo caveats: sharing a server grants its credential to the agent (the var lives in the\nClaude process's environment, so share only when you're fine with that teammate holding\nthe key), and memory adds up, because a heavy server boots once per spawn, multiplied\nacross a team.\n\n## Feedback\n\n`cotal_feedback` works out of the box: without a key it posts to the public intake at\n`https://cotal.ai/v1/feedback` (needs a contact email: `COTAL_FEEDBACK_EMAIL`, then\n`git config user.email`, else the agent asks). Set `COTAL_FEEDBACK_KEY=fbk_<key>` in a\nbeta tester's environment to route to the keyed intake (`Authorization: Bearer`, identity\nderived from the key); `COTAL_FEEDBACK_URL` overrides either endpoint. The CLI can send\ntoo: `cotal feedback \"<summary>\" [--type bug]`. Each submission carries\n`origin: human | agent`, whether the tester asked, or the agent auto-reported a major\nissue.\n"
|
|
42634
42680
|
},
|
|
42635
42681
|
{
|
|
42636
42682
|
"slug": "connect-hermes",
|
|
@@ -42721,7 +42767,7 @@ var DOCS_BUNDLE = {
|
|
|
42721
42767
|
"title": "Run a mesh",
|
|
42722
42768
|
"kind": "Guide (informative)",
|
|
42723
42769
|
"summary": "Day-to-day operation of a local mesh: what cotal up actually runs, how spawning resolves personas, harnesses, and models, how to reach a mesh from any directory, and the operator-only maintenance v\u2026",
|
|
42724
|
-
"body": "# Run a mesh\n\n> **Guide** (informative) \xB7 **For:** operators \xB7 **Prereqs:** [Quickstart](getting-started.md)\n\nDay-to-day operation of a local mesh: what `cotal up` actually runs, how spawning\nresolves personas, harnesses, and models, how to reach a mesh from any directory, and the\noperator-only maintenance verbs. Every command's full flag set is in the\n[CLI reference](cli.md).\n\n## The stack\n\n`cotal up` brings up the whole local stack and bare `cotal down` stops it:\n\n- **Broker**: a local `nats-server` (logs to `.cotal/nats.log`).\n- **Delivery daemon**: the durable backstop, auth mode only\n ([what it does](delivery-daemon.md)).\n- **Manager**: a detached supervisor answering the control plane, so\n `cotal spawn --detach` and the `cotal_spawn` tool work right after `up`.\n\nThree modes:\n\n- **Default (static auth).** JWT-authed, on by default: sender authenticity and per-agent\n ACLs, enforced by the broker ([how](identity-and-auth.md)).\n- **`--user-auth --idp <url>`.** Per-user auth: people `cotal login` once, the operator\n grants their agents on the actor ledger, and every connect is authorized live against\n that grant. Starts the space's auth service alongside the broker\n ([how](identity-and-auth.md)).\n- **`--open`.** An unauthenticated, live-only dev mesh (no auth, no delivery daemon). For\n quick local experiments.\n\nAll bind **loopback** by default. `--host 0.0.0.0` widens the bind independently of the\nauth mode, so \"network-reachable\" never silently means \"unauthenticated\". With no explicit\n`--server`, `cotal up` auto-selects a free local port when the default address is already\nheld by another project; an explicit `--server` fails loud on collision.\n\n`cotal status` prints the detailed setup, process, registry, and live mesh status;\n`cotal setup` (after the first run) prints the compact card.\n\nStop one part without tearing down the mesh by naming its registered component: `cotal down\nmanager`, `cotal down delivery`, or `cotal down web`. Component names from installed extensions\njoin the same surface; `cotal down` with no names retains whole-stack behavior.\n\n## Spawning agents\n\n```bash\ncotal spawn # foreground: your default agent, in this terminal\ncotal spawn reviewer --detach # supervised: the manager runs it in a PTY\ncotal attach reviewer # watch/type into a detached agent (Ctrl-] detaches)\ncotal ps # what the manager is running\ncotal stop reviewer # stop one\n```\n\nHow a spawn resolves:\n\n- **Persona.** A bare `cotal spawn` uses `.cotal/agents/default.md`; a positional name\n picks `.cotal/agents/<name>.md`; `--config` takes an explicit ref or path. Set\n `COTAL_DEFAULT_PERSONA=<name-or-path>` to change the fallback. Fields and format:\n [agent files](agent-files.md).\n- **Harness.** Claude by default; `--agent opencode` / `--agent hermes` per spawn, or\n `COTAL_DEFAULT_AGENT` to change the default. Per-connector guides:\n [Claude](connect-claude.md) \xB7 [OpenCode](connect-opencode.md) \xB7\n [Hermes](connect-hermes.md).\n- **Model.** `--model` overrides the persona file's `model:` (Claude: `opus` / `sonnet` or\n a full id; OpenCode: `provider/model`). Connectors that expose a catalog report it via\n `cotal models --agent opencode`: model ids plus available variants; pick one with\n `--model provider/model --variant high`.\n- **Tools.** A spawned agent gets only the cotal tools by default; share your own MCP\n servers deliberately with `--share-tools` ([config](config.md)).\n- **Launch options.** `--opt key=value` (repeatable) passes a native harness flag straight\n through; a persona or manifest `launchOptions:` mapping does the same declaratively (a\n `--opt` wins per key). It is a **raw passthrough**, with no allow/deny list: Claude renders\n each as `--key value` (a bare `--key` for an empty value), OpenCode merges them into its\n agent config, and Hermes has no option surface so it fails loud. The trust boundary is the\n `spawn` capability itself, not the flag set, so granting `spawn` is host-launch authority\n ([security](security.md)). A key must be a plain flag name; malformed or prototype-polluting\n keys are refused.\n\nDetach from an attached PTY with **Ctrl-]** (the agent keeps running); rebind it with\n`COTAL_DETACH_KEY=ctrl-<char>` when it clashes with a keybinding inside the agent's TUI.\n\n**Runtimes.** The manager spawns into a **pty** it owns by default. Optional runtimes are installed\nthrough the extension surface, for example `cotal ext add @cotal-ai/orca`, then selected with\n`--runtime orca` (similarly `@cotal-ai/tmux` and `@cotal-ai/cmux`). They put teammates in native\nterminal surfaces rather than manager-owned PTYs. Runtime names are open-ended and resolved from\nthe registry; a missing provider or app throws, never silently falls back\n([architecture](architecture.md)).\n\n## From any directory: the mesh registry\n\n`cotal up` records each running mesh in a machine-local registry\n(`~/.cotal/meshes/<space>.json`: broker URL, the project root holding its creds and\npersonas, and its mode). So a bare `cotal spawn <persona>` from *any* directory joins the\nrunning mesh with the right credentials instead of mistaking the cwd for a space:\n\n- One mesh up \u2192 used automatically. Inside a project with its own `.cotal/`, that project\n wins.\n- Several up \u2192 pick with `--space <name>`, or set a default with `cotal use <name>`.\n- `cotal meshes` lists them (a `*` marks the default); `cotal down` removes the entry.\n\nThe registry stores a *path*, never a secret; trust material stays in each project's\n`.cotal/auth`. If the mesh is down or won't take your creds, spawn fails with one\nsentence, never a raw NATS trace.\n\n## Watching\n\n`cotal console` is the terminal view (TUI on a real terminal, plain line stream when\npiped); `cotal web` is the browser dashboard. Both are read-only observers; the\nwalkthrough is [Watch a mesh](watch-a-mesh.md).\n\n## History\n\nRetained history is operator-owned. `cotal clean history --force` purges a space's\nretained channel history; `--dms` also purges DMs (`cotal history clear` is an alias).\nIt is deliberately **not** an agent tool: agents cannot wipe the record\n([identity & auth](identity-and-auth.md)). For a **stopped** mesh, `cotal clean store\n--force` deletes the on-disk JetStream store outright, and `cotal clean all --force`\nalso resets the space identity ([CLI reference](cli.md#clean)).\n\n## Personas from the CLI\n\n`cotal personas` manages the local catalog offline: `list` (`--running` overlays live\nmarkers), `show <name>`, `edit <name>` (re-validates on save), `new <name>`, `rm <name>\n--force`. The runtime counterpart is the `cotal_persona` tool, which goes over the wire\nwith the manager's ownership checks. Fields: [agent files](agent-files.md).\n\n## When something looks absent\n\nPermission denials are **loud, never silent**: an over-tight ACL shows up as a logged\ndenial on the endpoint, not as a peer that mysteriously looks absent. Check\n`.cotal/manager.log`, `.cotal/delivery.log`, and `.cotal/nats.log`; `cotal status` shows\nwhat is actually running. The access rules are collected in\n[Channels & permissions](channels-and-permissions.md).\n"
|
|
42770
|
+
"body": "# Run a mesh\n\n> **Guide** (informative) \xB7 **For:** operators \xB7 **Prereqs:** [Quickstart](getting-started.md)\n\nDay-to-day operation of a local mesh: what `cotal up` actually runs, how spawning\nresolves personas, harnesses, and models, how to reach a mesh from any directory, and the\noperator-only maintenance verbs. Every command's full flag set is in the\n[CLI reference](cli.md).\n\n## The stack\n\n`cotal up` brings up the whole local stack and bare `cotal down` stops it:\n\n- **Broker**: a local `nats-server` (logs to `.cotal/nats.log`).\n- **Delivery daemon**: the durable backstop, auth mode only\n ([what it does](delivery-daemon.md)).\n- **Manager**: a detached supervisor answering the control plane, so\n `cotal spawn --detach` and the `cotal_spawn` tool work right after `up`.\n\nThree modes:\n\n- **Default (static auth).** JWT-authed, on by default: sender authenticity and per-agent\n ACLs, enforced by the broker ([how](identity-and-auth.md)).\n- **`--user-auth --idp <url>`.** Per-user auth: people `cotal login` once, the operator\n grants their agents on the actor ledger, and every connect is authorized live against\n that grant. Starts the space's auth service alongside the broker\n ([how](identity-and-auth.md)).\n- **`--open`.** An unauthenticated, live-only dev mesh (no auth, no delivery daemon). For\n quick local experiments.\n\nAll bind **loopback** by default. `--host 0.0.0.0` widens the bind independently of the\nauth mode, so \"network-reachable\" never silently means \"unauthenticated\". With no explicit\n`--server`, `cotal up` auto-selects a free local port when the default address is already\nheld by another project; an explicit `--server` fails loud on collision.\n\n`cotal status` prints the detailed setup, process, registry, and live mesh status;\n`cotal setup` (after the first run) prints the compact card.\n\nStop one part without tearing down the mesh by naming its registered component: `cotal down\nmanager`, `cotal down delivery`, or `cotal down web`. Component names from installed extensions\njoin the same surface; `cotal down` with no names retains whole-stack behavior.\n\n## Spawning agents\n\n```bash\ncotal spawn # foreground: your default agent, in this terminal\ncotal spawn reviewer --detach # supervised: the manager runs it in a PTY\ncotal attach reviewer # watch/type into a detached agent (Ctrl-] detaches)\ncotal ps # what the manager is running\ncotal stop reviewer # stop one\n```\n\nHow a spawn resolves:\n\n- **Persona.** A bare `cotal spawn` uses `.cotal/agents/default.md`; a positional name\n picks `.cotal/agents/<name>.md`; `--config` takes an explicit ref or path. Set\n `COTAL_DEFAULT_PERSONA=<name-or-path>` to change the fallback. Fields and format:\n [agent files](agent-files.md).\n- **Harness.** Claude by default; `--agent opencode` / `--agent hermes` per spawn, or\n `COTAL_DEFAULT_AGENT` to change the default. Per-connector guides:\n [Claude](connect-claude.md) \xB7 [OpenCode](connect-opencode.md) \xB7\n [Hermes](connect-hermes.md).\n- **Model.** `--model` overrides the persona file's `model:` (Claude: `opus` / `sonnet` or\n a full id; OpenCode: `provider/model`). Connectors that expose a catalog report it via\n `cotal models --agent opencode`: model ids plus available variants; pick one with\n `--model provider/model --variant high`.\n- **Tools.** A spawned agent gets only the cotal tools by default; share your own MCP\n servers deliberately with `--share-tools` ([config](config.md)).\n- **Launch options.** `--opt key=value` (repeatable) passes a native harness flag straight\n through; a persona or manifest `launchOptions:` mapping does the same declaratively (a\n `--opt` wins per key). It is a **raw passthrough**, with no allow/deny list: Claude renders\n each as `--key value` (a bare `--key` for an empty value), OpenCode merges them into its\n agent config, and Hermes has no option surface so it fails loud. The trust boundary is the\n `spawn` capability itself, not the flag set, so granting `spawn` is host-launch authority\n ([security](security.md)). A key must be a plain flag name; malformed or prototype-polluting\n keys are refused.\n\nDetach from an attached PTY with **Ctrl-]** (the agent keeps running); rebind it with\n`COTAL_DETACH_KEY=ctrl-<char>` when it clashes with a keybinding inside the agent's TUI.\n\n**Runtimes.** The manager spawns into a **pty** it owns by default. Optional runtimes are installed\nthrough the extension surface, for example `cotal ext add @cotal-ai/orca`, then selected with\n`--runtime orca` (similarly `@cotal-ai/tmux` and `@cotal-ai/cmux`). They put teammates in native\nterminal surfaces rather than manager-owned PTYs. Runtime names are open-ended and resolved from\nthe registry; a missing provider or app throws, never silently falls back\n([architecture](architecture.md)).\n\n## From any directory: the mesh registry\n\n`cotal up` records each running mesh in a machine-local registry\n(`~/.cotal/meshes/<space>.json`: broker URL, the project root holding its creds and\npersonas, and its mode). So a bare `cotal spawn <persona>` from *any* directory joins the\nrunning mesh with the right credentials instead of mistaking the cwd for a space:\n\n- One mesh up \u2192 used automatically. Inside a project with its own `.cotal/`, that project\n wins.\n- Several up \u2192 pick with `--space <name>`, or set a default with `cotal use <name>`.\n- `cotal meshes` lists them (a `*` marks the default); `cotal down` removes the entry.\n\nThe registry stores a *path*, never a secret; trust material stays in each project's\n`.cotal/auth`. If the mesh is down or won't take your creds, spawn fails with one\nsentence, never a raw NATS trace.\n\n## Watching\n\n`cotal console` is the terminal view (TUI on a real terminal, plain line stream when\npiped); `cotal web` is the browser dashboard. Both are read-only observers; the\nwalkthrough is [Watch a mesh](watch-a-mesh.md).\n\n## History\n\nRetained history is operator-owned. `cotal clean history --force` purges a space's\nretained channel history; `--dms` also purges DMs (`cotal history clear` is an alias).\nIt is deliberately **not** an agent tool: agents cannot wipe the record\n([identity & auth](identity-and-auth.md)). For a **stopped** mesh, `cotal clean store\n--force` deletes the on-disk JetStream store outright, and `cotal clean all --force`\nalso resets the space identity ([CLI reference](cli.md#clean)).\n\n## Offline backup\n\nFor a coherent durable cut, preserve the whole stack first, then create the artifact while it stays\ndown:\n\n```bash\ncotal down --preserve-state\ncotal backup create ./space-backup # full by default\n# later: deliberately resume the unchanged source\ncotal up --detach\n# or, from another preserved cut, restore before the normal listener opens\ncotal up --restore ./space-backup --detach\n```\n\nUse `--store-dir` on both preservation and backup for a custom JetStream store. `registry` is the\nonly partial selection (`backup create ... --only registry`; `up --restore ... --restore-only\nregistry`). Backup never stops or restarts a mesh implicitly, never opens the original store, and\ndoes not contain credentials or trust secrets. Backup/restore in every auth mode \u2014 open included \u2014\nuses isolated, operation-specific maintenance logins; normal agent credentials cannot enter that\nlistener. Full\nrestore requires the same space and exact current local trust continuity, recreates conservative\nconsumer checkpoints bound to their snapshot stream sequence state, and resumes retained agents under\ntheir original principals. The trust commitment includes the cryptographically validated full\noperator/system/data-account root chain as well as static/user authority state. A registry-only\nrestore completes canonical empty infrastructure but leaves retained agents stopped because their\nDM/DLV/TASK/ACL state is outside that selection. Authenticated restore validates the complete space\ntrust bundle before staging or changing the preserved store. Interrupted ordinary resume retries the\nsame durable attempt after its prior listener is stopped. Restore re-entry can recover a surviving normal listener\nonly when its attempt nonce, NATS server name, process owner, endpoint, and target-store identity all\nmatch the fsynced proof. A provably dead uncommitted owner is retired under lock and replaced with a\nfresh attempt-bound listener; an occupied foreign listener or ambiguous owner is never adopted. The\nmanager commit validates while retained cleanup is still suppressed; the CLI durably records its\nattempt-bound 64-hex token in `manager-committed` / `resume-committed` before `finalizeResume` can\nrelease suppression. A retry from either committed state goes straight to exact-token finalization;\nfailure preserves the committed gate and retained cleanup suppression. Missing commit evidence,\ninterrupted finalization, a live recorded endpoint despite missing pidfiles, or ambiguous proof fails closed. See the [CLI\nbackup and restore contract](cli.md#backup-and-restore) for artifact, checkpoint, fallback,\ndisaster-consent, and degraded-recovery details.\n\n## Personas from the CLI\n\n`cotal personas` manages the local catalog offline: `list` (`--running` overlays live\nmarkers), `show <name>`, `edit <name>` (re-validates on save), `new <name>`, `rm <name>\n--force`. The runtime counterpart is the `cotal_persona` tool, which goes over the wire\nwith the manager's ownership checks. Fields: [agent files](agent-files.md).\n\n## When something looks absent\n\nPermission denials are **loud, never silent**: an over-tight ACL shows up as a logged\ndenial on the endpoint, not as a peer that mysteriously looks absent. Check\n`.cotal/manager.log`, `.cotal/delivery.log`, and `.cotal/nats.log`; `cotal status` shows\nwhat is actually running. The access rules are collected in\n[Channels & permissions](channels-and-permissions.md).\n"
|
|
42725
42771
|
},
|
|
42726
42772
|
{
|
|
42727
42773
|
"slug": "security",
|
|
@@ -43632,30 +43678,29 @@ function startControlServer(agent, endpoint, handle, opts = {}) {
|
|
|
43632
43678
|
}
|
|
43633
43679
|
|
|
43634
43680
|
// src/tools.ts
|
|
43635
|
-
import { tool } from "@opencode-ai/plugin";
|
|
43636
43681
|
function buildCotalTools(agent, config2) {
|
|
43637
43682
|
const tools = {};
|
|
43638
43683
|
for (const spec of cotalToolSpecs(config2, "opencode")) {
|
|
43639
43684
|
if (spec.name === "cotal_inbox") {
|
|
43640
|
-
tools.cotal_inbox =
|
|
43685
|
+
tools.cotal_inbox = {
|
|
43641
43686
|
description: "Pull and clear quiet-channel ambient waiting for you. Connector-managed automatic traffic stays queued; in focus mode, normal channel recall is also shown read-only.",
|
|
43642
43687
|
args: {},
|
|
43643
43688
|
async execute() {
|
|
43644
43689
|
const r = await spec.run(agent, config2, { scope: "pull-only" });
|
|
43645
43690
|
return r.isError ? `\u26A0 ${r.text}` : r.text;
|
|
43646
43691
|
}
|
|
43647
|
-
}
|
|
43692
|
+
};
|
|
43648
43693
|
continue;
|
|
43649
43694
|
}
|
|
43650
|
-
tools[spec.name] =
|
|
43695
|
+
tools[spec.name] = {
|
|
43651
43696
|
description: spec.description,
|
|
43652
|
-
// The shared spec carries a Zod raw shape
|
|
43697
|
+
// The shared spec carries a Zod raw shape, which is what OpenCode's tool `args` takes.
|
|
43653
43698
|
args: spec.schema ?? {},
|
|
43654
43699
|
async execute(args) {
|
|
43655
43700
|
const r = await spec.run(agent, config2, args ?? {});
|
|
43656
43701
|
return r.isError ? `\u26A0 ${r.text}` : r.text;
|
|
43657
43702
|
}
|
|
43658
|
-
}
|
|
43703
|
+
};
|
|
43659
43704
|
}
|
|
43660
43705
|
return tools;
|
|
43661
43706
|
}
|