@remits/remits-cli 0.1.164 → 0.1.166
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/README.md +2 -1
- package/index.js +142 -30
- package/package.json +1 -1
- package/skills/remits-cli/SKILL.md +21 -4
- package/skills/remits-cli/references/cli-state.md +13 -5
- package/skills/remits-cli/references/command-reference.md +7 -5
- package/skills/remits-cli/references/development-loop.md +11 -2
- package/skills/remits-cli/references/tool-reference.md +25 -0
- package/skills/remits-cli/references/troubleshooting.md +1 -1
package/README.md
CHANGED
|
@@ -101,7 +101,8 @@ remits-cli install --skills --overwrite true
|
|
|
101
101
|
- Auth sessions are stored per `accountId + dataMode + baseUrl`, so the same account can stay authenticated against both localhost and production without overwriting the other session.
|
|
102
102
|
- Active verification envelopes are scoped per `baseUrl + accountId + branch + workspace + dataMode`, so a localhost envelope does not silently capture evidence from a production command. Pass `--verify-envelope <id>` for an intentional cross-context attachment.
|
|
103
103
|
- `--base-url` and `--data-mode` are independent. `--base-url` chooses the Remits host (`http://localhost:8080` vs deployed prod), while `--data-mode` chooses the data segment on that host (`test` vs `prod`). Do not assume `--data-mode prod` means the deployed prod host, or that `--data-mode test` means localhost.
|
|
104
|
-
- `remits-cli start` scans
|
|
104
|
+
- `remits-cli start` scans for `account-info.json` files and updates `~/.remits-cli/account-repos.json` before bringing up the background service. While running, it rescans every five minutes using asynchronous directory reads, then reports the refreshed inventory to the platform. Repos cloned outside the CLI are discovered without a restart or a command run inside them. Both scans use `REMITS_CLI_SCAN_ROOTS` (default: the home directory), the same depth limit and directory exclusions. For immediate discovery, use `remits-cli agent repos --rescan` or **Rescan Account Repos** in the control center.
|
|
105
|
+
- Scans preserve manually selected repository directories and record competing checkouts as ambiguity. `remits-cli agent repos --account-id <id> --forget` suppresses rediscovery until an explicit registration (`--directory <path>`) or a CLI command run from that account repo restores it.
|
|
105
106
|
- Front-stage guides are **not** bundled with the CLI npm package. For account-work commands (`components`, `test`, `token`, `tools`, `tool`) and on `auth`, `remits-cli` downloads the latest guides from the authenticated `/cli/guides` endpoint and writes them into the current account repo: root `platform-overview.md` / `development-guide.md`, `guides/*.md`, `guides/features/*.md`, and the agent-guidance files `CLAUDE.md` / `AGENTS.md` / `GEMINI.md` (from `docs/front-stage/remits-components-zip-readme.md`).
|
|
106
107
|
- Guide sync requires authentication. If no valid session exists and the terminal is interactive, the CLI auto-authenticates first; in non-interactive contexts (CI, agent sandboxes) it skips silently so guides are only ever delivered to users who can authenticate to the platform.
|
|
107
108
|
- On every command, `remits-cli` refreshes the installed remits-cli `SKILL.md` and reference files for `claude`, `codex`, `gemini`, and `antigravity` so local agents stay on the latest packaged instructions.
|
package/index.js
CHANGED
|
@@ -4,18 +4,18 @@
|
|
|
4
4
|
## Table of Contents
|
|
5
5
|
|
|
6
6
|
- L22 Runtime Bootstrap And Shared State
|
|
7
|
-
-
|
|
8
|
-
-
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
16
|
-
-
|
|
17
|
-
-
|
|
18
|
-
-
|
|
7
|
+
- L126 Sessions, Account Resolution, And Production Guards
|
|
8
|
+
- L632 Local State, Workspaces, And Verification Evidence
|
|
9
|
+
- L2670 Account Repos, Guide Sync, And Platform Repo
|
|
10
|
+
- L3569 Component Discovery And HTTP Logging
|
|
11
|
+
- L4339 Skill Delivery And TOC Resolution
|
|
12
|
+
- L4654 Auth And Component Staging
|
|
13
|
+
- L5286 Component Summaries, Status, And Sync Gates
|
|
14
|
+
- L7610 Branches, Promotion, Commit, And Test Runs
|
|
15
|
+
- L9873 Tokens, Tools, Verification, And Config
|
|
16
|
+
- L11390 Service Dashboard And WebSocket Listener
|
|
17
|
+
- L13900 Agent And Ticket Workflows
|
|
18
|
+
- L18202 Help, Auto Update, And Command Dispatch
|
|
19
19
|
*/
|
|
20
20
|
|
|
21
21
|
/*
|
|
@@ -75,6 +75,7 @@ const DASHBOARD_HOST = '127.0.0.1';
|
|
|
75
75
|
const DEFAULT_DASHBOARD_PORT = Number(process.env.REMITS_CLI_DASHBOARD_PORT || 8787);
|
|
76
76
|
const DEFAULT_SCAN_MAX_DEPTH = Number(process.env.REMITS_CLI_SCAN_MAX_DEPTH || 8);
|
|
77
77
|
const SERVICE_MAINTENANCE_INTERVAL_MS = Number(process.env.REMITS_CLI_SERVICE_MAINTENANCE_INTERVAL_MS || 15000);
|
|
78
|
+
const ACCOUNT_REPO_RESCAN_INTERVAL_MS = 5 * 60 * 1000;
|
|
78
79
|
const WEBSOCKET_RECONNECT_DELAY_MS = Number(process.env.REMITS_CLI_WEBSOCKET_RECONNECT_DELAY_MS || 5000);
|
|
79
80
|
const WEBSOCKET_HEARTBEAT_MS = Number(process.env.REMITS_CLI_WEBSOCKET_HEARTBEAT_MS || 10000);
|
|
80
81
|
const WEBSOCKET_CONNECTION_TIMEOUT_MS = Number(process.env.REMITS_CLI_WEBSOCKET_CONNECTION_TIMEOUT_MS || 15000);
|
|
@@ -899,7 +900,7 @@ function localStateWarnings(cwd, flags = {}) {
|
|
|
899
900
|
.sort((a, b) => Date.parse(b.updatedAt || 0) - Date.parse(a.updatedAt || 0));
|
|
900
901
|
const shown = recent.slice(0, 3).map((a) => a.actor);
|
|
901
902
|
const stale = report.foreignActors.length - recent.length;
|
|
902
|
-
const setOne = 'Name this one with --
|
|
903
|
+
const setOne = 'Name this one with --local-agent <name> (or REMITS_LOCAL_AGENT_ID) if another agent shares this checkout; REMITS_AGENT_ID also declares agent identity to the platform.';
|
|
903
904
|
const pruneHint = stale
|
|
904
905
|
? stale + ' spent actor dir(s) (idle, or left behind by finished processes) can be cleared with `remits-cli doctor local-state --prune-actors`.'
|
|
905
906
|
: '';
|
|
@@ -3253,7 +3254,8 @@ function countAccountRepos(index) {
|
|
|
3253
3254
|
*/
|
|
3254
3255
|
function mergeAccountRepoEntries(entries, options = {}) {
|
|
3255
3256
|
const nextIndex = options.existingIndex ? { ...options.existingIndex } : {};
|
|
3256
|
-
const
|
|
3257
|
+
const sourceRank = (entry) => entry.source === 'manual' ? 2 : (entry.source === 'cwd' ? 1 : 0);
|
|
3258
|
+
const rank = (entry) => (isAccountCheckout(entry.directory) ? 3 : 0) + sourceRank(entry);
|
|
3257
3259
|
|
|
3258
3260
|
for (const entry of entries) {
|
|
3259
3261
|
if (!entry || !entry.accountId || !entry.directory) {
|
|
@@ -3261,8 +3263,15 @@ function mergeAccountRepoEntries(entries, options = {}) {
|
|
|
3261
3263
|
}
|
|
3262
3264
|
const key = String(entry.accountId);
|
|
3263
3265
|
const current = nextIndex[key];
|
|
3266
|
+
// Forget is an explicit opt-out, not a temporary absence until the next service scan.
|
|
3267
|
+
if (current && current.source === 'forgotten' && entry.source === 'scan') continue;
|
|
3264
3268
|
if (!current || !current.directory || current.directory === entry.directory) {
|
|
3265
|
-
nextIndex[key] = {
|
|
3269
|
+
nextIndex[key] = {
|
|
3270
|
+
...entry,
|
|
3271
|
+
source: current && current.directory === entry.directory && sourceRank(current) > sourceRank(entry)
|
|
3272
|
+
? current.source : entry.source,
|
|
3273
|
+
ambiguousWith: (current && current.ambiguousWith) || []
|
|
3274
|
+
};
|
|
3266
3275
|
continue;
|
|
3267
3276
|
}
|
|
3268
3277
|
|
|
@@ -3305,19 +3314,17 @@ function defaultAccountScanRoots() {
|
|
|
3305
3314
|
return [home].filter((dir) => fs.existsSync(dir));
|
|
3306
3315
|
}
|
|
3307
3316
|
|
|
3308
|
-
|
|
3317
|
+
// Share traversal rules between the synchronous command path and asynchronous service scans.
|
|
3318
|
+
// The driver supplies directory entries so the service never blocks its WebSocket on readdirSync.
|
|
3319
|
+
function* accountRepoScanSteps(dir, depth, matches, visited, platformMatches) {
|
|
3309
3320
|
const resolvedDir = path.resolve(dir);
|
|
3310
3321
|
if (visited.has(resolvedDir)) {
|
|
3311
3322
|
return;
|
|
3312
3323
|
}
|
|
3313
3324
|
visited.add(resolvedDir);
|
|
3314
3325
|
|
|
3315
|
-
|
|
3316
|
-
|
|
3317
|
-
entries = fs.readdirSync(resolvedDir, { withFileTypes: true });
|
|
3318
|
-
} catch (_) {
|
|
3319
|
-
return;
|
|
3320
|
-
}
|
|
3326
|
+
const entries = yield resolvedDir;
|
|
3327
|
+
if (!entries) return;
|
|
3321
3328
|
|
|
3322
3329
|
const names = new Set(entries.map((entry) => entry.name));
|
|
3323
3330
|
for (const entry of entries) {
|
|
@@ -3347,7 +3354,17 @@ function walkForAccountInfo(dir, depth, matches, visited, platformMatches) {
|
|
|
3347
3354
|
if (entry.name.startsWith('.') && entry.name !== '.config') {
|
|
3348
3355
|
continue;
|
|
3349
3356
|
}
|
|
3350
|
-
|
|
3357
|
+
yield* accountRepoScanSteps(path.join(resolvedDir, entry.name), depth + 1, matches, visited, platformMatches);
|
|
3358
|
+
}
|
|
3359
|
+
}
|
|
3360
|
+
|
|
3361
|
+
function walkForAccountInfo(dir, depth, matches, visited, platformMatches) {
|
|
3362
|
+
const steps = accountRepoScanSteps(dir, depth, matches, visited, platformMatches);
|
|
3363
|
+
let step = steps.next();
|
|
3364
|
+
while (!step.done) {
|
|
3365
|
+
let entries = null;
|
|
3366
|
+
try { entries = fs.readdirSync(step.value, { withFileTypes: true }); } catch (_) { /* unreadable */ }
|
|
3367
|
+
step = steps.next(entries);
|
|
3351
3368
|
}
|
|
3352
3369
|
}
|
|
3353
3370
|
|
|
@@ -3359,7 +3376,29 @@ function discoverAccountRepos() {
|
|
|
3359
3376
|
for (const root of roots) {
|
|
3360
3377
|
walkForAccountInfo(root, 0, matches, visited, platformMatches);
|
|
3361
3378
|
}
|
|
3379
|
+
return reconcileDiscoveredAccountRepos(roots, matches, platformMatches);
|
|
3380
|
+
}
|
|
3381
|
+
|
|
3382
|
+
async function discoverAccountReposInBackground() {
|
|
3383
|
+
const matches = [];
|
|
3384
|
+
const platformMatches = [];
|
|
3385
|
+
const visited = new Set();
|
|
3386
|
+
const roots = defaultAccountScanRoots();
|
|
3387
|
+
for (const root of roots) {
|
|
3388
|
+
const steps = accountRepoScanSteps(root, 0, matches, visited, platformMatches);
|
|
3389
|
+
let step = steps.next();
|
|
3390
|
+
while (!step.done) {
|
|
3391
|
+
let entries = null;
|
|
3392
|
+
try { entries = await fs.promises.readdir(step.value, { withFileTypes: true }); } catch (_) { /* unreadable */ }
|
|
3393
|
+
step = steps.next(entries);
|
|
3394
|
+
}
|
|
3395
|
+
}
|
|
3396
|
+
// Read the latest index only at reconciliation: CLI commands may register a repo during the scan.
|
|
3397
|
+
// Background maintenance discovers existing files; it must never clone or pull repositories.
|
|
3398
|
+
return reconcileDiscoveredAccountRepos(roots, matches, platformMatches, { background: true });
|
|
3399
|
+
}
|
|
3362
3400
|
|
|
3401
|
+
function reconcileDiscoveredAccountRepos(roots, matches, platformMatches, options = {}) {
|
|
3363
3402
|
const entries = [];
|
|
3364
3403
|
for (const file of matches) {
|
|
3365
3404
|
try {
|
|
@@ -3380,7 +3419,7 @@ function discoverAccountRepos() {
|
|
|
3380
3419
|
const found = platformMatches.find(looksLikePlatformRepo);
|
|
3381
3420
|
if (found) {
|
|
3382
3421
|
setPlatformRepoEntry(found, 'scan');
|
|
3383
|
-
} else {
|
|
3422
|
+
} else if (!options.background) {
|
|
3384
3423
|
try { ensurePlatformRepoLocal({ allowClone: true }); } catch (_) { /* best-effort */ }
|
|
3385
3424
|
}
|
|
3386
3425
|
}
|
|
@@ -3395,6 +3434,27 @@ function discoverAccountRepos() {
|
|
|
3395
3434
|
};
|
|
3396
3435
|
}
|
|
3397
3436
|
|
|
3437
|
+
let accountRepoDiscoveryInFlight = null;
|
|
3438
|
+
let lastAccountRepoDiscoveryAttemptAt = 0;
|
|
3439
|
+
|
|
3440
|
+
function refreshAccountReposIfDue(now = Date.now()) {
|
|
3441
|
+
if (accountRepoDiscoveryInFlight) return accountRepoDiscoveryInFlight;
|
|
3442
|
+
const lastScanAt = Date.parse(runtimeState.discovery.scannedAt || '') || 0;
|
|
3443
|
+
if (now - Math.max(lastScanAt, lastAccountRepoDiscoveryAttemptAt) < ACCOUNT_REPO_RESCAN_INTERVAL_MS) return;
|
|
3444
|
+
lastAccountRepoDiscoveryAttemptAt = now;
|
|
3445
|
+
accountRepoDiscoveryInFlight = discoverAccountReposInBackground()
|
|
3446
|
+
.then((summary) => {
|
|
3447
|
+
recordDiscoverySummary(summary);
|
|
3448
|
+
// Publish the refreshed inventory on the next maintenance tick, even if presence isn't due yet.
|
|
3449
|
+
for (const managedClient of managedClients) managedClient.lastPresenceAt = 0;
|
|
3450
|
+
})
|
|
3451
|
+
.catch((err) => {
|
|
3452
|
+
appendGlobalActivityLog('listener', 'repos.discovery.failed', { error: describeError(err) }, 'error');
|
|
3453
|
+
})
|
|
3454
|
+
.finally(() => { accountRepoDiscoveryInFlight = null; });
|
|
3455
|
+
return accountRepoDiscoveryInFlight;
|
|
3456
|
+
}
|
|
3457
|
+
|
|
3398
3458
|
function resolvePreferredAccountId(cwd, flags, session) {
|
|
3399
3459
|
// Explicit --account-id wins over the repo's account-info.json so users can
|
|
3400
3460
|
// target a child account (e.g. a customer) from a parent-account repo.
|
|
@@ -3669,7 +3729,9 @@ function collectComponents(cwd) {
|
|
|
3669
3729
|
component.html = content;
|
|
3670
3730
|
} else if (type === 'htmltemplate' && ext === 'json') {
|
|
3671
3731
|
component.previewData = content;
|
|
3672
|
-
} else if (type === 'tool' && ext === 'json') {
|
|
3732
|
+
} else if ((type === 'tool' || type === 'action') && ext === 'json') {
|
|
3733
|
+
// An Action's .json sidecar is its input schema (manual-run form and x-remits-operations), exactly as
|
|
3734
|
+
// repository sync maps it; staging used to drop it, so a staged declaration silently did not apply.
|
|
3673
3735
|
component.inputSchema = content;
|
|
3674
3736
|
} else if (type === 'i18n' && ext === 'json') {
|
|
3675
3737
|
component.messages = content;
|
|
@@ -3850,7 +3912,7 @@ function componentPathInfo(cwd, filePath) {
|
|
|
3850
3912
|
else if (type === 'embeddable' && ext === 'js') field = 'javascript';
|
|
3851
3913
|
else if (type === 'htmltemplate' && ext === 'html') field = 'html';
|
|
3852
3914
|
else if (type === 'htmltemplate' && ext === 'json') field = 'previewData';
|
|
3853
|
-
else if (type === 'tool' && ext === 'json') field = 'inputSchema';
|
|
3915
|
+
else if ((type === 'tool' || type === 'action') && ext === 'json') field = 'inputSchema';
|
|
3854
3916
|
else if (type === 'i18n' && ext === 'json') field = 'messages';
|
|
3855
3917
|
else if (type === 'schema' && ext === 'json') field = 'schema';
|
|
3856
3918
|
|
|
@@ -9130,10 +9192,18 @@ async function waitForToolStatus(api, cwd, options) {
|
|
|
9130
9192
|
|
|
9131
9193
|
// One compact progress line for a streamed TestSuite frame: a finished case, or the suite roll-up.
|
|
9132
9194
|
function testSuiteProgressLine(payload = {}) {
|
|
9195
|
+
if (payload.event === 'case_started') {
|
|
9196
|
+
return '[ws] running ' + (payload.activeCases || []).map((c) =>
|
|
9197
|
+
c.name + (c.traceId ? ' [trace ' + c.traceId + ']' : '')).join(', ');
|
|
9198
|
+
}
|
|
9133
9199
|
if (payload.total != null || Array.isArray(payload.tests)) {
|
|
9134
9200
|
const total = payload.total != null ? payload.total : (payload.tests || []).length;
|
|
9135
9201
|
const passed = payload.passed != null ? payload.passed : (payload.tests || []).filter((t) => t && t.passed).length;
|
|
9136
|
-
|
|
9202
|
+
const active = (payload.activeCases || []).map((c) => c.name + (c.traceId ? ' [trace ' + c.traceId + ']' : ''));
|
|
9203
|
+
const queued = (payload.shards || []).filter((s) => s.status === 'queued').length;
|
|
9204
|
+
return '[ws] ' + (payload.testName || 'suite') + ': ' + passed + '/' + total + ' passed' +
|
|
9205
|
+
(payload.status ? ' (' + payload.status + ')' : '') +
|
|
9206
|
+
(active.length ? '; running: ' + active.join(', ') : '') + (queued ? '; ' + queued + ' workers queued' : '');
|
|
9137
9207
|
}
|
|
9138
9208
|
const mark = payload.passed === false ? 'FAIL' : 'pass';
|
|
9139
9209
|
const detail = payload.outcome && payload.outcome !== 'passed' ? ' [' + payload.outcome + ']' : '';
|
|
@@ -9172,7 +9242,7 @@ function watchWebsocket(baseUrl, topicId, taskId, options = {}) {
|
|
|
9172
9242
|
if (payload.taskId && followed.length && !followed.includes(payload.taskId)) {
|
|
9173
9243
|
return;
|
|
9174
9244
|
}
|
|
9175
|
-
if (payload.tests || payload.name || payload.total != null) {
|
|
9245
|
+
if (payload.tests || payload.name || payload.total != null || payload.event === 'case_started') {
|
|
9176
9246
|
// Progress, not the payload. This used to print the whole suite result — every case, every
|
|
9177
9247
|
// stacktrace — as one enormous line immediately before the readable summary said the same thing,
|
|
9178
9248
|
// which buried the run world, the outcomes and the pivots an agent actually reads. --verbose still
|
|
@@ -9345,6 +9415,7 @@ async function testCommand(flags) {
|
|
|
9345
9415
|
workspace,
|
|
9346
9416
|
dataMode,
|
|
9347
9417
|
dataModeSource,
|
|
9418
|
+
caseConcurrency: flags.parallel !== undefined && runOptions.parallel === 1 ? 1 : undefined,
|
|
9348
9419
|
testId: /^\d+$/.test(String(testRef)) ? Number(testRef) : undefined,
|
|
9349
9420
|
testName: /^\d+$/.test(String(testRef)) ? undefined : String(testRef),
|
|
9350
9421
|
tests: names
|
|
@@ -15900,6 +15971,7 @@ async function ticketCommand(flags, subcommand) {
|
|
|
15900
15971
|
requiresRepositoryWork: firstDefined(flags['repo-work'], flags['requires-repository-work']),
|
|
15901
15972
|
tags: flags.tags ? String(flags.tags).split(',').map((v) => v.trim()).filter(Boolean) : undefined,
|
|
15902
15973
|
label: flags.label,
|
|
15974
|
+
...(action === 'artifact' ? ticketArtifactFields(flags) : {}),
|
|
15903
15975
|
url: flags.url,
|
|
15904
15976
|
body: flags.body,
|
|
15905
15977
|
role: flags.role,
|
|
@@ -16645,7 +16717,7 @@ function printTicketHelp() {
|
|
|
16645
16717
|
console.log(' tag --tags a,b Mark it, for a HUMAN reading the queue. When the');
|
|
16646
16718
|
console.log(' tickets genuinely share a cause, use `relate` instead or as well: the platform reads');
|
|
16647
16719
|
console.log(' that one, and a tag it cannot read will not stop a cluster being worked N times.');
|
|
16648
|
-
console.log(' artifact --type TYPE --label "..."
|
|
16720
|
+
console.log(' artifact --type TYPE --label "..." (--content "..." | --file PATH | --url U) [--filename F] [--content-type T]');
|
|
16649
16721
|
console.log(' Attach evidence — a log, a query result, a repro. Better than pasting it into a');
|
|
16650
16722
|
console.log(' worklog summary, where it is neither typed nor retrievable.');
|
|
16651
16723
|
console.log(' note --message "..." An INTERNAL note. The next worker and a reviewer');
|
|
@@ -16690,6 +16762,44 @@ function printTicketHelp() {
|
|
|
16690
16762
|
* One helper so the lease, the claim and the registration cannot describe this process three ways —
|
|
16691
16763
|
* the same reason the platform builds the tuple in exactly one class (AgentRunContext).
|
|
16692
16764
|
*/
|
|
16765
|
+
/**
|
|
16766
|
+
* The evidence half of `ticket artifact`. Bytes always travel as `contentBase64`: it is the one form every
|
|
16767
|
+
* deployed platform accepts (JSON cannot carry a byte[], and older platforms ignored a `content` string), so
|
|
16768
|
+
* `--content "text"` and `--file path` behave the same against every host. The help promised `--type` and
|
|
16769
|
+
* `--content` for a long time while neither was sent, and every artifact was refused as `type [null]`; the
|
|
16770
|
+
* refusals here name the missing flag before anything reaches the platform.
|
|
16771
|
+
*/
|
|
16772
|
+
function ticketArtifactFields(flags = {}, readFile = (p) => fs.readFileSync(p)) {
|
|
16773
|
+
const text = (v) => (typeof v === 'string' && v.trim() ? v : undefined);
|
|
16774
|
+
const type = text(flags.type);
|
|
16775
|
+
if (!type) {
|
|
16776
|
+
throw new Error('ticket artifact needs --type (screenshot, pdf, trace, video, log, test_result, link or other).');
|
|
16777
|
+
}
|
|
16778
|
+
if (!text(flags.label)) {
|
|
16779
|
+
throw new Error('ticket artifact needs --label "...".');
|
|
16780
|
+
}
|
|
16781
|
+
const fields = { type, filename: text(flags.filename), contentType: text(flags['content-type'] || flags.contentType) };
|
|
16782
|
+
const filePath = text(flags.file);
|
|
16783
|
+
const content = typeof flags.content === 'string' ? flags.content : undefined;
|
|
16784
|
+
if (filePath) {
|
|
16785
|
+
const bytes = readFile(filePath);
|
|
16786
|
+
fields.contentBase64 = Buffer.from(bytes).toString('base64');
|
|
16787
|
+
fields.filename = fields.filename || path.basename(filePath);
|
|
16788
|
+
if (!fields.contentType) {
|
|
16789
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
16790
|
+
fields.contentType = ({ '.png': 'image/png', '.jpg': 'image/jpeg', '.jpeg': 'image/jpeg', '.pdf': 'application/pdf',
|
|
16791
|
+
'.json': 'application/json', '.zip': 'application/zip', '.webm': 'video/webm', '.html': 'text/html',
|
|
16792
|
+
'.md': 'text/markdown', '.txt': 'text/plain', '.log': 'text/plain' })[ext];
|
|
16793
|
+
}
|
|
16794
|
+
} else if (content !== undefined) {
|
|
16795
|
+
fields.contentBase64 = Buffer.from(content, 'utf8').toString('base64');
|
|
16796
|
+
fields.contentType = fields.contentType || 'text/plain; charset=utf-8';
|
|
16797
|
+
} else if (!text(flags.url)) {
|
|
16798
|
+
throw new Error('ticket artifact needs something to attach: --content "...", --file PATH or --url URL.');
|
|
16799
|
+
}
|
|
16800
|
+
return fields;
|
|
16801
|
+
}
|
|
16802
|
+
|
|
16693
16803
|
function runContextPayload(flags = {}) {
|
|
16694
16804
|
const cwd = process.cwd();
|
|
16695
16805
|
return {
|
|
@@ -17493,7 +17603,7 @@ async function agentReposCommand(flags) {
|
|
|
17493
17603
|
if (Number.isFinite(accountId) && accountId > 0 && flagEnabled(flags.forget)) {
|
|
17494
17604
|
const index = readAccountRepoIndex();
|
|
17495
17605
|
const previous = index[String(accountId)];
|
|
17496
|
-
|
|
17606
|
+
index[String(accountId)] = { accountId, source: 'forgotten', updatedAt: new Date().toISOString() };
|
|
17497
17607
|
writeAccountRepoIndex(index);
|
|
17498
17608
|
console.log(previous
|
|
17499
17609
|
? 'account ' + accountId + ' removed (was ' + previous.directory + '). Its tickets will now park ' +
|
|
@@ -18001,6 +18111,8 @@ async function listenCommand(flags) {
|
|
|
18001
18111
|
activeSessionSignature = currentSignature;
|
|
18002
18112
|
}
|
|
18003
18113
|
|
|
18114
|
+
// Do not await the filesystem scan: a slow/unavailable volume must not stall socket upkeep.
|
|
18115
|
+
refreshAccountReposIfDue();
|
|
18004
18116
|
await runManagedWebsocketWatchdog();
|
|
18005
18117
|
updateRuntimeServiceState();
|
|
18006
18118
|
} catch (err) {
|
package/package.json
CHANGED
|
@@ -96,6 +96,13 @@ reference named after it.
|
|
|
96
96
|
"branch" or "verified"; say what was actually proven: Test pass, browser journey, corpus measurement,
|
|
97
97
|
sync mutation, or ticket lifecycle state. (`component-resolution.md`, `branch-variants.md`,
|
|
98
98
|
`development-loop.md`, `support-tickets.md`)
|
|
99
|
+
- **There are no environments to deploy to, so translate SDLC words before acting.** "Let me try it" means a
|
|
100
|
+
token link to your staged lane. "Put it in UAT" means a sync to the pre-production component branch, or
|
|
101
|
+
a link if UAT is a link on this account. "Push to prod" means the promotion loop. "Roll it back" means
|
|
102
|
+
`git revert` plus a sync, and it does not undo emails or external writes. People who simply sign in never
|
|
103
|
+
see staged code. Syncing a branch others use, every promotion gate, and a trunk edit on a release-train
|
|
104
|
+
account each need the person's go for that step. (the account repo's core platform guide → *How A Change Reaches People*,
|
|
105
|
+
`branch-variants.md`)
|
|
99
106
|
- **Embeddable verification is browser verification.** For page, widget, form, upload, or other
|
|
100
107
|
browser-facing changes, mint a URL with `remits-cli token` and drive it with `playwright-cli`.
|
|
101
108
|
Prefer this over `curl`: `curl` can prove an HTTP response or inspect an action payload, but it does
|
|
@@ -181,10 +188,15 @@ reference named after it.
|
|
|
181
188
|
from `./.remits-cli/actors/<local-agent>/tool-responses/<callId>.json` (or the legacy flat fallback
|
|
182
189
|
if `doctor local-state` says the response is there) before reporting the work as done.
|
|
183
190
|
(`tool-reference.md`)
|
|
184
|
-
- **Workspace
|
|
185
|
-
|
|
186
|
-
mirrors
|
|
187
|
-
`remits-cli doctor local-state` when inheriting one.
|
|
191
|
+
- **Workspace, local actor and agent identity are three different things.** `workspace` is the
|
|
192
|
+
server-side staging lane. The **local actor** is only the repo-local folder for session logs, tool
|
|
193
|
+
responses and verification mirrors: name it with `--local-agent <name>` or `REMITS_LOCAL_AGENT_ID` when
|
|
194
|
+
agents share a checkout, and run `remits-cli doctor local-state` when inheriting one. **Agent identity**
|
|
195
|
+
(`--agent-id` / `REMITS_AGENT_ID`) is what the PLATFORM sees: it is sent on every call, it makes the
|
|
196
|
+
call an `agent` to the account's policy rules, and it decides ticket ownership. `REMITS_AGENT_ID` also
|
|
197
|
+
names the local folder when no local actor is set, which is why setting it "just for the folder" changes
|
|
198
|
+
how the platform treats you. If you are an AI agent, setting it is correct: say who you are, and let the
|
|
199
|
+
account's agent rules apply to you. (`cli-state.md`, `agent-sessions.md`)
|
|
188
200
|
- **Writing the code is not finishing the job.** A change is complete when it is verified — a Test
|
|
189
201
|
component (preferred, because it becomes regression protection) or a browser flow through
|
|
190
202
|
`remits-cli token`. "Just do it" and "that's fine, commit it" are not evidence. If you genuinely
|
|
@@ -232,6 +244,11 @@ reference named after it.
|
|
|
232
244
|
- **Reading and investigating are parallel-safe; editing one account's repository is exclusive**, via a
|
|
233
245
|
lease. A refusal is not an error — carry on read-only and hand off with `ticket progress --next-step`.
|
|
234
246
|
Never wait or poll for a lease. (`support-tickets.md`)
|
|
247
|
+
- **`Refused by this account's '<rule>' rule` is a POLICY decision, not a missing capability.** The
|
|
248
|
+
sentence says who the call presented as (`[This call presents as agent (agentId=…)]`). Find the rule in
|
|
249
|
+
the account's `OPERATIONS.md` `remits-policy` block, report its id, its `when`/`forbid`, and why your call
|
|
250
|
+
matched, then ask a person. Never generalize it ("syncs are impossible here"), and never drop your agent
|
|
251
|
+
identity to get past it. (`support-tickets.md`)
|
|
235
252
|
- **A brief's `## Rules you will be held to` section is ENFORCED, not requested.** The platform refuses
|
|
236
253
|
the verb and the message says what to do instead — act on that sentence rather than retrying or
|
|
237
254
|
looking for another door. A `[require]` rule needs a person; hand off. (`support-tickets.md`)
|
|
@@ -76,13 +76,19 @@ Think about remits-cli as two cooperating layers:
|
|
|
76
76
|
- where a large tool response was written
|
|
77
77
|
- which tool schemas were most recently cached here
|
|
78
78
|
|
|
79
|
-
Per-repo state has two independent namespaces:
|
|
79
|
+
Per-repo state has two independent namespaces, and one identity sits beside them:
|
|
80
80
|
|
|
81
81
|
- **Workspace** (`--workspace`, `REMITS_WORKSPACE`, `.remits-cli/workspace`) chooses the server-side
|
|
82
82
|
Redis staging lane. It changes what staged entries a run resolves.
|
|
83
|
-
- **Local actor** (`--local-agent`, `REMITS_AGENT_ID`,
|
|
84
|
-
namespace under `.remits-cli/actors/<local-agent>/`. It
|
|
85
|
-
verification mirrors, and diagnostics are written.
|
|
83
|
+
- **Local actor** (`--local-agent` / `--actor`, then `REMITS_AGENT_ID`, then `REMITS_LOCAL_AGENT_ID`, then a
|
|
84
|
+
generated fallback) chooses the local filesystem namespace under `.remits-cli/actors/<local-agent>/`. It
|
|
85
|
+
changes where session logs, tool responses, verification mirrors, and diagnostics are written. To name
|
|
86
|
+
only the folder, use `--local-agent` or `REMITS_LOCAL_AGENT_ID`.
|
|
87
|
+
- **Agent identity** (`--agent-id`, then `REMITS_AGENT_ID`) is not local state. The CLI sends it to the
|
|
88
|
+
platform on every call (`X-Remits-Agent-Id` and the payload `agentId`), so the call is an `agent` to the
|
|
89
|
+
account's `remits-policy` rules, owns the tickets it claims, and is attributed to that agent. Without it
|
|
90
|
+
the call is the signed-in person. `REMITS_AGENT_ID` therefore does two jobs, folder and identity; an AI
|
|
91
|
+
agent should set it deliberately, as identity.
|
|
86
92
|
|
|
87
93
|
Run `remits-cli doctor local-state` to print the active actor, state directory, workspace source,
|
|
88
94
|
foreign actors, git-tracked `.remits-cli` files, legacy flat state, and large/suspicious logs.
|
|
@@ -108,7 +114,7 @@ Agents should use this mental model before guessing.
|
|
|
108
114
|
|
|
109
115
|
The account repository index file is located at: `{{ACCOUNT_REPO_INDEX_PATH}}`
|
|
110
116
|
|
|
111
|
-
This JSON file is automatically maintained by `remits-cli` and tracks
|
|
117
|
+
This JSON file is automatically maintained by `remits-cli` and tracks known Remits account repositories on this machine. It is updated when a CLI command runs from an account repo directory, at service startup, and by background discovery every five minutes while the service runs. Background scans use the same configured roots, depth limit and exclusions as startup; they report newly discovered repos to the platform without restarting. Use `remits-cli agent repos --rescan` for immediate discovery. The file maps account IDs to their metadata:
|
|
112
118
|
|
|
113
119
|
```json
|
|
114
120
|
{
|
|
@@ -127,6 +133,8 @@ This JSON file is automatically maintained by `remits-cli` and tracks all known
|
|
|
127
133
|
- Resolve account names and IDs without making remote API calls
|
|
128
134
|
- Support cross-account workflows where an agent in one repo needs context from another
|
|
129
135
|
|
|
136
|
+
Manual directory selections survive rescans; competing checkouts are recorded as ambiguity. `agent repos --account-id <id> --forget` stores an opt-out without a directory, so scans do not silently restore it. Explicitly register a directory or run a CLI command from that repo to restore registration.
|
|
137
|
+
|
|
130
138
|
If the index file doesn't exist yet, run any `remits-cli` command from an account repo to bootstrap it.
|
|
131
139
|
|
|
132
140
|
## Local State Files
|
|
@@ -234,7 +234,7 @@ remits-cli ticket deliver --ticket ID --channel email --to a@x [--template T] [-
|
|
|
234
234
|
remits-cli ticket deliveries --ticket ID [--channel email] # what is queued to go out, and what already went
|
|
235
235
|
remits-cli ticket delivered --ticket ID --delivery-id ID | ticket delivery-failed --ticket ID --delivery-id ID --reason "..."
|
|
236
236
|
remits-cli ticket tag --ticket ID --tags a,b # make a cluster of near-identical tickets visible as one
|
|
237
|
-
remits-cli ticket artifact --ticket ID --type TYPE --label "..."
|
|
237
|
+
remits-cli ticket artifact --ticket ID --type TYPE --label "..." (--content "..." | --file PATH | --url U)
|
|
238
238
|
remits-cli ticket participant --ticket ID --email E [--role watcher|requester|agent]
|
|
239
239
|
remits-cli ticket field --ticket ID --key K --value V # the ORGANIZATION's own field, outside the planning slots
|
|
240
240
|
remits-cli ticket reclaim [--ticket ID | --account-id ID] [--stale-hours 24] [--apply] # OPERATOR: take back an agent's abandoned ticket
|
|
@@ -284,10 +284,12 @@ remits-cli corpus retire --corpus <name> --case <caseKey> [--case ...] [--restor
|
|
|
284
284
|
`test run` starts a server-side task immediately. By default the CLI process polls until the task is
|
|
285
285
|
terminal; `--watch false` disables websocket progress streaming but still waits. Pass `--wait false` to
|
|
286
286
|
return after launch with the task id. A single run with `--names "a|b|c"` selects cases into one suite task,
|
|
287
|
-
and those cases execute sequentially
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
287
|
+
and those cases execute sequentially by default. Test metadata `caseConcurrency` (1–16, default 1)
|
|
288
|
+
enables native server workers. Explicit `--parallel N` overrides it; `--parallel 1` forces serial.
|
|
289
|
+
Parent/group IDs work with `test status` and corpus commands; case results retain child task and trace IDs.
|
|
290
|
+
Scheduled batches additionally accept `suiteConcurrency` (1–16, default 1) and overlap only suites marked
|
|
291
|
+
`parallelSafe: true`; other suites run as exclusive barriers. Avoid concurrency for shared mutable
|
|
292
|
+
fixtures or order-dependent setup. See the Test component guide's Configuring Parallel Execution section.
|
|
291
293
|
|
|
292
294
|
### Reading a failing run
|
|
293
295
|
|
|
@@ -430,8 +430,9 @@ id + content hash, platform sync vs local HEAD. If it is not the world you meant
|
|
|
430
430
|
about a different world.
|
|
431
431
|
|
|
432
432
|
The platform launch is asynchronous. By default `remits-cli test run` waits for its task to finish, and any
|
|
433
|
-
cases selected by `--names "a|b"` execute sequentially
|
|
434
|
-
|
|
433
|
+
cases selected by `--names "a|b"` execute sequentially by default. A Test sidecar can set
|
|
434
|
+
`caseConcurrency: 2` for native server workers, including scheduled runs. Explicit `--parallel N`
|
|
435
|
+
overrides that setting (`--parallel 1` forces serial). It splits the selected cases across N shards that run side by side, and the run
|
|
435
436
|
keeps ONE id (`grp-…`) for `test status` and every corpus command. An instance runs only a few suites at once
|
|
436
437
|
(two by default), so shards beyond that wait for a slot. Ask for 2–4. Starting separate
|
|
437
438
|
`test run --names "<case>" --wait false` commands also works for one-off proofs, but every such run is a separate
|
|
@@ -807,3 +808,11 @@ Some users want to review every change before staging. Others want you to move f
|
|
|
807
808
|
- If they say "you don't need to ask me" or "stop asking" — remember this preference and work autonomously through the full loop.
|
|
808
809
|
|
|
809
810
|
The default should be: make the change, stage it, verify it, and present the results. Only block on the user when you're genuinely unsure about intent.
|
|
811
|
+
|
|
812
|
+
**Four steps always need the person's go for that step, whatever they said earlier:**
|
|
813
|
+
- syncing a component branch that a pre-production or client account uses;
|
|
814
|
+
- each promotion gate (`features/subscriber-branch-promotions.md` → *The three approval gates*);
|
|
815
|
+
- unsubscribing an account or retiring a branch;
|
|
816
|
+
- editing trunk on an account that runs a release-train branch.
|
|
817
|
+
|
|
818
|
+
A general "just do it" covers staging and verifying, not these. Each one changes what live accounts run.
|
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
- [Component branches](#component-branches)
|
|
33
33
|
- [`mcp_cache`](#mcp_cache)
|
|
34
34
|
- [`mcp_sql_query`](#mcp_sql_query)
|
|
35
|
+
- [`mcp_usage`](#mcp_usage)
|
|
35
36
|
- [`mcp_index_search`](#mcp_index_search)
|
|
36
37
|
- [`mcp_get_guide`](#mcp_get_guide)
|
|
37
38
|
- [`mcp_test_fixture`](#mcp_test_fixture)
|
|
@@ -931,6 +932,30 @@ SELECT u.id, u.username, u.enabled FROM user u
|
|
|
931
932
|
Prefer the purpose-built tools when one fits — they apply account scoping, data-mode segmentation, and
|
|
932
933
|
resolution awareness that raw SQL does not. Reach for SQL when nothing else models the question.
|
|
933
934
|
|
|
935
|
+
### `mcp_usage`
|
|
936
|
+
Administrator-only usage reports and configuration through the platform's `usageControl` API. The
|
|
937
|
+
System Account owns this global tool; deploy the platform API before staging or syncing its wrapper.
|
|
938
|
+
|
|
939
|
+
| Parameter | Description |
|
|
940
|
+
|---|---|
|
|
941
|
+
| `operation` | `status` (default), `report`, `gaps`, `savePrice`, `replacePrice`, `endPrice`, `saveBillingMap`, `deleteBillingMap`, `saveManualCharge`, `deleteManualCharge`, `configure`, `runSettle`, `runBilling`, `flushMeter` |
|
|
942
|
+
| `query` | `usage(...)` selectors for reports/gaps: dates, accountId, includeDescendants, lanes, includeSuites, grouping. Omit accountId for platform scope. |
|
|
943
|
+
| `values` | Rate, mapping, charge or settings fields; status lists supported units and allowed settings. `replacePrice` requires the old id and full successor rate. |
|
|
944
|
+
| `apply` | `false` by default: mutations return their normalized plan. `true` applies the authorized change. Configuration is platform-wide, independently of the test/prod data selector. |
|
|
945
|
+
|
|
946
|
+
Read `status.pricingNotes` first. Rates start at future UTC hour boundaries; overlapping or historical
|
|
947
|
+
changes are refused. Use `replacePrice` for an atomic rate change. Do not price a storage byte counter
|
|
948
|
+
as byte-months or wall time as billed CPU time. `gaps` returns account/provider/model grouping so exact
|
|
949
|
+
missing rate keys are visible. Shared platform overhead is excluded from account hierarchies.
|
|
950
|
+
Provider invoices and estimates remain separate when filters do not match invoice scope.
|
|
951
|
+
|
|
952
|
+
```bash
|
|
953
|
+
remits-cli tool --name mcp_usage --input '{"operation":"status"}'
|
|
954
|
+
remits-cli tool --name mcp_usage --input '{"operation":"report","query":{"accountId":41,"includeDescendants":true,"from":"2026-10-01"}}'
|
|
955
|
+
```
|
|
956
|
+
|
|
957
|
+
The full field contract and examples are in `features/usage-and-cost.md` → Configuring usage through MCP.
|
|
958
|
+
|
|
934
959
|
### `mcp_index_search`
|
|
935
960
|
Query and **diagnose** the Vertex AI Search index through the platform's Vertex DSL. Use it to check what a
|
|
936
961
|
RAG/search-backed component actually retrieves before blaming the component.
|
|
@@ -62,7 +62,7 @@
|
|
|
62
62
|
| Unsure whether a run used staged vs DB source | Query `mcp_system_logs` for `Using Cached BCD` — `Signature: cli:<hash>` = staged, `version:<N>:<sourceHash12>` = DB. CLI/MCP tool results also report `componentSource` and `componentSignature` when available. |
|
|
63
63
|
| Service already running | Run `remits-cli status` to get the dashboard URL, or `remits-cli stop` before restarting. |
|
|
64
64
|
| Control center URL unknown | Read `~/.remits-cli/service-state.json` or run `remits-cli status` |
|
|
65
|
-
| Dashboard missing repos |
|
|
65
|
+
| Dashboard or GitHub menu missing local repos | The running service rediscovers repos every five minutes. Use `remits-cli agent repos --rescan` or the control center rescan action for immediate discovery; allow the next presence update (normally within 30 seconds). Check scan roots if still absent. A forgotten account needs explicit registration with `agent repos --account-id <id> --directory <path>`. |
|
|
66
66
|
| Agent dispatch to wrong directory | Check `~/.remits-cli/account-repos.json` has the correct directory and that you resolved the account type correctly. A `CLIENT` ticket may still belong to a parent `PLATFORM` or `PRODUCT` repo for code changes. |
|
|
67
67
|
|
|
68
68
|
### When Something Doesn't Work as Expected
|