@rosthq/cli 0.6.7 → 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.d.ts.map +1 -1
- package/dist/index.js +946 -142
- package/dist/index.js.map +4 -4
- package/dist/operations.d.ts.map +1 -1
- package/dist/runner-serve.d.ts.map +1 -1
- package/dist/runner-service.d.ts +36 -0
- package/dist/runner-service.d.ts.map +1 -0
- package/dist/token-store.d.ts +66 -1
- package/dist/token-store.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -472,13 +472,13 @@ function __disposeResources(env) {
|
|
|
472
472
|
}
|
|
473
473
|
return next();
|
|
474
474
|
}
|
|
475
|
-
function __rewriteRelativeImportExtension(
|
|
476
|
-
if (typeof
|
|
477
|
-
return
|
|
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
|
|
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,
|
|
14030
|
-
if (!
|
|
14029
|
+
function getElementAtPath(obj, path5) {
|
|
14030
|
+
if (!path5)
|
|
14031
14031
|
return obj;
|
|
14032
|
-
return
|
|
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(
|
|
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(
|
|
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,
|
|
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 }, [...
|
|
14595
|
+
issue2.errors.map((issues) => processError({ issues }, [...path5, ...issue2.path]));
|
|
14596
14596
|
} else if (issue2.code === "invalid_key") {
|
|
14597
|
-
processError({ issues: issue2.issues }, [...
|
|
14597
|
+
processError({ issues: issue2.issues }, [...path5, ...issue2.path]);
|
|
14598
14598
|
} else if (issue2.code === "invalid_element") {
|
|
14599
|
-
processError({ issues: issue2.issues }, [...
|
|
14599
|
+
processError({ issues: issue2.issues }, [...path5, ...issue2.path]);
|
|
14600
14600
|
} else {
|
|
14601
|
-
const fullpath = [...
|
|
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,
|
|
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 }, [...
|
|
14632
|
+
issue2.errors.map((issues) => processError({ issues }, [...path5, ...issue2.path]));
|
|
14633
14633
|
} else if (issue2.code === "invalid_key") {
|
|
14634
|
-
processError({ issues: issue2.issues }, [...
|
|
14634
|
+
processError({ issues: issue2.issues }, [...path5, ...issue2.path]);
|
|
14635
14635
|
} else if (issue2.code === "invalid_element") {
|
|
14636
|
-
processError({ issues: issue2.issues }, [...
|
|
14636
|
+
processError({ issues: issue2.issues }, [...path5, ...issue2.path]);
|
|
14637
14637
|
} else {
|
|
14638
|
-
const fullpath = [...
|
|
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
|
|
14671
|
-
for (const seg of
|
|
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
|
|
27364
|
-
if (
|
|
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 (
|
|
27369
|
-
const key =
|
|
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
|
|
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}${
|
|
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 ${
|
|
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,
|
|
33060
|
-
const url2 = new URL(
|
|
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:
|
|
33090
|
+
path: path5,
|
|
33091
33091
|
query,
|
|
33092
33092
|
body,
|
|
33093
33093
|
headers
|
|
33094
33094
|
}) {
|
|
33095
|
-
const url2 = buildUrl(options.baseUrl,
|
|
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,
|
|
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(
|
|
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(
|
|
34059
|
-
return this.uploadOrUpdate("POST",
|
|
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(
|
|
34099
|
+
async uploadToSignedUrl(path5, token, fileBody, fileOptions) {
|
|
34100
34100
|
var _this3 = this;
|
|
34101
|
-
const cleanPath = _this3._removeEmptyFolders(
|
|
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(
|
|
34170
|
+
async createSignedUploadUrl(path5, options) {
|
|
34171
34171
|
var _this4 = this;
|
|
34172
34172
|
return _this4.handleOperation(async () => {
|
|
34173
|
-
let _path = _this4._getFinalPath(
|
|
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:
|
|
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(
|
|
34243
|
-
return this.uploadOrUpdate("PUT",
|
|
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(
|
|
34394
|
+
async createSignedUrl(path5, expiresIn, options) {
|
|
34395
34395
|
var _this8 = this;
|
|
34396
34396
|
return _this8.handleOperation(async () => {
|
|
34397
|
-
let _path = _this8._getFinalPath(
|
|
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(
|
|
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(
|
|
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(
|
|
34570
|
+
async info(path5) {
|
|
34571
34571
|
var _this10 = this;
|
|
34572
|
-
const _path = _this10._getFinalPath(
|
|
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(
|
|
34593
|
+
async exists(path5) {
|
|
34594
34594
|
var _this11 = this;
|
|
34595
|
-
const _path = _this11._getFinalPath(
|
|
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(
|
|
34675
|
-
const _path = this._getFinalPath(
|
|
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(
|
|
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:
|
|
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(
|
|
34883
|
-
return `${this.bucketId}/${
|
|
34882
|
+
_getFinalPath(path5) {
|
|
34883
|
+
return `${this.bucketId}/${path5.replace(/^\/+/, "")}`;
|
|
34884
34884
|
}
|
|
34885
|
-
_removeEmptyFolders(
|
|
34886
|
-
return
|
|
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 =
|
|
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
|
-
|
|
37308
|
-
|
|
37309
|
-
"
|
|
37310
|
-
"-s",
|
|
37311
|
-
|
|
37312
|
-
|
|
37313
|
-
|
|
37314
|
-
|
|
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(
|
|
37504
|
+
return new FileTokenStore(tokenPath);
|
|
37377
37505
|
}
|
|
37378
37506
|
const platform = options.platform ?? process.platform;
|
|
37379
37507
|
if (platform === "darwin") {
|
|
37380
|
-
return new
|
|
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
|
|
37385
|
-
`For headless Linux,
|
|
37386
|
-
"
|
|
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
|
|
37805
|
+
function assertNever3(_x) {
|
|
37573
37806
|
throw new Error();
|
|
37574
37807
|
}
|
|
37575
|
-
util2.assertNever =
|
|
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:
|
|
37931
|
-
const fullPath = [...
|
|
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,
|
|
38280
|
+
constructor(parent, value, path5, key) {
|
|
38048
38281
|
this._cachedPath = [];
|
|
38049
38282
|
this.parent = parent;
|
|
38050
38283
|
this.data = value;
|
|
38051
|
-
this._path =
|
|
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",
|
|
@@ -42157,7 +42462,7 @@ Decisions should be recorded as human decisions. Handoffs should attach to seats
|
|
|
42157
42462
|
order: 45,
|
|
42158
42463
|
title: "How agents work",
|
|
42159
42464
|
summary: "How {{brand}} agents operate inside seats, use Charters, report work, and escalate beyond authority.",
|
|
42160
|
-
version: "2026-
|
|
42465
|
+
version: "2026-07-02.1",
|
|
42161
42466
|
public: true,
|
|
42162
42467
|
audiences: ["human", "cli", "mcp", "in_app_agent"],
|
|
42163
42468
|
stages: ["staffing", "operating_rhythm"],
|
|
@@ -42255,7 +42560,7 @@ Beyond each seat's own manifest, the company can set one ceiling on how much an
|
|
|
42255
42560
|
|
|
42256
42561
|
## What humans should review
|
|
42257
42562
|
|
|
42258
|
-
The Agents page shows fleet health first: live state, work evidence, failed runs, open held actions, cost, and the next operator action. Work evidence distinguishes fresh real runs, stale runs, no completed run yet, queued or running work orders, and recent failures. A failure link opens the failed run itself; queued or stale rows route the operator to the seat's agent operations instead of pretending there is a hidden run to inspect. The page also shows configuration completeness for each staffed agent seat: Steward chain, lane or runtime substrate, vaulted credentials, granted tools, model config, sandbox dry run, and go-live state, each with a remediation link when incomplete. A seat's Trust Card drills into the same facts: run history, one-run detail, tool-call outcomes, product-visible errors, transcript references, loaded Skill versions, and held confirmations or escalations. Open confirmations stay visible until they are decided or expire, even when they are older than the default activity window. Approving or rejecting a held confirmation from the seat page is scoped to that seat; rejection can include a short reason. For a live agent, the seat page's agent operations panel can queue one immediate governed run through the same \`agent.run_now\` command path used by CLI and MCP; it does not expand authority or change the saved schedule.
|
|
42563
|
+
The Agents page shows fleet health first: live state, work evidence, failed runs, open held actions, cost, and the next operator action. Work evidence distinguishes fresh real runs, stale runs, no completed run yet, queued or running work orders, and recent failures. A failure link opens the failed run itself; queued or stale rows route the operator to the seat's agent operations instead of pretending there is a hidden run to inspect. The page also shows configuration completeness for each staffed agent seat: Steward chain, lane or runtime substrate, vaulted credentials, granted tools, model config, sandbox dry run, and go-live state, each with a remediation link when incomplete. A seat's Trust Card drills into the same facts: run history, one-run detail, tool-call outcomes, product-visible errors, transcript references, loaded Skill versions, and held confirmations or escalations. Open confirmations stay visible until they are decided or expire, even when they are older than the default activity window. Approving or rejecting a held confirmation from the seat page is scoped to that seat; rejection can include a short reason. For a live agent, the seat page's agent operations panel can queue one immediate governed run through the same \`agent.run_now\` command path used by CLI and MCP; it does not expand authority or change the saved schedule. Seat pages render legacy Charter manifest scope text with customer-safe labels when old setup data used internal placeholder wording.
|
|
42259
42564
|
|
|
42260
42565
|
Review the first dry runs, fleet overview, tool-call audit rows, escalations, deliverables, and Signal impact. A seat's completed-work area combines explicit deliverables, work-log evidence, and successful run summaries into one operator-facing trail with safe links labelled as internal, Linear, GitHub, or external. The fleet view at \`/agents\` shows every staffed agent seat at a glance; the agent-native equivalents are \`{{cli}} command agent.list_fleet --json '{}'\` / \`rost_list_agent_fleet\` for the compact overview, and \`{{cli}} agent fleet-digest --json\` / \`agent.fleet_digest\` / \`rost_get_dogfood_fleet_health_digest\` for the daily dogfood evidence bundle. The digest returns live/idle state, 24h/7d turns, 7d/30d spend, recent failed runs, unresolved product errors, failed notifications, and a next action per seat.
|
|
42261
42566
|
|
|
@@ -42310,7 +42615,7 @@ Connecting a tool is a human-controlled step. The agent can recommend a tool, ex
|
|
|
42310
42615
|
|
|
42311
42616
|
## Generic REST connector
|
|
42312
42617
|
|
|
42313
|
-
For an API with no dedicated connector, the generic REST tool lets a seat read an HTTPS endpoint with a credential you paste through the vault \u2014 no {{brand}}-owned app. The agent-facing tool is GET-only. It is escalate-by-default: the agent may only call a host and explicit path prefix a steward has signed onto the allowlist; any other host or path is refused and escalated, with no request made. Mutating methods and request bodies fail before credential binding. A path prefix of \`/\` means the steward intentionally approved every path. The connector sets the Authorization header from the vaulted credential itself \u2014 the agent never sees the token, and the secret is redacted from the response before it reaches the agent, the audit row, or the logs. The token is only ever sent over HTTPS, only to the signed host and scoped path, and a redirect is never followed \u2014 so an allowlisted endpoint cannot bounce the call (and the token) to another host. A sandbox dry run of a REST tool makes no real request.
|
|
42618
|
+
For an API with no dedicated connector, the generic REST tool lets a seat read an HTTPS endpoint with a credential you paste through the vault \u2014 no {{brand}}-owned app. The agent-facing tool is GET-only. It is escalate-by-default: the agent may only call a host and explicit path prefix a steward has signed onto the allowlist; any other host or path is refused and escalated, with no request made. Mutating methods and request bodies fail before credential binding. A path prefix of \`/\` means the steward intentionally approved every path. The connector sets the Authorization header from the vaulted credential itself \u2014 the agent never sees the token, and the secret is redacted from the response before it reaches the agent, the audit row, or the logs. The token is only ever sent over HTTPS, only to the signed host and scoped path, and a redirect is never followed \u2014 so an allowlisted endpoint cannot bounce the call (and the token) to another host. Signal-source pulls also resolve the host at execution time, reject private or reserved addresses, and pin the outbound socket to the validated address so DNS rebinding cannot move the request after approval. A sandbox dry run of a REST tool makes no real request.
|
|
42314
42619
|
|
|
42315
42620
|
## Slack post connector
|
|
42316
42621
|
|
|
@@ -42359,7 +42664,7 @@ There is exactly one way to give a connected tool its credential, and it is the
|
|
|
42359
42664
|
order: 47,
|
|
42360
42665
|
title: "Available tools guide",
|
|
42361
42666
|
summary: "How to think about tool categories available to seats and what each category should be used for.",
|
|
42362
|
-
version: "2026-
|
|
42667
|
+
version: "2026-06-30.1",
|
|
42363
42668
|
public: true,
|
|
42364
42669
|
audiences: ["human", "cli", "mcp", "in_app_agent"],
|
|
42365
42670
|
stages: ["staffing"],
|
|
@@ -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.
|
|
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,12 +43174,13 @@ 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\` |
|
|
42875
43181
|
| \`{{cli}} command usage.report|usage.snapshot\` | \`usage.report\`, \`usage.snapshot\` | Read the AI-workforce ROI report (per-period agent turns, token/run cost, human-accepted value, exceptions, approvals) from immutable usage snapshots, and freeze a period's totals into a new snapshot (insert-only, idempotent). | Tenant | \`{{cli}} command usage.report --json\`; \`{{cli}} command usage.snapshot --json\` |
|
|
42876
43182
|
| \`{{cli}} settings get|update|product-learning|agent-policy|rename\` | \`settings.get\`, \`settings.update\`, \`settings.product_learning.get\`, \`settings.product_learning.update\`, \`settings.agent_policy.get\`, \`settings.agent_policy.update\`, \`tenant.rename\` | Read tenant settings, update budget caps, read or update product-learning participation, read or set the company autonomy ceiling, and rename the company. Budget, product-learning, and policy updates stop at human confirmation in non-interactive CLI/MCP sessions; company rename is owner-only and human-gated. | Tenant / Tenant-admin for updates | \`{{cli}} settings product-learning get --json\`; \`{{cli}} settings agent-policy get --json\`; \`{{cli}} settings rename --company "Acme Operations"\` |
|
|
42877
|
-
| \`{{cli}} member invite|update|remove\` | \`member.invite\`, \`member.update\`, \`member.remove\` | Manage tenant members. | Tenant | \`{{cli}} member invite --email ops@example.com --role member\` |
|
|
43183
|
+
| \`{{cli}} member invite|update|remove\` | \`member.invite\`, \`member.update\`, \`member.remove\` | Manage tenant members; execution requires an owner or admin membership. | Tenant | \`{{cli}} member invite --email ops@example.com --role member\` |
|
|
42878
43184
|
| \`{{cli}} agent templates|create|setup|tools|dry-run|go-live|status|run-now|fleet-digest|get-run|show\` | \`agent_template.list\`, \`agent.create_from_template\`, \`agent.create_custom\`, \`agent_setup.get\`, \`agent_setup.update\`, \`agent.configure_tools\`, \`agent.run_dry_run\`, \`agent.go_live\`, \`agent.status\`, \`agent.run_now\`, \`agent.fleet_digest\`, \`agent.get_run\`, \`agent.show_markdown\` | Run the full agent setup and operation flow: list templates, create a draft from a template or guided custom answers (with \`--model\` and \`--effort\`), read or answer setup state, connect or decline tools, dry-run, go live, run on demand, capture the fleet-health digest, read one run's transcript/error diagnostics, and show a markdown readout. Human CLI dry-runs print the rehearsal transcript and, when present, the per-tool preview labels (\`Would run\`, \`Blocked\`, \`Escalated\`) before go-live. Create and go-live stop at human gates; the dry-run is ungated by human approval but requires a signed manifest first. | Tenant and seat | \`{{cli}} agent fleet-digest --json\` |
|
|
42879
43185
|
| \`{{cli}} tools list\` | \`tool.catalog\` | List the discoverable tool catalog the builder reads (id, scope tiers, credential requirement, access policy, and execution-boundary guidance). | Tenant | \`{{cli}} tools list --json\` |
|
|
42880
43186
|
| \`{{cli}} skills list|get|file|assigned|check-dependencies\` | \`skill.list\`, \`skill.get\`, \`skill.file.get\`, \`skill.assigned.list\`, \`skill.check_dependencies\` | Discover reusable Skills, read descriptors and stored package files, list approved Seat assignments, and compare required/optional tool dependencies with a Seat's signed Charter manifest. Skills are instructions, not authority. | Tenant-admin; seat-scoped reads for assigned Skills | \`{{cli}} skills list --json\`; \`{{cli}} skills file --slug invoice-review --path SKILL.md\` |
|
|
@@ -43046,9 +43352,9 @@ Several rows here are seat-operating commands (\`task.create\`, the \`signal.*\`
|
|
|
43046
43352
|
| \`rost_check_integration_readiness\` | \`integration.readiness\` | Return the connector setup checklist: OAuth env/callback, tenant connection, scopes, latest test state, external verification/CASA, handler availability, and DER-coded next actions. | Tenant | Call with \`{"provider":"google"}\`; metadata only, no secrets or vault refs. |
|
|
43047
43353
|
| \`rost_get_integration_status\` | \`integration.status\` | Read one integration's metadata by provider or integration id. | Tenant | Call with \`{"provider":"google"}\` or \`{"integration_id":"<id>"}\`. |
|
|
43048
43354
|
| \`rost_test_integration_connection\` | \`integration.test\` | Run the provider-specific connection test and update integration health. | Tenant | Call with \`{"provider":"google"}\`; result is bounded metadata only. |
|
|
43049
|
-
| \`rost_invite_member\` | \`member.invite\` | Create a pending tenant invite for a human teammate. | Tenant |
|
|
43050
|
-
| \`rost_update_member_role\` | \`member.update\` | Change a tenant member's role. | Tenant |
|
|
43051
|
-
| \`rost_remove_member\` | \`member.remove\` | Remove a tenant member. | Tenant |
|
|
43355
|
+
| \`rost_invite_member\` | \`member.invite\` | Create a pending tenant invite for a human teammate. | Tenant | Owner/admin only; call with \`email\` and \`role\`. |
|
|
43356
|
+
| \`rost_update_member_role\` | \`member.update\` | Change a tenant member's role. | Tenant | Owner/admin only; call with \`member_id\` and \`role\`; expect human confirmation. |
|
|
43357
|
+
| \`rost_remove_member\` | \`member.remove\` | Remove a tenant member. | Tenant | Owner/admin only; call with \`member_id\`; blocked if it would orphan an agent steward chain. |
|
|
43052
43358
|
| \`rost_get_tenant_settings\` | \`settings.get\` | Read tenant operating settings and integration status. | Tenant | Call with \`{}\`; metadata only. |
|
|
43053
43359
|
| \`rost_update_tenant_settings\` | \`settings.update\` | Update tenant AI budget caps. | Tenant | Call with \`soft_cap_usd\` or \`hard_cap_usd\`; expect human confirmation. |
|
|
43054
43360
|
| \`rost_rename_company\` | \`tenant.rename\` | Rename the current company. | Tenant-admin | Call with \`company_name\`; expect human confirmation. |
|
|
@@ -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.
|
|
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.
|
|
43529
|
-
4.
|
|
43530
|
-
5. charter-
|
|
43531
|
-
6.
|
|
43532
|
-
7.
|
|
43533
|
-
8.
|
|
43534
|
-
9.
|
|
43535
|
-
10.
|
|
43536
|
-
11.
|
|
43537
|
-
12.
|
|
43538
|
-
13.
|
|
43539
|
-
14.
|
|
43540
|
-
15.
|
|
43541
|
-
16. agent-skill-
|
|
43542
|
-
17.
|
|
43543
|
-
18.
|
|
43544
|
-
19.
|
|
43545
|
-
20.
|
|
43546
|
-
21.
|
|
43547
|
-
22.
|
|
43548
|
-
23.
|
|
43549
|
-
24.
|
|
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
|
|
|
@@ -43700,7 +44008,7 @@ Agents can suggest commitments and report progress. They should not create a new
|
|
|
43700
44008
|
order: 61,
|
|
43701
44009
|
title: "Signal guide",
|
|
43702
44010
|
summary: "How to define and read measurables so the company runs on evidence instead of status theater.",
|
|
43703
|
-
version: "2026-07-02.
|
|
44011
|
+
version: "2026-07-02.4",
|
|
43704
44012
|
public: true,
|
|
43705
44013
|
audiences: ["human", "cli", "mcp", "in_app_agent"],
|
|
43706
44014
|
stages: ["operating_rhythm"],
|
|
@@ -43784,12 +44092,14 @@ Once a measurable's number lives in a connected system, you can teach {{brand}}
|
|
|
43784
44092
|
- \`signal.bind\` persists the validated recipe as an inert \`draft\` binding. It requires the \`preview_token\` from a preview of the same recipe, so nothing is bound that was not first seen working.
|
|
43785
44093
|
- \`signal.bind_confirm\` is human-only. It activates the binding (\`draft -> active\`); an agent that calls it produces a pending confirmation instead. Only after a human confirms does the scheduled pull begin writing confirmed readings.
|
|
43786
44094
|
|
|
43787
|
-
Discovery always returns a \`recipe_presets\` array; it is empty when {{brand}} does not recognize a safe source type. Today
|
|
44095
|
+
Discovery always returns a \`recipe_presets\` array; it is empty when {{brand}} does not recognize a safe source type. Today the proven self-filling path is Baserow REST through \`integration.connect_rest\`: a tenant admin creates or rotates the Baserow connection through the command/vault path, then Signal runs \`integration.discover -> signal.preview -> signal.bind -> signal.bind_confirm\` against that connected source. Baserow row-field recipes and custom filtered-count recipes are supported when the human supplies the real table, field, filter, host, and value path. A first-class filtered-count preset is still a follow-up; do not imply every filtered count appears as a one-click preset. Google Sheets Signal pulls are not live yet: Google uses OAuth JSON credentials and remains withheld from REST presets until the pull executor extracts and refreshes the Google access token instead of treating the stored credential blob as a bearer token. A preset includes a label, cadence, required metadata, a draft \`rest_for_signals\` recipe, and the host allowlist to preview with. The human still fills in the real Baserow table/row/field or exact REST URL and value path before running \`signal.preview\`; presets are starter recipes, not secret stores.
|
|
43788
44096
|
|
|
43789
44097
|
Recipes are validated data, never code: a read-only GET, a closed set of non-secret headers, and a restricted scalar path to the value. Every fetch passes one SSRF guard (HTTPS only, a steward-confirmed host allowlist set at bind, and a runtime check that the host does not resolve to a private address). The preset output never contains \`nango://\` handles, vault refs, tokens, or static credential headers. Deterministic pulls write \`integration\`-sourced readings on the measurable's cadence.
|
|
43790
44098
|
|
|
43791
44099
|
In the web app, the same workflow lives on \`/signal\` in the "Connect a self-filling source" panel. It shows each measurable's setup state \u2014 manual, agent draft, source draft, or auto-filled \u2014 plus the latest safe pull outcome when one exists. Choose a connected source, discover its non-secret metadata, preview a GET URL and value path, bind the working preview as a draft, then confirm the draft before scheduled pulls can write readings. The UI never displays credential material, vault refs, raw response bodies, or recipe secrets; if no connected source exists, connect one from Settings first.
|
|
43792
44100
|
|
|
44101
|
+
A source binding can inform a measurable without automatically changing a Cascade goal. The proven Leiluna Baserow binding uses \`informs\`. Use \`drives_status\` only after a second business anchor confirms that this exact measurable should compute a childless goal's status; otherwise the Signal should stay visible as evidence and exception context, not an automatic goal-status driver.
|
|
44102
|
+
|
|
43793
44103
|
## Collect a Signal by judgment (the Signal agent)
|
|
43794
44104
|
|
|
43795
44105
|
Some measurables cannot be a deterministic pull \u2014 the number lives in a PDF, in messy records that need reconciling, or is a judgment estimate of progress. For those, mark the measurable \`agent\`-sourced and let a per-seat Signal agent read it. The agent proposes each reading with \`signal.report\` (scope: seat): one draft reading for one of its OWN seat's agent-sourced measurables, with a \`value\` and a short \`note\` citing the source. The reading lands unconfirmed, exactly like a deterministic draft \u2014 a human clears it with \`signal.confirm_reading\`. \`signal.report\` refuses any measurable not owned by the acting seat and any measurable whose source is not \`agent\`, and it never confirms its own reading. Deliver it as the \`stock/signal-collector\` template (a read-only agent whose only durable grant is \`signal.report\`) or run the \`collect-seat-signals\` how-to from an occupant agent. When the agent cannot read a number confidently \u2014 ambiguous or conflicting sources \u2014 it escalates instead of guessing.
|
|
@@ -44115,7 +44425,7 @@ Do not claim that a live charge happened unless Stripe test/live evidence proves
|
|
|
44115
44425
|
order: 72,
|
|
44116
44426
|
title: "Settings guide",
|
|
44117
44427
|
summary: "How to use Settings as the control plane for company access, channels, providers, tokens, and operating defaults.",
|
|
44118
|
-
version: "2026-07-02.
|
|
44428
|
+
version: "2026-07-02.3",
|
|
44119
44429
|
public: true,
|
|
44120
44430
|
audiences: ["human", "cli", "mcp", "in_app_agent"],
|
|
44121
44431
|
stages: ["company_setup", "staffing"],
|
|
@@ -44201,6 +44511,7 @@ The company autonomy ceiling is a single tenant-wide dial that caps how much ANY
|
|
|
44201
44511
|
|
|
44202
44512
|
- Read it with \`settings.agent_policy.get\` (CLI: \`{{cli}} settings agent-policy get\`).
|
|
44203
44513
|
- Set it with \`settings.agent_policy.update\` (CLI: \`{{cli}} settings agent-policy set --profile locked_down\`). Owner-only and human-gated.
|
|
44514
|
+
- In the signed-in app, tenant owners manage the same policy from **Settings \u2192 Company guardrails**. Non-owners can see the current posture but cannot change it.
|
|
44204
44515
|
- Profiles are named ceilings: \`locked_down\` (agents autonomously read/draft only; every write/send escalates), \`balanced\` (the default; read/draft autonomous, writes escalate), \`high_autonomy\` (approval-marked write tools may run autonomously; only full-access escalates), and \`custom\` + \`max_autonomous_risk\` (\`low\`, \`medium\`, \`high\`, \`critical\`).
|
|
44205
44516
|
- An explicitly set ceiling is ENFORCED by default. Enforcement has two modes: \`enforce\` blocks an over-ceiling autonomous call and routes it to the steward (it is never a dead-end deny \u2014 a human can still approve), and \`observe\` allows the call but logs it. The platform default for a company that never set a policy is balanced + observe, so existing live agents are never retro-bricked.
|
|
44206
44517
|
- When enforcement is on, signing a manifest or taking an agent live is REFUSED if the manifest grants always_allow to a tool whose risk exceeds the ceiling; the error names the exact ceiling, tool, and risk in its \`details\`. Lower that tool to always_ask, or raise the ceiling, then retry.
|
|
@@ -44214,7 +44525,7 @@ Agents may explain which setting is needed and why. They should not ask users to
|
|
|
44214
44525
|
order: 73,
|
|
44215
44526
|
title: "Members and invites guide",
|
|
44216
44527
|
summary: "How to invite humans into the company without confusing user access with seat accountability.",
|
|
44217
|
-
version: "2026-
|
|
44528
|
+
version: "2026-07-02.1",
|
|
44218
44529
|
public: true,
|
|
44219
44530
|
audiences: ["human", "cli", "mcp", "in_app_agent"],
|
|
44220
44531
|
stages: ["company_setup", "staffing"],
|
|
@@ -44239,6 +44550,8 @@ Invite the person as a tenant member. Then assign them to one or more seats only
|
|
|
44239
44550
|
|
|
44240
44551
|
Tenant roles control app access. Seats control operating accountability. Do not attach goals, tasks, or measurables directly to a user when the work belongs to a seat.
|
|
44241
44552
|
|
|
44553
|
+
Owners and admins can invite people and manage member roles. Use \`external_implementer\` plus setup-limited access for a temporary implementation partner helping configure onboarding, Responsibility Graph, Compass, Charters, and agent setup. Do not grant external implementers sensitive run history, hidden tool arguments, credentials, Memory, or company-operating data unless an owner explicitly expands their profile.
|
|
44554
|
+
|
|
44242
44555
|
## Agent guidance
|
|
44243
44556
|
|
|
44244
44557
|
When a user asks to add a person, clarify whether they mean app access, seat occupancy, or both. Use invites for access and staffing commands for occupancies.`
|
|
@@ -44287,7 +44600,7 @@ Every notification should include the seat, cause, evidence, and requested decis
|
|
|
44287
44600
|
order: 75,
|
|
44288
44601
|
title: "Local runner guide",
|
|
44289
44602
|
summary: "How local agent sessions and runner surfaces should operate through {{brand}} without bypassing Charters or audit.",
|
|
44290
|
-
version: "2026-
|
|
44603
|
+
version: "2026-07-02.1",
|
|
44291
44604
|
public: true,
|
|
44292
44605
|
audiences: ["human", "cli", "mcp", "in_app_agent"],
|
|
44293
44606
|
stages: ["staffing", "operating_rhythm"],
|
|
@@ -44313,6 +44626,26 @@ The local runner is for human-controlled local agent work. It should retrieve {{
|
|
|
44313
44626
|
5. Record work, Friction, handoffs, and escalations.
|
|
44314
44627
|
6. Revoke access when the session ends.
|
|
44315
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
|
+
|
|
44316
44649
|
## Inspect and control runners from CLI or MCP
|
|
44317
44650
|
|
|
44318
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.
|
|
@@ -46168,9 +46501,9 @@ var COMMAND_MANIFEST = [
|
|
|
46168
46501
|
{ "id": "measurable_template.adopt", "namespace": "measurable_template", "action": "adopt", "title": "Adopt a measurable template", "description": "Create a seat measurable from a catalog template (unit/direction/cadence from the template; target optionally overridden). The seat owns it; readings attach afterward.", "requiredScope": "seat", "confirmation": "none", "exposeOverMcp": true, "fields": [{ "name": "template_id", "flag": "template-id", "type": "string", "required": true }, { "name": "seat_id", "flag": "seat-id", "type": "string", "required": true }, { "name": "target", "flag": "target", "type": "number", "required": false }], "hasComplexInput": false, "help": "Create a seat measurable from a catalog template (unit/direction/cadence from the template; target optionally overridden). Adoption is a human act \u2014 source is human." },
|
|
46169
46502
|
{ "id": "measurable_template.list", "namespace": "measurable_template", "action": "list", "title": "List measurable templates", "description": "List the measurable template catalog (Sales/Finance/Ops/Marketing/HR) with unit, direction, cadence, and a suggested target. Adopt one with measurable_template.adopt.", "requiredScope": "tenant", "confirmation": "none", "exposeOverMcp": true, "fields": [{ "name": "function", "flag": "function", "type": "enum", "required": false, "enumValues": ["sales", "finance", "operations", "marketing", "hr"] }], "hasComplexInput": false, "help": "List the measurable template catalog by function (Sales/Finance/Ops/Marketing/HR); adopt one with measurable_template.adopt to create a seat measurable." },
|
|
46170
46503
|
{ "id": "measurable.create", "namespace": "measurable", "action": "create", "title": "Add a measurable", "description": "Create a measurable (a Signal a seat owns) with its target, unit, direction, and cadence. The seat owns it; readings attach to the measurable. Source is 'human' \u2014 a human-defined measurable a human logs.", "requiredScope": "seat", "confirmation": "none", "exposeOverMcp": true, "fields": [{ "name": "seat_id", "flag": "seat-id", "type": "string", "required": true }, { "name": "name", "flag": "name", "type": "string", "required": true }, { "name": "unit", "flag": "unit", "type": "string", "required": true }, { "name": "direction", "flag": "direction", "type": "enum", "required": true, "enumValues": ["up_good", "down_good"] }, { "name": "target", "flag": "target", "type": "number", "required": true }, { "name": "cadence", "flag": "cadence", "type": "enum", "required": true, "enumValues": ["daily", "weekly", "monthly", "quarterly", "annual"] }], "hasComplexInput": false, "help": "Add a measurable a seat owns (name, unit, direction, target, cadence) before recording readings against it." },
|
|
46171
|
-
{ "id": "member.invite", "namespace": "member", "action": "invite", "title": "Invite member", "description": "Create a pending tenant invite for a human teammate. The acceptance link is delivered out of band, not in the command result.", "requiredScope": "tenant", "confirmation": "none", "exposeOverMcp": true, "fields": [{ "name": "email", "flag": "email", "type": "string", "required": true }, { "name": "role", "flag": "role", "type": "enum", "required": true, "enumValues": ["owner", "steward", "member"] }, { "name": "byo_key", "flag": "byo-key", "type": "boolean", "required": false }], "hasComplexInput": false, "help": "Invite humans to the company by seat-aware role; the invite token is never returned over CLI or MCP." },
|
|
46504
|
+
{ "id": "member.invite", "namespace": "member", "action": "invite", "title": "Invite member", "description": "Create a pending tenant invite for a human teammate. The acceptance link is delivered out of band, not in the command result.", "requiredScope": "tenant", "confirmation": "none", "exposeOverMcp": true, "fields": [{ "name": "email", "flag": "email", "type": "string", "required": true }, { "name": "role", "flag": "role", "type": "enum", "required": true, "enumValues": ["owner", "admin", "steward", "member"] }, { "name": "member_type", "flag": "member-type", "type": "enum", "required": false, "enumValues": ["internal", "external_implementer"] }, { "name": "access_profile", "flag": "access-profile", "type": "enum", "required": false, "enumValues": ["full_setup", "setup_limited", "seat_scoped", "custom"] }, { "name": "aicos_access_scope", "flag": "aicos-access-scope", "type": "enum", "required": false, "enumValues": ["none", "own_scope", "setup", "company_read", "company_operate"] }, { "name": "byo_key", "flag": "byo-key", "type": "boolean", "required": false }], "hasComplexInput": false, "help": "Invite humans to the company by seat-aware role; the invite token is never returned over CLI or MCP." },
|
|
46172
46505
|
{ "id": "member.remove", "namespace": "member", "action": "remove", "title": "Remove member", "description": "Remove a tenant member. Blocked when it would orphan an agent steward chain.", "requiredScope": "tenant", "confirmation": "human_required", "exposeOverMcp": true, "fields": [{ "name": "member_id", "flag": "member-id", "type": "string", "required": true }], "hasComplexInput": false, "help": "Remove a member; the no-orphan-agent guard blocks removing the only steward in an agent chain." },
|
|
46173
|
-
{ "id": "member.update", "namespace": "member", "action": "update", "title": "Update member role", "description": "Change a tenant member's role.", "requiredScope": "tenant", "confirmation": "human_required", "exposeOverMcp": true, "fields": [{ "name": "member_id", "flag": "member-id", "type": "string", "required": true }, { "name": "role", "flag": "role", "type": "enum", "required": true, "enumValues": ["owner", "steward", "member"] }], "hasComplexInput": false, "help": "Change a member's role; role changes are human-gated." },
|
|
46506
|
+
{ "id": "member.update", "namespace": "member", "action": "update", "title": "Update member role", "description": "Change a tenant member's role.", "requiredScope": "tenant", "confirmation": "human_required", "exposeOverMcp": true, "fields": [{ "name": "member_id", "flag": "member-id", "type": "string", "required": true }, { "name": "role", "flag": "role", "type": "enum", "required": true, "enumValues": ["owner", "admin", "steward", "member"] }], "hasComplexInput": false, "help": "Change a member's role; role changes are human-gated." },
|
|
46174
46507
|
{ "id": "model.catalog", "namespace": "model", "action": "catalog", "title": "List model catalog", "description": "List the guided model tiers the agent builder reads \u2014 recommendation, token prices, cost band, effort, model id, and when to choose or avoid each tier.", "requiredScope": "tenant", "confirmation": "none", "exposeOverMcp": true, "fields": [], "hasComplexInput": false, "help": "List guided model tiers with recommendations, token prices, cost bands, best-fit work, and model ids before choosing --model for an agent." },
|
|
46175
46508
|
{ "id": "notification.list_errors", "namespace": "notification", "action": "list_errors", "title": "List notification errors", "description": "List recent failed notification deliveries for the tenant.", "requiredScope": "tenant", "confirmation": "none", "exposeOverMcp": true, "fields": [{ "name": "limit", "flag": "limit", "type": "integer", "required": false }], "hasComplexInput": false, "help": "List recent failed notification deliveries when diagnosing missed escalations or briefs." },
|
|
46176
46509
|
{ "id": "notification.settings.get", "namespace": "notification", "action": "settings.get", "title": "Get notification settings", "description": "Read tenant notification preferences (escalations, Sync brief, default channel).", "requiredScope": "tenant", "confirmation": "none", "exposeOverMcp": true, "fields": [], "hasComplexInput": false, "help": "Read notification preferences before changing escalation or Sync brief delivery." },
|
|
@@ -46178,7 +46511,7 @@ var COMMAND_MANIFEST = [
|
|
|
46178
46511
|
{ "id": "notification.test", "namespace": "notification", "action": "test", "title": "Send test notification", "description": "Emit an in-app test notification to the acting human to verify delivery is wired.", "requiredScope": "tenant", "confirmation": "none", "exposeOverMcp": true, "fields": [], "hasComplexInput": false, "help": "Send an in-app test notification to confirm the delivery path is wired." },
|
|
46179
46512
|
{ "id": "onboarding.advance_step", "namespace": "onboarding", "action": "advance_step", "title": "Advance onboarding step", "description": "Persist the current onboarding step after server-side validation.", "requiredScope": "tenant", "confirmation": "none", "exposeOverMcp": true, "fields": [{ "name": "step", "flag": "step", "type": "string", "required": true }, { "name": "completed", "flag": "completed", "type": "boolean", "required": false }], "hasComplexInput": true, "help": "Advance onboarding only after the current setup step is represented in the tenant state." },
|
|
46180
46513
|
{ "id": "onboarding.attach_reference", "namespace": "onboarding", "action": "attach_reference", "title": "Attach reference document", "description": "Attach a company reference document (text/markdown) so Compass and Charters can cite it. Reference-only \u2014 it does not build a Compass; convert each meaningful doc to markdown and attach it with a title, kind, and source.", "requiredScope": "tenant", "confirmation": "none", "exposeOverMcp": true, "fields": [{ "name": "text", "flag": "text", "type": "string", "required": true }, { "name": "filename", "flag": "filename", "type": "string", "required": false }, { "name": "mime_type", "flag": "mime-type", "type": "enum", "required": false, "enumValues": ["text/plain", "text/markdown", "text/csv"] }, { "name": "title", "flag": "title", "type": "string", "required": false }, { "name": "kind", "flag": "kind", "type": "enum", "required": false, "enumValues": ["plan", "financial", "org_chart", "process"] }, { "name": "source", "flag": "source", "type": "string", "required": false }], "hasComplexInput": false, "help": "Convert each meaningful company doc to markdown and attach it as a reference (title, kind, source) so Compass and Charters can cite it; this attaches a reference, it does not build a Compass \u2014 author Compass directly.", "example": { "text": "# Company handbook\n\nWe build precision components for the aerospace supply chain.", "filename": "handbook.md", "mime_type": "text/markdown", "title": "Company handbook", "kind": "plan", "source": "handbook/overview.md" } },
|
|
46181
|
-
{ "id": "onboarding.create_invite", "namespace": "onboarding", "action": "create_invite", "title": "Create onboarding invite", "description": "Create a pending invite for an onboarding team member.", "requiredScope": "tenant", "confirmation": "none", "exposeOverMcp": true, "fields": [{ "name": "email", "flag": "email", "type": "string", "required": true }, { "name": "role", "flag": "role", "type": "enum", "required": false, "enumValues": ["owner", "steward", "member"] }], "hasComplexInput": true, "help": "Invites should give humans access to the company without assigning work directly to people instead of seats." },
|
|
46514
|
+
{ "id": "onboarding.create_invite", "namespace": "onboarding", "action": "create_invite", "title": "Create onboarding invite", "description": "Create a pending invite for an onboarding team member.", "requiredScope": "tenant", "confirmation": "none", "exposeOverMcp": true, "fields": [{ "name": "email", "flag": "email", "type": "string", "required": true }, { "name": "role", "flag": "role", "type": "enum", "required": false, "enumValues": ["owner", "admin", "steward", "member"] }], "hasComplexInput": true, "help": "Invites should give humans access to the company without assigning work directly to people instead of seats." },
|
|
46182
46515
|
{ "id": "onboarding.finish", "namespace": "onboarding", "action": "finish", "title": "Finish onboarding", "description": "Mark onboarding complete after the existing onboarding requirements pass.", "requiredScope": "tenant", "confirmation": "human_required", "exposeOverMcp": true, "fields": [], "hasComplexInput": false, "help": "Finish onboarding when the graph, at least one Charter, and the first operating rhythm are clear." },
|
|
46183
46516
|
{ "id": "onboarding.status", "namespace": "onboarding", "action": "status", "title": "Onboard status", "description": "Return current onboarding progress, graph summary, and the next recommended actions.", "requiredScope": "tenant", "confirmation": "none", "exposeOverMcp": true, "fields": [], "hasComplexInput": false, "help": "Start by checking onboarding status, then use the implementation method guide to choose the next setup step." },
|
|
46184
46517
|
{ "id": "onboarding.upload_context", "namespace": "onboarding", "action": "upload_context", "title": "Upload org context", "description": "Compatibility alias for onboarding.attach_reference: attach a company reference document (text/markdown) that Compass and Charters can cite. Reference-only \u2014 it does not build a Compass.", "requiredScope": "tenant", "confirmation": "none", "exposeOverMcp": true, "fields": [{ "name": "text", "flag": "text", "type": "string", "required": true }, { "name": "filename", "flag": "filename", "type": "string", "required": false }, { "name": "mime_type", "flag": "mime-type", "type": "enum", "required": false, "enumValues": ["text/plain", "text/markdown", "text/csv"] }, { "name": "title", "flag": "title", "type": "string", "required": false }, { "name": "kind", "flag": "kind", "type": "enum", "required": false, "enumValues": ["plan", "financial", "org_chart", "process"] }, { "name": "source", "flag": "source", "type": "string", "required": false }], "hasComplexInput": false, "help": "Compatibility alias for onboarding.attach_reference: attach a company reference document so Compass and Charters can cite it. It does not build a Compass.", "example": { "text": "# Company handbook\n\nWe build precision components for the aerospace supply chain.", "filename": "handbook.md", "mime_type": "text/markdown", "title": "Company handbook", "kind": "plan", "source": "handbook/overview.md" } },
|
|
@@ -47950,6 +48283,9 @@ function operationUsageLines(bin) {
|
|
|
47950
48283
|
`${bin} error list|resolve|supersede`,
|
|
47951
48284
|
`${bin} sync brief|compile|complete`,
|
|
47952
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>`,
|
|
47953
48289
|
`${bin} notification settings|test|errors`,
|
|
47954
48290
|
`${bin} settings get|update|product-learning|agent-policy|rename`,
|
|
47955
48291
|
`${bin} member invite|update|remove`,
|
|
@@ -48524,8 +48860,8 @@ function pendingConfirmationFromError(error51) {
|
|
|
48524
48860
|
import { execFile as execFileCallback2 } from "node:child_process";
|
|
48525
48861
|
import { access, readFile as readFile3 } from "node:fs/promises";
|
|
48526
48862
|
import { delimiter } from "node:path";
|
|
48527
|
-
import { promisify as
|
|
48528
|
-
var
|
|
48863
|
+
import { promisify as promisify2 } from "node:util";
|
|
48864
|
+
var execFile2 = promisify2(execFileCallback2);
|
|
48529
48865
|
async function runDoctor(options) {
|
|
48530
48866
|
const env = options.env ?? process.env;
|
|
48531
48867
|
const store = options.store ?? createTokenStore();
|
|
@@ -48573,7 +48909,7 @@ async function checkPath(env) {
|
|
|
48573
48909
|
}
|
|
48574
48910
|
async function probeCli(command) {
|
|
48575
48911
|
try {
|
|
48576
|
-
const { stdout, stderr } = await
|
|
48912
|
+
const { stdout, stderr } = await execFile2(command, ["--version"], { timeout: 1500 });
|
|
48577
48913
|
const version4 = (stdout || stderr).trim().split("\n")[0]?.trim();
|
|
48578
48914
|
return version4 ? `installed (${version4})` : "installed";
|
|
48579
48915
|
} catch {
|
|
@@ -48648,8 +48984,8 @@ import { chmod, mkdir as mkdir2, readFile as readFile4, rm as rm2, writeFile as
|
|
|
48648
48984
|
import { hostname as hostname3, tmpdir } from "node:os";
|
|
48649
48985
|
import path3 from "node:path";
|
|
48650
48986
|
import { setTimeout as sleep2 } from "node:timers/promises";
|
|
48651
|
-
import { promisify as
|
|
48652
|
-
var
|
|
48987
|
+
import { promisify as promisify3 } from "node:util";
|
|
48988
|
+
var execFile3 = promisify3(execFileCallback3);
|
|
48653
48989
|
async function runRunnerServe(options) {
|
|
48654
48990
|
const env = options.env ?? process.env;
|
|
48655
48991
|
const fetchImpl = options.fetchImpl ?? fetch;
|
|
@@ -48688,8 +49024,11 @@ ${usage()}
|
|
|
48688
49024
|
options.io.stdout.write(`heartbeat ok #${beat} runner_id=${state.runner_id} claude=${capabilities.claude.installed} codex=${capabilities.codex.installed}
|
|
48689
49025
|
`);
|
|
48690
49026
|
}
|
|
48691
|
-
if (config2.execute
|
|
48692
|
-
|
|
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
|
+
}
|
|
48693
49032
|
}
|
|
48694
49033
|
} catch (error51) {
|
|
48695
49034
|
failed = true;
|
|
@@ -48709,6 +49048,7 @@ function parseServeArgs(args, appUrl2, env) {
|
|
|
48709
49048
|
let heartbeatMs = Number(env.RUNNER_HEARTBEAT_MS ?? 3e4);
|
|
48710
49049
|
let execute2 = env.RUNNER_EXECUTE === "1";
|
|
48711
49050
|
let once = false;
|
|
49051
|
+
let runtime = parseRuntime(env.RUNNER_RUNTIME ?? "auto");
|
|
48712
49052
|
let userCode = env.RUNNER_USER_CODE;
|
|
48713
49053
|
for (let index = 0; index < args.length; index += 1) {
|
|
48714
49054
|
const token = args[index];
|
|
@@ -48724,6 +49064,9 @@ function parseServeArgs(args, appUrl2, env) {
|
|
|
48724
49064
|
} else if (token === "--user-code") {
|
|
48725
49065
|
userCode = requiredValue(args, index);
|
|
48726
49066
|
index += 1;
|
|
49067
|
+
} else if (token === "--runtime") {
|
|
49068
|
+
runtime = parseRuntime(requiredValue(args, index));
|
|
49069
|
+
index += 1;
|
|
48727
49070
|
} else if (token === "--execute") {
|
|
48728
49071
|
execute2 = true;
|
|
48729
49072
|
} else if (token === "--once") {
|
|
@@ -48735,7 +49078,19 @@ function parseServeArgs(args, appUrl2, env) {
|
|
|
48735
49078
|
if (!Number.isSafeInteger(heartbeatMs) || heartbeatMs < 1e3) {
|
|
48736
49079
|
throw new Error("--heartbeat-ms must be an integer >= 1000.");
|
|
48737
49080
|
}
|
|
48738
|
-
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;
|
|
48739
49094
|
}
|
|
48740
49095
|
function requiredValue(args, index) {
|
|
48741
49096
|
const value = args[index + 1];
|
|
@@ -48825,7 +49180,7 @@ async function detectCapabilities() {
|
|
|
48825
49180
|
}
|
|
48826
49181
|
async function probe(command) {
|
|
48827
49182
|
try {
|
|
48828
|
-
const { stdout, stderr } = await
|
|
49183
|
+
const { stdout, stderr } = await execFile3(command, ["--version"], { timeout: 1500 });
|
|
48829
49184
|
return { installed: true, version: (stdout || stderr).trim() || null, auth_state: "ready" };
|
|
48830
49185
|
} catch {
|
|
48831
49186
|
return { installed: false, version: null, auth_state: "unknown" };
|
|
@@ -48850,7 +49205,7 @@ async function post2(fetchImpl, appUrl2, pathName, body, token) {
|
|
|
48850
49205
|
}
|
|
48851
49206
|
return { status: response.status, json: json2 };
|
|
48852
49207
|
}
|
|
48853
|
-
async function claimAndExecute(fetchImpl, appUrl2, state, io) {
|
|
49208
|
+
async function claimAndExecute(fetchImpl, appUrl2, state, io, runtime) {
|
|
48854
49209
|
const claimed = await post2(fetchImpl, appUrl2, "/api/runner/work-orders/claim", {}, state.runner_secret);
|
|
48855
49210
|
const workOrder = claimed.json.work_order;
|
|
48856
49211
|
if (claimed.status !== 200 || !workOrder || typeof workOrder !== "object") {
|
|
@@ -48864,12 +49219,12 @@ async function claimAndExecute(fetchImpl, appUrl2, state, io) {
|
|
|
48864
49219
|
if (started.status !== 200) {
|
|
48865
49220
|
throw new Error(`work order start failed ${started.status}: ${redactForLog(JSON.stringify(started.json))}`);
|
|
48866
49221
|
}
|
|
48867
|
-
const result = await
|
|
49222
|
+
const result = await runLocalTurn(appUrl2, workOrder, runtime);
|
|
48868
49223
|
const reported = await post2(fetchImpl, appUrl2, `/api/runner/work-orders/${encodeURIComponent(workOrderId)}/result`, {
|
|
48869
49224
|
status: result.ok ? "succeeded" : "failed",
|
|
48870
49225
|
summary: result.summary,
|
|
48871
49226
|
log_ref: `runner-local:${workOrderId}`,
|
|
48872
|
-
model: "claude-cli"
|
|
49227
|
+
model: runtime === "codex" ? "codex-cli" : "claude-cli"
|
|
48873
49228
|
}, state.runner_secret);
|
|
48874
49229
|
if (reported.status !== 200) {
|
|
48875
49230
|
throw new Error(`work order result failed ${reported.status}: ${redactForLog(JSON.stringify(reported.json))}`);
|
|
@@ -48877,7 +49232,28 @@ async function claimAndExecute(fetchImpl, appUrl2, state, io) {
|
|
|
48877
49232
|
io.stdout.write(`work_order_id=${workOrderId} status=${result.ok ? "succeeded" : "failed"}
|
|
48878
49233
|
`);
|
|
48879
49234
|
}
|
|
48880
|
-
|
|
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) {
|
|
48881
49257
|
const mcp = typeof workOrder.mcp === "object" && workOrder.mcp !== null ? workOrder.mcp : {};
|
|
48882
49258
|
const token = typeof mcp.token === "string" ? mcp.token : "";
|
|
48883
49259
|
const url2 = typeof mcp.url === "string" ? `${appUrl2.replace(/\/+$/, "")}${mcp.url}` : `${appUrl2.replace(/\/+$/, "")}/mcp`;
|
|
@@ -48898,6 +49274,7 @@ async function runClaudeTurn(appUrl2, workOrder) {
|
|
|
48898
49274
|
await writeFile2(configPath, `${mcpConfig}
|
|
48899
49275
|
`, { mode: 384 });
|
|
48900
49276
|
await chmod(configPath, 384);
|
|
49277
|
+
const { command, args } = buildTurnCommand(runtime, prompt, configPath);
|
|
48901
49278
|
return new Promise((resolve2) => {
|
|
48902
49279
|
let settled = false;
|
|
48903
49280
|
const finish = (result) => {
|
|
@@ -48908,7 +49285,7 @@ async function runClaudeTurn(appUrl2, workOrder) {
|
|
|
48908
49285
|
void rm2(configPath, { force: true });
|
|
48909
49286
|
resolve2(result);
|
|
48910
49287
|
};
|
|
48911
|
-
const child = spawn(
|
|
49288
|
+
const child = spawn(command, args, {
|
|
48912
49289
|
cwd: tmpdir(),
|
|
48913
49290
|
stdio: ["ignore", "pipe", "pipe"]
|
|
48914
49291
|
});
|
|
@@ -48935,7 +49312,431 @@ async function runClaudeTurn(appUrl2, workOrder) {
|
|
|
48935
49312
|
});
|
|
48936
49313
|
}
|
|
48937
49314
|
function usage() {
|
|
48938
|
-
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, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
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)}`);
|
|
48939
49740
|
}
|
|
48940
49741
|
|
|
48941
49742
|
// src/index.ts
|
|
@@ -49085,6 +49886,9 @@ async function main(argv = process.argv.slice(2), options = {}) {
|
|
|
49085
49886
|
env: process.env
|
|
49086
49887
|
});
|
|
49087
49888
|
}
|
|
49889
|
+
if (command === "runner" && isRunnerServiceInvocation(args)) {
|
|
49890
|
+
return runRunnerService({ io, args, appUrl: config2.appUrl });
|
|
49891
|
+
}
|
|
49088
49892
|
if (!session) {
|
|
49089
49893
|
io.stderr.write(`Not logged in. Run ${cliBrand.binName} login first.
|
|
49090
49894
|
`);
|
|
@@ -49562,7 +50366,7 @@ function isCliEntrypoint(moduleUrl, argvPath) {
|
|
|
49562
50366
|
return false;
|
|
49563
50367
|
}
|
|
49564
50368
|
try {
|
|
49565
|
-
return
|
|
50369
|
+
return realpathSync2(fileURLToPath3(moduleUrl)) === realpathSync2(argvPath);
|
|
49566
50370
|
} catch {
|
|
49567
50371
|
return moduleUrl === pathToFileURL(argvPath).href;
|
|
49568
50372
|
}
|