@rosthq/cli 0.6.8 → 0.7.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 CHANGED
@@ -472,13 +472,13 @@ function __disposeResources(env) {
472
472
  }
473
473
  return next();
474
474
  }
475
- function __rewriteRelativeImportExtension(path4, preserveJsx) {
476
- if (typeof path4 === "string" && /^\.\.?\//.test(path4)) {
477
- return path4.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function(m, tsx, d, ext, cm) {
475
+ function __rewriteRelativeImportExtension(path5, preserveJsx) {
476
+ if (typeof path5 === "string" && /^\.\.?\//.test(path5)) {
477
+ return path5.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function(m, tsx, d, ext, cm) {
478
478
  return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : d + ext + "." + cm.toLowerCase() + "js";
479
479
  });
480
480
  }
481
- return path4;
481
+ return path5;
482
482
  }
483
483
  var extendStatics, __assign, __createBinding, __setModuleDefault, ownKeys, _SuppressedError, tslib_es6_default;
484
484
  var init_tslib_es6 = __esm({
@@ -13257,7 +13257,7 @@ var require_main3 = __commonJS({
13257
13257
  });
13258
13258
 
13259
13259
  // src/index.ts
13260
- import { realpathSync } from "node:fs";
13260
+ import { realpathSync as realpathSync2 } from "node:fs";
13261
13261
  import { fileURLToPath as fileURLToPath3, pathToFileURL } from "node:url";
13262
13262
 
13263
13263
  // ../../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/classic/external.js
@@ -14026,10 +14026,10 @@ function mergeDefs(...defs) {
14026
14026
  function cloneDef(schema) {
14027
14027
  return mergeDefs(schema._zod.def);
14028
14028
  }
14029
- function getElementAtPath(obj, path4) {
14030
- if (!path4)
14029
+ function getElementAtPath(obj, path5) {
14030
+ if (!path5)
14031
14031
  return obj;
14032
- return path4.reduce((acc, key) => acc?.[key], obj);
14032
+ return path5.reduce((acc, key) => acc?.[key], obj);
14033
14033
  }
14034
14034
  function promiseAllObject(promisesObj) {
14035
14035
  const keys = Object.keys(promisesObj);
@@ -14438,11 +14438,11 @@ function explicitlyAborted(x, startIndex = 0) {
14438
14438
  }
14439
14439
  return false;
14440
14440
  }
14441
- function prefixIssues(path4, issues) {
14441
+ function prefixIssues(path5, issues) {
14442
14442
  return issues.map((iss) => {
14443
14443
  var _a3;
14444
14444
  (_a3 = iss).path ?? (_a3.path = []);
14445
- iss.path.unshift(path4);
14445
+ iss.path.unshift(path5);
14446
14446
  return iss;
14447
14447
  });
14448
14448
  }
@@ -14589,16 +14589,16 @@ function flattenError(error51, mapper = (issue2) => issue2.message) {
14589
14589
  }
14590
14590
  function formatError(error51, mapper = (issue2) => issue2.message) {
14591
14591
  const fieldErrors = { _errors: [] };
14592
- const processError = (error52, path4 = []) => {
14592
+ const processError = (error52, path5 = []) => {
14593
14593
  for (const issue2 of error52.issues) {
14594
14594
  if (issue2.code === "invalid_union" && issue2.errors.length) {
14595
- issue2.errors.map((issues) => processError({ issues }, [...path4, ...issue2.path]));
14595
+ issue2.errors.map((issues) => processError({ issues }, [...path5, ...issue2.path]));
14596
14596
  } else if (issue2.code === "invalid_key") {
14597
- processError({ issues: issue2.issues }, [...path4, ...issue2.path]);
14597
+ processError({ issues: issue2.issues }, [...path5, ...issue2.path]);
14598
14598
  } else if (issue2.code === "invalid_element") {
14599
- processError({ issues: issue2.issues }, [...path4, ...issue2.path]);
14599
+ processError({ issues: issue2.issues }, [...path5, ...issue2.path]);
14600
14600
  } else {
14601
- const fullpath = [...path4, ...issue2.path];
14601
+ const fullpath = [...path5, ...issue2.path];
14602
14602
  if (fullpath.length === 0) {
14603
14603
  fieldErrors._errors.push(mapper(issue2));
14604
14604
  } else {
@@ -14625,17 +14625,17 @@ function formatError(error51, mapper = (issue2) => issue2.message) {
14625
14625
  }
14626
14626
  function treeifyError(error51, mapper = (issue2) => issue2.message) {
14627
14627
  const result = { errors: [] };
14628
- const processError = (error52, path4 = []) => {
14628
+ const processError = (error52, path5 = []) => {
14629
14629
  var _a3, _b;
14630
14630
  for (const issue2 of error52.issues) {
14631
14631
  if (issue2.code === "invalid_union" && issue2.errors.length) {
14632
- issue2.errors.map((issues) => processError({ issues }, [...path4, ...issue2.path]));
14632
+ issue2.errors.map((issues) => processError({ issues }, [...path5, ...issue2.path]));
14633
14633
  } else if (issue2.code === "invalid_key") {
14634
- processError({ issues: issue2.issues }, [...path4, ...issue2.path]);
14634
+ processError({ issues: issue2.issues }, [...path5, ...issue2.path]);
14635
14635
  } else if (issue2.code === "invalid_element") {
14636
- processError({ issues: issue2.issues }, [...path4, ...issue2.path]);
14636
+ processError({ issues: issue2.issues }, [...path5, ...issue2.path]);
14637
14637
  } else {
14638
- const fullpath = [...path4, ...issue2.path];
14638
+ const fullpath = [...path5, ...issue2.path];
14639
14639
  if (fullpath.length === 0) {
14640
14640
  result.errors.push(mapper(issue2));
14641
14641
  continue;
@@ -14667,8 +14667,8 @@ function treeifyError(error51, mapper = (issue2) => issue2.message) {
14667
14667
  }
14668
14668
  function toDotPath(_path) {
14669
14669
  const segs = [];
14670
- const path4 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
14671
- for (const seg of path4) {
14670
+ const path5 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
14671
+ for (const seg of path5) {
14672
14672
  if (typeof seg === "number")
14673
14673
  segs.push(`[${seg}]`);
14674
14674
  else if (typeof seg === "symbol")
@@ -27360,13 +27360,13 @@ function resolveRef(ref, ctx) {
27360
27360
  if (!ref.startsWith("#")) {
27361
27361
  throw new Error("External $ref is not supported, only local refs (#/...) are allowed");
27362
27362
  }
27363
- const path4 = ref.slice(1).split("/").filter(Boolean);
27364
- if (path4.length === 0) {
27363
+ const path5 = ref.slice(1).split("/").filter(Boolean);
27364
+ if (path5.length === 0) {
27365
27365
  return ctx.rootSchema;
27366
27366
  }
27367
27367
  const defsKey = ctx.version === "draft-2020-12" ? "$defs" : "definitions";
27368
- if (path4[0] === defsKey) {
27369
- const key = path4[1];
27368
+ if (path5[0] === defsKey) {
27369
+ const key = path5[1];
27370
27370
  if (!key || !ctx.defs[key]) {
27371
27371
  throw new Error(`Reference not found: ${ref}`);
27372
27372
  }
@@ -27828,7 +27828,7 @@ var CommandClient = class {
27828
27828
  this.fetchImpl = options.fetchImpl ?? fetch;
27829
27829
  }
27830
27830
  async execute(commandId, body = {}, options = {}) {
27831
- const path4 = `/api/commands/${encodeURIComponent(commandId)}`;
27831
+ const path5 = `/api/commands/${encodeURIComponent(commandId)}`;
27832
27832
  const headers = {
27833
27833
  authorization: `Bearer ${this.token}`,
27834
27834
  "content-type": "application/json"
@@ -27836,7 +27836,7 @@ var CommandClient = class {
27836
27836
  if (options.targetSeatId !== void 0 && options.targetSeatId.length > 0) {
27837
27837
  headers["x-rost-seat"] = options.targetSeatId;
27838
27838
  }
27839
- const response = await this.fetchImpl(`${this.appUrl}${path4}`, {
27839
+ const response = await this.fetchImpl(`${this.appUrl}${path5}`, {
27840
27840
  method: "POST",
27841
27841
  headers,
27842
27842
  body: JSON.stringify(body)
@@ -27849,7 +27849,7 @@ var CommandClient = class {
27849
27849
  const snippet = redactSecrets(text.replace(/\s+/g, " ").trim()).slice(0, 200);
27850
27850
  throw new CommandClientError(
27851
27851
  response.status,
27852
- `server returned HTTP ${response.status} (non-JSON) from ${path4}` + (snippet ? ` \u2014 ${snippet}` : "") + " \u2014 the deployment may be unhealthy"
27852
+ `server returned HTTP ${response.status} (non-JSON) from ${path5}` + (snippet ? ` \u2014 ${snippet}` : "") + " \u2014 the deployment may be unhealthy"
27853
27853
  );
27854
27854
  }
27855
27855
  const parsed = commandResponseSchema.parse(raw);
@@ -33056,8 +33056,8 @@ var IcebergError = class extends Error {
33056
33056
  return this.status === 419;
33057
33057
  }
33058
33058
  };
33059
- function buildUrl(baseUrl, path4, query) {
33060
- const url2 = new URL(path4, baseUrl);
33059
+ function buildUrl(baseUrl, path5, query) {
33060
+ const url2 = new URL(path5, baseUrl);
33061
33061
  if (query) {
33062
33062
  for (const [key, value] of Object.entries(query)) {
33063
33063
  if (value !== void 0) {
@@ -33087,12 +33087,12 @@ function createFetchClient(options) {
33087
33087
  return {
33088
33088
  async request({
33089
33089
  method,
33090
- path: path4,
33090
+ path: path5,
33091
33091
  query,
33092
33092
  body,
33093
33093
  headers
33094
33094
  }) {
33095
- const url2 = buildUrl(options.baseUrl, path4, query);
33095
+ const url2 = buildUrl(options.baseUrl, path5, query);
33096
33096
  const authHeaders = await buildAuthHeaders(options.auth);
33097
33097
  const res = await fetchFn(url2, {
33098
33098
  method,
@@ -33954,7 +33954,7 @@ var StorageFileApi = class extends BaseApiClient {
33954
33954
  * @param path The relative file path. Should be of the format `folder/subfolder/filename.png`. The bucket must already exist before attempting to upload.
33955
33955
  * @param fileBody The body of the file to be stored in the bucket.
33956
33956
  */
33957
- async uploadOrUpdate(method, path4, fileBody, fileOptions) {
33957
+ async uploadOrUpdate(method, path5, fileBody, fileOptions) {
33958
33958
  var _this = this;
33959
33959
  return _this.handleOperation(async () => {
33960
33960
  let body;
@@ -33978,7 +33978,7 @@ var StorageFileApi = class extends BaseApiClient {
33978
33978
  if ((typeof ReadableStream !== "undefined" && body instanceof ReadableStream || body && typeof body === "object" && "pipe" in body && typeof body.pipe === "function") && !options.duplex) options.duplex = "half";
33979
33979
  }
33980
33980
  if (fileOptions === null || fileOptions === void 0 ? void 0 : fileOptions.headers) for (const [key, value] of Object.entries(fileOptions.headers)) headers = setHeader(headers, key, value);
33981
- const cleanPath = _this._removeEmptyFolders(path4);
33981
+ const cleanPath = _this._removeEmptyFolders(path5);
33982
33982
  const _path = _this._getFinalPath(cleanPath);
33983
33983
  const data = await (method == "PUT" ? put : post)(_this.fetch, `${_this.url}/object/${_path}`, body, _objectSpread22({ headers }, (options === null || options === void 0 ? void 0 : options.duplex) ? { duplex: options.duplex } : {}));
33984
33984
  return {
@@ -34055,8 +34055,8 @@ var StorageFileApi = class extends BaseApiClient {
34055
34055
  * - Refer to the [Storage guide](/docs/guides/storage/security/access-control) on how access control works
34056
34056
  * - For React Native, using either `Blob`, `File` or `FormData` does not work as intended. Upload file using `ArrayBuffer` from base64 file data instead, see example below.
34057
34057
  */
34058
- async upload(path4, fileBody, fileOptions) {
34059
- return this.uploadOrUpdate("POST", path4, fileBody, fileOptions);
34058
+ async upload(path5, fileBody, fileOptions) {
34059
+ return this.uploadOrUpdate("POST", path5, fileBody, fileOptions);
34060
34060
  }
34061
34061
  /**
34062
34062
  * Upload a file with a token generated from `createSignedUploadUrl`.
@@ -34096,9 +34096,9 @@ var StorageFileApi = class extends BaseApiClient {
34096
34096
  * - `objects` table permissions: none
34097
34097
  * - Refer to the [Storage guide](/docs/guides/storage/security/access-control) on how access control works
34098
34098
  */
34099
- async uploadToSignedUrl(path4, token, fileBody, fileOptions) {
34099
+ async uploadToSignedUrl(path5, token, fileBody, fileOptions) {
34100
34100
  var _this3 = this;
34101
- const cleanPath = _this3._removeEmptyFolders(path4);
34101
+ const cleanPath = _this3._removeEmptyFolders(path5);
34102
34102
  const _path = _this3._getFinalPath(cleanPath);
34103
34103
  const url2 = new URL(_this3.url + `/object/upload/sign/${_path}`);
34104
34104
  url2.searchParams.set("token", token);
@@ -34167,10 +34167,10 @@ var StorageFileApi = class extends BaseApiClient {
34167
34167
  * - `objects` table permissions: `insert`
34168
34168
  * - Refer to the [Storage guide](/docs/guides/storage/security/access-control) on how access control works
34169
34169
  */
34170
- async createSignedUploadUrl(path4, options) {
34170
+ async createSignedUploadUrl(path5, options) {
34171
34171
  var _this4 = this;
34172
34172
  return _this4.handleOperation(async () => {
34173
- let _path = _this4._getFinalPath(path4);
34173
+ let _path = _this4._getFinalPath(path5);
34174
34174
  const headers = _objectSpread22({}, _this4.headers);
34175
34175
  if (options === null || options === void 0 ? void 0 : options.upsert) headers["x-upsert"] = "true";
34176
34176
  const data = await post(_this4.fetch, `${_this4.url}/object/upload/sign/${_path}`, {}, { headers });
@@ -34179,7 +34179,7 @@ var StorageFileApi = class extends BaseApiClient {
34179
34179
  if (!token) throw new StorageError("No token returned by API");
34180
34180
  return {
34181
34181
  signedUrl: url2.toString(),
34182
- path: path4,
34182
+ path: path5,
34183
34183
  token
34184
34184
  };
34185
34185
  });
@@ -34239,8 +34239,8 @@ var StorageFileApi = class extends BaseApiClient {
34239
34239
  * - Refer to the [Storage guide](/docs/guides/storage/security/access-control) on how access control works
34240
34240
  * - For React Native, using either `Blob`, `File` or `FormData` does not work as intended. Update file using `ArrayBuffer` from base64 file data instead, see example below.
34241
34241
  */
34242
- async update(path4, fileBody, fileOptions) {
34243
- return this.uploadOrUpdate("PUT", path4, fileBody, fileOptions);
34242
+ async update(path5, fileBody, fileOptions) {
34243
+ return this.uploadOrUpdate("PUT", path5, fileBody, fileOptions);
34244
34244
  }
34245
34245
  /**
34246
34246
  * Moves an existing file to a new path in the same bucket.
@@ -34391,10 +34391,10 @@ var StorageFileApi = class extends BaseApiClient {
34391
34391
  * - `objects` table permissions: `select`
34392
34392
  * - Refer to the [Storage guide](/docs/guides/storage/security/access-control) on how access control works
34393
34393
  */
34394
- async createSignedUrl(path4, expiresIn, options) {
34394
+ async createSignedUrl(path5, expiresIn, options) {
34395
34395
  var _this8 = this;
34396
34396
  return _this8.handleOperation(async () => {
34397
- let _path = _this8._getFinalPath(path4);
34397
+ let _path = _this8._getFinalPath(path5);
34398
34398
  const hasTransform = typeof (options === null || options === void 0 ? void 0 : options.transform) === "object" && options.transform !== null && Object.keys(options.transform).length > 0;
34399
34399
  let data = await post(_this8.fetch, `${_this8.url}/object/sign/${_path}`, _objectSpread22({ expiresIn }, hasTransform ? { transform: options.transform } : {}), { headers: _this8.headers });
34400
34400
  const query = new URLSearchParams();
@@ -34530,13 +34530,13 @@ var StorageFileApi = class extends BaseApiClient {
34530
34530
  * - `objects` table permissions: `select`
34531
34531
  * - Refer to the [Storage guide](/docs/guides/storage/security/access-control) on how access control works
34532
34532
  */
34533
- download(path4, options, parameters) {
34533
+ download(path5, options, parameters) {
34534
34534
  const renderPath = typeof (options === null || options === void 0 ? void 0 : options.transform) === "object" && options.transform !== null && Object.keys(options.transform).length > 0 ? "render/image/authenticated" : "object";
34535
34535
  const query = new URLSearchParams();
34536
34536
  if (options === null || options === void 0 ? void 0 : options.transform) this.applyTransformOptsToQuery(query, options.transform);
34537
34537
  if ((options === null || options === void 0 ? void 0 : options.cacheNonce) != null) query.set("cacheNonce", String(options.cacheNonce));
34538
34538
  const queryString = query.toString();
34539
- const _path = this._getFinalPath(path4);
34539
+ const _path = this._getFinalPath(path5);
34540
34540
  const downloadFn = () => get(this.fetch, `${this.url}/${renderPath}/${_path}${queryString ? `?${queryString}` : ""}`, {
34541
34541
  headers: this.headers,
34542
34542
  noResolveJson: true
@@ -34567,9 +34567,9 @@ var StorageFileApi = class extends BaseApiClient {
34567
34567
  * }
34568
34568
  * ```
34569
34569
  */
34570
- async info(path4) {
34570
+ async info(path5) {
34571
34571
  var _this10 = this;
34572
- const _path = _this10._getFinalPath(path4);
34572
+ const _path = _this10._getFinalPath(path5);
34573
34573
  return _this10.handleOperation(async () => {
34574
34574
  return recursiveToCamel(await get(_this10.fetch, `${_this10.url}/object/info/${_path}`, { headers: _this10.headers }));
34575
34575
  });
@@ -34590,9 +34590,9 @@ var StorageFileApi = class extends BaseApiClient {
34590
34590
  * .exists('folder/avatar1.png')
34591
34591
  * ```
34592
34592
  */
34593
- async exists(path4) {
34593
+ async exists(path5) {
34594
34594
  var _this11 = this;
34595
- const _path = _this11._getFinalPath(path4);
34595
+ const _path = _this11._getFinalPath(path5);
34596
34596
  try {
34597
34597
  await head(_this11.fetch, `${_this11.url}/object/${_path}`, { headers: _this11.headers });
34598
34598
  return {
@@ -34671,8 +34671,8 @@ var StorageFileApi = class extends BaseApiClient {
34671
34671
  * - `objects` table permissions: none
34672
34672
  * - Refer to the [Storage guide](/docs/guides/storage/security/access-control) on how access control works
34673
34673
  */
34674
- getPublicUrl(path4, options) {
34675
- const _path = this._getFinalPath(path4);
34674
+ getPublicUrl(path5, options) {
34675
+ const _path = this._getFinalPath(path5);
34676
34676
  const query = new URLSearchParams();
34677
34677
  if (options === null || options === void 0 ? void 0 : options.download) query.set("download", options.download === true ? "" : options.download);
34678
34678
  if (options === null || options === void 0 ? void 0 : options.transform) this.applyTransformOptsToQuery(query, options.transform);
@@ -34811,10 +34811,10 @@ var StorageFileApi = class extends BaseApiClient {
34811
34811
  * - `objects` table permissions: `select`
34812
34812
  * - Refer to the [Storage guide](/docs/guides/storage/security/access-control) on how access control works
34813
34813
  */
34814
- async list(path4, options, parameters) {
34814
+ async list(path5, options, parameters) {
34815
34815
  var _this13 = this;
34816
34816
  return _this13.handleOperation(async () => {
34817
- const body = _objectSpread22(_objectSpread22(_objectSpread22({}, DEFAULT_SEARCH_OPTIONS), options), {}, { prefix: path4 || "" });
34817
+ const body = _objectSpread22(_objectSpread22(_objectSpread22({}, DEFAULT_SEARCH_OPTIONS), options), {}, { prefix: path5 || "" });
34818
34818
  return await post(_this13.fetch, `${_this13.url}/object/list/${_this13.bucketId}`, body, { headers: _this13.headers }, parameters);
34819
34819
  });
34820
34820
  }
@@ -34879,11 +34879,11 @@ var StorageFileApi = class extends BaseApiClient {
34879
34879
  if (typeof Buffer !== "undefined") return Buffer.from(data).toString("base64");
34880
34880
  return btoa(data);
34881
34881
  }
34882
- _getFinalPath(path4) {
34883
- return `${this.bucketId}/${path4.replace(/^\/+/, "")}`;
34882
+ _getFinalPath(path5) {
34883
+ return `${this.bucketId}/${path5.replace(/^\/+/, "")}`;
34884
34884
  }
34885
- _removeEmptyFolders(path4) {
34886
- return path4.replace(/^\/|\/$/g, "").replace(/\/+/g, "/");
34885
+ _removeEmptyFolders(path5) {
34886
+ return path5.replace(/^\/|\/$/g, "").replace(/\/+/g, "/");
34887
34887
  }
34888
34888
  /** Modifies the `query`, appending values the from `transform` */
34889
34889
  applyTransformOptsToQuery(query, transform2) {
@@ -37273,17 +37273,30 @@ import { constants as fsConstants } from "node:fs";
37273
37273
  import { mkdir, readFile, rm, stat, writeFile } from "node:fs/promises";
37274
37274
  import { homedir } from "node:os";
37275
37275
  import path from "node:path";
37276
- import { promisify as promisify2 } from "node:util";
37277
- var execFile2 = promisify2(execFileCallback);
37278
37276
  var sessionSchema = external_exports.object({
37279
37277
  accessToken: external_exports.string().min(1),
37280
37278
  refreshToken: external_exports.string().min(1).optional(),
37281
37279
  expiresAt: external_exports.string().datetime({ offset: true }).optional()
37282
37280
  }).strict();
37281
+ var runExecFile = (file2, args, options) => new Promise((resolve2, reject) => {
37282
+ const child = execFileCallback(file2, [...args], (error51, stdout, stderr) => {
37283
+ if (error51) {
37284
+ reject(error51);
37285
+ return;
37286
+ }
37287
+ resolve2({ stdout, stderr });
37288
+ });
37289
+ const { stdin } = child;
37290
+ if (stdin) {
37291
+ stdin.on("error", () => {
37292
+ });
37293
+ stdin.end(options?.input ?? "");
37294
+ }
37295
+ });
37283
37296
  var serviceName = `${cliBrand.binName}-cli`;
37284
37297
  var accountName = "session";
37285
37298
  var KeychainTokenStore = class {
37286
- constructor(run2 = execFile2) {
37299
+ constructor(run2 = runExecFile) {
37287
37300
  this.run = run2;
37288
37301
  }
37289
37302
  run;
@@ -37304,16 +37317,14 @@ var KeychainTokenStore = class {
37304
37317
  }
37305
37318
  async write(session) {
37306
37319
  sessionSchema.parse(session);
37307
- await this.run("security", [
37308
- "add-generic-password",
37309
- "-U",
37310
- "-s",
37311
- serviceName,
37312
- "-a",
37313
- accountName,
37314
- "-w",
37315
- JSON.stringify(session)
37316
- ]);
37320
+ const secret = JSON.stringify(session);
37321
+ await this.run(
37322
+ "security",
37323
+ ["add-generic-password", "-U", "-s", serviceName, "-a", accountName, "-w"],
37324
+ { input: `${secret}
37325
+ ${secret}
37326
+ ` }
37327
+ );
37317
37328
  }
37318
37329
  async clear() {
37319
37330
  try {
@@ -37326,6 +37337,122 @@ var KeychainTokenStore = class {
37326
37337
  return "macOS Keychain";
37327
37338
  }
37328
37339
  };
37340
+ var DarwinTokenStore = class extends KeychainTokenStore {
37341
+ constructor(run2 = runExecFile, filePath) {
37342
+ super(run2);
37343
+ this.filePath = filePath;
37344
+ }
37345
+ filePath;
37346
+ backend = null;
37347
+ fileStore = null;
37348
+ async read() {
37349
+ return await this.selectBackend() === "file" ? this.file().read() : super.read();
37350
+ }
37351
+ async write(session) {
37352
+ if (await this.selectBackend() === "file") {
37353
+ await this.file().write(session);
37354
+ return;
37355
+ }
37356
+ await super.write(session);
37357
+ }
37358
+ async clear() {
37359
+ if (await this.selectBackend() === "file") {
37360
+ await this.file().clear();
37361
+ return;
37362
+ }
37363
+ await super.clear();
37364
+ }
37365
+ describe() {
37366
+ return this.backend === "file" ? "owner-only file store (no usable macOS keychain)" : super.describe();
37367
+ }
37368
+ file() {
37369
+ return this.fileStore ??= new FileTokenStore(this.filePath);
37370
+ }
37371
+ async selectBackend() {
37372
+ if (this.backend) {
37373
+ return this.backend;
37374
+ }
37375
+ try {
37376
+ await this.run("security", ["find-generic-password", "-s", serviceName, "-a", accountName, "-w"]);
37377
+ this.backend = "keychain";
37378
+ } catch (error51) {
37379
+ this.backend = isKeychainUnusableError(error51) ? "file" : "keychain";
37380
+ }
37381
+ return this.backend;
37382
+ }
37383
+ };
37384
+ var WindowsCredentialTokenStore = class {
37385
+ constructor(run2 = runExecFile) {
37386
+ this.run = run2;
37387
+ }
37388
+ run;
37389
+ async read() {
37390
+ try {
37391
+ const { stdout } = await this.run("powershell", powershellArgs(windowsReadScript(windowsCredentialTarget)));
37392
+ const trimmed = stdout.trim();
37393
+ if (trimmed === "") {
37394
+ return null;
37395
+ }
37396
+ return sessionSchema.parse(JSON.parse(trimmed));
37397
+ } catch {
37398
+ return null;
37399
+ }
37400
+ }
37401
+ async write(session) {
37402
+ sessionSchema.parse(session);
37403
+ await this.run(
37404
+ "powershell",
37405
+ powershellArgs(windowsWriteScript(windowsCredentialTarget)),
37406
+ { input: JSON.stringify(session) }
37407
+ );
37408
+ }
37409
+ async clear() {
37410
+ try {
37411
+ await this.run("powershell", powershellArgs(windowsClearScript(windowsCredentialTarget)));
37412
+ } catch {
37413
+ return;
37414
+ }
37415
+ }
37416
+ describe() {
37417
+ return "Windows Credential Manager";
37418
+ }
37419
+ };
37420
+ var SecretServiceTokenStore = class {
37421
+ constructor(run2 = runExecFile) {
37422
+ this.run = run2;
37423
+ }
37424
+ run;
37425
+ async read() {
37426
+ try {
37427
+ const { stdout } = await this.run("secret-tool", ["lookup", "service", serviceName, "account", accountName]);
37428
+ const trimmed = stdout.trim();
37429
+ if (trimmed === "") {
37430
+ return null;
37431
+ }
37432
+ return sessionSchema.parse(JSON.parse(trimmed));
37433
+ } catch {
37434
+ return null;
37435
+ }
37436
+ }
37437
+ async write(session) {
37438
+ sessionSchema.parse(session);
37439
+ await this.run(
37440
+ "secret-tool",
37441
+ ["store", `--label=${cliBrand.name} CLI session`, "service", serviceName, "account", accountName],
37442
+ { input: JSON.stringify(session) }
37443
+ );
37444
+ }
37445
+ async clear() {
37446
+ try {
37447
+ await this.run("secret-tool", ["clear", "service", serviceName, "account", accountName]);
37448
+ } catch {
37449
+ return;
37450
+ }
37451
+ }
37452
+ describe() {
37453
+ return "Secret Service";
37454
+ }
37455
+ };
37329
37456
  var FileTokenStore = class {
37330
37457
  constructor(filePath = defaultTokenPath()) {
37331
37458
  this.filePath = filePath;
@@ -37366,6 +37493,7 @@ var FileTokenStore = class {
37366
37493
  function createTokenStore(options = {}) {
37367
37494
  const env = options.env ?? process.env;
37368
37495
  const allowFileStoreKey = brandEnvKey2("CLI_ALLOW_FILE_TOKEN_STORE");
37496
+ const tokenPath = env[brandEnvKey2("CLI_TOKEN_PATH")];
37369
37497
  if (env[allowFileStoreKey] === "1") {
37370
37498
  const warning = [
37371
37499
  "WARNING: using the development-only plain-file CLI token store.",
@@ -37373,17 +37501,23 @@ function createTokenStore(options = {}) {
37373
37501
  ].join(" ");
37374
37502
  (options.stderr ?? process.stderr).write(`${warning}
37375
37503
  `);
37376
- return new FileTokenStore(env[brandEnvKey2("CLI_TOKEN_PATH")]);
37504
+ return new FileTokenStore(tokenPath);
37377
37505
  }
37378
37506
  const platform = options.platform ?? process.platform;
37379
37507
  if (platform === "darwin") {
37380
- return new KeychainTokenStore(options.execFile);
37508
+ return new DarwinTokenStore(options.execFile, tokenPath);
37509
+ }
37510
+ if (platform === "win32") {
37511
+ return new WindowsCredentialTokenStore(options.execFile);
37512
+ }
37513
+ if (platform === "linux" && env[brandEnvKey2("CLI_USE_SECRET_SERVICE")] === "1") {
37514
+ return new SecretServiceTokenStore(options.execFile);
37381
37515
  }
37382
37516
  throw new Error(
37383
37517
  [
37384
- "No OS credential store is available (the CLI uses the macOS Keychain by default; there is no Windows or Linux keychain integration).",
37385
- `For headless Linux, CI runners, or any no-keychain host, export ${allowFileStoreKey}=1 (set it for every invocation) to opt into the development-only plain-file token store.`,
37386
- "It is not encrypted: it writes an owner-only file (directory 0700, file 0600) holding only CLI session tokens \u2014 never tenant secrets, API keys, or vault refs."
37518
+ "No OS credential store is available (the CLI uses the macOS Keychain by default, Windows Credential Manager on Windows, and the Linux Secret Service when enabled).",
37519
+ `For headless Linux, export ${brandEnvKey2("CLI_USE_SECRET_SERVICE")}=1 to use the Secret Service, or ${allowFileStoreKey}=1 (set it for every invocation) to opt into the development-only plain-file token store.`,
37520
+ "The file store is not encrypted: it writes an owner-only file (directory 0700, file 0600) holding only CLI session tokens \u2014 never tenant secrets, API keys, or vault refs."
37387
37521
  ].join(" ")
37388
37522
  );
37389
37523
  }
@@ -37393,6 +37527,105 @@ function defaultTokenPath() {
37393
37527
  function brandEnvKey2(suffix) {
37394
37528
  return `${cliBrand.envPrefix}_${suffix}`;
37395
37529
  }
37530
+ function isKeychainUnusableError(error51) {
37531
+ if (isKeychainItemMissing(error51)) {
37532
+ return false;
37533
+ }
37534
+ return true;
37535
+ }
37536
+ function isKeychainItemMissing(error51) {
37537
+ if (!error51 || typeof error51 !== "object") {
37538
+ return false;
37539
+ }
37540
+ if (error51.code === 44) {
37541
+ return true;
37542
+ }
37543
+ return /could not be found|SecKeychainSearchCopyNext/i.test(errorText(error51));
37544
+ }
37545
+ function errorText(error51) {
37546
+ if (!error51 || typeof error51 !== "object") {
37547
+ return String(error51 ?? "");
37548
+ }
37549
+ const record2 = error51;
37550
+ const parts = [];
37551
+ for (const key of ["message", "stderr", "stdout"]) {
37552
+ const value = record2[key];
37553
+ if (typeof value === "string") {
37554
+ parts.push(value);
37555
+ }
37556
+ }
37557
+ return parts.join("\n");
37558
+ }
37559
+ var windowsCredentialTarget = `${serviceName}/${accountName}`;
37560
+ function powershellArgs(script) {
37561
+ return ["-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-Command", script];
37562
+ }
37563
+ function psQuote(value) {
37564
+ return `'${value.replace(/'/g, "''")}'`;
37565
+ }
37566
+ var WINDOWS_CRED_MODULE = [
37567
+ "$ErrorActionPreference = 'Stop'",
37568
+ "Add-Type -TypeDefinition @'",
37569
+ "using System;",
37570
+ "using System.Runtime.InteropServices;",
37571
+ "public static class RostCred {",
37572
+ " [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]",
37573
+ " public struct CREDENTIAL {",
37574
+ " public uint Flags; public uint Type;",
37575
+ " [MarshalAs(UnmanagedType.LPWStr)] public string TargetName;",
37576
+ " [MarshalAs(UnmanagedType.LPWStr)] public string Comment;",
37577
+ " public System.Runtime.InteropServices.ComTypes.FILETIME LastWritten;",
37578
+ " public uint CredentialBlobSize; public IntPtr CredentialBlob;",
37579
+ " public uint Persist; public uint AttributeCount; public IntPtr Attributes;",
37580
+ " [MarshalAs(UnmanagedType.LPWStr)] public string TargetAlias;",
37581
+ " [MarshalAs(UnmanagedType.LPWStr)] public string UserName;",
37582
+ " }",
37583
+ ' [DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]',
37584
+ " public static extern bool CredWriteW(ref CREDENTIAL cred, uint flags);",
37585
+ ' [DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]',
37586
+ " public static extern bool CredReadW(string target, uint type, uint flags, out IntPtr cred);",
37587
+ ' [DllImport("advapi32.dll")] public static extern void CredFree(IntPtr cred);',
37588
+ ' [DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]',
37589
+ " public static extern bool CredDeleteW(string target, uint type, uint flags);",
37590
+ "}",
37591
+ "'@"
37592
+ ].join("\n");
37593
+ function windowsWriteScript(target) {
37594
+ return [
37595
+ WINDOWS_CRED_MODULE,
37596
+ "$secret = [Console]::In.ReadToEnd()",
37597
+ "$bytes = [System.Text.Encoding]::Unicode.GetBytes($secret)",
37598
+ "$blob = [Runtime.InteropServices.Marshal]::AllocHGlobal($bytes.Length)",
37599
+ "[Runtime.InteropServices.Marshal]::Copy($bytes, 0, $blob, $bytes.Length)",
37600
+ "$cred = New-Object RostCred+CREDENTIAL",
37601
+ "$cred.Type = 1",
37602
+ `$cred.TargetName = ${psQuote(target)}`,
37603
+ "$cred.UserName = 'session'",
37604
+ "$cred.CredentialBlobSize = $bytes.Length",
37605
+ "$cred.CredentialBlob = $blob",
37606
+ "$cred.Persist = 2",
37607
+ "$ok = [RostCred]::CredWriteW([ref]$cred, 0)",
37608
+ "[Runtime.InteropServices.Marshal]::FreeHGlobal($blob)",
37609
+ "if (-not $ok) { exit 1 }"
37610
+ ].join("\n");
37611
+ }
37612
+ function windowsReadScript(target) {
37613
+ return [
37614
+ WINDOWS_CRED_MODULE,
37615
+ "$ptr = [IntPtr]::Zero",
37616
+ `if (-not [RostCred]::CredReadW(${psQuote(target)}, 1, 0, [ref]$ptr)) { exit 0 }`,
37617
+ "$cred = [Runtime.InteropServices.Marshal]::PtrToStructure($ptr, [Type][RostCred+CREDENTIAL])",
37618
+ "$secret = [Runtime.InteropServices.Marshal]::PtrToStringUni($cred.CredentialBlob, [int]($cred.CredentialBlobSize / 2))",
37619
+ "[RostCred]::CredFree($ptr)",
37620
+ "[Console]::Out.Write($secret)"
37621
+ ].join("\n");
37622
+ }
37623
+ function windowsClearScript(target) {
37624
+ return [
37625
+ WINDOWS_CRED_MODULE,
37626
+ `[void][RostCred]::CredDeleteW(${psQuote(target)}, 1, 0)`
37627
+ ].join("\n");
37628
+ }
37396
37629
 
37397
37630
  // ../../packages/prompts/src/index.ts
37398
37631
  import { readFileSync } from "node:fs";
@@ -37569,10 +37802,10 @@ var util;
37569
37802
  function assertIs2(_arg) {
37570
37803
  }
37571
37804
  util2.assertIs = assertIs2;
37572
- function assertNever2(_x) {
37805
+ function assertNever3(_x) {
37573
37806
  throw new Error();
37574
37807
  }
37575
- util2.assertNever = assertNever2;
37808
+ util2.assertNever = assertNever3;
37576
37809
  util2.arrayToEnum = (items) => {
37577
37810
  const obj = {};
37578
37811
  for (const item of items) {
@@ -37927,8 +38160,8 @@ function getErrorMap2() {
37927
38160
 
37928
38161
  // ../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/helpers/parseUtil.js
37929
38162
  var makeIssue = (params) => {
37930
- const { data, path: path4, errorMaps, issueData } = params;
37931
- const fullPath = [...path4, ...issueData.path || []];
38163
+ const { data, path: path5, errorMaps, issueData } = params;
38164
+ const fullPath = [...path5, ...issueData.path || []];
37932
38165
  const fullIssue = {
37933
38166
  ...issueData,
37934
38167
  path: fullPath
@@ -38044,11 +38277,11 @@ var errorUtil;
38044
38277
 
38045
38278
  // ../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/types.js
38046
38279
  var ParseInputLazyPath = class {
38047
- constructor(parent, value, path4, key) {
38280
+ constructor(parent, value, path5, key) {
38048
38281
  this._cachedPath = [];
38049
38282
  this.parent = parent;
38050
38283
  this.data = value;
38051
- this._path = path4;
38284
+ this._path = path5;
38052
38285
  this._key = key;
38053
38286
  }
38054
38287
  get path() {
@@ -41638,6 +41871,78 @@ The Compass is drafted, then activated by a human through supersession.
41638
41871
  ## When to stop for confirmation
41639
41872
 
41640
41873
  \`onboarding.finish\`, \`compass.approve_version\`, \`compass.reject_draft\`, and \`compass.set\` are \`human_required\`. \`onboarding.advance_step\`, \`onboarding.create_invite\`, \`onboarding.attach_reference\`, \`compass.answer_gap\`, and drafting a Compass are \`none\` \u2014 none of them returns a pending confirmation. \`none\` is not the same as "agent-callable", though: \`onboarding.create_invite\` and \`compass.answer_gap\` record the human who ran them, so they run as a person, not from an agent session, while advancing a step, attaching a reference, and drafting a Compass are safe for an agent. Approving a version (a supersession) is a human act. The agent drafts and surfaces the approve link. See the confirmations guide.`
41874
+ },
41875
+ {
41876
+ slug: "aicos-chat-guide",
41877
+ order: 15,
41878
+ title: "AICOS chat guide",
41879
+ summary: "How the AI Chief of Staff chat works in the authenticated app shell and what it can safely do today.",
41880
+ version: "2026-07-02.3",
41881
+ public: true,
41882
+ audiences: ["human", "in_app_agent"],
41883
+ stages: ["company_setup", "operating_rhythm"],
41884
+ relatedCommandIds: ["confirmation.approve", "onboarding.status", "graph.get", "seat.get"],
41885
+ legal: {
41886
+ publicRisk: "low",
41887
+ notes: [
41888
+ "Describes the authenticated AICOS chat surface without promising autonomous action.",
41889
+ "Keeps authority, lane, and confirmation language aligned with the platform governance model."
41890
+ ]
41891
+ },
41892
+ sources: [
41893
+ {
41894
+ label: "AICOS Company Chief of Staff plan",
41895
+ internalPath: "docs/plans/2026-07-01-003-feat-aicos-company-chief-of-staff-plan.md",
41896
+ note: "Defines the AICOS product contract, lane portability, and governance boundaries."
41897
+ },
41898
+ {
41899
+ label: "MVP implementation plan",
41900
+ internalPath: "docs/specs/MVP-implementation-plan.md",
41901
+ note: "FR-10.6 records the AICOS extension and chat harness rollout."
41902
+ }
41903
+ ],
41904
+ body: `# AICOS chat guide
41905
+
41906
+ AICOS is the AI Chief of Staff for an authenticated {{brand}} tenant. It is a governed agent seat in the Responsibility Graph, not an anonymous public chatbot and not a hidden superuser.
41907
+
41908
+ ## Where AICOS appears
41909
+
41910
+ In the authenticated app shell, AICOS opens from the floating assistant control. The panel is available across normal app pages and keeps the same thread while you move through the product. Full-bleed public, login, onboarding, docs, invite, and demo routes do not receive the authenticated shell.
41911
+
41912
+ Each thread has a purpose:
41913
+
41914
+ - **Onboarding** \u2014 setup help and page-aware setup context.
41915
+ - **Seat** \u2014 seat and agent setup context.
41916
+ - **General** \u2014 company operating help.
41917
+
41918
+ You can reopen prior threads or start a new one. Session history is stored in tenant-scoped conversation tables so chat history is not reconstructed from analytics, events, or run logs.
41919
+
41920
+ ## What context AICOS receives
41921
+
41922
+ Each turn carries server-normalized page context: route template, current path, safe search state, selected seat or object ids when authorized, and onboarding state when relevant. The server derives tenant and user authority from the authenticated session. Client-supplied tenant context is never accepted as authority.
41923
+
41924
+ The current chat harness shows the effective lane and model. The initial lane is cloud, with a deterministic harness response while live model execution, action execution, and runner execution waves land. The contract is intentionally backend-neutral: future cloud and runner turns use the same sessions, messages, lane labels, sources, and linked artifacts.
41925
+
41926
+ Model behavior is now explicit in the AICOS session contract. A thread can run in **Auto** or use a pinned compatible model from the guided catalog. Auto chooses a fast tier for short low-risk turns, the balanced default for normal operating help, and a higher-reasoning tier for onboarding synthesis or large context. The panel shows the effective model, disables lower-context choices once the active thread outgrows them, and tells the user to start a new session for those models. Session context is estimated and compacted into a durable summary when it crosses the configured threshold; raw messages remain in history, but future model calls can use the summary rather than resending the whole thread. Platform allowance and BYOK state are checked before routing; an exhausted included allowance keeps AICOS in guided mode and points the user toward BYOK/manual continuation. Because the DER-1068 implementation still uses the deterministic harness, it does not create fake llm_calls rows or settle fake allowance usage; reservation/settlement belongs to the real provider call path.
41927
+
41928
+ AICOS grounds answers from two source classes before producing user-visible setup or operating guidance:
41929
+
41930
+ - **Reference documents** \u2014 public {{brand}} guides such as this chat guide, onboarding guidance, the Responsibility Graph playbook, tool-access guidance, and confirmation guidance. AICOS retrieves the narrowest relevant guide before setup, Charter, Signal, Friction, Cascade, Sync, tool-access, or onboarding advice.
41931
+ - **Tenant read models** \u2014 bounded summaries of tenant operating state, including onboarding status, graph/setup readiness, Compass/Charter readiness, operating health, agent/system health, approvals/inbox, and selected Seat context. These read models expose counts and short observations rather than raw database dumps.
41932
+
41933
+ Assistant messages can carry source counts and persisted source metadata so a thread can later show which reference docs or read models informed the answer.
41934
+
41935
+ ## Governance boundary
41936
+
41937
+ AICOS can preserve context and explain what it is ready to do, but durable operating changes still use the same {{brand}} controls:
41938
+
41939
+ - Tenant isolation is enforced by RLS and server-side tenant context.
41940
+ - Raw secrets, vault refs, hidden tool arguments, storage refs, provider payloads, agent custom prompts, and secret-shaped text are not stored in chat history or read-model context.
41941
+ - Durable decisions remain human decisions.
41942
+ - Action execution must route through command guards and confirmation when required.
41943
+ - Expanding authority, approving Charters or Compass versions, going live, credentials, external sends, money, destructive operations, and policy changes stay human-gated.
41944
+
41945
+ Treat AICOS as a coordinator over the operating system. It can become more useful as model grounding, read tools, draft actions, and runner execution are enabled, but it does not bypass the Responsibility Graph, Seat Steward accountability, command guards, or confirmations.`
41641
41946
  },
41642
41947
  {
41643
41948
  slug: "responsibility-graph-playbook",
@@ -42412,7 +42717,7 @@ External connectors are being rolled out provider by provider, conservatively (r
42412
42717
  order: 48,
42413
42718
  title: "CLI and MCP installation guide",
42414
42719
  summary: "Install the public CLI, register remote token-backed MCP clients, and find the full command and tool catalog.",
42415
- version: "2026-07-02.5",
42720
+ version: "2026-07-02.6",
42416
42721
  public: true,
42417
42722
  audiences: ["human", "cli", "mcp", "in_app_agent"],
42418
42723
  stages: ["company_setup", "staffing"],
@@ -42869,6 +43174,7 @@ The signed-in app exposes the same Skill command surface under **Skills** in the
42869
43174
  | \`{{cli}} error list|resolve|supersede\` | \`error_log.list\`, \`error_log.resolve\`, \`error_log.supersede\` | List active, acknowledged, resolved, or superseded product error logs and let a human acknowledge, resolve, or supersede stale errors with evidence links. | Tenant | \`{{cli}} error list --resolved active --json\`; \`{{cli}} error resolve --error-log-id <id> --reason "Fixed in PR #123"\` |
42870
43175
  | \`{{cli}} sync brief|compile|complete\` | \`sync.brief.get\`, \`sync.brief.compile\`, \`sync.run.complete\` | Compile, read, and complete a weekly Sync. | Tenant | \`{{cli}} sync brief --json\` |
42871
43176
  | \`{{cli}} runner list|status|diagnose|repair|work-orders|revoke|serve\` | \`runner.list\`, \`runner.status\`, \`runner.diagnose\`, \`runner.repair\`, \`work_order.list\`, \`runner.revoke\`, runner endpoint APIs | Inspect heartbeat and execute-readiness evidence, inspect work orders, diagnose offline runners, get repair guidance, revoke a runner, or run the headless local runner loop. | Tenant | \`{{cli}} runner diagnose --runner-id <id> --json\`; \`{{cli}} runner serve --once\` |
43177
+ | \`{{cli}} runner install-service|start|stop|restart|status|logs|uninstall --name <name>\` | launchd/service manager (macOS today) + \`{{cli}} runner serve\` | Install and manage an always-on local runner service that keeps the runner online across reboots; the service shells out to \`{{cli}} runner serve --execute\` and stores no secret in the service definition, its arguments, or logs. Non-macOS emits an explicit unsupported message. | Tenant (local machine) | \`{{cli}} runner install-service --name mac-mini --execute\`; \`{{cli}} runner logs --name mac-mini\` |
42872
43178
  | \`{{cli}} notification settings|test|errors\` | \`notification.settings.get\`, \`notification.test\`, \`notification.list_errors\` | Read notification settings, send a test, and list failed deliveries with linked product error source, seat id, and run id when available. | Tenant | \`{{cli}} notification errors --limit 10 --json\` |
42873
43179
  | \`{{cli}} integration list|readiness|status|test\` / credential flow for Baserow REST connect | \`integration.connect_rest\`, \`integration.list\`, \`integration.readiness\`, \`integration.status\`, \`integration.test\` | Create a vault-backed Baserow REST integration through the credential flow, list connector metadata, read setup-readiness, read one connector's health, and run provider-specific tests without exposing credentials. | Tenant-admin for connect; tenant for reads/tests | \`{{cli}} integration readiness --provider google --json\` |
42874
43180
  | \`{{cli}} system health\` | \`system.health\` | Read the scoped system-health snapshot across agent runs, unresolved errors, Signals, Cascade setup, work loop, integrations, runners, notifications, and Sync Brief readiness. | Tenant, member, or seat token | \`{{cli}} system health --json\`; \`{{cli}} system health --seat <id> --json\` |
@@ -43461,7 +43767,7 @@ Set \`ROST_SKILL_INSTALL_ROOT\` only for a sandbox or CI override. Local copies
43461
43767
  order: 55,
43462
43768
  title: "Agent reference map",
43463
43769
  summary: "Where CLI sessions, MCP clients, and in-app agents should retrieve {{brand}} guidance before recommending setup changes.",
43464
- version: "2026-07-02.2",
43770
+ version: "2026-07-02.3",
43465
43771
  public: true,
43466
43772
  audiences: ["cli", "mcp", "in_app_agent"],
43467
43773
  stages: ["company_setup", "graph_design", "charter_design", "staffing", "operating_rhythm"],
@@ -43525,28 +43831,29 @@ Agents that run on {{brand}}-managed inference draw against a tenant inference b
43525
43831
 
43526
43832
  1. rost-implementation-method
43527
43833
  2. compass-authoring-guide
43528
- 3. responsibility-graph-playbook
43529
- 4. charter-design-playbook
43530
- 5. charter-authoring-deep-dive
43531
- 6. agent-staffing-playbook
43532
- 7. add-agents-guide
43533
- 8. custom-agents-guide
43534
- 9. agent-builder-guide
43535
- 10. how-agents-work
43536
- 11. tool-access-and-vault
43537
- 12. available-tools-guide
43538
- 13. mcp-and-cli-guide
43539
- 14. skill-builder-guide
43540
- 15. agent-skill-authoring-guide
43541
- 16. agent-skill-setup-guide
43542
- 17. ai-model-data-handling-guide
43543
- 18. cascade-guide
43544
- 19. signal-guide
43545
- 20. friction-guide
43546
- 21. confirmations-guide
43547
- 22. steward-queue-guide
43548
- 23. sync-rhythm-playbook
43549
- 24. billing-and-pricing-guide
43834
+ 3. aicos-chat-guide
43835
+ 4. responsibility-graph-playbook
43836
+ 5. charter-design-playbook
43837
+ 6. charter-authoring-deep-dive
43838
+ 7. agent-staffing-playbook
43839
+ 8. add-agents-guide
43840
+ 9. custom-agents-guide
43841
+ 10. agent-builder-guide
43842
+ 11. how-agents-work
43843
+ 12. tool-access-and-vault
43844
+ 13. available-tools-guide
43845
+ 14. mcp-and-cli-guide
43846
+ 15. skill-builder-guide
43847
+ 16. agent-skill-authoring-guide
43848
+ 17. agent-skill-setup-guide
43849
+ 18. ai-model-data-handling-guide
43850
+ 19. cascade-guide
43851
+ 20. signal-guide
43852
+ 21. friction-guide
43853
+ 22. confirmations-guide
43854
+ 23. steward-queue-guide
43855
+ 24. sync-rhythm-playbook
43856
+ 25. billing-and-pricing-guide
43550
43857
 
43551
43858
  ## Workflow to guide map
43552
43859
 
@@ -43554,6 +43861,7 @@ Read the listed guide before recommending or running each workflow. Every workfl
43554
43861
 
43555
43862
  - Connect a local agent (CLI login, MCP token, client registration): mcp-and-cli-guide, then agent-reference-map.
43556
43863
  - Company onboarding (context, Compass draft, finish): rost-implementation-method, then settings-members-and-invites-guide for invites.
43864
+ - Use the in-app AI Chief of Staff chat or explain its current authority boundary: aicos-chat-guide, then confirmations-guide for any durable change.
43557
43865
  - Author a strong Compass directly (attach references, draft, answer gaps, approve): compass-authoring-guide \u2014 it shows the full JSON contract, what good vs weak looks like per field, and worked examples. Attaching reference docs (onboarding.attach_reference) is reference-only; you author the Compass, the server does not build it for you.
43558
43866
  - Design the graph (create, rename, reparent, set type): responsibility-graph-playbook.
43559
43867
  - Write Charters (draft, edit, approve, sign manifest): charter-design-playbook for the narrative, then charter-authoring-deep-dive for the full document contract field by field (decision_authority, escalation_rules, measurables, budget, permission_manifest, seat-type), then tool-access-and-vault for permissions. Submit the finished Charter directly with charter.update_draft or charter.set.
@@ -43577,11 +43885,11 @@ Read the listed guide before recommending or running each workflow. Every workfl
43577
43885
 
43578
43886
  ## Stage map
43579
43887
 
43580
- - Company setup: rost-implementation-method, compass-authoring-guide, settings-guide, settings-members-and-invites-guide
43888
+ - Company setup: rost-implementation-method, compass-authoring-guide, aicos-chat-guide, settings-guide, settings-members-and-invites-guide
43581
43889
  - Graph design: responsibility-graph-playbook
43582
43890
  - Charter design: charter-design-playbook, charter-authoring-deep-dive
43583
43891
  - Staffing: agent-staffing-playbook, add-agents-guide, custom-agents-guide, agent-builder-guide, how-agents-work, tool-access-and-vault, available-tools-guide, skill-builder-guide, agent-skill-authoring-guide, agent-skill-setup-guide, mcp-and-cli-guide, stock-agents-guide
43584
- - Operating rhythm: cascade-guide, signal-guide, friction-guide, confirmations-guide, steward-queue-guide, sync-rhythm-playbook, notifications-guide
43892
+ - Operating rhythm: aicos-chat-guide, cascade-guide, signal-guide, friction-guide, confirmations-guide, steward-queue-guide, sync-rhythm-playbook, notifications-guide
43585
43893
  - Local agents and runners: mcp-and-cli-guide, runner-guide, agent-reference-map
43586
43894
  - Security and troubleshooting: ai-model-data-handling-guide, security-model-guide, troubleshooting-guide
43587
43895
 
@@ -44292,7 +44600,7 @@ Every notification should include the seat, cause, evidence, and requested decis
44292
44600
  order: 75,
44293
44601
  title: "Local runner guide",
44294
44602
  summary: "How local agent sessions and runner surfaces should operate through {{brand}} without bypassing Charters or audit.",
44295
- version: "2026-06-30.1",
44603
+ version: "2026-07-02.1",
44296
44604
  public: true,
44297
44605
  audiences: ["human", "cli", "mcp", "in_app_agent"],
44298
44606
  stages: ["staffing", "operating_rhythm"],
@@ -44318,6 +44626,26 @@ The local runner is for human-controlled local agent work. It should retrieve {{
44318
44626
  5. Record work, Friction, handoffs, and escalations.
44319
44627
  6. Revoke access when the session ends.
44320
44628
 
44629
+ ## Install a local Runner
44630
+
44631
+ Use Local Runner when a Seat should run on a machine you control. Install it from the public CLI \u2014 no repository checkout and no desktop app are required. A signed desktop app is planned; until verified installers ship, Settings > Runners shows no desktop download link.
44632
+
44633
+ Recommended paths:
44634
+
44635
+ - **Always-on host:** \`{{cli}} runner install-service --name <runner-name> --execute\` installs a background service (macOS launchd user agent) that keeps the runner online across reboots. Manage it with \`{{cli}} runner start|stop|restart|status|logs|uninstall --name <runner-name>\`.
44636
+ - **Manual smoke:** \`{{cli}} runner serve --once\` heartbeats once and exits; \`{{cli}} runner serve --execute\` claims and executes runner work orders while it runs.
44637
+
44638
+ Two ways to authenticate and pair \u2014 both first-class, an agent can drive either entirely from the terminal:
44639
+
44640
+ - **CLI-native (agent):** \`{{cli}} login --device\` prints a code for one human OAuth approval; after that the session is stored locally and the agent runs unattended ("approve once, then autonomous"). Then \`{{cli}} runner serve\`/\`install-service\` pairs against your active company \u2014 an authenticated CLI can also self-issue a pairing code with \`runner.pairing.start\` and claim it.
44641
+ - **Web-guided (human):** issue a pairing code from Settings > Runners > Add a Runner, then on the host run \`{{cli}} runner serve --user-code <code>\`. The host stores only the runner bearer secret in an owner-only file and never logs in.
44642
+
44643
+ Headless and service accounts are supported first-class: when no usable OS keychain exists (for example a service or SSH account), the CLI stores the session in an owner-only file (directory 0700, file 0600) automatically \u2014 no development flag. That file holds only the CLI session token, never tenant secrets, API keys, or vault refs.
44644
+
44645
+ Choose the local runtime with \`--runtime auto|claude|codex\` (default \`auto\`). The same short-lived seat-scoped MCP token and server-side guard apply regardless of runtime; results are tagged \`claude-cli\` or \`codex-cli\`.
44646
+
44647
+ The runner bearer secret is stored locally and is never displayed. Each claimed turn receives a short-lived seat-scoped MCP token from {{brand}}, and server-side guards still decide what tools the Seat may use.
44648
+
44321
44649
  ## Inspect and control runners from CLI or MCP
44322
44650
 
44323
44651
  - Pair a new runner: \`runner.pairing.start\` / \`rost_start_runner_pairing\` with \`name\` and \`platform\` returns a human pairing code. This is owner/admin-only because it mints a short-lived runner pairing session that leads to machine credentials.
@@ -47955,6 +48283,9 @@ function operationUsageLines(bin) {
47955
48283
  `${bin} error list|resolve|supersede`,
47956
48284
  `${bin} sync brief|compile|complete`,
47957
48285
  `${bin} runner list|status|diagnose|repair|work-orders|revoke|serve`,
48286
+ // Local runner service lifecycle (DER-1142). The orchestrator mirrors this
48287
+ // exact line into the mcp-and-cli-guide so the help/guide parity gate passes.
48288
+ `${bin} runner install-service|start|stop|restart|status|logs|uninstall --name <name>`,
47958
48289
  `${bin} notification settings|test|errors`,
47959
48290
  `${bin} settings get|update|product-learning|agent-policy|rename`,
47960
48291
  `${bin} member invite|update|remove`,
@@ -48529,8 +48860,8 @@ function pendingConfirmationFromError(error51) {
48529
48860
  import { execFile as execFileCallback2 } from "node:child_process";
48530
48861
  import { access, readFile as readFile3 } from "node:fs/promises";
48531
48862
  import { delimiter } from "node:path";
48532
- import { promisify as promisify3 } from "node:util";
48533
- var execFile3 = promisify3(execFileCallback2);
48863
+ import { promisify as promisify2 } from "node:util";
48864
+ var execFile2 = promisify2(execFileCallback2);
48534
48865
  async function runDoctor(options) {
48535
48866
  const env = options.env ?? process.env;
48536
48867
  const store = options.store ?? createTokenStore();
@@ -48578,7 +48909,7 @@ async function checkPath(env) {
48578
48909
  }
48579
48910
  async function probeCli(command) {
48580
48911
  try {
48581
- const { stdout, stderr } = await execFile3(command, ["--version"], { timeout: 1500 });
48912
+ const { stdout, stderr } = await execFile2(command, ["--version"], { timeout: 1500 });
48582
48913
  const version4 = (stdout || stderr).trim().split("\n")[0]?.trim();
48583
48914
  return version4 ? `installed (${version4})` : "installed";
48584
48915
  } catch {
@@ -48653,8 +48984,8 @@ import { chmod, mkdir as mkdir2, readFile as readFile4, rm as rm2, writeFile as
48653
48984
  import { hostname as hostname3, tmpdir } from "node:os";
48654
48985
  import path3 from "node:path";
48655
48986
  import { setTimeout as sleep2 } from "node:timers/promises";
48656
- import { promisify as promisify4 } from "node:util";
48657
- var execFile4 = promisify4(execFileCallback3);
48987
+ import { promisify as promisify3 } from "node:util";
48988
+ var execFile3 = promisify3(execFileCallback3);
48658
48989
  async function runRunnerServe(options) {
48659
48990
  const env = options.env ?? process.env;
48660
48991
  const fetchImpl = options.fetchImpl ?? fetch;
@@ -48693,8 +49024,11 @@ ${usage()}
48693
49024
  options.io.stdout.write(`heartbeat ok #${beat} runner_id=${state.runner_id} claude=${capabilities.claude.installed} codex=${capabilities.codex.installed}
48694
49025
  `);
48695
49026
  }
48696
- if (config2.execute && capabilities.claude.installed) {
48697
- await claimAndExecute(fetchImpl, options.appUrl, state, options.io);
49027
+ if (config2.execute) {
49028
+ const localRuntime = effectiveExecutionRuntime(config2.runtime, capabilities);
49029
+ if (localRuntime) {
49030
+ await claimAndExecute(fetchImpl, options.appUrl, state, options.io, localRuntime);
49031
+ }
48698
49032
  }
48699
49033
  } catch (error51) {
48700
49034
  failed = true;
@@ -48714,6 +49048,7 @@ function parseServeArgs(args, appUrl2, env) {
48714
49048
  let heartbeatMs = Number(env.RUNNER_HEARTBEAT_MS ?? 3e4);
48715
49049
  let execute2 = env.RUNNER_EXECUTE === "1";
48716
49050
  let once = false;
49051
+ let runtime = parseRuntime(env.RUNNER_RUNTIME ?? "auto");
48717
49052
  let userCode = env.RUNNER_USER_CODE;
48718
49053
  for (let index = 0; index < args.length; index += 1) {
48719
49054
  const token = args[index];
@@ -48729,6 +49064,9 @@ function parseServeArgs(args, appUrl2, env) {
48729
49064
  } else if (token === "--user-code") {
48730
49065
  userCode = requiredValue(args, index);
48731
49066
  index += 1;
49067
+ } else if (token === "--runtime") {
49068
+ runtime = parseRuntime(requiredValue(args, index));
49069
+ index += 1;
48732
49070
  } else if (token === "--execute") {
48733
49071
  execute2 = true;
48734
49072
  } else if (token === "--once") {
@@ -48740,7 +49078,19 @@ function parseServeArgs(args, appUrl2, env) {
48740
49078
  if (!Number.isSafeInteger(heartbeatMs) || heartbeatMs < 1e3) {
48741
49079
  throw new Error("--heartbeat-ms must be an integer >= 1000.");
48742
49080
  }
48743
- return { name, stateFile, heartbeatMs, execute: execute2, once, ...userCode ? { userCode } : {} };
49081
+ return { name, stateFile, heartbeatMs, execute: execute2, once, runtime, ...userCode ? { userCode } : {} };
49082
+ }
49083
+ function parseRuntime(value) {
49084
+ if (value === "auto" || value === "claude" || value === "codex") {
49085
+ return value;
49086
+ }
49087
+ throw new Error(`--runtime must be one of auto, claude, codex (got ${value}).`);
49088
+ }
49089
+ function effectiveExecutionRuntime(runtime, capabilities) {
49090
+ if (runtime === "codex") {
49091
+ return capabilities.codex.installed ? "codex" : null;
49092
+ }
49093
+ return capabilities.claude.installed ? "claude" : null;
48744
49094
  }
48745
49095
  function requiredValue(args, index) {
48746
49096
  const value = args[index + 1];
@@ -48830,7 +49180,7 @@ async function detectCapabilities() {
48830
49180
  }
48831
49181
  async function probe(command) {
48832
49182
  try {
48833
- const { stdout, stderr } = await execFile4(command, ["--version"], { timeout: 1500 });
49183
+ const { stdout, stderr } = await execFile3(command, ["--version"], { timeout: 1500 });
48834
49184
  return { installed: true, version: (stdout || stderr).trim() || null, auth_state: "ready" };
48835
49185
  } catch {
48836
49186
  return { installed: false, version: null, auth_state: "unknown" };
@@ -48855,7 +49205,7 @@ async function post2(fetchImpl, appUrl2, pathName, body, token) {
48855
49205
  }
48856
49206
  return { status: response.status, json: json2 };
48857
49207
  }
48858
- async function claimAndExecute(fetchImpl, appUrl2, state, io) {
49208
+ async function claimAndExecute(fetchImpl, appUrl2, state, io, runtime) {
48859
49209
  const claimed = await post2(fetchImpl, appUrl2, "/api/runner/work-orders/claim", {}, state.runner_secret);
48860
49210
  const workOrder = claimed.json.work_order;
48861
49211
  if (claimed.status !== 200 || !workOrder || typeof workOrder !== "object") {
@@ -48869,12 +49219,12 @@ async function claimAndExecute(fetchImpl, appUrl2, state, io) {
48869
49219
  if (started.status !== 200) {
48870
49220
  throw new Error(`work order start failed ${started.status}: ${redactForLog(JSON.stringify(started.json))}`);
48871
49221
  }
48872
- const result = await runClaudeTurn(appUrl2, workOrder);
49222
+ const result = await runLocalTurn(appUrl2, workOrder, runtime);
48873
49223
  const reported = await post2(fetchImpl, appUrl2, `/api/runner/work-orders/${encodeURIComponent(workOrderId)}/result`, {
48874
49224
  status: result.ok ? "succeeded" : "failed",
48875
49225
  summary: result.summary,
48876
49226
  log_ref: `runner-local:${workOrderId}`,
48877
- model: "claude-cli"
49227
+ model: runtime === "codex" ? "codex-cli" : "claude-cli"
48878
49228
  }, state.runner_secret);
48879
49229
  if (reported.status !== 200) {
48880
49230
  throw new Error(`work order result failed ${reported.status}: ${redactForLog(JSON.stringify(reported.json))}`);
@@ -48882,7 +49232,28 @@ async function claimAndExecute(fetchImpl, appUrl2, state, io) {
48882
49232
  io.stdout.write(`work_order_id=${workOrderId} status=${result.ok ? "succeeded" : "failed"}
48883
49233
  `);
48884
49234
  }
48885
- async function runClaudeTurn(appUrl2, workOrder) {
49235
+ function runLocalTurn(appUrl2, workOrder, runtime) {
49236
+ return runtime === "codex" ? runCodexTurn(appUrl2, workOrder) : runClaudeTurn(appUrl2, workOrder);
49237
+ }
49238
+ function runClaudeTurn(appUrl2, workOrder) {
49239
+ return spawnRunnerTurn(appUrl2, workOrder, "claude");
49240
+ }
49241
+ function runCodexTurn(appUrl2, workOrder) {
49242
+ return spawnRunnerTurn(appUrl2, workOrder, "codex");
49243
+ }
49244
+ function buildTurnCommand(runtime, prompt, configPath) {
49245
+ if (runtime === "codex") {
49246
+ return {
49247
+ command: "codex",
49248
+ args: ["exec", prompt, "--mcp-config", configPath, "--sandbox", "read-only", "--skip-git-repo-check"]
49249
+ };
49250
+ }
49251
+ return {
49252
+ command: "claude",
49253
+ args: ["-p", prompt, "--mcp-config", configPath, "--strict-mcp-config", "--output-format", "text"]
49254
+ };
49255
+ }
49256
+ async function spawnRunnerTurn(appUrl2, workOrder, runtime) {
48886
49257
  const mcp = typeof workOrder.mcp === "object" && workOrder.mcp !== null ? workOrder.mcp : {};
48887
49258
  const token = typeof mcp.token === "string" ? mcp.token : "";
48888
49259
  const url2 = typeof mcp.url === "string" ? `${appUrl2.replace(/\/+$/, "")}${mcp.url}` : `${appUrl2.replace(/\/+$/, "")}/mcp`;
@@ -48903,6 +49274,7 @@ async function runClaudeTurn(appUrl2, workOrder) {
48903
49274
  await writeFile2(configPath, `${mcpConfig}
48904
49275
  `, { mode: 384 });
48905
49276
  await chmod(configPath, 384);
49277
+ const { command, args } = buildTurnCommand(runtime, prompt, configPath);
48906
49278
  return new Promise((resolve2) => {
48907
49279
  let settled = false;
48908
49280
  const finish = (result) => {
@@ -48913,7 +49285,7 @@ async function runClaudeTurn(appUrl2, workOrder) {
48913
49285
  void rm2(configPath, { force: true });
48914
49286
  resolve2(result);
48915
49287
  };
48916
- const child = spawn("claude", ["-p", prompt, "--mcp-config", configPath, "--strict-mcp-config", "--output-format", "text"], {
49288
+ const child = spawn(command, args, {
48917
49289
  cwd: tmpdir(),
48918
49290
  stdio: ["ignore", "pipe", "pipe"]
48919
49291
  });
@@ -48940,7 +49312,431 @@ async function runClaudeTurn(appUrl2, workOrder) {
48940
49312
  });
48941
49313
  }
48942
49314
  function usage() {
48943
- return "Usage: rost runner serve [--name <text>] [--state-file <path>] [--heartbeat-ms <n>] [--user-code <code>] [--once] [--execute]";
49315
+ return "Usage: rost runner serve [--name <text>] [--state-file <path>] [--heartbeat-ms <n>] [--user-code <code>] [--runtime auto|claude|codex] [--once] [--execute]";
49316
+ }
49317
+
49318
+ // src/runner-service.ts
49319
+ import { execFile as execFileCallback4 } from "node:child_process";
49320
+ import { realpathSync } from "node:fs";
49321
+ import { access as access2, mkdir as mkdir3, readFile as readFile5, rm as rm3, writeFile as writeFile3 } from "node:fs/promises";
49322
+ import { homedir as homedir2 } from "node:os";
49323
+ import path4 from "node:path";
49324
+ import { promisify as promisify4 } from "node:util";
49325
+ var execFile4 = promisify4(execFileCallback4);
49326
+ var SERVICE_VERBS = ["install-service", "start", "stop", "restart", "status", "logs", "uninstall"];
49327
+ function isServiceVerb(value) {
49328
+ return value !== void 0 && SERVICE_VERBS.includes(value);
49329
+ }
49330
+ function isRunnerServiceInvocation(args) {
49331
+ const verb = args[0];
49332
+ if (verb === "status") {
49333
+ return args.includes("--name");
49334
+ }
49335
+ return isServiceVerb(verb);
49336
+ }
49337
+ function runnerServicePaths(homeDir, runnerName) {
49338
+ const safeName = safeRunnerName(runnerName);
49339
+ return {
49340
+ launchAgentPath: path4.join(homeDir, "Library", "LaunchAgents", `${serviceLabel(safeName)}.plist`),
49341
+ stateFile: path4.join(homeDir, "Library", "Application Support", cliBrand.name, `runner-${safeName}.json`),
49342
+ logDir: path4.join(homeDir, "Library", "Logs", cliBrand.name, "runner", safeName)
49343
+ };
49344
+ }
49345
+ function renderLaunchdPlist(config2) {
49346
+ const programArgs = [
49347
+ config2.binPath,
49348
+ "runner",
49349
+ "serve",
49350
+ ...config2.execute ? ["--execute"] : [],
49351
+ "--name",
49352
+ config2.runnerName,
49353
+ "--state-file",
49354
+ config2.stateFile
49355
+ ];
49356
+ const programArgsXml = programArgs.map((arg) => ` <string>${escapePlist(arg)}</string>`).join("\n");
49357
+ return `<?xml version="1.0" encoding="UTF-8"?>
49358
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
49359
+ <plist version="1.0">
49360
+ <dict>
49361
+ <key>Label</key>
49362
+ <string>${escapePlist(serviceLabel(config2.runnerName))}</string>
49363
+ <key>ProgramArguments</key>
49364
+ <array>
49365
+ ${programArgsXml}
49366
+ </array>
49367
+ <key>EnvironmentVariables</key>
49368
+ <dict>
49369
+ <key>${cliBrand.envPrefix}_APP_URL</key>
49370
+ <string>${escapePlist(config2.appUrl)}</string>
49371
+ </dict>
49372
+ <key>KeepAlive</key>
49373
+ <true/>
49374
+ <key>RunAtLoad</key>
49375
+ <true/>
49376
+ <key>StandardOutPath</key>
49377
+ <string>${escapePlist(path4.join(config2.logDir, "stdout.log"))}</string>
49378
+ <key>StandardErrorPath</key>
49379
+ <string>${escapePlist(path4.join(config2.logDir, "stderr.log"))}</string>
49380
+ </dict>
49381
+ </plist>
49382
+ `;
49383
+ }
49384
+ async function runRunnerService(options) {
49385
+ const verb = options.args[0];
49386
+ if (!isServiceVerb(verb)) {
49387
+ options.io.stderr.write(`Unknown runner service command: ${verb ?? ""}
49388
+ ${serviceUsage()}
49389
+ `);
49390
+ return 1;
49391
+ }
49392
+ if (options.args.includes("--help") || options.args.includes("-h")) {
49393
+ options.io.stdout.write(`${serviceUsage()}
49394
+ `);
49395
+ return 0;
49396
+ }
49397
+ let parsed;
49398
+ try {
49399
+ parsed = parseServiceArgs(options.args.slice(1));
49400
+ } catch (error51) {
49401
+ options.io.stderr.write(`${error51 instanceof Error ? error51.message : String(error51)}
49402
+ ${serviceUsage()}
49403
+ `);
49404
+ return 1;
49405
+ }
49406
+ const cmd = {
49407
+ io: options.io,
49408
+ runnerName: parsed.runnerName,
49409
+ appUrl: options.appUrl,
49410
+ execute: parsed.execute,
49411
+ binPath: options.binPath ?? resolveBinPath(),
49412
+ homeDir: options.homeDir ?? homedir2(),
49413
+ platform: options.platform ?? process.platform,
49414
+ exec: options.exec ?? defaultExec,
49415
+ uid: options.uid ?? (process.getuid ? process.getuid() : 0)
49416
+ };
49417
+ switch (verb) {
49418
+ case "install-service":
49419
+ return installService(cmd);
49420
+ case "start":
49421
+ return startService(cmd);
49422
+ case "stop":
49423
+ return stopService(cmd);
49424
+ case "restart":
49425
+ return restartService(cmd);
49426
+ case "status":
49427
+ return statusService(cmd);
49428
+ case "logs":
49429
+ return logsService(cmd);
49430
+ case "uninstall":
49431
+ return uninstallService(cmd);
49432
+ default:
49433
+ return assertNever2(verb);
49434
+ }
49435
+ }
49436
+ async function installService(options) {
49437
+ if (options.platform !== "darwin") {
49438
+ return unsupportedPlatform(options.io, options.platform);
49439
+ }
49440
+ const safeName = safeRunnerName(options.runnerName);
49441
+ const paths = runnerServicePaths(options.homeDir, safeName);
49442
+ const label = serviceLabel(safeName);
49443
+ await mkdir3(paths.logDir, { recursive: true });
49444
+ await mkdir3(path4.dirname(paths.stateFile), { recursive: true });
49445
+ await mkdir3(path4.dirname(paths.launchAgentPath), { recursive: true });
49446
+ const plist = renderLaunchdPlist({
49447
+ binPath: options.binPath,
49448
+ runnerName: safeName,
49449
+ stateFile: paths.stateFile,
49450
+ logDir: paths.logDir,
49451
+ appUrl: options.appUrl,
49452
+ execute: options.execute
49453
+ });
49454
+ await writeFile3(paths.launchAgentPath, plist, { mode: 420 });
49455
+ await bootoutQuietly(options.exec, options.uid, paths.launchAgentPath);
49456
+ try {
49457
+ await options.exec("launchctl", ["bootstrap", domainTarget(options.uid), paths.launchAgentPath]);
49458
+ } catch (error51) {
49459
+ options.io.stderr.write(
49460
+ `Failed to load the runner service: ${redactForLog(error51 instanceof Error ? error51.message : String(error51))}
49461
+ `
49462
+ );
49463
+ return 1;
49464
+ }
49465
+ options.io.stdout.write(
49466
+ `${[
49467
+ `Installed runner service ${label}.`,
49468
+ `plist: ${paths.launchAgentPath}`,
49469
+ `state file: ${paths.stateFile}`,
49470
+ `logs: ${paths.logDir}`,
49471
+ `execute: ${options.execute ? "on (claims and runs work orders)" : "off (heartbeat only)"}`,
49472
+ `The runner pairs on first run; its bearer secret is stored locally and never printed.`
49473
+ ].join("\n")}
49474
+ `
49475
+ );
49476
+ return 0;
49477
+ }
49478
+ async function startService(options) {
49479
+ if (options.platform !== "darwin") {
49480
+ return unsupportedPlatform(options.io, options.platform);
49481
+ }
49482
+ const safeName = safeRunnerName(options.runnerName);
49483
+ const paths = runnerServicePaths(options.homeDir, safeName);
49484
+ const label = serviceLabel(safeName);
49485
+ if (!await fileExists(paths.launchAgentPath)) {
49486
+ options.io.stderr.write(
49487
+ `No runner service found for "${safeName}". Run \`${cliBrand.binName} runner install-service --name ${safeName}\` first.
49488
+ `
49489
+ );
49490
+ return 1;
49491
+ }
49492
+ try {
49493
+ await options.exec("launchctl", ["bootstrap", domainTarget(options.uid), paths.launchAgentPath]);
49494
+ options.io.stdout.write(`Started runner service ${label}.
49495
+ `);
49496
+ return 0;
49497
+ } catch (bootstrapError) {
49498
+ try {
49499
+ await options.exec("launchctl", ["kickstart", "-k", serviceTarget(options.uid, label)]);
49500
+ options.io.stdout.write(`Runner service ${label} was already loaded; restarted it.
49501
+ `);
49502
+ return 0;
49503
+ } catch (kickstartError) {
49504
+ options.io.stderr.write(
49505
+ `Failed to start runner service ${label}: ${redactForLog(kickstartError instanceof Error ? kickstartError.message : String(kickstartError))}
49506
+ `
49507
+ );
49508
+ return 1;
49509
+ }
49510
+ }
49511
+ }
49512
+ async function stopService(options) {
49513
+ if (options.platform !== "darwin") {
49514
+ return unsupportedPlatform(options.io, options.platform);
49515
+ }
49516
+ const safeName = safeRunnerName(options.runnerName);
49517
+ const paths = runnerServicePaths(options.homeDir, safeName);
49518
+ const label = serviceLabel(safeName);
49519
+ try {
49520
+ await options.exec("launchctl", ["bootout", domainTarget(options.uid), paths.launchAgentPath]);
49521
+ options.io.stdout.write(`Stopped runner service ${label}.
49522
+ `);
49523
+ } catch {
49524
+ options.io.stdout.write(`Runner service ${label} was not running.
49525
+ `);
49526
+ }
49527
+ return 0;
49528
+ }
49529
+ async function restartService(options) {
49530
+ if (options.platform !== "darwin") {
49531
+ return unsupportedPlatform(options.io, options.platform);
49532
+ }
49533
+ const safeName = safeRunnerName(options.runnerName);
49534
+ const label = serviceLabel(safeName);
49535
+ try {
49536
+ await options.exec("launchctl", ["kickstart", "-k", serviceTarget(options.uid, label)]);
49537
+ options.io.stdout.write(`Restarted runner service ${label}.
49538
+ `);
49539
+ return 0;
49540
+ } catch (error51) {
49541
+ options.io.stderr.write(
49542
+ `Failed to restart runner service ${label}: ${redactForLog(error51 instanceof Error ? error51.message : String(error51))}. Is it installed? Run \`${cliBrand.binName} runner install-service --name ${safeName}\`.
49543
+ `
49544
+ );
49545
+ return 1;
49546
+ }
49547
+ }
49548
+ async function statusService(options) {
49549
+ if (options.platform !== "darwin") {
49550
+ return unsupportedPlatform(options.io, options.platform);
49551
+ }
49552
+ const safeName = safeRunnerName(options.runnerName);
49553
+ const paths = runnerServicePaths(options.homeDir, safeName);
49554
+ const label = serviceLabel(safeName);
49555
+ const installed = await fileExists(paths.launchAgentPath);
49556
+ try {
49557
+ const { stdout } = await options.exec("launchctl", ["print", serviceTarget(options.uid, label)]);
49558
+ const state = extractLaunchdState(stdout);
49559
+ options.io.stdout.write(
49560
+ `${[
49561
+ `runner service: ${label}`,
49562
+ `installed: ${installed ? "yes" : "no"}`,
49563
+ `loaded: yes`,
49564
+ `state: ${state ?? "unknown"}`,
49565
+ `plist: ${paths.launchAgentPath}`,
49566
+ `logs: ${paths.logDir}`
49567
+ ].join("\n")}
49568
+ `
49569
+ );
49570
+ return 0;
49571
+ } catch {
49572
+ options.io.stdout.write(
49573
+ `${[
49574
+ `runner service: ${label}`,
49575
+ `installed: ${installed ? "yes" : "no"}`,
49576
+ `loaded: no`,
49577
+ `plist: ${paths.launchAgentPath}`,
49578
+ `logs: ${paths.logDir}`
49579
+ ].join("\n")}
49580
+ `
49581
+ );
49582
+ return 1;
49583
+ }
49584
+ }
49585
+ async function logsService(options) {
49586
+ if (options.platform !== "darwin") {
49587
+ return unsupportedPlatform(options.io, options.platform);
49588
+ }
49589
+ const safeName = safeRunnerName(options.runnerName);
49590
+ const paths = runnerServicePaths(options.homeDir, safeName);
49591
+ const label = serviceLabel(safeName);
49592
+ const stdoutLog = path4.join(paths.logDir, "stdout.log");
49593
+ const stderrLog = path4.join(paths.logDir, "stderr.log");
49594
+ options.io.stdout.write(
49595
+ `${[
49596
+ `runner service logs for ${label}:`,
49597
+ ` stdout: ${stdoutLog}`,
49598
+ ` stderr: ${stderrLog}`,
49599
+ `Follow live with: tail -f ${JSON.stringify(stdoutLog)} ${JSON.stringify(stderrLog)}`
49600
+ ].join("\n")}
49601
+ `
49602
+ );
49603
+ for (const [name, file2] of [["stdout", stdoutLog], ["stderr", stderrLog]]) {
49604
+ const tail = await readTail(file2, 4e3);
49605
+ if (tail !== null && tail.trim().length > 0) {
49606
+ options.io.stdout.write(`
49607
+ ----- ${name} (tail) -----
49608
+ ${redactForLog(tail)}
49609
+ `);
49610
+ }
49611
+ }
49612
+ return 0;
49613
+ }
49614
+ async function uninstallService(options) {
49615
+ if (options.platform !== "darwin") {
49616
+ return unsupportedPlatform(options.io, options.platform);
49617
+ }
49618
+ const safeName = safeRunnerName(options.runnerName);
49619
+ const paths = runnerServicePaths(options.homeDir, safeName);
49620
+ const label = serviceLabel(safeName);
49621
+ await bootoutQuietly(options.exec, options.uid, paths.launchAgentPath);
49622
+ let removed = false;
49623
+ try {
49624
+ await rm3(paths.launchAgentPath, { force: true });
49625
+ removed = true;
49626
+ } catch (error51) {
49627
+ options.io.stderr.write(
49628
+ `Failed to remove ${paths.launchAgentPath}: ${redactForLog(error51 instanceof Error ? error51.message : String(error51))}
49629
+ `
49630
+ );
49631
+ }
49632
+ options.io.stdout.write(
49633
+ `${[
49634
+ `Uninstalled runner service ${label}.`,
49635
+ removed ? `Removed ${paths.launchAgentPath}.` : `Could not remove ${paths.launchAgentPath}; remove it manually.`,
49636
+ `Left the runner state file at ${paths.stateFile} (it holds the local bearer secret).`,
49637
+ `Delete that file to forget this runner locally, and run \`${cliBrand.binName} runner revoke <runner-id>\` to revoke it in ${cliBrand.name}.`
49638
+ ].join("\n")}
49639
+ `
49640
+ );
49641
+ return removed ? 0 : 1;
49642
+ }
49643
+ function parseServiceArgs(args) {
49644
+ let runnerName;
49645
+ let execute2 = false;
49646
+ for (let index = 0; index < args.length; index += 1) {
49647
+ const token = args[index];
49648
+ if (token === "--name") {
49649
+ const value = args[index + 1];
49650
+ if (value === void 0 || value.startsWith("--")) {
49651
+ throw new Error("--name requires a value.");
49652
+ }
49653
+ runnerName = value;
49654
+ index += 1;
49655
+ } else if (token === "--execute") {
49656
+ execute2 = true;
49657
+ } else {
49658
+ throw new Error(`Unexpected runner service argument: ${token ?? ""}`);
49659
+ }
49660
+ }
49661
+ if (runnerName === void 0 || runnerName.trim().length === 0) {
49662
+ throw new Error("--name <runner-name> is required.");
49663
+ }
49664
+ return { runnerName, execute: execute2 };
49665
+ }
49666
+ function serviceUsage() {
49667
+ const bin = cliBrand.binName;
49668
+ return [
49669
+ `Usage:`,
49670
+ ` ${bin} runner install-service --name <name> [--execute]`,
49671
+ ` ${bin} runner start|stop|restart|status|logs|uninstall --name <name>`
49672
+ ].join("\n");
49673
+ }
49674
+ function safeRunnerName(runnerName) {
49675
+ const safe = runnerName.replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "");
49676
+ return safe.length > 0 ? safe : "runner";
49677
+ }
49678
+ function serviceLabel(safeName) {
49679
+ return `com.${cliBrand.binName}.runner.${safeName}`;
49680
+ }
49681
+ function domainTarget(uid) {
49682
+ return `gui/${uid}`;
49683
+ }
49684
+ function serviceTarget(uid, label) {
49685
+ return `gui/${uid}/${label}`;
49686
+ }
49687
+ async function bootoutQuietly(exec, uid, plistPath) {
49688
+ try {
49689
+ await exec("launchctl", ["bootout", domainTarget(uid), plistPath]);
49690
+ } catch {
49691
+ }
49692
+ }
49693
+ function unsupportedPlatform(io, platform) {
49694
+ io.stderr.write(
49695
+ `Service install is not supported on ${platform} yet. Run \`${cliBrand.binName} runner serve --execute\` under a process manager (for example systemd, pm2, or nssm) to keep a runner online.
49696
+ `
49697
+ );
49698
+ return 1;
49699
+ }
49700
+ function resolveBinPath() {
49701
+ const entry = process.argv[1];
49702
+ if (entry === void 0 || entry.length === 0) {
49703
+ return cliBrand.binName;
49704
+ }
49705
+ try {
49706
+ return realpathSync(entry);
49707
+ } catch {
49708
+ return entry;
49709
+ }
49710
+ }
49711
+ function extractLaunchdState(printOutput) {
49712
+ const match = printOutput.match(/state\s*=\s*(\S+)/);
49713
+ return match ? match[1] ?? null : null;
49714
+ }
49715
+ async function fileExists(file2) {
49716
+ try {
49717
+ await access2(file2);
49718
+ return true;
49719
+ } catch {
49720
+ return false;
49721
+ }
49722
+ }
49723
+ async function readTail(file2, maxBytes) {
49724
+ try {
49725
+ const content = await readFile5(file2, "utf8");
49726
+ return content.length > maxBytes ? content.slice(content.length - maxBytes) : content;
49727
+ } catch {
49728
+ return null;
49729
+ }
49730
+ }
49731
+ function escapePlist(value) {
49732
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
49733
+ }
49734
+ var defaultExec = async (file2, args) => {
49735
+ const result = await execFile4(file2, args);
49736
+ return { stdout: String(result.stdout), stderr: String(result.stderr) };
49737
+ };
49738
+ function assertNever2(value) {
49739
+ throw new Error(`Unhandled runner service verb: ${String(value)}`);
48944
49740
  }
48945
49741
 
48946
49742
  // src/index.ts
@@ -49090,6 +49886,9 @@ async function main(argv = process.argv.slice(2), options = {}) {
49090
49886
  env: process.env
49091
49887
  });
49092
49888
  }
49889
+ if (command === "runner" && isRunnerServiceInvocation(args)) {
49890
+ return runRunnerService({ io, args, appUrl: config2.appUrl });
49891
+ }
49093
49892
  if (!session) {
49094
49893
  io.stderr.write(`Not logged in. Run ${cliBrand.binName} login first.
49095
49894
  `);
@@ -49567,7 +50366,7 @@ function isCliEntrypoint(moduleUrl, argvPath) {
49567
50366
  return false;
49568
50367
  }
49569
50368
  try {
49570
- return realpathSync(fileURLToPath3(moduleUrl)) === realpathSync(argvPath);
50369
+ return realpathSync2(fileURLToPath3(moduleUrl)) === realpathSync2(argvPath);
49571
50370
  } catch {
49572
50371
  return moduleUrl === pathToFileURL(argvPath).href;
49573
50372
  }