@remits/remits-cli 0.1.165 → 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 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 the machine for `account-info.json` files and rebuilds `~/.remits-cli/account-repos.json` before bringing up the background service.
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
- - L125 Sessions, Account Resolution, And Production Guards
8
- - L631 Local State, Workspaces, And Verification Evidence
9
- - L2669 Account Repos, Guide Sync, And Platform Repo
10
- - L3509 Component Discovery And HTTP Logging
11
- - L4279 Skill Delivery And TOC Resolution
12
- - L4594 Auth And Component Staging
13
- - L5226 Component Summaries, Status, And Sync Gates
14
- - L7550 Branches, Promotion, Commit, And Test Runs
15
- - L9813 Tokens, Tools, Verification, And Config
16
- - L11330 Service Dashboard And WebSocket Listener
17
- - L13840 Agent And Ticket Workflows
18
- - L18101 Help, Auto Update, And Command Dispatch
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);
@@ -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 rank = (entry) => (isAccountCheckout(entry.directory) ? 2 : 0) + (entry.source === 'cwd' ? 1 : 0);
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] = { ...entry, ambiguousWith: (current && current.ambiguousWith) || [] };
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
- function walkForAccountInfo(dir, depth, matches, visited, platformMatches) {
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
- let entries;
3316
- try {
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
- walkForAccountInfo(path.join(resolvedDir, entry.name), depth + 1, matches, visited, platformMatches);
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
+ }
3362
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
+ }
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.
@@ -15911,6 +15971,7 @@ async function ticketCommand(flags, subcommand) {
15911
15971
  requiresRepositoryWork: firstDefined(flags['repo-work'], flags['requires-repository-work']),
15912
15972
  tags: flags.tags ? String(flags.tags).split(',').map((v) => v.trim()).filter(Boolean) : undefined,
15913
15973
  label: flags.label,
15974
+ ...(action === 'artifact' ? ticketArtifactFields(flags) : {}),
15914
15975
  url: flags.url,
15915
15976
  body: flags.body,
15916
15977
  role: flags.role,
@@ -16656,7 +16717,7 @@ function printTicketHelp() {
16656
16717
  console.log(' tag --tags a,b Mark it, for a HUMAN reading the queue. When the');
16657
16718
  console.log(' tickets genuinely share a cause, use `relate` instead or as well: the platform reads');
16658
16719
  console.log(' that one, and a tag it cannot read will not stop a cluster being worked N times.');
16659
- console.log(' artifact --type TYPE --label "..." [--url U | --content "..."]');
16720
+ console.log(' artifact --type TYPE --label "..." (--content "..." | --file PATH | --url U) [--filename F] [--content-type T]');
16660
16721
  console.log(' Attach evidence — a log, a query result, a repro. Better than pasting it into a');
16661
16722
  console.log(' worklog summary, where it is neither typed nor retrievable.');
16662
16723
  console.log(' note --message "..." An INTERNAL note. The next worker and a reviewer');
@@ -16701,6 +16762,44 @@ function printTicketHelp() {
16701
16762
  * One helper so the lease, the claim and the registration cannot describe this process three ways —
16702
16763
  * the same reason the platform builds the tuple in exactly one class (AgentRunContext).
16703
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
+
16704
16803
  function runContextPayload(flags = {}) {
16705
16804
  const cwd = process.cwd();
16706
16805
  return {
@@ -17504,7 +17603,7 @@ async function agentReposCommand(flags) {
17504
17603
  if (Number.isFinite(accountId) && accountId > 0 && flagEnabled(flags.forget)) {
17505
17604
  const index = readAccountRepoIndex();
17506
17605
  const previous = index[String(accountId)];
17507
- delete index[String(accountId)];
17606
+ index[String(accountId)] = { accountId, source: 'forgotten', updatedAt: new Date().toISOString() };
17508
17607
  writeAccountRepoIndex(index);
17509
17608
  console.log(previous
17510
17609
  ? 'account ' + accountId + ' removed (was ' + previous.directory + '). Its tickets will now park ' +
@@ -18012,6 +18111,8 @@ async function listenCommand(flags) {
18012
18111
  activeSessionSignature = currentSignature;
18013
18112
  }
18014
18113
 
18114
+ // Do not await the filesystem scan: a slow/unavailable volume must not stall socket upkeep.
18115
+ refreshAccountReposIfDue();
18015
18116
  await runManagedWebsocketWatchdog();
18016
18117
  updateRuntimeServiceState();
18017
18118
  } catch (err) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@remits/remits-cli",
3
- "version": "0.1.165",
3
+ "version": "0.1.166",
4
4
  "description": "Local CLI for auth, component sync, and live test execution against Remits",
5
5
  "license": "MIT",
6
6
  "private": false,
@@ -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
@@ -114,7 +114,7 @@ Agents should use this mental model before guessing.
114
114
 
115
115
  The account repository index file is located at: `{{ACCOUNT_REPO_INDEX_PATH}}`
116
116
 
117
- This JSON file is automatically maintained by `remits-cli` and tracks all known Remits account repositories on this machine. It is updated whenever any `remits-cli` command runs from an account repo directory. The file maps account IDs to their metadata:
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:
118
118
 
119
119
  ```json
120
120
  {
@@ -133,6 +133,8 @@ This JSON file is automatically maintained by `remits-cli` and tracks all known
133
133
  - Resolve account names and IDs without making remote API calls
134
134
  - Support cross-account workflows where an agent in one repo needs context from another
135
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
+
136
138
  If the index file doesn't exist yet, run any `remits-cli` command from an account repo to bootstrap it.
137
139
 
138
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 "..." [--url U | --content "..."]
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
@@ -808,3 +808,11 @@ Some users want to review every change before staging. Others want you to move f
808
808
  - If they say "you don't need to ask me" or "stop asking" — remember this preference and work autonomously through the full loop.
809
809
 
810
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 | Rebuild `~/.remits-cli/account-repos.json` with `remits-cli start` or the control center rescan action |
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