primcli 1.35.0 → 1.36.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.
@@ -3,7 +3,7 @@ import { S as resolveListenSubscription, _ as acquireListenLock, a as mailLockBu
3
3
  import { a as startBackgroundListen, n as backgroundListenToken, o as stopBackgroundListen, r as runBackgroundListen, s as verifyBackgroundListenTarget, t as backgroundListenStatus } from "../listen-background-D6UhIbkO.js";
4
4
  import { t as guardJsonOutputCommands } from "../json-output-KAIuP9rG.js";
5
5
  import { Args, Command, Errors, Flags, ux } from "@oclif/core";
6
- import { chmodSync, closeSync, constants, existsSync, fstatSync, fsyncSync, linkSync, lstatSync, mkdirSync, openSync, opendirSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, statSync, unlinkSync, watch, writeFileSync } from "node:fs";
6
+ import { chmodSync, closeSync, constants, copyFileSync, existsSync, fstatSync, fsyncSync, linkSync, lstatSync, mkdirSync, openSync, opendirSync, readFileSync, readdirSync, readlinkSync, realpathSync, renameSync, rmSync, statSync, symlinkSync, unlinkSync, watch, writeFileSync } from "node:fs";
7
7
  import { createHash, randomUUID, scryptSync } from "node:crypto";
8
8
  import path, { basename, dirname, isAbsolute, join, normalize, relative, resolve, sep } from "node:path";
9
9
  import { setTimeout as setTimeout$1 } from "node:timers/promises";
@@ -49915,8 +49915,8 @@ function preparePresenceAliveEmail(input, dependencies) {
49915
49915
  });
49916
49916
  }
49917
49917
  const MAX_INTERACTION_BYTES = 65536;
49918
- const UUID$5 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
49919
- const WIRE_ID = new RegExp(`^${UUID$5.source.slice(1, -1)}@[^\\s@]+$`, "i");
49918
+ const UUID$6 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
49919
+ const WIRE_ID = new RegExp(`^${UUID$6.source.slice(1, -1)}@[^\\s@]+$`, "i");
49920
49920
  function scalarString(value) {
49921
49921
  for (let i = 0; i < value.length; i++) {
49922
49922
  const code = value.charCodeAt(i);
@@ -50027,7 +50027,7 @@ function envelope$1(value) {
50027
50027
  "expires_at",
50028
50028
  "payload"
50029
50029
  ].every((key) => Object.hasOwn(obj, key))) return invalid;
50030
- if (typeof obj.interaction_id !== "string" || !WIRE_ID.test(obj.interaction_id) || typeof obj.protocol !== "string" || !obj.protocol.trim() || typeof obj.protocol_version !== "number" || !Number.isSafeInteger(obj.protocol_version) || obj.protocol_version < 1 || typeof obj.step !== "string" || !obj.step.trim() || typeof obj.step_id !== "string" || !UUID$5.test(obj.step_id) || !(obj.prev_step_id === null || typeof obj.prev_step_id === "string" && UUID$5.test(obj.prev_step_id)) || !(obj.expires_at === null || typeof obj.expires_at === "string") || !Object.hasOwn(obj, "payload")) return invalid;
50030
+ if (typeof obj.interaction_id !== "string" || !WIRE_ID.test(obj.interaction_id) || typeof obj.protocol !== "string" || !obj.protocol.trim() || typeof obj.protocol_version !== "number" || !Number.isSafeInteger(obj.protocol_version) || obj.protocol_version < 1 || typeof obj.step !== "string" || !obj.step.trim() || typeof obj.step_id !== "string" || !UUID$6.test(obj.step_id) || !(obj.prev_step_id === null || typeof obj.prev_step_id === "string" && UUID$6.test(obj.prev_step_id)) || !(obj.expires_at === null || typeof obj.expires_at === "string") || !Object.hasOwn(obj, "payload")) return invalid;
50031
50031
  return {
50032
50032
  status: "valid",
50033
50033
  envelope: obj
@@ -51010,6 +51010,333 @@ async function connectAgent(params) {
51010
51010
  }
51011
51011
  }
51012
51012
  //#endregion
51013
+ //#region src/oclif/contacts.ts
51014
+ var ContactsApiError = class extends Error {
51015
+ directoryAvailable = false;
51016
+ constructor(payload) {
51017
+ super("Contact request failed.");
51018
+ this.payload = payload;
51019
+ }
51020
+ };
51021
+ function contactAddress(value) {
51022
+ const address = value.trim().toLowerCase();
51023
+ if (address.length > 254 || !/^[A-Za-z0-9.!#$%&'*+/=?^_`{|}~-]+@[A-Za-z0-9](?:[A-Za-z0-9.-]*[A-Za-z0-9])?$/.test(address)) throw new Error("Provide a bare contact email address.");
51024
+ return address;
51025
+ }
51026
+ function contactAgentAddress(explicit, connected) {
51027
+ if (!explicit && !connected) throw new Error("Pass --agent or select a connected agent profile.");
51028
+ const address = contactAddress(explicit ?? connected ?? "");
51029
+ if (connected && address !== contactAddress(connected)) throw new Error("This connected profile can manage only its own agent contacts.");
51030
+ return address;
51031
+ }
51032
+ function version(value) {
51033
+ if (!/^[a-f\d]{8}(?:-[a-f\d]{4}){3}-[a-f\d]{12}$/i.test(value)) throw new Error("The contact version must be the UUID returned by the API.");
51034
+ return value;
51035
+ }
51036
+ async function response$1(pending) {
51037
+ const result = await pending;
51038
+ if (result.error !== void 0) throw new ContactsApiError(result.error);
51039
+ if (result.data?.success !== true || result.data.data == null) throw new Error("The contacts API returned an incomplete response.");
51040
+ return result.data;
51041
+ }
51042
+ function pageCursor(value) {
51043
+ const cursor = value.meta?.cursor;
51044
+ if (cursor === null) return null;
51045
+ if (typeof cursor !== "string") throw new Error("The contacts API omitted pagination state; no write was attempted.");
51046
+ return contactAddress(cursor);
51047
+ }
51048
+ async function membershipVersion(client, agent, address, signal) {
51049
+ let cursor;
51050
+ const seen = /* @__PURE__ */ new Set();
51051
+ for (let page = 0; page < 1e3; page++) {
51052
+ signal?.throwIfAborted();
51053
+ const result = await response$1(listAgentContacts({
51054
+ client,
51055
+ path: { agent_address: agent },
51056
+ query: {
51057
+ limit: 100,
51058
+ cursor
51059
+ },
51060
+ signal,
51061
+ responseStyle: "fields"
51062
+ }));
51063
+ if (!Array.isArray(result.data)) throw new Error("The contacts API returned an invalid page.");
51064
+ const next = pageCursor(result);
51065
+ const found = result.data.find((row) => row.contact_address === address);
51066
+ if (found) {
51067
+ if (found.agent_address !== agent) throw new Error("The contacts API returned a different agent.");
51068
+ return version(found.version);
51069
+ }
51070
+ if (next === null) throw new Error("Agent contact not found. Use agent contacts add to create it.");
51071
+ if (seen.has(next)) throw new Error("Contact pagination repeated a cursor; no write was attempted.");
51072
+ seen.add(next);
51073
+ cursor = next;
51074
+ }
51075
+ throw new Error("Contact lookup exceeded its page limit; pass an explicit --if-version.");
51076
+ }
51077
+ /** Public generated operations only. Every mutation uses one explicit CAS condition. */
51078
+ async function runContactRequest(client, request) {
51079
+ const { action, target, signal } = request;
51080
+ signal?.throwIfAborted();
51081
+ const address = action === "list" ? void 0 : contactAddress(request.address ?? "");
51082
+ const agent = target === "agent" ? contactAgentAddress(request.agent) : void 0;
51083
+ const limit = request.limit ?? 50;
51084
+ if (!Number.isInteger(limit) || limit < 1 || limit > 100) throw new Error("--limit must be between 1 and 100.");
51085
+ const cursor = request.cursor === void 0 ? void 0 : contactAddress(request.cursor);
51086
+ if (request.name !== void 0 && request.clearName) throw new Error("Use either --name or --clear-name.");
51087
+ if (request.purpose !== void 0 && request.clearPurpose) throw new Error("Use either --purpose or --clear-purpose.");
51088
+ if (request.name !== void 0 && Array.from(request.name).length > 200) throw new Error("Contact names may contain at most 200 characters.");
51089
+ if (request.purpose !== void 0 && Array.from(request.purpose).length > 2e3) throw new Error("Contact purposes may contain at most 2000 characters.");
51090
+ const expected = request.ifVersion === void 0 ? void 0 : version(request.ifVersion);
51091
+ if (action === "add" && expected) throw new Error("Add never overwrites an existing contact. Use update with --if-version.");
51092
+ if (action === "list") {
51093
+ const result = target === "directory" ? await response$1(listContacts({
51094
+ client,
51095
+ signal,
51096
+ query: {
51097
+ cursor,
51098
+ limit
51099
+ },
51100
+ responseStyle: "fields"
51101
+ })) : await response$1(listAgentContacts({
51102
+ client,
51103
+ signal,
51104
+ path: { agent_address: agent ?? "" },
51105
+ query: {
51106
+ cursor,
51107
+ limit
51108
+ },
51109
+ responseStyle: "fields"
51110
+ }));
51111
+ if (!Array.isArray(result.data)) throw new Error("The contacts API returned an invalid page.");
51112
+ pageCursor(result);
51113
+ return result;
51114
+ }
51115
+ if (!address) throw new Error("Provide a contact email address.");
51116
+ if (target === "directory") {
51117
+ const name = request.clearName ? null : request.name;
51118
+ if (action === "update" && name === void 0) throw new Error("Pass --name or --clear-name to update the contact.");
51119
+ if (action === "get" || (action === "update" || action === "remove") && !expected) {
51120
+ const result = await response$1(getContact({
51121
+ client,
51122
+ signal,
51123
+ path: { address },
51124
+ responseStyle: "fields"
51125
+ }));
51126
+ if (result.data.address !== address) throw new Error("The contacts API returned a different contact.");
51127
+ if (action === "get") return result.data;
51128
+ request = {
51129
+ ...request,
51130
+ ifVersion: version(result.data.version)
51131
+ };
51132
+ }
51133
+ signal?.throwIfAborted();
51134
+ if (action === "remove") return (await response$1(deleteContact({
51135
+ client,
51136
+ signal,
51137
+ path: { address },
51138
+ query: { if_version: version(request.ifVersion ?? "") },
51139
+ responseStyle: "fields"
51140
+ }))).data;
51141
+ const result = await response$1(putContact({
51142
+ client,
51143
+ signal,
51144
+ path: { address },
51145
+ body: {
51146
+ ...action === "add" ? { if_absent: true } : { if_version: version(request.ifVersion ?? "") },
51147
+ ...name === void 0 ? {} : { display_name: name }
51148
+ },
51149
+ responseStyle: "fields"
51150
+ }));
51151
+ if (result.data.address !== address) throw new Error("The contacts API returned a different contact.");
51152
+ return result.data;
51153
+ }
51154
+ if (!agent) throw new Error("Provide an agent address.");
51155
+ if (action === "get") throw new Error("Use agent contacts list to inspect memberships.");
51156
+ const purpose = request.clearPurpose ? null : request.purpose;
51157
+ if (action === "update" && purpose === void 0 && request.notify === void 0) throw new Error("Pass --purpose, --clear-purpose, --notify or --no-notify.");
51158
+ const path = {
51159
+ agent_address: agent,
51160
+ contact_address: address
51161
+ };
51162
+ if (action === "add") {
51163
+ if ((await response$1(putContact({
51164
+ client,
51165
+ signal,
51166
+ path: { address },
51167
+ body: { if_absent: true },
51168
+ responseStyle: "fields"
51169
+ }))).data.address !== address) throw new Error("The contacts API returned a different contact.");
51170
+ }
51171
+ const ifVersion = action === "add" ? void 0 : expected ?? await membershipVersion(client, agent, address, signal);
51172
+ if (action === "remove") {
51173
+ signal?.throwIfAborted();
51174
+ return (await response$1(deleteAgentContact({
51175
+ client,
51176
+ signal,
51177
+ path,
51178
+ query: { if_version: version(ifVersion ?? "") },
51179
+ responseStyle: "fields"
51180
+ }))).data;
51181
+ }
51182
+ try {
51183
+ signal?.throwIfAborted();
51184
+ const result = await response$1(putAgentContact({
51185
+ client,
51186
+ signal,
51187
+ path,
51188
+ body: {
51189
+ ...action === "add" ? { if_absent: true } : { if_version: version(ifVersion ?? "") },
51190
+ ...purpose === void 0 ? {} : { purpose },
51191
+ ...request.notify === void 0 ? {} : { notify: request.notify }
51192
+ },
51193
+ responseStyle: "fields"
51194
+ }));
51195
+ if (result.data.agent_address !== agent || result.data.contact_address !== address) throw new Error("The contacts API returned a different membership.");
51196
+ return result.data;
51197
+ } catch (error) {
51198
+ if (action === "add") {
51199
+ const failure = error instanceof ContactsApiError ? error : new ContactsApiError({
51200
+ code: "contact_membership_unsaved",
51201
+ message: error instanceof Error ? error.message : "Agent contact request failed."
51202
+ });
51203
+ failure.directoryAvailable = true;
51204
+ throw failure;
51205
+ }
51206
+ throw error;
51207
+ }
51208
+ }
51209
+ //#endregion
51210
+ //#region src/oclif/address-notes.ts
51211
+ var AddressNotesApiError = class extends Error {
51212
+ constructor(payload, status) {
51213
+ super("Address note request failed.");
51214
+ this.payload = payload;
51215
+ this.status = status;
51216
+ }
51217
+ };
51218
+ const security$2 = [{
51219
+ scheme: "bearer",
51220
+ type: "http"
51221
+ }];
51222
+ function addressNoteName(value) {
51223
+ if (!/^[A-Za-z0-9][A-Za-z0-9_.-]{0,127}$/.test(value)) throw new Error("Note name must start with a letter or digit and contain at most 128 letters, digits, underscores, dots, or hyphens.");
51224
+ return value;
51225
+ }
51226
+ function addressNoteVersion(value) {
51227
+ if (!/^[0-9]{1,19}$/.test(value)) throw new Error("Note version must be the decimal version returned by the API.");
51228
+ return value;
51229
+ }
51230
+ function addressNoteTarget(explicit, connected, write) {
51231
+ if (!explicit && !connected) throw new Error("Pass --address or select a connected agent profile.");
51232
+ const address = contactAddress(explicit ?? connected ?? "");
51233
+ if (write && connected && address !== contactAddress(connected)) throw new Error("This connected profile can write only its own address notes.");
51234
+ return address;
51235
+ }
51236
+ function note(value, address, name) {
51237
+ if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("The address notes API returned an invalid note.");
51238
+ const row = value;
51239
+ if (row.address !== address || typeof row.name !== "string" || name !== void 0 && row.name !== name || row.visibility !== "private" && row.visibility !== "public" || typeof row.version !== "string" || !/^[0-9]{1,19}$/.test(row.version) || typeof row.created_at !== "string" || typeof row.updated_at !== "string" || !("value" in row)) throw new Error("The address notes API returned an invalid note.");
51240
+ return row;
51241
+ }
51242
+ function envelope(value) {
51243
+ if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("The address notes API returned an invalid response.");
51244
+ const row = value;
51245
+ if (row.success !== true || !("data" in row)) throw new Error("The address notes API returned an incomplete response.");
51246
+ return row;
51247
+ }
51248
+ async function result(pending) {
51249
+ const response = await pending;
51250
+ if (response.error !== void 0) throw new AddressNotesApiError(response.error, response.response?.status);
51251
+ return envelope(response.data).data;
51252
+ }
51253
+ async function getNote(client, address, name, signal) {
51254
+ return note(await result(client.get({
51255
+ security: security$2,
51256
+ url: "/address-notes/{address}/{name}",
51257
+ path: {
51258
+ address,
51259
+ name
51260
+ },
51261
+ responseStyle: "fields",
51262
+ ...signal ? { signal } : {}
51263
+ })), address, name);
51264
+ }
51265
+ async function runAddressNotesRequest(client, request) {
51266
+ const address = contactAddress(request.address);
51267
+ if (request.action === "list") {
51268
+ const limit = request.limit ?? 50;
51269
+ if (!Number.isInteger(limit) || limit < 1 || limit > 100) throw new Error("--limit must be between 1 and 100.");
51270
+ const prefix = request.prefix;
51271
+ if (prefix !== void 0 && !/^[A-Za-z0-9_.-]{0,128}$/.test(prefix)) throw new Error("--prefix may contain at most 128 letters, digits, underscores, dots, or hyphens.");
51272
+ const cursor = request.cursor === void 0 ? void 0 : addressNoteName(request.cursor);
51273
+ const response = await client.get({
51274
+ security: security$2,
51275
+ url: "/address-notes",
51276
+ query: {
51277
+ address,
51278
+ prefix,
51279
+ cursor,
51280
+ limit
51281
+ },
51282
+ responseStyle: "fields"
51283
+ });
51284
+ if (response.error !== void 0) throw new AddressNotesApiError(response.error, response.response?.status);
51285
+ const body = envelope(response.data);
51286
+ if (!Array.isArray(body.data) || !body.meta || typeof body.meta !== "object" || Array.isArray(body.meta)) throw new Error("The address notes API returned an invalid page.");
51287
+ const next = body.meta.cursor;
51288
+ if (next !== null && (typeof next !== "string" || addressNoteName(next) !== next)) throw new Error("The address notes API omitted valid pagination state.");
51289
+ return {
51290
+ ...body,
51291
+ data: body.data.map((row) => note(row, address))
51292
+ };
51293
+ }
51294
+ const name = addressNoteName(request.name ?? "");
51295
+ if (request.action === "get") return getNote(client, address, name, request.signal);
51296
+ if (request.ifAbsent && request.ifVersion !== void 0) throw new Error("Use either --if-absent or --if-version.");
51297
+ const ifVersion = request.ifVersion === void 0 ? void 0 : addressNoteVersion(request.ifVersion);
51298
+ if (request.action === "set") {
51299
+ if (request.value === void 0) throw new Error("Provide a note value.");
51300
+ let condition;
51301
+ if (request.ifAbsent) condition = { if_absent: true };
51302
+ else if (ifVersion) condition = { if_version: ifVersion };
51303
+ else try {
51304
+ condition = { if_version: (await getNote(client, address, name)).version };
51305
+ } catch (error) {
51306
+ if (!(error instanceof AddressNotesApiError) || error.status !== 404) throw error;
51307
+ condition = { if_absent: true };
51308
+ }
51309
+ return note(await result(client.put({
51310
+ security: security$2,
51311
+ url: "/address-notes/{address}/{name}",
51312
+ path: {
51313
+ address,
51314
+ name
51315
+ },
51316
+ body: {
51317
+ value: request.value,
51318
+ ...condition,
51319
+ ...request.visibility ? { visibility: request.visibility } : {}
51320
+ },
51321
+ headers: { "Content-Type": "application/json" },
51322
+ responseStyle: "fields"
51323
+ })), address, name);
51324
+ }
51325
+ const version = ifVersion ?? (await getNote(client, address, name)).version;
51326
+ const data = await result(client.delete({
51327
+ security: security$2,
51328
+ url: "/address-notes/{address}/{name}",
51329
+ path: {
51330
+ address,
51331
+ name
51332
+ },
51333
+ query: { if_version: version },
51334
+ responseStyle: "fields"
51335
+ }));
51336
+ if (!data || typeof data !== "object" || Array.isArray(data) || typeof data.deleted !== "boolean") throw new Error("The address notes API returned an invalid deletion response.");
51337
+ return data;
51338
+ }
51339
+ //#endregion
51013
51340
  //#region src/oclif/agent-setup-read.ts
51014
51341
  function retryDelay(response, attempt, now) {
51015
51342
  const raw = response?.headers.get("retry-after")?.trim();
@@ -51255,203 +51582,6 @@ function evaluateContactPolicy(options) {
51255
51582
  source: "default"
51256
51583
  };
51257
51584
  }
51258
- //#endregion
51259
- //#region src/oclif/contacts.ts
51260
- var ContactsApiError = class extends Error {
51261
- directoryAvailable = false;
51262
- constructor(payload) {
51263
- super("Contact request failed.");
51264
- this.payload = payload;
51265
- }
51266
- };
51267
- function contactAddress(value) {
51268
- const address = value.trim().toLowerCase();
51269
- if (address.length > 254 || !/^[A-Za-z0-9.!#$%&'*+/=?^_`{|}~-]+@[A-Za-z0-9](?:[A-Za-z0-9.-]*[A-Za-z0-9])?$/.test(address)) throw new Error("Provide a bare contact email address.");
51270
- return address;
51271
- }
51272
- function contactAgentAddress(explicit, connected) {
51273
- if (!explicit && !connected) throw new Error("Pass --agent or select a connected agent profile.");
51274
- const address = contactAddress(explicit ?? connected ?? "");
51275
- if (connected && address !== contactAddress(connected)) throw new Error("This connected profile can manage only its own agent contacts.");
51276
- return address;
51277
- }
51278
- function version(value) {
51279
- if (!/^[a-f\d]{8}(?:-[a-f\d]{4}){3}-[a-f\d]{12}$/i.test(value)) throw new Error("The contact version must be the UUID returned by the API.");
51280
- return value;
51281
- }
51282
- async function response$1(pending) {
51283
- const result = await pending;
51284
- if (result.error !== void 0) throw new ContactsApiError(result.error);
51285
- if (result.data?.success !== true || result.data.data == null) throw new Error("The contacts API returned an incomplete response.");
51286
- return result.data;
51287
- }
51288
- function pageCursor(value) {
51289
- const cursor = value.meta?.cursor;
51290
- if (cursor === null) return null;
51291
- if (typeof cursor !== "string") throw new Error("The contacts API omitted pagination state; no write was attempted.");
51292
- return contactAddress(cursor);
51293
- }
51294
- async function membershipVersion(client, agent, address, signal) {
51295
- let cursor;
51296
- const seen = /* @__PURE__ */ new Set();
51297
- for (let page = 0; page < 1e3; page++) {
51298
- signal?.throwIfAborted();
51299
- const result = await response$1(listAgentContacts({
51300
- client,
51301
- path: { agent_address: agent },
51302
- query: {
51303
- limit: 100,
51304
- cursor
51305
- },
51306
- signal,
51307
- responseStyle: "fields"
51308
- }));
51309
- if (!Array.isArray(result.data)) throw new Error("The contacts API returned an invalid page.");
51310
- const next = pageCursor(result);
51311
- const found = result.data.find((row) => row.contact_address === address);
51312
- if (found) {
51313
- if (found.agent_address !== agent) throw new Error("The contacts API returned a different agent.");
51314
- return version(found.version);
51315
- }
51316
- if (next === null) throw new Error("Agent contact not found. Use agent contacts add to create it.");
51317
- if (seen.has(next)) throw new Error("Contact pagination repeated a cursor; no write was attempted.");
51318
- seen.add(next);
51319
- cursor = next;
51320
- }
51321
- throw new Error("Contact lookup exceeded its page limit; pass an explicit --if-version.");
51322
- }
51323
- /** Public generated operations only. Every mutation uses one explicit CAS condition. */
51324
- async function runContactRequest(client, request) {
51325
- const { action, target, signal } = request;
51326
- signal?.throwIfAborted();
51327
- const address = action === "list" ? void 0 : contactAddress(request.address ?? "");
51328
- const agent = target === "agent" ? contactAgentAddress(request.agent) : void 0;
51329
- const limit = request.limit ?? 50;
51330
- if (!Number.isInteger(limit) || limit < 1 || limit > 100) throw new Error("--limit must be between 1 and 100.");
51331
- const cursor = request.cursor === void 0 ? void 0 : contactAddress(request.cursor);
51332
- if (request.name !== void 0 && request.clearName) throw new Error("Use either --name or --clear-name.");
51333
- if (request.purpose !== void 0 && request.clearPurpose) throw new Error("Use either --purpose or --clear-purpose.");
51334
- if (request.name !== void 0 && Array.from(request.name).length > 200) throw new Error("Contact names may contain at most 200 characters.");
51335
- if (request.purpose !== void 0 && Array.from(request.purpose).length > 2e3) throw new Error("Contact purposes may contain at most 2000 characters.");
51336
- const expected = request.ifVersion === void 0 ? void 0 : version(request.ifVersion);
51337
- if (action === "add" && expected) throw new Error("Add never overwrites an existing contact. Use update with --if-version.");
51338
- if (action === "list") {
51339
- const result = target === "directory" ? await response$1(listContacts({
51340
- client,
51341
- signal,
51342
- query: {
51343
- cursor,
51344
- limit
51345
- },
51346
- responseStyle: "fields"
51347
- })) : await response$1(listAgentContacts({
51348
- client,
51349
- signal,
51350
- path: { agent_address: agent ?? "" },
51351
- query: {
51352
- cursor,
51353
- limit
51354
- },
51355
- responseStyle: "fields"
51356
- }));
51357
- if (!Array.isArray(result.data)) throw new Error("The contacts API returned an invalid page.");
51358
- pageCursor(result);
51359
- return result;
51360
- }
51361
- if (!address) throw new Error("Provide a contact email address.");
51362
- if (target === "directory") {
51363
- const name = request.clearName ? null : request.name;
51364
- if (action === "update" && name === void 0) throw new Error("Pass --name or --clear-name to update the contact.");
51365
- if (action === "get" || (action === "update" || action === "remove") && !expected) {
51366
- const result = await response$1(getContact({
51367
- client,
51368
- signal,
51369
- path: { address },
51370
- responseStyle: "fields"
51371
- }));
51372
- if (result.data.address !== address) throw new Error("The contacts API returned a different contact.");
51373
- if (action === "get") return result.data;
51374
- request = {
51375
- ...request,
51376
- ifVersion: version(result.data.version)
51377
- };
51378
- }
51379
- signal?.throwIfAborted();
51380
- if (action === "remove") return (await response$1(deleteContact({
51381
- client,
51382
- signal,
51383
- path: { address },
51384
- query: { if_version: version(request.ifVersion ?? "") },
51385
- responseStyle: "fields"
51386
- }))).data;
51387
- const result = await response$1(putContact({
51388
- client,
51389
- signal,
51390
- path: { address },
51391
- body: {
51392
- ...action === "add" ? { if_absent: true } : { if_version: version(request.ifVersion ?? "") },
51393
- ...name === void 0 ? {} : { display_name: name }
51394
- },
51395
- responseStyle: "fields"
51396
- }));
51397
- if (result.data.address !== address) throw new Error("The contacts API returned a different contact.");
51398
- return result.data;
51399
- }
51400
- if (!agent) throw new Error("Provide an agent address.");
51401
- if (action === "get") throw new Error("Use agent contacts list to inspect memberships.");
51402
- const purpose = request.clearPurpose ? null : request.purpose;
51403
- if (action === "update" && purpose === void 0 && request.notify === void 0) throw new Error("Pass --purpose, --clear-purpose, --notify or --no-notify.");
51404
- const path = {
51405
- agent_address: agent,
51406
- contact_address: address
51407
- };
51408
- if (action === "add") {
51409
- if ((await response$1(putContact({
51410
- client,
51411
- signal,
51412
- path: { address },
51413
- body: { if_absent: true },
51414
- responseStyle: "fields"
51415
- }))).data.address !== address) throw new Error("The contacts API returned a different contact.");
51416
- }
51417
- const ifVersion = action === "add" ? void 0 : expected ?? await membershipVersion(client, agent, address, signal);
51418
- if (action === "remove") {
51419
- signal?.throwIfAborted();
51420
- return (await response$1(deleteAgentContact({
51421
- client,
51422
- signal,
51423
- path,
51424
- query: { if_version: version(ifVersion ?? "") },
51425
- responseStyle: "fields"
51426
- }))).data;
51427
- }
51428
- try {
51429
- signal?.throwIfAborted();
51430
- const result = await response$1(putAgentContact({
51431
- client,
51432
- signal,
51433
- path,
51434
- body: {
51435
- ...action === "add" ? { if_absent: true } : { if_version: version(ifVersion ?? "") },
51436
- ...purpose === void 0 ? {} : { purpose },
51437
- ...request.notify === void 0 ? {} : { notify: request.notify }
51438
- },
51439
- responseStyle: "fields"
51440
- }));
51441
- if (result.data.agent_address !== agent || result.data.contact_address !== address) throw new Error("The contacts API returned a different membership.");
51442
- return result.data;
51443
- } catch (error) {
51444
- if (action === "add") {
51445
- const failure = error instanceof ContactsApiError ? error : new ContactsApiError({
51446
- code: "contact_membership_unsaved",
51447
- message: error instanceof Error ? error.message : "Agent contact request failed."
51448
- });
51449
- failure.directoryAvailable = true;
51450
- throw failure;
51451
- }
51452
- throw error;
51453
- }
51454
- }
51455
51585
  const CONTACT_POLICY_RETRY_MIN_MS = 1e3;
51456
51586
  const CONTACT_POLICY_RETRY_MAX_MS = 3e4;
51457
51587
  const NETWORK_ADMISSION_PENDING_RETRY_MS = 5e3;
@@ -51932,7 +52062,7 @@ async function enableOwner(context) {
51932
52062
  await refresh();
51933
52063
  return (await policy.admit(context.identity.ownerAddress, (/* @__PURE__ */ new Date()).toISOString(), context.signal))?.kind === "allowed" ? "enabled" : "silenced";
51934
52064
  }
51935
- function defaults() {
52065
+ function defaults$1() {
51936
52066
  return {
51937
52067
  now: Date.now,
51938
52068
  sleep: async (ms) => {
@@ -52022,7 +52152,7 @@ async function setupAgent(params) {
52022
52152
  const timeoutMs = params.timeoutMs ?? 3e4;
52023
52153
  if (!Number.isSafeInteger(timeoutMs) || timeoutMs < 0 || timeoutMs > 12e4) throw fail("Setup timeout must be between zero and two minutes.");
52024
52154
  const dependencies = {
52025
- ...defaults(),
52155
+ ...defaults$1(),
52026
52156
  ...params.dependencies
52027
52157
  };
52028
52158
  if ((params.receiverMode ?? "native") === "native") try {
@@ -52147,11 +52277,537 @@ async function setupAgent(params) {
52147
52277
  }
52148
52278
  }
52149
52279
  //#endregion
52280
+ //#region src/oclif/connect-skill.ts
52281
+ const CONNECT_SKILL_NAME = "primitive-connect";
52282
+ const UUID$5 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
52283
+ /**
52284
+ * The runtime whose tool environment names this exact session. A session ID
52285
+ * that matches neither runtime's variable has no detectable runtime.
52286
+ */
52287
+ function detectAgentRuntime(session, env = process.env) {
52288
+ const wanted = session.toLowerCase();
52289
+ const claude = env.CLAUDE_CODE_SESSION_ID?.trim().toLowerCase();
52290
+ const codex = (env.CODEX_THREAD_ID?.trim() || env.CODEX_SESSION_ID?.trim())?.toLowerCase();
52291
+ if (!UUID$5.test(wanted)) return null;
52292
+ if (claude === wanted && codex !== wanted) return "claude";
52293
+ if (codex === wanted && claude !== wanted) return "codex";
52294
+ return null;
52295
+ }
52296
+ function connectSkillTarget(options) {
52297
+ const env = options.env ?? process.env;
52298
+ const cwd = resolve(options.cwd ?? process.cwd());
52299
+ return join(options.runtime === "claude" ? options.project ? join(cwd, ".claude") : resolve(env.CLAUDE_CONFIG_DIR || join(homedir(), ".claude")) : options.project ? join(cwd, ".agents") : resolve(env.CODEX_HOME || join(homedir(), ".codex")), "skills", CONNECT_SKILL_NAME);
52300
+ }
52301
+ /** Mirrors scripts/skill-files.mjs so installed and bundled versions compare equal. */
52302
+ function included(relativePath) {
52303
+ if (relativePath.split("/").some((part) => part.startsWith(".") || part === "node_modules")) return false;
52304
+ return !/\.test\.[cm]?[jt]s$/.test(relativePath);
52305
+ }
52306
+ function listFiles(root) {
52307
+ const files = [];
52308
+ const walk = (directory, prefix) => {
52309
+ for (const entry of readdirSync(directory, { withFileTypes: true })) {
52310
+ const relative = prefix ? `${prefix}/${entry.name}` : entry.name;
52311
+ if (entry.isDirectory()) walk(join(directory, entry.name), relative);
52312
+ else if (entry.isFile() && included(relative)) files.push(relative);
52313
+ }
52314
+ };
52315
+ walk(root, "");
52316
+ return files.sort();
52317
+ }
52318
+ function hashFiles(root, files = listFiles(root)) {
52319
+ const hashes = {};
52320
+ for (const file of files) hashes[file] = createHash("sha256").update(readFileSync(join(root, file))).digest("hex");
52321
+ return hashes;
52322
+ }
52323
+ function connectSkillVersion(hashes) {
52324
+ const digest = createHash("sha256");
52325
+ for (const file of Object.keys(hashes).sort()) digest.update(`${file}\0${hashes[file]}\n`);
52326
+ return digest.digest("hex").slice(0, 16);
52327
+ }
52328
+ /** Read the copy shipped in this CLI package and refuse one that does not match its manifest. */
52329
+ function readBundledConnectSkill(packageRoot) {
52330
+ const skillsDir = join(packageRoot, "dist", "skills");
52331
+ const directory = join(skillsDir, CONNECT_SKILL_NAME);
52332
+ const manifest = JSON.parse(readFileSync(join(skillsDir, `${CONNECT_SKILL_NAME}.json`), "utf8"));
52333
+ const expected = manifest.files && typeof manifest.files === "object" && !Array.isArray(manifest.files) ? manifest.files : null;
52334
+ const actual = hashFiles(directory);
52335
+ if (manifest.name !== "primitive-connect" || !expected || typeof manifest.version !== "string" || connectSkillVersion(actual) !== manifest.version || connectSkillVersion(expected) !== manifest.version) throw new Error("The bundled skill does not match its manifest.");
52336
+ return {
52337
+ directory,
52338
+ version: manifest.version,
52339
+ commit: typeof manifest.source?.commit === "string" ? manifest.source.commit : null,
52340
+ files: actual
52341
+ };
52342
+ }
52343
+ /** Two skill copies declare the same helper dependencies. */
52344
+ function sameDependencies(a, b) {
52345
+ const digest = (path) => {
52346
+ try {
52347
+ return createHash("sha256").update(readFileSync(path)).digest("hex");
52348
+ } catch {
52349
+ return null;
52350
+ }
52351
+ };
52352
+ return ["package.json", "package-lock.json"].every((file) => digest(join(a, file)) === digest(join(b, file)));
52353
+ }
52354
+ /** Leftovers younger than this may belong to a refresh still in progress. */
52355
+ const STALE_LEFTOVER_MS = 6e4;
52356
+ /**
52357
+ * A refresh moves the old copy aside just before moving the new one in. If a
52358
+ * process stopped between those renames, put the old copy back so the skill
52359
+ * path is never left empty, and remove abandoned staging and retired copies.
52360
+ */
52361
+ function recoverInterruptedInstall(root, target) {
52362
+ let entries;
52363
+ try {
52364
+ entries = readdirSync(root);
52365
+ } catch {
52366
+ return false;
52367
+ }
52368
+ const stale = (name) => {
52369
+ try {
52370
+ return Date.now() - lstatSync(join(root, name)).mtimeMs > STALE_LEFTOVER_MS;
52371
+ } catch {
52372
+ return false;
52373
+ }
52374
+ };
52375
+ const retired = entries.filter((name) => name.startsWith(`.${CONNECT_SKILL_NAME}.retired-`)).filter(stale).sort((a, b) => lstatSync(join(root, b)).mtimeMs - lstatSync(join(root, a)).mtimeMs);
52376
+ let recovered = false;
52377
+ let missing = false;
52378
+ try {
52379
+ lstatSync(target);
52380
+ } catch {
52381
+ missing = true;
52382
+ }
52383
+ if (missing && retired[0]) try {
52384
+ renameSync(join(root, retired[0]), target);
52385
+ recovered = true;
52386
+ retired.shift();
52387
+ } catch {}
52388
+ let realDirectory = false;
52389
+ try {
52390
+ realDirectory = lstatSync(target).isDirectory();
52391
+ } catch {
52392
+ realDirectory = false;
52393
+ }
52394
+ for (const name of retired) {
52395
+ const deps = join(root, name, "node_modules");
52396
+ if (realDirectory && existsSync(deps) && !existsSync(join(target, "node_modules")) && sameDependencies(join(root, name), target)) try {
52397
+ renameSync(deps, join(target, "node_modules"));
52398
+ } catch {}
52399
+ }
52400
+ for (const name of [...retired, ...entries.filter((entry) => entry.startsWith(`.${CONNECT_SKILL_NAME}.staging-`)).filter(stale)]) rmSync(join(root, name), {
52401
+ recursive: true,
52402
+ force: true
52403
+ });
52404
+ return recovered;
52405
+ }
52406
+ function declaresConnectSkill(directory) {
52407
+ try {
52408
+ const text = readFileSync(join(directory, "SKILL.md"), "utf8");
52409
+ const frontmatter = /^---\r?\n([\s\S]*?)\r?\n---/.exec(text)?.[1] ?? "";
52410
+ return /^name:\s*["']?primitive-connect["']?\s*$/m.test(frontmatter);
52411
+ } catch {
52412
+ return false;
52413
+ }
52414
+ }
52415
+ function otherCopies(target) {
52416
+ const root = dirname(target);
52417
+ try {
52418
+ return readdirSync(root, { withFileTypes: true }).filter((entry) => !entry.name.startsWith(".") && entry.name !== "primitive-connect" && (entry.isDirectory() || entry.isSymbolicLink())).map((entry) => join(root, entry.name)).filter(declaresConnectSkill).sort();
52419
+ } catch {
52420
+ return [];
52421
+ }
52422
+ }
52423
+ function installedVersion(target) {
52424
+ try {
52425
+ if (!statSync(target).isDirectory()) return null;
52426
+ return connectSkillVersion(hashFiles(target));
52427
+ } catch {
52428
+ return null;
52429
+ }
52430
+ }
52431
+ /**
52432
+ * Install or refresh the bundled skill for one runtime. The same version is
52433
+ * left untouched. A different copy is replaced in one rename, never beside a
52434
+ * second directory, and a symlink is replaced without touching its target.
52435
+ */
52436
+ function installConnectSkill(options) {
52437
+ const target = connectSkillTarget(options);
52438
+ const base = {
52439
+ runtime: options.runtime,
52440
+ path: target,
52441
+ version: options.bundle.version
52442
+ };
52443
+ const copies = () => {
52444
+ const found = otherCopies(target);
52445
+ return found.length ? { otherCopies: found } : {};
52446
+ };
52447
+ const root = dirname(target);
52448
+ const recovered = recoverInterruptedInstall(root, target);
52449
+ let existing = null;
52450
+ try {
52451
+ existing = lstatSync(target);
52452
+ } catch {
52453
+ existing = null;
52454
+ }
52455
+ const previous = existing ? installedVersion(target) : null;
52456
+ const link = existing?.isSymbolicLink() ? readlinkSync(target) : null;
52457
+ const notes = {
52458
+ ...recovered ? { recovered: true } : {},
52459
+ ...link !== null ? { replacedLink: true } : {}
52460
+ };
52461
+ if (link === null && previous === options.bundle.version) return {
52462
+ ...base,
52463
+ state: "unchanged",
52464
+ ...notes,
52465
+ ...copies()
52466
+ };
52467
+ const staging = join(root, `.${CONNECT_SKILL_NAME}.staging-${randomUUID()}`);
52468
+ const retired = join(root, `.${CONNECT_SKILL_NAME}.retired-${randomUUID()}`);
52469
+ try {
52470
+ mkdirSync(root, { recursive: true });
52471
+ for (const file of Object.keys(options.bundle.files)) {
52472
+ mkdirSync(dirname(join(staging, file)), { recursive: true });
52473
+ copyFileSync(join(options.bundle.directory, file), join(staging, file));
52474
+ }
52475
+ if (connectSkillVersion(hashFiles(staging)) !== options.bundle.version) throw new Error("Copied skill files do not match the bundle.");
52476
+ let dependencies;
52477
+ if (link === null && existing?.isDirectory() && existsSync(join(target, "node_modules"))) dependencies = sameDependencies(target, staging) ? "kept" : "reinstall_needed";
52478
+ if (link !== null) unlinkSync(target);
52479
+ else if (existing) renameSync(target, retired);
52480
+ try {
52481
+ renameSync(staging, target);
52482
+ } catch (error) {
52483
+ if (link !== null) symlinkSync(link, target);
52484
+ else if (existing && existsSync(retired)) renameSync(retired, target);
52485
+ throw error;
52486
+ }
52487
+ if (dependencies === "kept") try {
52488
+ renameSync(join(retired, "node_modules"), join(target, "node_modules"));
52489
+ } catch {
52490
+ dependencies = "reinstall_needed";
52491
+ }
52492
+ rmSync(retired, {
52493
+ recursive: true,
52494
+ force: true
52495
+ });
52496
+ return {
52497
+ ...base,
52498
+ state: existing ? "updated" : "installed",
52499
+ ...previous && previous !== options.bundle.version ? { previousVersion: previous } : {},
52500
+ ...dependencies ? { dependencies } : {},
52501
+ ...notes,
52502
+ ...copies()
52503
+ };
52504
+ } catch (error) {
52505
+ rmSync(staging, {
52506
+ recursive: true,
52507
+ force: true
52508
+ });
52509
+ return {
52510
+ ...base,
52511
+ state: "failed",
52512
+ reason: error?.code ?? "skill_install_failed",
52513
+ ...notes,
52514
+ ...copies()
52515
+ };
52516
+ }
52517
+ }
52518
+ //#endregion
52519
+ //#region src/oclif/agent-connect-flow.ts
52520
+ /** What this CLI version can do in one `agent connect --session` call. */
52521
+ const AGENT_CONNECT_CAPABILITIES = [
52522
+ "invitation_claim_from_stdin",
52523
+ "verification_reply",
52524
+ "native_background_receiver",
52525
+ "claude_external_hooks",
52526
+ "bundled_skill_install",
52527
+ "agent_info_seed",
52528
+ "resume"
52529
+ ];
52530
+ const AGENT_INFO_NOTE = "AGENT_INFO";
52531
+ const MAX_NAME_LENGTH = 80;
52532
+ const MAX_INFO_LENGTH = 1e3;
52533
+ const MAIL_CHECK_WAIT_MS = 2e4;
52534
+ const PENDING_AGENT_INFO_FILE = "agent-info-pending.json";
52535
+ function readPendingAgentInfo(path) {
52536
+ try {
52537
+ const saved = readMailJson(path);
52538
+ if (saved && typeof saved === "object" && !Array.isArray(saved) && saved.version === 1) {
52539
+ const value = saved.value;
52540
+ if (typeof value === "string" && value.length > 0) return value;
52541
+ }
52542
+ } catch {}
52543
+ return null;
52544
+ }
52545
+ function savedReceiverMode(configDir, profileName) {
52546
+ try {
52547
+ const saved = readMailJson(join(agentProfileDirectory(configDir, profileName), "setup.json"));
52548
+ if (!saved || typeof saved !== "object" || Array.isArray(saved)) return null;
52549
+ return saved.receiverMode === "external" ? "external" : "native";
52550
+ } catch {
52551
+ return null;
52552
+ }
52553
+ }
52554
+ /** Best effort: a lost pending note only means --name/--info must be passed again. */
52555
+ function savePendingAgentInfo(path, value) {
52556
+ try {
52557
+ writeMailJson(path, {
52558
+ version: 1,
52559
+ value
52560
+ });
52561
+ } catch {}
52562
+ }
52563
+ function clearPendingAgentInfo(path) {
52564
+ try {
52565
+ removeMailFile(path);
52566
+ } catch {}
52567
+ }
52568
+ function defaultAgentProfileName(session) {
52569
+ return `session-${session.toLowerCase()}`;
52570
+ }
52571
+ /** A short private role note; never a transcript or secret. */
52572
+ function agentInfoValue(name, info) {
52573
+ const clean = (value, max, label) => {
52574
+ if (value === void 0) return void 0;
52575
+ const trimmed = value.trim();
52576
+ if (!trimmed || trimmed.length > max || [...trimmed].some((character) => {
52577
+ const code = character.charCodeAt(0);
52578
+ return code < 32 && character !== "\n" || code === 127;
52579
+ })) throw new AgentConnectionSetupError(`--${label} must be 1-${max} characters without control characters.`);
52580
+ return trimmed;
52581
+ };
52582
+ const cleanName = clean(name, MAX_NAME_LENGTH, "name");
52583
+ const cleanInfo = clean(info, MAX_INFO_LENGTH, "info");
52584
+ if (cleanName && cleanInfo) return `${cleanName}: ${cleanInfo}`;
52585
+ return cleanName ?? cleanInfo ?? null;
52586
+ }
52587
+ function defaults(options) {
52588
+ return {
52589
+ setupAgent,
52590
+ installClaudeWakeHook,
52591
+ installSkill(runtime) {
52592
+ return installConnectSkill({
52593
+ bundle: readBundledConnectSkill(options.packageRoot),
52594
+ runtime,
52595
+ project: options.project,
52596
+ cwd: options.cwd,
52597
+ env: options.env
52598
+ });
52599
+ },
52600
+ async seedAgentInfo(profileName, value) {
52601
+ const profile = loadConnectedAgentProfile(options.configDir, profileName);
52602
+ if (!profile) return "failed";
52603
+ const client = new PrimitiveApiClient({
52604
+ apiKey: profile.api_key,
52605
+ apiBaseUrl: profile.api_base_url
52606
+ });
52607
+ try {
52608
+ await runAddressNotesRequest(client.client, {
52609
+ action: "set",
52610
+ address: profile.agent_address,
52611
+ name: AGENT_INFO_NOTE,
52612
+ value,
52613
+ visibility: "private",
52614
+ ifAbsent: true
52615
+ });
52616
+ return "created";
52617
+ } catch (error) {
52618
+ if (error instanceof AddressNotesApiError && error.status === 409) return "already_present";
52619
+ return "failed";
52620
+ }
52621
+ },
52622
+ async awaitMailCheck(profileName, since) {
52623
+ const deadline = Date.now() + (options.mailCheckWaitMs ?? MAIL_CHECK_WAIT_MS);
52624
+ for (;;) {
52625
+ const status = agentConnectionStatus(options.configDir, profileName);
52626
+ const at = status.status === "configured" ? status.receiving.lastSuccessfulMailCheckAt : null;
52627
+ if (at && Date.parse(at) >= since) return {
52628
+ state: "confirmed",
52629
+ lastSuccessfulMailCheckAt: at
52630
+ };
52631
+ if (Date.now() >= deadline) return {
52632
+ state: "pending",
52633
+ lastSuccessfulMailCheckAt: null
52634
+ };
52635
+ await setTimeout$1(250);
52636
+ }
52637
+ }
52638
+ };
52639
+ }
52640
+ /**
52641
+ * One call: self check, install the matching skill, claim and verify, start
52642
+ * receiving, seed AGENT_INFO, and report everything as one result. Every step
52643
+ * after the claim is safe to repeat with --resume.
52644
+ */
52645
+ async function runAgentConnect(options) {
52646
+ const env = options.env ?? process.env;
52647
+ const dependencies = {
52648
+ ...defaults(options),
52649
+ ...Object.fromEntries(Object.entries(options.dependencies ?? {}).filter(([, value]) => value !== void 0))
52650
+ };
52651
+ if (!SESSION_UUID$1.test(options.session)) throw new AgentConnectionSetupError("--session requires the exact loaded session UUID.");
52652
+ const runtime = detectAgentRuntime(options.session, env);
52653
+ const profileName = agentProfileName(options.profileName ?? defaultAgentProfileName(options.session));
52654
+ const receiver = options.receiver ?? (options.resume ? savedReceiverMode(options.configDir, profileName) : null) ?? (runtime === "claude" ? "external" : "native");
52655
+ if (receiver === "external" && env.CLAUDE_CODE_SESSION_ID?.trim().toLowerCase() !== options.session.toLowerCase()) throw new AgentConnectionSetupError("External receiving requires this exact Claude session ID. No invitation was claimed.");
52656
+ const agentInfo = agentInfoValue(options.name, options.info);
52657
+ const skipped = [];
52658
+ let skill;
52659
+ if (options.skill === false) skill = {
52660
+ state: "skipped",
52661
+ runtime,
52662
+ path: null,
52663
+ version: null,
52664
+ reason: "not_requested"
52665
+ };
52666
+ else if (!runtime) skill = {
52667
+ state: "skipped",
52668
+ runtime: null,
52669
+ path: null,
52670
+ version: null,
52671
+ reason: "runtime_unknown"
52672
+ };
52673
+ else try {
52674
+ skill = dependencies.installSkill(runtime);
52675
+ } catch {
52676
+ skill = {
52677
+ state: "failed",
52678
+ runtime,
52679
+ path: null,
52680
+ version: null,
52681
+ reason: "bundled_skill_unavailable"
52682
+ };
52683
+ }
52684
+ if (skill.state === "skipped" || skill.state === "failed") skipped.push({
52685
+ step: "skill",
52686
+ reason: skill.reason ?? skill.state
52687
+ });
52688
+ const startedAt = Date.now();
52689
+ const result = await dependencies.setupAgent({
52690
+ configDir: options.configDir,
52691
+ profileName,
52692
+ session: options.session,
52693
+ receiverMode: receiver,
52694
+ resume: options.resume,
52695
+ contactRequests: options.contactRequests,
52696
+ ...options.resume ? {} : { invitation: await options.readInvitation() }
52697
+ });
52698
+ const verified = result.verification.state === "reply_submitted";
52699
+ let externalHook = null;
52700
+ if (receiver === "external") if (verified) externalHook = dependencies.installClaudeWakeHook({
52701
+ cliPath: options.cliPath,
52702
+ configDir: options.configDir,
52703
+ profileName: result.identity.profileName,
52704
+ agentAddress: result.identity.agentAddress,
52705
+ sessionId: options.session,
52706
+ env
52707
+ });
52708
+ else skipped.push({
52709
+ step: "receiver",
52710
+ reason: "pending_verification"
52711
+ });
52712
+ let mailCheck = null;
52713
+ if (receiver === "native") if (result.receiving.state === "healthy") {
52714
+ mailCheck = await dependencies.awaitMailCheck(profileName, startedAt);
52715
+ if (mailCheck.state === "pending") skipped.push({
52716
+ step: "mail_check",
52717
+ reason: "not_yet_observed"
52718
+ });
52719
+ } else skipped.push({
52720
+ step: "receiver",
52721
+ reason: verified ? "not_ready" : "pending_verification"
52722
+ });
52723
+ const pendingPath = join(agentProfileDirectory(options.configDir, profileName), PENDING_AGENT_INFO_FILE);
52724
+ const noteValue = agentInfo ?? readPendingAgentInfo(pendingPath);
52725
+ let agentInfoState = "not_requested";
52726
+ if (noteValue === null) skipped.push({
52727
+ step: "agent_info",
52728
+ reason: "not_requested"
52729
+ });
52730
+ else if (!verified) {
52731
+ agentInfoState = "pending_verification";
52732
+ savePendingAgentInfo(pendingPath, noteValue);
52733
+ skipped.push({
52734
+ step: "agent_info",
52735
+ reason: "pending_verification"
52736
+ });
52737
+ } else {
52738
+ agentInfoState = await dependencies.seedAgentInfo(profileName, noteValue);
52739
+ if (agentInfoState === "failed") {
52740
+ savePendingAgentInfo(pendingPath, noteValue);
52741
+ skipped.push({
52742
+ step: "agent_info",
52743
+ reason: "failed"
52744
+ });
52745
+ } else clearPendingAgentInfo(pendingPath);
52746
+ if (agentInfoState === "already_present") skipped.push({
52747
+ step: "agent_info",
52748
+ reason: "already_present"
52749
+ });
52750
+ }
52751
+ const receiving = receiver === "external" ? {
52752
+ ...result.receiving,
52753
+ mode: "external",
52754
+ hook: externalHook
52755
+ } : {
52756
+ ...result.receiving,
52757
+ mode: "native",
52758
+ mailCheck: mailCheck?.state ?? "not_started",
52759
+ lastSuccessfulMailCheckAt: mailCheck?.lastSuccessfulMailCheckAt ?? null
52760
+ };
52761
+ const receiverReady = receiver === "external" ? externalHook === "installed_unverified" : result.receiving.state === "healthy";
52762
+ const resumeCommand = [
52763
+ options.invocation ?? "primitive",
52764
+ "agent connect",
52765
+ `--profile ${result.identity.profileName}`,
52766
+ `--session ${options.session}`,
52767
+ `--receiver ${receiver}`,
52768
+ "--resume",
52769
+ ...options.contactRequests ? ["--contact-requests"] : [],
52770
+ ...options.skill === false ? ["--no-skill"] : [],
52771
+ ...options.project ? ["--project"] : [],
52772
+ "--json"
52773
+ ].join(" ");
52774
+ return {
52775
+ status: verified && receiverReady ? "connected" : "pending",
52776
+ address: result.identity.agentAddress,
52777
+ orgId: result.identity.orgId,
52778
+ ownerAddress: result.identity.ownerAddress,
52779
+ profile: result.identity.profileName,
52780
+ sessionId: options.session,
52781
+ runtime,
52782
+ cli: {
52783
+ version: options.cliVersion,
52784
+ capabilities: [...AGENT_CONNECT_CAPABILITIES]
52785
+ },
52786
+ skill,
52787
+ verification: result.verification,
52788
+ receiving,
52789
+ ...result.ownerNotifications ? { ownerNotifications: result.ownerNotifications } : {},
52790
+ agentInfo: agentInfoState,
52791
+ skipped,
52792
+ selectProfile: `PRIMITIVE_AGENT_PROFILE=${result.identity.profileName}`,
52793
+ resumeCommand,
52794
+ guidance: result.guidance,
52795
+ identity: result.identity,
52796
+ externalHook
52797
+ };
52798
+ }
52799
+ //#endregion
52150
52800
  //#region src/oclif/commands/agent-connect.ts
52801
+ /** npx runs the CLI from its cache; resume through npx too so the command works without a global install. */
52802
+ function invocation(entry) {
52803
+ return entry && /[\\/]_npx[\\/]/.test(entry) ? "npx -y primitive@latest" : "primitive";
52804
+ }
52151
52805
  var AgentConnectCommand = class AgentConnectCommand extends Command {
52152
- static description = "Claim an owner's private setup invitation from piped stdin and save a separate connected-agent profile. Add --session to answer one authenticated setup challenge and enable owner notifications. The default receiver preflights and starts a native session listener; --receiver external requires the exact Claude session and installs a fail-open Stop hook and resume SessionStart hook after the verification reply. A real idle mail event must still verify wake. --resume continues saved progress without reading stdin or replaying a claim or uncertain verification send. Verification reply submission is separate from delivery and receiver health. Both official production (https://api.primitive.dev/v1) and staging (https://api.primitive-staging-1.com/v1) invitations pin their API origin. Never pass an invitation as a command argument. Select the saved identity with PRIMITIVE_AGENT_PROFILE. Use --status --json to inspect saved identity and local receiver health offline.";
52806
+ static description = "Connect this coding session to Primitive in one call. With --session, the command checks this CLI's capabilities, installs or refreshes the matching primitive-connect skill for the detected runtime (Claude Code or Codex) from files bundled in this CLI, claims the owner's private setup invitation from piped stdin, answers one authenticated setup challenge, enables owner notifications, starts receiving, seeds a private AGENT_INFO note from --name and --info when absent, and prints one JSON result. The receiver defaults to external hooks in Claude Code and the native background listener elsewhere. Native receiving starts or reuses a supervised background listener and waits briefly for its first successful mail check; external receiving installs the exact Claude session's fail-open Stop hook and resume SessionStart hook after the verification reply; a real idle mail event must still verify wake. The profile defaults to session-<session>. --resume continues saved progress without reading stdin or replaying a claim or uncertain verification send. Verification reply submission is separate from delivery and receiver health. Both official production (https://api.primitive.dev/v1) and staging (https://api.primitive-staging-1.com/v1) invitations pin their API origin. Never pass an invitation as a command argument. Select the saved identity with PRIMITIVE_AGENT_PROFILE. Use --profile with --status --json to inspect saved identity and local receiver health offline.";
52153
52807
  static summary = "Connect and verify an agent address";
52154
52808
  static examples = [
52809
+ "<%= config.bin %> agent connect --session \"$CODEX_THREAD_ID\" --name Research --info \"Reviews pull requests\" --json < private-invitation.txt",
52810
+ "<%= config.bin %> agent connect --session \"$CLAUDE_CODE_SESSION_ID\" --contact-requests --json < private-invitation.txt",
52155
52811
  "<%= config.bin %> agent connect --profile work --session 11111111-1111-4111-8111-111111111111 --contact-requests --json < private-invitation.txt",
52156
52812
  "<%= config.bin %> agent connect --profile work --session 11111111-1111-4111-8111-111111111111 --contact-requests --resume --json",
52157
52813
  "<%= config.bin %> agent connect --profile work --session 11111111-1111-4111-8111-111111111111 --receiver external --contact-requests --json < private-invitation.txt",
@@ -52160,16 +52816,13 @@ var AgentConnectCommand = class AgentConnectCommand extends Command {
52160
52816
  "PRIMITIVE_AGENT_PROFILE=work <%= config.bin %> whoami --json"
52161
52817
  ];
52162
52818
  static flags = {
52163
- profile: Flags.string({
52164
- required: true,
52165
- description: "Local agent profile name; existing OAuth login is preserved"
52166
- }),
52819
+ profile: Flags.string({ description: "Local agent profile name; defaults to session-<session> with --session. Existing OAuth login is preserved" }),
52167
52820
  session: Flags.string({
52168
52821
  description: "Exact current session UUID; verify email and configure receiving after claiming",
52169
52822
  exclusive: ["status"]
52170
52823
  }),
52171
52824
  receiver: Flags.string({
52172
- description: "Native starts a supported session receiver; external installs the exact Claude session's fail-open Stop hook and resume SessionStart hook",
52825
+ description: "Native starts a supported session receiver; external installs the exact Claude session's fail-open Stop hook and resume SessionStart hook. Defaults to external in Claude Code and native elsewhere",
52173
52826
  options: ["native", "external"],
52174
52827
  dependsOn: ["session"]
52175
52828
  }),
@@ -52183,6 +52836,23 @@ var AgentConnectCommand = class AgentConnectCommand extends Command {
52183
52836
  dependsOn: ["session"],
52184
52837
  exclusive: ["status"]
52185
52838
  }),
52839
+ name: Flags.string({
52840
+ description: "Short agent name for a private AGENT_INFO note, written only when the note is absent",
52841
+ dependsOn: ["session"]
52842
+ }),
52843
+ info: Flags.string({
52844
+ description: "Short role and capability description for a private AGENT_INFO note, written only when the note is absent",
52845
+ dependsOn: ["session"]
52846
+ }),
52847
+ skill: Flags.boolean({
52848
+ description: "Install or refresh the primitive-connect skill bundled with this CLI for the detected runtime",
52849
+ default: true,
52850
+ allowNo: true
52851
+ }),
52852
+ project: Flags.boolean({
52853
+ description: "Install the skill into this project (.claude/skills or .agents/skills) instead of the user skills folder",
52854
+ dependsOn: ["session"]
52855
+ }),
52186
52856
  status: Flags.boolean({ description: "Read saved identity and local receiver health offline without reading stdin" }),
52187
52857
  json: Flags.boolean({ description: "Print setup status and identity metadata, never the credential" })
52188
52858
  };
@@ -52190,44 +52860,43 @@ var AgentConnectCommand = class AgentConnectCommand extends Command {
52190
52860
  const { flags } = await this.parse(AgentConnectCommand);
52191
52861
  try {
52192
52862
  if (flags.status) {
52863
+ if (!flags.profile) throw new AgentConnectionSetupError("Pass --profile with --status.");
52193
52864
  const result = agentConnectionStatus(this.config.configDir, flags.profile);
52194
52865
  if (flags.json) this.log(JSON.stringify(result));
52195
52866
  else this.log(result.status === "configured" ? `Agent profile ${flags.profile} is configured for ${result.identity.agentAddress}. Local receiving: ${result.receiving.state}${result.receiving.reason ? ` (${result.receiving.reason})` : ""}.` : `Agent profile ${flags.profile} is not configured.`);
52196
52867
  return;
52197
52868
  }
52198
52869
  if (flags.session) {
52199
- if (flags.receiver === "external" && process.env.CLAUDE_CODE_SESSION_ID !== flags.session) throw new AgentConnectionSetupError("External receiving requires this exact Claude session ID. No invitation was claimed.");
52200
- const result = await setupAgent({
52870
+ const output = await runAgentConnect({
52201
52871
  configDir: this.config.configDir,
52202
- profileName: flags.profile,
52872
+ packageRoot: this.config.root,
52873
+ cliVersion: this.config.version,
52874
+ cliPath: process.argv[1] ?? "",
52875
+ invocation: invocation(process.argv[1]),
52203
52876
  session: flags.session,
52204
- receiverMode: flags.receiver,
52877
+ profileName: flags.profile,
52878
+ receiver: flags.receiver,
52205
52879
  resume: flags.resume,
52206
52880
  contactRequests: flags["contact-requests"],
52207
- ...flags.resume ? {} : { invitation: await readAgentInvitation(process.stdin, process.stdin.isTTY) }
52881
+ name: flags.name,
52882
+ info: flags.info,
52883
+ skill: flags.skill,
52884
+ project: flags.project,
52885
+ readInvitation: () => readAgentInvitation(process.stdin, process.stdin.isTTY)
52208
52886
  });
52209
- const externalHook = flags.receiver === "external" && result.verification.state === "reply_submitted" ? installClaudeWakeHook({
52210
- cliPath: process.argv[1] ?? "",
52211
- configDir: this.config.configDir,
52212
- profileName: result.identity.profileName,
52213
- agentAddress: result.identity.agentAddress,
52214
- sessionId: flags.session
52215
- }) : null;
52216
- const output = {
52217
- ...result,
52218
- externalHook
52219
- };
52887
+ const external = output.receiving.mode === "external";
52220
52888
  if (flags.json) this.log(JSON.stringify(output));
52221
52889
  else {
52222
- this.log(`Agent ${result.identity.agentAddress}: verification ${result.verification.state}; receiving ${result.receiving.state}.`);
52223
- this.log(`Select it with PRIMITIVE_AGENT_PROFILE=${result.identity.profileName}.`);
52224
- if (result.ownerNotifications === "silenced") this.log("Owner notifications remain silenced by the existing preference or policy.");
52225
- if (result.receiving.state === "external_setup_required") this.log(externalHook === "installed_unverified" ? "External receive hook installed. Idle wake still needs a live mail check." : "External receive hook is unavailable. Pairing may complete, but this session will not wake automatically.");
52226
- if (result.receiving.state === "not_ready") this.log(`Resume: ${result.resumeCommand}`);
52890
+ this.log(`Agent ${output.address}: verification ${output.verification.state}; receiving ${output.receiving.state}; skill ${output.skill.state}.`);
52891
+ this.log(`Select it with ${output.selectProfile}.`);
52892
+ if (output.ownerNotifications === "silenced") this.log("Owner notifications remain silenced by the existing preference or policy.");
52893
+ if (external && output.verification.state === "reply_submitted") this.log(output.externalHook === "installed_unverified" ? "External receive hook installed. Idle wake still needs a live mail check." : "External receive hook is unavailable. Pairing may complete, but this session will not wake automatically.");
52894
+ if (output.status !== "connected") this.log(`Resume: ${output.resumeCommand}`);
52227
52895
  }
52228
- if (externalHook === "unavailable" || flags.receiver !== "external" && result.receiving.state !== "healthy" || result.verification.state !== "reply_submitted") process.exitCode = 2;
52896
+ if (external && output.externalHook !== "installed_unverified" || !external && output.receiving.state !== "healthy" || output.verification.state !== "reply_submitted") process.exitCode = 2;
52229
52897
  return;
52230
52898
  }
52899
+ if (!flags.profile) throw new AgentConnectionSetupError("Pass --session to connect this session, or --profile for a claim-only or status check.");
52231
52900
  const result = await connectAgent({
52232
52901
  configDir: this.config.configDir,
52233
52902
  profileName: flags.profile,
@@ -52239,7 +52908,7 @@ var AgentConnectCommand = class AgentConnectCommand extends Command {
52239
52908
  this.log(`Select it with PRIMITIVE_AGENT_PROFILE=${result.identity.profileName}.`);
52240
52909
  }
52241
52910
  } catch (error) {
52242
- throw new Errors.CLIError(error instanceof AgentConnectionSetupError ? error.message : flags.session ? `Setup paused. Preserve the private profile and run primitive agent connect --profile ${flags.profile} --session ${flags.session}${flags.receiver === "external" ? " --receiver external" : ""} --resume${flags["contact-requests"] ? " --contact-requests" : ""} --json. Do not manually resend verification or reclaim the invitation.` : "Agent setup did not complete. Preserve the private profile and request a fresh owner invitation if a claim may have been submitted.", { exit: 1 });
52911
+ throw new Errors.CLIError(error instanceof AgentConnectionSetupError ? error.message : flags.session ? `Setup paused. Preserve the private profile and run ${invocation(process.argv[1])} agent connect --profile ${flags.profile ?? defaultAgentProfileName(flags.session)} --session ${flags.session}${flags.receiver ? ` --receiver ${flags.receiver}` : ""} --resume${flags["contact-requests"] ? " --contact-requests" : ""} --json. Do not manually resend verification or reclaim the invitation.` : "Agent setup did not complete. Preserve the private profile and request a fresh owner invitation if a claim may have been submitted.", { exit: 1 });
52243
52912
  }
52244
52913
  }
52245
52914
  };
@@ -53093,136 +53762,6 @@ var AgentEnrollCommand = class AgentEnrollCommand extends Command {
53093
53762
  }
53094
53763
  };
53095
53764
  //#endregion
53096
- //#region src/oclif/address-notes.ts
53097
- var AddressNotesApiError = class extends Error {
53098
- constructor(payload, status) {
53099
- super("Address note request failed.");
53100
- this.payload = payload;
53101
- this.status = status;
53102
- }
53103
- };
53104
- const security$2 = [{
53105
- scheme: "bearer",
53106
- type: "http"
53107
- }];
53108
- function addressNoteName(value) {
53109
- if (!/^[A-Za-z0-9][A-Za-z0-9_.-]{0,127}$/.test(value)) throw new Error("Note name must start with a letter or digit and contain at most 128 letters, digits, underscores, dots, or hyphens.");
53110
- return value;
53111
- }
53112
- function addressNoteVersion(value) {
53113
- if (!/^[0-9]{1,19}$/.test(value)) throw new Error("Note version must be the decimal version returned by the API.");
53114
- return value;
53115
- }
53116
- function addressNoteTarget(explicit, connected, write) {
53117
- if (!explicit && !connected) throw new Error("Pass --address or select a connected agent profile.");
53118
- const address = contactAddress(explicit ?? connected ?? "");
53119
- if (write && connected && address !== contactAddress(connected)) throw new Error("This connected profile can write only its own address notes.");
53120
- return address;
53121
- }
53122
- function note(value, address, name) {
53123
- if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("The address notes API returned an invalid note.");
53124
- const row = value;
53125
- if (row.address !== address || typeof row.name !== "string" || name !== void 0 && row.name !== name || row.visibility !== "private" && row.visibility !== "public" || typeof row.version !== "string" || !/^[0-9]{1,19}$/.test(row.version) || typeof row.created_at !== "string" || typeof row.updated_at !== "string" || !("value" in row)) throw new Error("The address notes API returned an invalid note.");
53126
- return row;
53127
- }
53128
- function envelope(value) {
53129
- if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("The address notes API returned an invalid response.");
53130
- const row = value;
53131
- if (row.success !== true || !("data" in row)) throw new Error("The address notes API returned an incomplete response.");
53132
- return row;
53133
- }
53134
- async function result(pending) {
53135
- const response = await pending;
53136
- if (response.error !== void 0) throw new AddressNotesApiError(response.error, response.response?.status);
53137
- return envelope(response.data).data;
53138
- }
53139
- async function getNote(client, address, name, signal) {
53140
- return note(await result(client.get({
53141
- security: security$2,
53142
- url: "/address-notes/{address}/{name}",
53143
- path: {
53144
- address,
53145
- name
53146
- },
53147
- responseStyle: "fields",
53148
- ...signal ? { signal } : {}
53149
- })), address, name);
53150
- }
53151
- async function runAddressNotesRequest(client, request) {
53152
- const address = contactAddress(request.address);
53153
- if (request.action === "list") {
53154
- const limit = request.limit ?? 50;
53155
- if (!Number.isInteger(limit) || limit < 1 || limit > 100) throw new Error("--limit must be between 1 and 100.");
53156
- const prefix = request.prefix;
53157
- if (prefix !== void 0 && !/^[A-Za-z0-9_.-]{0,128}$/.test(prefix)) throw new Error("--prefix may contain at most 128 letters, digits, underscores, dots, or hyphens.");
53158
- const cursor = request.cursor === void 0 ? void 0 : addressNoteName(request.cursor);
53159
- const response = await client.get({
53160
- security: security$2,
53161
- url: "/address-notes",
53162
- query: {
53163
- address,
53164
- prefix,
53165
- cursor,
53166
- limit
53167
- },
53168
- responseStyle: "fields"
53169
- });
53170
- if (response.error !== void 0) throw new AddressNotesApiError(response.error, response.response?.status);
53171
- const body = envelope(response.data);
53172
- if (!Array.isArray(body.data) || !body.meta || typeof body.meta !== "object" || Array.isArray(body.meta)) throw new Error("The address notes API returned an invalid page.");
53173
- const next = body.meta.cursor;
53174
- if (next !== null && (typeof next !== "string" || addressNoteName(next) !== next)) throw new Error("The address notes API omitted valid pagination state.");
53175
- return {
53176
- ...body,
53177
- data: body.data.map((row) => note(row, address))
53178
- };
53179
- }
53180
- const name = addressNoteName(request.name ?? "");
53181
- if (request.action === "get") return getNote(client, address, name, request.signal);
53182
- if (request.ifAbsent && request.ifVersion !== void 0) throw new Error("Use either --if-absent or --if-version.");
53183
- const ifVersion = request.ifVersion === void 0 ? void 0 : addressNoteVersion(request.ifVersion);
53184
- if (request.action === "set") {
53185
- if (request.value === void 0) throw new Error("Provide a note value.");
53186
- let condition;
53187
- if (request.ifAbsent) condition = { if_absent: true };
53188
- else if (ifVersion) condition = { if_version: ifVersion };
53189
- else try {
53190
- condition = { if_version: (await getNote(client, address, name)).version };
53191
- } catch (error) {
53192
- if (!(error instanceof AddressNotesApiError) || error.status !== 404) throw error;
53193
- condition = { if_absent: true };
53194
- }
53195
- return note(await result(client.put({
53196
- security: security$2,
53197
- url: "/address-notes/{address}/{name}",
53198
- path: {
53199
- address,
53200
- name
53201
- },
53202
- body: {
53203
- value: request.value,
53204
- ...condition,
53205
- ...request.visibility ? { visibility: request.visibility } : {}
53206
- },
53207
- headers: { "Content-Type": "application/json" },
53208
- responseStyle: "fields"
53209
- })), address, name);
53210
- }
53211
- const version = ifVersion ?? (await getNote(client, address, name)).version;
53212
- const data = await result(client.delete({
53213
- security: security$2,
53214
- url: "/address-notes/{address}/{name}",
53215
- path: {
53216
- address,
53217
- name
53218
- },
53219
- query: { if_version: version },
53220
- responseStyle: "fields"
53221
- }));
53222
- if (!data || typeof data !== "object" || Array.isArray(data) || typeof data.deleted !== "boolean") throw new Error("The address notes API returned an invalid deletion response.");
53223
- return data;
53224
- }
53225
- //#endregion
53226
53765
  //#region src/oclif/commands/agent-notes.ts
53227
53766
  const commonFlags$2 = {
53228
53767
  address: Flags.string({ description: "Address to target; writes by a connected profile can target only its own address" }),
@@ -61760,7 +62299,7 @@ const PRIMITIVE_TEAM_AUTHOR = {
61760
62299
  url: "https://primitive.dev"
61761
62300
  };
61762
62301
  const SDK_VERSION_RANGE = "^1.34.0";
61763
- const CLI_VERSION_RANGE = "^1.35.0";
62302
+ const CLI_VERSION_RANGE = "^1.36.0";
61764
62303
  const ESBUILD_VERSION_RANGE = "^0.27.0";
61765
62304
  function renderHandler() {
61766
62305
  return `// env.PRIMITIVE_API_KEY, env.PRIMITIVE_WEBHOOK_SECRET, and