@rosthq/cli 0.6.8 → 0.7.1

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,82 @@ 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.5",
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
+ During onboarding, the deterministic harness may execute only explicit low-risk draft actions. In the Responsibility Graph step, a user with setup/action authority can ask AICOS to create one named draft seat per turn; the seat stays vacant, is traceable to the AICOS message through artifact sources, and still needs human review before staffing, Charters, manifests, or go-live. Compass and Charter help remains draft guidance and clarifying questions until the live drafting planner lands. AICOS never silently finishes onboarding, approves Compass or Charter content, signs manifests, goes live, stores credentials, changes autonomy policy, or invites external people; those actions remain human-confirmed.
41929
+
41930
+ AICOS posture is resolved on the server from the current user's AICOS access scope plus the tenant's Company guardrails. **Report-only** can read, explain, and draft guidance, but it cannot mutate setup or operating state. **Balanced** can perform narrow low-risk internal draft/setup actions and stops at human gates. **High autonomy** can perform allowed internal low-risk actions without changing the hard prohibitions: AICOS still cannot approve its own Charter or manifest, handle credentials, send externally, spend money, perform destructive/admin changes, go live, expand policy/access, or receive hidden sensitive data.
41931
+
41932
+ AICOS grounds answers from two source classes before producing user-visible setup or operating guidance:
41933
+
41934
+ - **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.
41935
+ - **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.
41936
+
41937
+ 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.
41938
+
41939
+ ## Governance boundary
41940
+
41941
+ AICOS can preserve context and explain what it is ready to do, but durable operating changes still use the same {{brand}} controls:
41942
+
41943
+ - Tenant isolation is enforced by RLS and server-side tenant context.
41944
+ - 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.
41945
+ - Durable decisions remain human decisions.
41946
+ - Action execution must route through command guards and confirmation when required.
41947
+ - Expanding authority, approving Charters or Compass versions, going live, credentials, external sends, money, destructive operations, and policy changes stay human-gated.
41948
+
41949
+ 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
41950
  },
41642
41951
  {
41643
41952
  slug: "responsibility-graph-playbook",
@@ -42412,7 +42721,7 @@ External connectors are being rolled out provider by provider, conservatively (r
42412
42721
  order: 48,
42413
42722
  title: "CLI and MCP installation guide",
42414
42723
  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",
42724
+ version: "2026-07-02.6",
42416
42725
  public: true,
42417
42726
  audiences: ["human", "cli", "mcp", "in_app_agent"],
42418
42727
  stages: ["company_setup", "staffing"],
@@ -42869,6 +43178,7 @@ The signed-in app exposes the same Skill command surface under **Skills** in the
42869
43178
  | \`{{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
43179
  | \`{{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
43180
  | \`{{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\` |
43181
+ | \`{{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
43182
  | \`{{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
43183
  | \`{{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
43184
  | \`{{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 +43771,7 @@ Set \`ROST_SKILL_INSTALL_ROOT\` only for a sandbox or CI override. Local copies
43461
43771
  order: 55,
43462
43772
  title: "Agent reference map",
43463
43773
  summary: "Where CLI sessions, MCP clients, and in-app agents should retrieve {{brand}} guidance before recommending setup changes.",
43464
- version: "2026-07-02.2",
43774
+ version: "2026-07-02.3",
43465
43775
  public: true,
43466
43776
  audiences: ["cli", "mcp", "in_app_agent"],
43467
43777
  stages: ["company_setup", "graph_design", "charter_design", "staffing", "operating_rhythm"],
@@ -43525,28 +43835,29 @@ Agents that run on {{brand}}-managed inference draw against a tenant inference b
43525
43835
 
43526
43836
  1. rost-implementation-method
43527
43837
  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
43838
+ 3. aicos-chat-guide
43839
+ 4. responsibility-graph-playbook
43840
+ 5. charter-design-playbook
43841
+ 6. charter-authoring-deep-dive
43842
+ 7. agent-staffing-playbook
43843
+ 8. add-agents-guide
43844
+ 9. custom-agents-guide
43845
+ 10. agent-builder-guide
43846
+ 11. how-agents-work
43847
+ 12. tool-access-and-vault
43848
+ 13. available-tools-guide
43849
+ 14. mcp-and-cli-guide
43850
+ 15. skill-builder-guide
43851
+ 16. agent-skill-authoring-guide
43852
+ 17. agent-skill-setup-guide
43853
+ 18. ai-model-data-handling-guide
43854
+ 19. cascade-guide
43855
+ 20. signal-guide
43856
+ 21. friction-guide
43857
+ 22. confirmations-guide
43858
+ 23. steward-queue-guide
43859
+ 24. sync-rhythm-playbook
43860
+ 25. billing-and-pricing-guide
43550
43861
 
43551
43862
  ## Workflow to guide map
43552
43863
 
@@ -43554,6 +43865,7 @@ Read the listed guide before recommending or running each workflow. Every workfl
43554
43865
 
43555
43866
  - Connect a local agent (CLI login, MCP token, client registration): mcp-and-cli-guide, then agent-reference-map.
43556
43867
  - Company onboarding (context, Compass draft, finish): rost-implementation-method, then settings-members-and-invites-guide for invites.
43868
+ - 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
43869
  - 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
43870
  - Design the graph (create, rename, reparent, set type): responsibility-graph-playbook.
43559
43871
  - 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 +43889,11 @@ Read the listed guide before recommending or running each workflow. Every workfl
43577
43889
 
43578
43890
  ## Stage map
43579
43891
 
43580
- - Company setup: rost-implementation-method, compass-authoring-guide, settings-guide, settings-members-and-invites-guide
43892
+ - Company setup: rost-implementation-method, compass-authoring-guide, aicos-chat-guide, settings-guide, settings-members-and-invites-guide
43581
43893
  - Graph design: responsibility-graph-playbook
43582
43894
  - Charter design: charter-design-playbook, charter-authoring-deep-dive
43583
43895
  - 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
43896
+ - Operating rhythm: aicos-chat-guide, cascade-guide, signal-guide, friction-guide, confirmations-guide, steward-queue-guide, sync-rhythm-playbook, notifications-guide
43585
43897
  - Local agents and runners: mcp-and-cli-guide, runner-guide, agent-reference-map
43586
43898
  - Security and troubleshooting: ai-model-data-handling-guide, security-model-guide, troubleshooting-guide
43587
43899
 
@@ -44292,7 +44604,7 @@ Every notification should include the seat, cause, evidence, and requested decis
44292
44604
  order: 75,
44293
44605
  title: "Local runner guide",
44294
44606
  summary: "How local agent sessions and runner surfaces should operate through {{brand}} without bypassing Charters or audit.",
44295
- version: "2026-06-30.1",
44607
+ version: "2026-07-02.2",
44296
44608
  public: true,
44297
44609
  audiences: ["human", "cli", "mcp", "in_app_agent"],
44298
44610
  stages: ["staffing", "operating_rhythm"],
@@ -44318,6 +44630,26 @@ The local runner is for human-controlled local agent work. It should retrieve {{
44318
44630
  5. Record work, Friction, handoffs, and escalations.
44319
44631
  6. Revoke access when the session ends.
44320
44632
 
44633
+ ## Install a local Runner
44634
+
44635
+ 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.
44636
+
44637
+ Recommended paths:
44638
+
44639
+ - **Always-on host:** \`{{cli}} runner install-service --name <runner-name> --execute\` installs a background service (macOS launchd user agent, \`user/<uid>\` domain) that keeps the runner online. Manage it with \`{{cli}} runner start|stop|restart|status|logs|uninstall --name <runner-name>\`. On macOS this loads reliably when the runner user is logged in at the console; on a purely headless SSH box where that user is not at the console, run \`{{cli}} runner serve\` under a process supervisor instead.
44640
+ - **Manual smoke:** \`{{cli}} runner serve --once\` heartbeats once and exits; \`{{cli}} runner serve --execute\` claims and executes runner work orders while it runs.
44641
+
44642
+ Two ways to authenticate and pair \u2014 both first-class, an agent can drive either entirely from the terminal:
44643
+
44644
+ - **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.
44645
+ - **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.
44646
+
44647
+ 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.
44648
+
44649
+ 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\`.
44650
+
44651
+ 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.
44652
+
44321
44653
  ## Inspect and control runners from CLI or MCP
44322
44654
 
44323
44655
  - 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 +48287,9 @@ function operationUsageLines(bin) {
47955
48287
  `${bin} error list|resolve|supersede`,
47956
48288
  `${bin} sync brief|compile|complete`,
47957
48289
  `${bin} runner list|status|diagnose|repair|work-orders|revoke|serve`,
48290
+ // Local runner service lifecycle (DER-1142). The orchestrator mirrors this
48291
+ // exact line into the mcp-and-cli-guide so the help/guide parity gate passes.
48292
+ `${bin} runner install-service|start|stop|restart|status|logs|uninstall --name <name>`,
47958
48293
  `${bin} notification settings|test|errors`,
47959
48294
  `${bin} settings get|update|product-learning|agent-policy|rename`,
47960
48295
  `${bin} member invite|update|remove`,
@@ -48529,8 +48864,8 @@ function pendingConfirmationFromError(error51) {
48529
48864
  import { execFile as execFileCallback2 } from "node:child_process";
48530
48865
  import { access, readFile as readFile3 } from "node:fs/promises";
48531
48866
  import { delimiter } from "node:path";
48532
- import { promisify as promisify3 } from "node:util";
48533
- var execFile3 = promisify3(execFileCallback2);
48867
+ import { promisify as promisify2 } from "node:util";
48868
+ var execFile2 = promisify2(execFileCallback2);
48534
48869
  async function runDoctor(options) {
48535
48870
  const env = options.env ?? process.env;
48536
48871
  const store = options.store ?? createTokenStore();
@@ -48578,7 +48913,7 @@ async function checkPath(env) {
48578
48913
  }
48579
48914
  async function probeCli(command) {
48580
48915
  try {
48581
- const { stdout, stderr } = await execFile3(command, ["--version"], { timeout: 1500 });
48916
+ const { stdout, stderr } = await execFile2(command, ["--version"], { timeout: 1500 });
48582
48917
  const version4 = (stdout || stderr).trim().split("\n")[0]?.trim();
48583
48918
  return version4 ? `installed (${version4})` : "installed";
48584
48919
  } catch {
@@ -48653,8 +48988,8 @@ import { chmod, mkdir as mkdir2, readFile as readFile4, rm as rm2, writeFile as
48653
48988
  import { hostname as hostname3, tmpdir } from "node:os";
48654
48989
  import path3 from "node:path";
48655
48990
  import { setTimeout as sleep2 } from "node:timers/promises";
48656
- import { promisify as promisify4 } from "node:util";
48657
- var execFile4 = promisify4(execFileCallback3);
48991
+ import { promisify as promisify3 } from "node:util";
48992
+ var execFile3 = promisify3(execFileCallback3);
48658
48993
  async function runRunnerServe(options) {
48659
48994
  const env = options.env ?? process.env;
48660
48995
  const fetchImpl = options.fetchImpl ?? fetch;
@@ -48693,8 +49028,11 @@ ${usage()}
48693
49028
  options.io.stdout.write(`heartbeat ok #${beat} runner_id=${state.runner_id} claude=${capabilities.claude.installed} codex=${capabilities.codex.installed}
48694
49029
  `);
48695
49030
  }
48696
- if (config2.execute && capabilities.claude.installed) {
48697
- await claimAndExecute(fetchImpl, options.appUrl, state, options.io);
49031
+ if (config2.execute) {
49032
+ const localRuntime = effectiveExecutionRuntime(config2.runtime, capabilities);
49033
+ if (localRuntime) {
49034
+ await claimAndExecute(fetchImpl, options.appUrl, state, options.io, localRuntime);
49035
+ }
48698
49036
  }
48699
49037
  } catch (error51) {
48700
49038
  failed = true;
@@ -48714,6 +49052,7 @@ function parseServeArgs(args, appUrl2, env) {
48714
49052
  let heartbeatMs = Number(env.RUNNER_HEARTBEAT_MS ?? 3e4);
48715
49053
  let execute2 = env.RUNNER_EXECUTE === "1";
48716
49054
  let once = false;
49055
+ let runtime = parseRuntime(env.RUNNER_RUNTIME ?? "auto");
48717
49056
  let userCode = env.RUNNER_USER_CODE;
48718
49057
  for (let index = 0; index < args.length; index += 1) {
48719
49058
  const token = args[index];
@@ -48729,6 +49068,9 @@ function parseServeArgs(args, appUrl2, env) {
48729
49068
  } else if (token === "--user-code") {
48730
49069
  userCode = requiredValue(args, index);
48731
49070
  index += 1;
49071
+ } else if (token === "--runtime") {
49072
+ runtime = parseRuntime(requiredValue(args, index));
49073
+ index += 1;
48732
49074
  } else if (token === "--execute") {
48733
49075
  execute2 = true;
48734
49076
  } else if (token === "--once") {
@@ -48740,7 +49082,19 @@ function parseServeArgs(args, appUrl2, env) {
48740
49082
  if (!Number.isSafeInteger(heartbeatMs) || heartbeatMs < 1e3) {
48741
49083
  throw new Error("--heartbeat-ms must be an integer >= 1000.");
48742
49084
  }
48743
- return { name, stateFile, heartbeatMs, execute: execute2, once, ...userCode ? { userCode } : {} };
49085
+ return { name, stateFile, heartbeatMs, execute: execute2, once, runtime, ...userCode ? { userCode } : {} };
49086
+ }
49087
+ function parseRuntime(value) {
49088
+ if (value === "auto" || value === "claude" || value === "codex") {
49089
+ return value;
49090
+ }
49091
+ throw new Error(`--runtime must be one of auto, claude, codex (got ${value}).`);
49092
+ }
49093
+ function effectiveExecutionRuntime(runtime, capabilities) {
49094
+ if (runtime === "codex") {
49095
+ return capabilities.codex.installed ? "codex" : null;
49096
+ }
49097
+ return capabilities.claude.installed ? "claude" : null;
48744
49098
  }
48745
49099
  function requiredValue(args, index) {
48746
49100
  const value = args[index + 1];
@@ -48772,10 +49126,16 @@ async function pairIfNeeded(client, fetchImpl, appUrl2, config2, io) {
48772
49126
  `);
48773
49127
  return existing;
48774
49128
  }
48775
- if (!client) {
48776
- throw new Error(`No paired runner state at ${config2.stateFile}. Run with a CLI login once to pair this runner before starting without a CLI session.`);
49129
+ let userCode;
49130
+ if (config2.userCode !== void 0) {
49131
+ userCode = config2.userCode;
49132
+ } else if (client) {
49133
+ userCode = await startPairing(client, config2);
49134
+ } else {
49135
+ throw new Error(
49136
+ `No paired runner state at ${config2.stateFile}. Provide --user-code <code> from Settings > Runners (Add a runner), or run \`${cliBrand.binName} login --device\` once to pair this runner.`
49137
+ );
48777
49138
  }
48778
- const userCode = config2.userCode ?? await startPairing(client, config2);
48779
49139
  const claim = await post2(fetchImpl, appUrl2, "/api/runner/pairing/claim", { user_code: userCode });
48780
49140
  const runnerSecret = claim.json.runner_secret;
48781
49141
  const runnerId = claim.json.runner_id;
@@ -48830,7 +49190,7 @@ async function detectCapabilities() {
48830
49190
  }
48831
49191
  async function probe(command) {
48832
49192
  try {
48833
- const { stdout, stderr } = await execFile4(command, ["--version"], { timeout: 1500 });
49193
+ const { stdout, stderr } = await execFile3(command, ["--version"], { timeout: 1500 });
48834
49194
  return { installed: true, version: (stdout || stderr).trim() || null, auth_state: "ready" };
48835
49195
  } catch {
48836
49196
  return { installed: false, version: null, auth_state: "unknown" };
@@ -48855,7 +49215,7 @@ async function post2(fetchImpl, appUrl2, pathName, body, token) {
48855
49215
  }
48856
49216
  return { status: response.status, json: json2 };
48857
49217
  }
48858
- async function claimAndExecute(fetchImpl, appUrl2, state, io) {
49218
+ async function claimAndExecute(fetchImpl, appUrl2, state, io, runtime) {
48859
49219
  const claimed = await post2(fetchImpl, appUrl2, "/api/runner/work-orders/claim", {}, state.runner_secret);
48860
49220
  const workOrder = claimed.json.work_order;
48861
49221
  if (claimed.status !== 200 || !workOrder || typeof workOrder !== "object") {
@@ -48869,12 +49229,12 @@ async function claimAndExecute(fetchImpl, appUrl2, state, io) {
48869
49229
  if (started.status !== 200) {
48870
49230
  throw new Error(`work order start failed ${started.status}: ${redactForLog(JSON.stringify(started.json))}`);
48871
49231
  }
48872
- const result = await runClaudeTurn(appUrl2, workOrder);
49232
+ const result = await runLocalTurn(appUrl2, workOrder, runtime);
48873
49233
  const reported = await post2(fetchImpl, appUrl2, `/api/runner/work-orders/${encodeURIComponent(workOrderId)}/result`, {
48874
49234
  status: result.ok ? "succeeded" : "failed",
48875
49235
  summary: result.summary,
48876
49236
  log_ref: `runner-local:${workOrderId}`,
48877
- model: "claude-cli"
49237
+ model: runtime === "codex" ? "codex-cli" : "claude-cli"
48878
49238
  }, state.runner_secret);
48879
49239
  if (reported.status !== 200) {
48880
49240
  throw new Error(`work order result failed ${reported.status}: ${redactForLog(JSON.stringify(reported.json))}`);
@@ -48882,7 +49242,28 @@ async function claimAndExecute(fetchImpl, appUrl2, state, io) {
48882
49242
  io.stdout.write(`work_order_id=${workOrderId} status=${result.ok ? "succeeded" : "failed"}
48883
49243
  `);
48884
49244
  }
48885
- async function runClaudeTurn(appUrl2, workOrder) {
49245
+ function runLocalTurn(appUrl2, workOrder, runtime) {
49246
+ return runtime === "codex" ? runCodexTurn(appUrl2, workOrder) : runClaudeTurn(appUrl2, workOrder);
49247
+ }
49248
+ function runClaudeTurn(appUrl2, workOrder) {
49249
+ return spawnRunnerTurn(appUrl2, workOrder, "claude");
49250
+ }
49251
+ function runCodexTurn(appUrl2, workOrder) {
49252
+ return spawnRunnerTurn(appUrl2, workOrder, "codex");
49253
+ }
49254
+ function buildTurnCommand(runtime, prompt, configPath) {
49255
+ if (runtime === "codex") {
49256
+ return {
49257
+ command: "codex",
49258
+ args: ["exec", prompt, "--mcp-config", configPath, "--sandbox", "read-only", "--skip-git-repo-check"]
49259
+ };
49260
+ }
49261
+ return {
49262
+ command: "claude",
49263
+ args: ["-p", prompt, "--mcp-config", configPath, "--strict-mcp-config", "--output-format", "text"]
49264
+ };
49265
+ }
49266
+ async function spawnRunnerTurn(appUrl2, workOrder, runtime) {
48886
49267
  const mcp = typeof workOrder.mcp === "object" && workOrder.mcp !== null ? workOrder.mcp : {};
48887
49268
  const token = typeof mcp.token === "string" ? mcp.token : "";
48888
49269
  const url2 = typeof mcp.url === "string" ? `${appUrl2.replace(/\/+$/, "")}${mcp.url}` : `${appUrl2.replace(/\/+$/, "")}/mcp`;
@@ -48903,6 +49284,7 @@ async function runClaudeTurn(appUrl2, workOrder) {
48903
49284
  await writeFile2(configPath, `${mcpConfig}
48904
49285
  `, { mode: 384 });
48905
49286
  await chmod(configPath, 384);
49287
+ const { command, args } = buildTurnCommand(runtime, prompt, configPath);
48906
49288
  return new Promise((resolve2) => {
48907
49289
  let settled = false;
48908
49290
  const finish = (result) => {
@@ -48913,7 +49295,7 @@ async function runClaudeTurn(appUrl2, workOrder) {
48913
49295
  void rm2(configPath, { force: true });
48914
49296
  resolve2(result);
48915
49297
  };
48916
- const child = spawn("claude", ["-p", prompt, "--mcp-config", configPath, "--strict-mcp-config", "--output-format", "text"], {
49298
+ const child = spawn(command, args, {
48917
49299
  cwd: tmpdir(),
48918
49300
  stdio: ["ignore", "pipe", "pipe"]
48919
49301
  });
@@ -48940,7 +49322,431 @@ async function runClaudeTurn(appUrl2, workOrder) {
48940
49322
  });
48941
49323
  }
48942
49324
  function usage() {
48943
- return "Usage: rost runner serve [--name <text>] [--state-file <path>] [--heartbeat-ms <n>] [--user-code <code>] [--once] [--execute]";
49325
+ return "Usage: rost runner serve [--name <text>] [--state-file <path>] [--heartbeat-ms <n>] [--user-code <code>] [--runtime auto|claude|codex] [--once] [--execute]";
49326
+ }
49327
+
49328
+ // src/runner-service.ts
49329
+ import { execFile as execFileCallback4 } from "node:child_process";
49330
+ import { realpathSync } from "node:fs";
49331
+ import { access as access2, mkdir as mkdir3, readFile as readFile5, rm as rm3, writeFile as writeFile3 } from "node:fs/promises";
49332
+ import { homedir as homedir2 } from "node:os";
49333
+ import path4 from "node:path";
49334
+ import { promisify as promisify4 } from "node:util";
49335
+ var execFile4 = promisify4(execFileCallback4);
49336
+ var SERVICE_VERBS = ["install-service", "start", "stop", "restart", "status", "logs", "uninstall"];
49337
+ function isServiceVerb(value) {
49338
+ return value !== void 0 && SERVICE_VERBS.includes(value);
49339
+ }
49340
+ function isRunnerServiceInvocation(args) {
49341
+ const verb = args[0];
49342
+ if (verb === "status") {
49343
+ return args.includes("--name");
49344
+ }
49345
+ return isServiceVerb(verb);
49346
+ }
49347
+ function runnerServicePaths(homeDir, runnerName) {
49348
+ const safeName = safeRunnerName(runnerName);
49349
+ return {
49350
+ launchAgentPath: path4.join(homeDir, "Library", "LaunchAgents", `${serviceLabel(safeName)}.plist`),
49351
+ stateFile: path4.join(homeDir, "Library", "Application Support", cliBrand.name, `runner-${safeName}.json`),
49352
+ logDir: path4.join(homeDir, "Library", "Logs", cliBrand.name, "runner", safeName)
49353
+ };
49354
+ }
49355
+ function renderLaunchdPlist(config2) {
49356
+ const programArgs = [
49357
+ config2.binPath,
49358
+ "runner",
49359
+ "serve",
49360
+ ...config2.execute ? ["--execute"] : [],
49361
+ "--name",
49362
+ config2.runnerName,
49363
+ "--state-file",
49364
+ config2.stateFile
49365
+ ];
49366
+ const programArgsXml = programArgs.map((arg) => ` <string>${escapePlist(arg)}</string>`).join("\n");
49367
+ return `<?xml version="1.0" encoding="UTF-8"?>
49368
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
49369
+ <plist version="1.0">
49370
+ <dict>
49371
+ <key>Label</key>
49372
+ <string>${escapePlist(serviceLabel(config2.runnerName))}</string>
49373
+ <key>ProgramArguments</key>
49374
+ <array>
49375
+ ${programArgsXml}
49376
+ </array>
49377
+ <key>EnvironmentVariables</key>
49378
+ <dict>
49379
+ <key>${cliBrand.envPrefix}_APP_URL</key>
49380
+ <string>${escapePlist(config2.appUrl)}</string>
49381
+ </dict>
49382
+ <key>KeepAlive</key>
49383
+ <true/>
49384
+ <key>RunAtLoad</key>
49385
+ <true/>
49386
+ <key>StandardOutPath</key>
49387
+ <string>${escapePlist(path4.join(config2.logDir, "stdout.log"))}</string>
49388
+ <key>StandardErrorPath</key>
49389
+ <string>${escapePlist(path4.join(config2.logDir, "stderr.log"))}</string>
49390
+ </dict>
49391
+ </plist>
49392
+ `;
49393
+ }
49394
+ async function runRunnerService(options) {
49395
+ const verb = options.args[0];
49396
+ if (!isServiceVerb(verb)) {
49397
+ options.io.stderr.write(`Unknown runner service command: ${verb ?? ""}
49398
+ ${serviceUsage()}
49399
+ `);
49400
+ return 1;
49401
+ }
49402
+ if (options.args.includes("--help") || options.args.includes("-h")) {
49403
+ options.io.stdout.write(`${serviceUsage()}
49404
+ `);
49405
+ return 0;
49406
+ }
49407
+ let parsed;
49408
+ try {
49409
+ parsed = parseServiceArgs(options.args.slice(1));
49410
+ } catch (error51) {
49411
+ options.io.stderr.write(`${error51 instanceof Error ? error51.message : String(error51)}
49412
+ ${serviceUsage()}
49413
+ `);
49414
+ return 1;
49415
+ }
49416
+ const cmd = {
49417
+ io: options.io,
49418
+ runnerName: parsed.runnerName,
49419
+ appUrl: options.appUrl,
49420
+ execute: parsed.execute,
49421
+ binPath: options.binPath ?? resolveBinPath(),
49422
+ homeDir: options.homeDir ?? homedir2(),
49423
+ platform: options.platform ?? process.platform,
49424
+ exec: options.exec ?? defaultExec,
49425
+ uid: options.uid ?? (process.getuid ? process.getuid() : 0)
49426
+ };
49427
+ switch (verb) {
49428
+ case "install-service":
49429
+ return installService(cmd);
49430
+ case "start":
49431
+ return startService(cmd);
49432
+ case "stop":
49433
+ return stopService(cmd);
49434
+ case "restart":
49435
+ return restartService(cmd);
49436
+ case "status":
49437
+ return statusService(cmd);
49438
+ case "logs":
49439
+ return logsService(cmd);
49440
+ case "uninstall":
49441
+ return uninstallService(cmd);
49442
+ default:
49443
+ return assertNever2(verb);
49444
+ }
49445
+ }
49446
+ async function installService(options) {
49447
+ if (options.platform !== "darwin") {
49448
+ return unsupportedPlatform(options.io, options.platform);
49449
+ }
49450
+ const safeName = safeRunnerName(options.runnerName);
49451
+ const paths = runnerServicePaths(options.homeDir, safeName);
49452
+ const label = serviceLabel(safeName);
49453
+ await mkdir3(paths.logDir, { recursive: true });
49454
+ await mkdir3(path4.dirname(paths.stateFile), { recursive: true });
49455
+ await mkdir3(path4.dirname(paths.launchAgentPath), { recursive: true });
49456
+ const plist = renderLaunchdPlist({
49457
+ binPath: options.binPath,
49458
+ runnerName: safeName,
49459
+ stateFile: paths.stateFile,
49460
+ logDir: paths.logDir,
49461
+ appUrl: options.appUrl,
49462
+ execute: options.execute
49463
+ });
49464
+ await writeFile3(paths.launchAgentPath, plist, { mode: 420 });
49465
+ await bootoutQuietly(options.exec, options.uid, paths.launchAgentPath);
49466
+ try {
49467
+ await options.exec("launchctl", ["bootstrap", domainTarget(options.uid), paths.launchAgentPath]);
49468
+ } catch (error51) {
49469
+ options.io.stderr.write(
49470
+ `Failed to load the runner service: ${redactForLog(error51 instanceof Error ? error51.message : String(error51))}
49471
+ `
49472
+ );
49473
+ return 1;
49474
+ }
49475
+ options.io.stdout.write(
49476
+ `${[
49477
+ `Installed runner service ${label}.`,
49478
+ `plist: ${paths.launchAgentPath}`,
49479
+ `state file: ${paths.stateFile}`,
49480
+ `logs: ${paths.logDir}`,
49481
+ `execute: ${options.execute ? "on (claims and runs work orders)" : "off (heartbeat only)"}`,
49482
+ `The runner pairs on first run; its bearer secret is stored locally and never printed.`
49483
+ ].join("\n")}
49484
+ `
49485
+ );
49486
+ return 0;
49487
+ }
49488
+ async function startService(options) {
49489
+ if (options.platform !== "darwin") {
49490
+ return unsupportedPlatform(options.io, options.platform);
49491
+ }
49492
+ const safeName = safeRunnerName(options.runnerName);
49493
+ const paths = runnerServicePaths(options.homeDir, safeName);
49494
+ const label = serviceLabel(safeName);
49495
+ if (!await fileExists(paths.launchAgentPath)) {
49496
+ options.io.stderr.write(
49497
+ `No runner service found for "${safeName}". Run \`${cliBrand.binName} runner install-service --name ${safeName}\` first.
49498
+ `
49499
+ );
49500
+ return 1;
49501
+ }
49502
+ try {
49503
+ await options.exec("launchctl", ["bootstrap", domainTarget(options.uid), paths.launchAgentPath]);
49504
+ options.io.stdout.write(`Started runner service ${label}.
49505
+ `);
49506
+ return 0;
49507
+ } catch (bootstrapError) {
49508
+ try {
49509
+ await options.exec("launchctl", ["kickstart", "-k", serviceTarget(options.uid, label)]);
49510
+ options.io.stdout.write(`Runner service ${label} was already loaded; restarted it.
49511
+ `);
49512
+ return 0;
49513
+ } catch (kickstartError) {
49514
+ options.io.stderr.write(
49515
+ `Failed to start runner service ${label}: ${redactForLog(kickstartError instanceof Error ? kickstartError.message : String(kickstartError))}
49516
+ `
49517
+ );
49518
+ return 1;
49519
+ }
49520
+ }
49521
+ }
49522
+ async function stopService(options) {
49523
+ if (options.platform !== "darwin") {
49524
+ return unsupportedPlatform(options.io, options.platform);
49525
+ }
49526
+ const safeName = safeRunnerName(options.runnerName);
49527
+ const paths = runnerServicePaths(options.homeDir, safeName);
49528
+ const label = serviceLabel(safeName);
49529
+ try {
49530
+ await options.exec("launchctl", ["bootout", domainTarget(options.uid), paths.launchAgentPath]);
49531
+ options.io.stdout.write(`Stopped runner service ${label}.
49532
+ `);
49533
+ } catch {
49534
+ options.io.stdout.write(`Runner service ${label} was not running.
49535
+ `);
49536
+ }
49537
+ return 0;
49538
+ }
49539
+ async function restartService(options) {
49540
+ if (options.platform !== "darwin") {
49541
+ return unsupportedPlatform(options.io, options.platform);
49542
+ }
49543
+ const safeName = safeRunnerName(options.runnerName);
49544
+ const label = serviceLabel(safeName);
49545
+ try {
49546
+ await options.exec("launchctl", ["kickstart", "-k", serviceTarget(options.uid, label)]);
49547
+ options.io.stdout.write(`Restarted runner service ${label}.
49548
+ `);
49549
+ return 0;
49550
+ } catch (error51) {
49551
+ options.io.stderr.write(
49552
+ `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}\`.
49553
+ `
49554
+ );
49555
+ return 1;
49556
+ }
49557
+ }
49558
+ async function statusService(options) {
49559
+ if (options.platform !== "darwin") {
49560
+ return unsupportedPlatform(options.io, options.platform);
49561
+ }
49562
+ const safeName = safeRunnerName(options.runnerName);
49563
+ const paths = runnerServicePaths(options.homeDir, safeName);
49564
+ const label = serviceLabel(safeName);
49565
+ const installed = await fileExists(paths.launchAgentPath);
49566
+ try {
49567
+ const { stdout } = await options.exec("launchctl", ["print", serviceTarget(options.uid, label)]);
49568
+ const state = extractLaunchdState(stdout);
49569
+ options.io.stdout.write(
49570
+ `${[
49571
+ `runner service: ${label}`,
49572
+ `installed: ${installed ? "yes" : "no"}`,
49573
+ `loaded: yes`,
49574
+ `state: ${state ?? "unknown"}`,
49575
+ `plist: ${paths.launchAgentPath}`,
49576
+ `logs: ${paths.logDir}`
49577
+ ].join("\n")}
49578
+ `
49579
+ );
49580
+ return 0;
49581
+ } catch {
49582
+ options.io.stdout.write(
49583
+ `${[
49584
+ `runner service: ${label}`,
49585
+ `installed: ${installed ? "yes" : "no"}`,
49586
+ `loaded: no`,
49587
+ `plist: ${paths.launchAgentPath}`,
49588
+ `logs: ${paths.logDir}`
49589
+ ].join("\n")}
49590
+ `
49591
+ );
49592
+ return 1;
49593
+ }
49594
+ }
49595
+ async function logsService(options) {
49596
+ if (options.platform !== "darwin") {
49597
+ return unsupportedPlatform(options.io, options.platform);
49598
+ }
49599
+ const safeName = safeRunnerName(options.runnerName);
49600
+ const paths = runnerServicePaths(options.homeDir, safeName);
49601
+ const label = serviceLabel(safeName);
49602
+ const stdoutLog = path4.join(paths.logDir, "stdout.log");
49603
+ const stderrLog = path4.join(paths.logDir, "stderr.log");
49604
+ options.io.stdout.write(
49605
+ `${[
49606
+ `runner service logs for ${label}:`,
49607
+ ` stdout: ${stdoutLog}`,
49608
+ ` stderr: ${stderrLog}`,
49609
+ `Follow live with: tail -f ${JSON.stringify(stdoutLog)} ${JSON.stringify(stderrLog)}`
49610
+ ].join("\n")}
49611
+ `
49612
+ );
49613
+ for (const [name, file2] of [["stdout", stdoutLog], ["stderr", stderrLog]]) {
49614
+ const tail = await readTail(file2, 4e3);
49615
+ if (tail !== null && tail.trim().length > 0) {
49616
+ options.io.stdout.write(`
49617
+ ----- ${name} (tail) -----
49618
+ ${redactForLog(tail)}
49619
+ `);
49620
+ }
49621
+ }
49622
+ return 0;
49623
+ }
49624
+ async function uninstallService(options) {
49625
+ if (options.platform !== "darwin") {
49626
+ return unsupportedPlatform(options.io, options.platform);
49627
+ }
49628
+ const safeName = safeRunnerName(options.runnerName);
49629
+ const paths = runnerServicePaths(options.homeDir, safeName);
49630
+ const label = serviceLabel(safeName);
49631
+ await bootoutQuietly(options.exec, options.uid, paths.launchAgentPath);
49632
+ let removed = false;
49633
+ try {
49634
+ await rm3(paths.launchAgentPath, { force: true });
49635
+ removed = true;
49636
+ } catch (error51) {
49637
+ options.io.stderr.write(
49638
+ `Failed to remove ${paths.launchAgentPath}: ${redactForLog(error51 instanceof Error ? error51.message : String(error51))}
49639
+ `
49640
+ );
49641
+ }
49642
+ options.io.stdout.write(
49643
+ `${[
49644
+ `Uninstalled runner service ${label}.`,
49645
+ removed ? `Removed ${paths.launchAgentPath}.` : `Could not remove ${paths.launchAgentPath}; remove it manually.`,
49646
+ `Left the runner state file at ${paths.stateFile} (it holds the local bearer secret).`,
49647
+ `Delete that file to forget this runner locally, and run \`${cliBrand.binName} runner revoke <runner-id>\` to revoke it in ${cliBrand.name}.`
49648
+ ].join("\n")}
49649
+ `
49650
+ );
49651
+ return removed ? 0 : 1;
49652
+ }
49653
+ function parseServiceArgs(args) {
49654
+ let runnerName;
49655
+ let execute2 = false;
49656
+ for (let index = 0; index < args.length; index += 1) {
49657
+ const token = args[index];
49658
+ if (token === "--name") {
49659
+ const value = args[index + 1];
49660
+ if (value === void 0 || value.startsWith("--")) {
49661
+ throw new Error("--name requires a value.");
49662
+ }
49663
+ runnerName = value;
49664
+ index += 1;
49665
+ } else if (token === "--execute") {
49666
+ execute2 = true;
49667
+ } else {
49668
+ throw new Error(`Unexpected runner service argument: ${token ?? ""}`);
49669
+ }
49670
+ }
49671
+ if (runnerName === void 0 || runnerName.trim().length === 0) {
49672
+ throw new Error("--name <runner-name> is required.");
49673
+ }
49674
+ return { runnerName, execute: execute2 };
49675
+ }
49676
+ function serviceUsage() {
49677
+ const bin = cliBrand.binName;
49678
+ return [
49679
+ `Usage:`,
49680
+ ` ${bin} runner install-service --name <name> [--execute]`,
49681
+ ` ${bin} runner start|stop|restart|status|logs|uninstall --name <name>`
49682
+ ].join("\n");
49683
+ }
49684
+ function safeRunnerName(runnerName) {
49685
+ const safe = runnerName.replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "");
49686
+ return safe.length > 0 ? safe : "runner";
49687
+ }
49688
+ function serviceLabel(safeName) {
49689
+ return `com.${cliBrand.binName}.runner.${safeName}`;
49690
+ }
49691
+ function domainTarget(uid) {
49692
+ return `user/${uid}`;
49693
+ }
49694
+ function serviceTarget(uid, label) {
49695
+ return `user/${uid}/${label}`;
49696
+ }
49697
+ async function bootoutQuietly(exec, uid, plistPath) {
49698
+ try {
49699
+ await exec("launchctl", ["bootout", domainTarget(uid), plistPath]);
49700
+ } catch {
49701
+ }
49702
+ }
49703
+ function unsupportedPlatform(io, platform) {
49704
+ io.stderr.write(
49705
+ `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.
49706
+ `
49707
+ );
49708
+ return 1;
49709
+ }
49710
+ function resolveBinPath() {
49711
+ const entry = process.argv[1];
49712
+ if (entry === void 0 || entry.length === 0) {
49713
+ return cliBrand.binName;
49714
+ }
49715
+ try {
49716
+ return realpathSync(entry);
49717
+ } catch {
49718
+ return entry;
49719
+ }
49720
+ }
49721
+ function extractLaunchdState(printOutput) {
49722
+ const match = printOutput.match(/state\s*=\s*(\S+)/);
49723
+ return match ? match[1] ?? null : null;
49724
+ }
49725
+ async function fileExists(file2) {
49726
+ try {
49727
+ await access2(file2);
49728
+ return true;
49729
+ } catch {
49730
+ return false;
49731
+ }
49732
+ }
49733
+ async function readTail(file2, maxBytes) {
49734
+ try {
49735
+ const content = await readFile5(file2, "utf8");
49736
+ return content.length > maxBytes ? content.slice(content.length - maxBytes) : content;
49737
+ } catch {
49738
+ return null;
49739
+ }
49740
+ }
49741
+ function escapePlist(value) {
49742
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
49743
+ }
49744
+ var defaultExec = async (file2, args) => {
49745
+ const result = await execFile4(file2, args);
49746
+ return { stdout: String(result.stdout), stderr: String(result.stderr) };
49747
+ };
49748
+ function assertNever2(value) {
49749
+ throw new Error(`Unhandled runner service verb: ${String(value)}`);
48944
49750
  }
48945
49751
 
48946
49752
  // src/index.ts
@@ -49090,6 +49896,9 @@ async function main(argv = process.argv.slice(2), options = {}) {
49090
49896
  env: process.env
49091
49897
  });
49092
49898
  }
49899
+ if (command === "runner" && isRunnerServiceInvocation(args)) {
49900
+ return runRunnerService({ io, args, appUrl: config2.appUrl });
49901
+ }
49093
49902
  if (!session) {
49094
49903
  io.stderr.write(`Not logged in. Run ${cliBrand.binName} login first.
49095
49904
  `);
@@ -49567,7 +50376,7 @@ function isCliEntrypoint(moduleUrl, argvPath) {
49567
50376
  return false;
49568
50377
  }
49569
50378
  try {
49570
- return realpathSync(fileURLToPath3(moduleUrl)) === realpathSync(argvPath);
50379
+ return realpathSync2(fileURLToPath3(moduleUrl)) === realpathSync2(argvPath);
49571
50380
  } catch {
49572
50381
  return moduleUrl === pathToFileURL(argvPath).href;
49573
50382
  }