@remits/remits-cli 0.1.99 → 0.1.101
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 +5 -1
- package/index.js +226 -15
- package/package.json +1 -1
- package/skills/remits-cli/SKILL.md +81 -10
package/README.md
CHANGED
|
@@ -16,6 +16,7 @@ On each command run, `remits-cli` checks the npm `latest` version for `@remits/r
|
|
|
16
16
|
remits-cli auth --base-url https://your-remits-host --account-id 123
|
|
17
17
|
remits-cli start
|
|
18
18
|
remits-cli status
|
|
19
|
+
remits-cli whoami
|
|
19
20
|
remits-cli stop
|
|
20
21
|
remits-cli install --skills
|
|
21
22
|
remits-cli tools
|
|
@@ -34,6 +35,8 @@ remits-cli components sync --branch feature_branch --dry-run --summary
|
|
|
34
35
|
remits-cli components sync --branch feature_branch --force-tombstones
|
|
35
36
|
remits-cli token --path page/my-embeddable
|
|
36
37
|
remits-cli token --path page/my-embeddable --variant-branch feature_branch
|
|
38
|
+
remits-cli token inspect --token https://example.test/s/<tokenKey>/page/my-embeddable
|
|
39
|
+
remits-cli tool --name mcp_account_user_admin --data-mode prod --input '{"action":"account_create","parentAccountId":4,"name":"Freto","type":"PRODUCT","dryRun":true}'
|
|
37
40
|
remits-cli data-mode
|
|
38
41
|
remits-cli data-mode set prod
|
|
39
42
|
remits-cli sessions list
|
|
@@ -95,7 +98,8 @@ remits-cli install --skills --overwrite true
|
|
|
95
98
|
- Most non-lifecycle commands auto-start the background service if authenticated sessions already exist and no service is running.
|
|
96
99
|
- Successful `remits-cli auth` also attempts to auto-start the background service.
|
|
97
100
|
- `remits-cli start` starts a detached background process by default. Use `remits-cli start --foreground true` only when you want to run the daemon in the current terminal.
|
|
98
|
-
- `remits-cli status` reports whether the background service is alive
|
|
101
|
+
- `remits-cli status` reports whether the background service is alive, prints the dashboard URL when available, and prints the resolved session tuple: Account ID, User ID, current git branch, and active data mode.
|
|
102
|
+
- `remits-cli whoami` prints only the resolved session tuple. Use `--base-url`, `--account-id`, and `--data-mode` to prove the exact host/account/lane before running a tool or test.
|
|
99
103
|
- `remits-cli stop` stops the background service, kills the shared tmux session, and clears pane tracking state.
|
|
100
104
|
- The service starts a localhost dashboard that acts as a control center for remits-cli integration state.
|
|
101
105
|
- The dashboard shows websocket connection health, topic subscriptions, tmux session/panes, the discovered account repo index, global state files, and per-repo remits-cli files.
|
package/index.js
CHANGED
|
@@ -278,9 +278,133 @@ function printResolvedBaseUrl(baseUrl) {
|
|
|
278
278
|
console.log('Base URL:', normalizeBaseUrl(baseUrl || DEFAULT_BASE_URL));
|
|
279
279
|
}
|
|
280
280
|
|
|
281
|
-
|
|
282
|
-
|
|
281
|
+
function accountNameFromAccountInfo(info) {
|
|
282
|
+
return (info && (
|
|
283
|
+
(info.resolution && info.resolution.accountName) ||
|
|
284
|
+
(info.repoContext && info.repoContext.accountInfoAccountName) ||
|
|
285
|
+
info.name ||
|
|
286
|
+
info.accountName ||
|
|
287
|
+
(info.account && info.account.name)
|
|
288
|
+
)) || null;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function resolveSessionIdentity(cwd, flags = {}) {
|
|
292
|
+
let accountId = null;
|
|
293
|
+
const fromFlag = Number(flags['account-id']);
|
|
294
|
+
if (Number.isFinite(fromFlag) && fromFlag > 0) {
|
|
295
|
+
accountId = fromFlag;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
const accountInfo = loadAccountInfo(cwd);
|
|
299
|
+
if (!accountId && accountInfo) {
|
|
300
|
+
accountId = Number(accountIdFromAccountInfo(accountInfo));
|
|
301
|
+
try { updateAccountRepoIndex(cwd); } catch (_) { /* best-effort */ }
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
const requestedBaseUrl = flags['base-url'] ? normalizeBaseUrl(flags['base-url']) : null;
|
|
305
|
+
const requestedDataMode = hasExplicitDataModeFlag(flags) ? resolveDataMode(flags, null) : null;
|
|
306
|
+
const session = readSession({
|
|
307
|
+
accountId,
|
|
308
|
+
baseUrl: requestedBaseUrl,
|
|
309
|
+
dataMode: requestedDataMode
|
|
310
|
+
}, {
|
|
311
|
+
strictBaseUrl: Boolean(requestedBaseUrl),
|
|
312
|
+
strictDataMode: Boolean(requestedDataMode)
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
if (!accountId && session && session.accountId) {
|
|
316
|
+
accountId = Number(session.accountId);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
const dataMode = requestedDataMode || resolveDataMode(flags, session);
|
|
320
|
+
const branchName = flags.branch || flags.branchName || currentBranch(cwd);
|
|
321
|
+
const user = session && session.user ? session.user : {};
|
|
322
|
+
const repoName = accountNameFromAccountInfo(accountInfo);
|
|
323
|
+
const localAccountInfoAccountId = accountInfo ? Number(accountIdFromAccountInfo(accountInfo)) : null;
|
|
324
|
+
const sessionResolutionWarning = session
|
|
325
|
+
? buildSessionResolutionWarning(accountId, requestedBaseUrl, session)
|
|
326
|
+
: null;
|
|
327
|
+
|
|
328
|
+
return {
|
|
329
|
+
authenticated: Boolean(session && session.token),
|
|
330
|
+
accountId: accountId || (session && session.accountId) || null,
|
|
331
|
+
accountName: (session && session.account && session.account.name) || null,
|
|
332
|
+
localAccountName: repoName,
|
|
333
|
+
localAccountInfoAccountId: Number.isFinite(localAccountInfoAccountId) ? localAccountInfoAccountId : null,
|
|
334
|
+
userId: user.id || session?.userId || null,
|
|
335
|
+
userName: user.name || user.username || user.email || null,
|
|
336
|
+
branchName,
|
|
337
|
+
dataMode,
|
|
338
|
+
baseUrl: normalizeBaseUrl((session && session.baseUrl) || requestedBaseUrl || DEFAULT_BASE_URL),
|
|
339
|
+
updatedAt: session ? session.updatedAt : null,
|
|
340
|
+
sessionResolutionWarning
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
function printSessionIdentity(identity, options = {}) {
|
|
345
|
+
if (options.json) {
|
|
346
|
+
console.log(JSON.stringify(identity, null, 2));
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
if (!identity.authenticated) {
|
|
351
|
+
console.log('Active session: not authenticated');
|
|
352
|
+
console.log('Account ID:', identity.accountId || 'unresolved');
|
|
353
|
+
console.log('User ID:', 'unresolved');
|
|
354
|
+
console.log('Branch:', identity.branchName || 'unresolved');
|
|
355
|
+
console.log('Data mode:', identity.dataMode || DEFAULT_DATA_MODE);
|
|
356
|
+
console.log('Base URL:', identity.baseUrl);
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
console.log('Active session:');
|
|
361
|
+
console.log(' Account ID:', identity.accountId);
|
|
362
|
+
if (identity.accountName) {
|
|
363
|
+
console.log(' Account:', identity.accountName);
|
|
364
|
+
}
|
|
365
|
+
if (identity.localAccountName) {
|
|
366
|
+
let localDetails = identity.localAccountInfoAccountId ? 'id ' + identity.localAccountInfoAccountId : '';
|
|
367
|
+
if (identity.localAccountInfoAccountId && identity.accountId &&
|
|
368
|
+
Number(identity.localAccountInfoAccountId) !== Number(identity.accountId)) {
|
|
369
|
+
localDetails += '; command targets account ' + identity.accountId;
|
|
370
|
+
}
|
|
371
|
+
console.log(' Local repo account:', identity.localAccountName + (localDetails ? ' (' + localDetails + ')' : ''));
|
|
372
|
+
}
|
|
373
|
+
console.log(' User ID:', identity.userId || 'unknown');
|
|
374
|
+
if (identity.userName) {
|
|
375
|
+
console.log(' User:', identity.userName);
|
|
376
|
+
}
|
|
377
|
+
console.log(' Branch:', identity.branchName);
|
|
378
|
+
console.log(' Data mode:', identity.dataMode);
|
|
379
|
+
console.log(' Base URL:', identity.baseUrl);
|
|
380
|
+
if (identity.updatedAt) {
|
|
381
|
+
console.log(' Session updated:', identity.updatedAt);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// Tools/actions whose names signal they mutate state rather than only reading it. Used solely to make
|
|
386
|
+
// data-lane banners precise — never to block or permit anything.
|
|
283
387
|
const MUTATING_TOOL_PATTERN = /(^|_)(patch|edit|create|commit|update|delete|remove|write|set|assign|run|execute|send|pause|unpause|interrupt|restore|repair|migrate|sync)(_|$)/i;
|
|
388
|
+
const MUTATING_TOOL_ACTION_PATTERN = /(^|_)(patch|edit|create|commit|update|delete|remove|write|set|assign|run|execute|send|pause|unpause|interrupt|restore|repair|migrate|sync|add|upsert|reparent|structure|accept|complete|release|record|clear|retire|subscribe|unsubscribe|stop|cancel|kill)(_|$)/i;
|
|
389
|
+
|
|
390
|
+
function toolInputAction(input) {
|
|
391
|
+
if (!input || typeof input !== 'object' || Array.isArray(input)) return null;
|
|
392
|
+
const value = input.action || input.controlAction || input.command || input.mode;
|
|
393
|
+
if (value === undefined || value === null || value === true) return null;
|
|
394
|
+
const normalized = String(value).trim();
|
|
395
|
+
return normalized || null;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
function isMutatingToolCall(toolName, input) {
|
|
399
|
+
if (MUTATING_TOOL_PATTERN.test(String(toolName || ''))) return true;
|
|
400
|
+
const action = toolInputAction(input);
|
|
401
|
+
return action ? MUTATING_TOOL_ACTION_PATTERN.test(action) : false;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
function toolOperationLabel(toolName, input) {
|
|
405
|
+
const action = toolInputAction(input);
|
|
406
|
+
return 'tool ' + String(toolName) + (action ? ' action ' + action : '');
|
|
407
|
+
}
|
|
284
408
|
|
|
285
409
|
function looksLikeDryRun(input) {
|
|
286
410
|
if (!input || typeof input !== 'object') return false;
|
|
@@ -294,25 +418,29 @@ function isLocalBaseUrl(baseUrl) {
|
|
|
294
418
|
}
|
|
295
419
|
|
|
296
420
|
/**
|
|
297
|
-
* One consistent banner across
|
|
298
|
-
*
|
|
299
|
-
*
|
|
421
|
+
* One consistent banner across risky data-lane surfaces. Agents have repeatedly crossed prod/test
|
|
422
|
+
* boundaries by assumption, so the banner is loud, states the account it resolved, and distinguishes
|
|
423
|
+
* live production writes from isolated test-data writes.
|
|
300
424
|
*/
|
|
301
425
|
function printProdDataBanner({ dataMode, accountId, baseUrl, operation, mutating = false, dryRun = false }) {
|
|
302
426
|
const normalizedDataMode = String(dataMode || '').toLowerCase();
|
|
303
427
|
const prodData = normalizedDataMode === 'prod';
|
|
304
428
|
const prodHost = baseUrl && !isLocalBaseUrl(baseUrl);
|
|
305
|
-
if (!prodData && !prodHost) return;
|
|
429
|
+
if (!prodData && !prodHost && !mutating) return;
|
|
306
430
|
const bar = '='.repeat(72);
|
|
307
431
|
let headline;
|
|
308
432
|
if (prodData) {
|
|
309
433
|
headline = dryRun
|
|
310
434
|
? 'PROD DATA — DRY RUN (no write will be attempted)'
|
|
311
435
|
: (mutating ? 'PROD DATA WRITE — this runs against LIVE production data' : 'PROD DATA READ — this reads LIVE production data');
|
|
312
|
-
} else {
|
|
436
|
+
} else if (prodHost) {
|
|
313
437
|
headline = dryRun
|
|
314
438
|
? 'PROD HOST / TEST DATA — DRY RUN'
|
|
315
439
|
: (mutating ? 'PROD HOST / TEST DATA WRITE — deployed app, isolated test data' : 'PROD HOST / TEST DATA READ — deployed app, isolated test data');
|
|
440
|
+
} else {
|
|
441
|
+
headline = dryRun
|
|
442
|
+
? 'TEST DATA — DRY RUN (no write will be attempted)'
|
|
443
|
+
: 'TEST DATA WRITE — isolated test data';
|
|
316
444
|
}
|
|
317
445
|
console.log(bar);
|
|
318
446
|
console.log(' ' + headline);
|
|
@@ -421,6 +549,17 @@ function sessionJsonlFile(cwd) {
|
|
|
421
549
|
}
|
|
422
550
|
|
|
423
551
|
const CONTENT_FIELDS = new Set(['source', 'html', 'javascript', 'css', 'schema', 'inputSchema', 'previewData', 'messages']);
|
|
552
|
+
const SECRET_LOG_FIELDS = new Set([
|
|
553
|
+
'token',
|
|
554
|
+
'tokeninput',
|
|
555
|
+
'tokenkey',
|
|
556
|
+
'authtoken',
|
|
557
|
+
'xauthtoken',
|
|
558
|
+
'x-auth-token',
|
|
559
|
+
'remitstoken',
|
|
560
|
+
'x-remits-token',
|
|
561
|
+
'authorization'
|
|
562
|
+
]);
|
|
424
563
|
|
|
425
564
|
function sanitizeForLog(value) {
|
|
426
565
|
if (value === null || value === undefined) {
|
|
@@ -434,7 +573,7 @@ function sanitizeForLog(value) {
|
|
|
434
573
|
}
|
|
435
574
|
const out = {};
|
|
436
575
|
for (const [k, v] of Object.entries(value)) {
|
|
437
|
-
if (k.toLowerCase()
|
|
576
|
+
if (SECRET_LOG_FIELDS.has(k.toLowerCase())) {
|
|
438
577
|
out[k] = '[redacted]';
|
|
439
578
|
} else if (CONTENT_FIELDS.has(k) && typeof v === 'string') {
|
|
440
579
|
out[k] = '[' + v.length + ' chars]';
|
|
@@ -2777,6 +2916,12 @@ async function testCommand(flags) {
|
|
|
2777
2916
|
}
|
|
2778
2917
|
|
|
2779
2918
|
async function tokenCommand(flags) {
|
|
2919
|
+
const subcommand = flags._ && flags._[1];
|
|
2920
|
+
if (subcommand === 'inspect' || subcommand === 'details' || subcommand === 'decode') {
|
|
2921
|
+
await tokenInspectCommand(flags);
|
|
2922
|
+
return;
|
|
2923
|
+
}
|
|
2924
|
+
|
|
2780
2925
|
const cwd = process.cwd();
|
|
2781
2926
|
ensureLocalState(cwd);
|
|
2782
2927
|
const sessionContext = resolveSessionContext(cwd, flags);
|
|
@@ -2829,6 +2974,35 @@ async function tokenCommand(flags) {
|
|
|
2829
2974
|
console.log(JSON.stringify(output, null, 2));
|
|
2830
2975
|
}
|
|
2831
2976
|
|
|
2977
|
+
async function tokenInspectCommand(flags) {
|
|
2978
|
+
const cwd = process.cwd();
|
|
2979
|
+
ensureLocalState(cwd);
|
|
2980
|
+
const sessionContext = resolveSessionContext(cwd, flags);
|
|
2981
|
+
const { session, accountId } = sessionContext;
|
|
2982
|
+
const baseUrl = flags['base-url'] || session.baseUrl || DEFAULT_BASE_URL;
|
|
2983
|
+
const positional = flags._ && flags._[2];
|
|
2984
|
+
const tokenInput = flags.token || flags['token-key'] || flags.tokenKey || flags.value || flags.url || positional;
|
|
2985
|
+
|
|
2986
|
+
if (!tokenInput || tokenInput === true) {
|
|
2987
|
+
throw new Error('Token inspection requires --token <token|tokenKey|URL>, --token-key <key>, --url <URL>, or a positional value.');
|
|
2988
|
+
}
|
|
2989
|
+
|
|
2990
|
+
const api = buildAxios(baseUrl, session.token);
|
|
2991
|
+
const data = await loggedPost(api, cwd, '/cli/tokenInspect', {
|
|
2992
|
+
token: session.token,
|
|
2993
|
+
accountId,
|
|
2994
|
+
tokenInput
|
|
2995
|
+
}).then((r) => r.data);
|
|
2996
|
+
|
|
2997
|
+
if (!data.success) {
|
|
2998
|
+
throw new Error(data.message || 'Failed to inspect token');
|
|
2999
|
+
}
|
|
3000
|
+
|
|
3001
|
+
printSessionResolutionWarning(sessionContext);
|
|
3002
|
+
printResolvedBaseUrl(baseUrl);
|
|
3003
|
+
console.log(JSON.stringify(data, null, 2));
|
|
3004
|
+
}
|
|
3005
|
+
|
|
2832
3006
|
async function toolsCommand(flags) {
|
|
2833
3007
|
const cwd = process.cwd();
|
|
2834
3008
|
ensureLocalState(cwd);
|
|
@@ -2957,8 +3131,8 @@ async function toolCommand(flags) {
|
|
|
2957
3131
|
dataMode,
|
|
2958
3132
|
accountId,
|
|
2959
3133
|
baseUrl,
|
|
2960
|
-
operation:
|
|
2961
|
-
mutating:
|
|
3134
|
+
operation: toolOperationLabel(toolName, input),
|
|
3135
|
+
mutating: isMutatingToolCall(toolName, input),
|
|
2962
3136
|
dryRun: looksLikeDryRun(input)
|
|
2963
3137
|
});
|
|
2964
3138
|
|
|
@@ -5263,8 +5437,28 @@ async function waitForListenerStopped(timeoutMs = 5000) {
|
|
|
5263
5437
|
return !isListenerRunning();
|
|
5264
5438
|
}
|
|
5265
5439
|
|
|
5266
|
-
async function listenStatusCommand() {
|
|
5440
|
+
async function listenStatusCommand(flags = {}) {
|
|
5441
|
+
const wantsJson = flagEnabled(flags.json);
|
|
5267
5442
|
const state = readServiceState();
|
|
5443
|
+
const identity = resolveSessionIdentity(process.cwd(), flags);
|
|
5444
|
+
|
|
5445
|
+
if (wantsJson) {
|
|
5446
|
+
console.log(JSON.stringify({
|
|
5447
|
+
service: {
|
|
5448
|
+
running: isListenerRunning(),
|
|
5449
|
+
pid: isListenerRunning() && fs.existsSync(LISTENER_PID_FILE)
|
|
5450
|
+
? fs.readFileSync(LISTENER_PID_FILE, 'utf8').trim()
|
|
5451
|
+
: null,
|
|
5452
|
+
dashboardUrl: state.dashboardUrl || null,
|
|
5453
|
+
indexedRepos: state.discovery && Number.isFinite(state.discovery.repoCount)
|
|
5454
|
+
? state.discovery.repoCount
|
|
5455
|
+
: null
|
|
5456
|
+
},
|
|
5457
|
+
session: identity
|
|
5458
|
+
}, null, 2));
|
|
5459
|
+
return;
|
|
5460
|
+
}
|
|
5461
|
+
|
|
5268
5462
|
if (isListenerRunning()) {
|
|
5269
5463
|
const pid = fs.readFileSync(LISTENER_PID_FILE, 'utf8').trim();
|
|
5270
5464
|
console.log('remits-cli service is running (pid: ' + pid + ')');
|
|
@@ -5277,6 +5471,14 @@ async function listenStatusCommand() {
|
|
|
5277
5471
|
} else {
|
|
5278
5472
|
console.log('remits-cli service is not running.');
|
|
5279
5473
|
}
|
|
5474
|
+
printSessionResolutionWarning(identity);
|
|
5475
|
+
printSessionIdentity(identity);
|
|
5476
|
+
}
|
|
5477
|
+
|
|
5478
|
+
async function whoamiCommand(flags = {}) {
|
|
5479
|
+
const identity = resolveSessionIdentity(process.cwd(), flags);
|
|
5480
|
+
printSessionResolutionWarning(identity);
|
|
5481
|
+
printSessionIdentity(identity, { json: flagEnabled(flags.json) });
|
|
5280
5482
|
}
|
|
5281
5483
|
|
|
5282
5484
|
function npmCommand() {
|
|
@@ -5524,8 +5726,10 @@ function printToolsHelp() {
|
|
|
5524
5726
|
|
|
5525
5727
|
function printTokenHelp() {
|
|
5526
5728
|
console.log('Usage: remits-cli token [--base-url URL] [--branch BRANCH] [--path embeddable/path] [--data-mode test|prod] [--as-account ID] [--variant-branch NAME|none]');
|
|
5729
|
+
console.log(' remits-cli token inspect --token <token|tokenKey|URL> [--base-url URL] [--account-id ID]');
|
|
5527
5730
|
console.log('');
|
|
5528
5731
|
console.log('Mints a branch-aware browser URL for embeddable verification.');
|
|
5732
|
+
console.log('Inspect decodes a persisted Remits token and prints token metadata, recognized routing fields, safety/dataMode evidence, and full context.');
|
|
5529
5733
|
}
|
|
5530
5734
|
|
|
5531
5735
|
async function main() {
|
|
@@ -5546,7 +5750,7 @@ async function main() {
|
|
|
5546
5750
|
|
|
5547
5751
|
// Auto-start the background service if not already running.
|
|
5548
5752
|
// Skip for lifecycle subcommands and help.
|
|
5549
|
-
const isLifecycleCmd = command === 'listen' || command === 'start' || command === 'stop' || command === 'status';
|
|
5753
|
+
const isLifecycleCmd = command === 'listen' || command === 'start' || command === 'stop' || command === 'status' || command === 'whoami';
|
|
5550
5754
|
const isHelpCmd = !command || command === 'help' || command === '--help' || wantsHelp;
|
|
5551
5755
|
if (!wantsJson && !isHelpCmd && !isLifecycleCmd && !isListenerRunning()) {
|
|
5552
5756
|
try {
|
|
@@ -5575,7 +5779,8 @@ async function main() {
|
|
|
5575
5779
|
console.log(' remits-cli config [set] [--agent claude|codex|gemini]');
|
|
5576
5780
|
console.log(' remits-cli start [--foreground true] [--port 8787]');
|
|
5577
5781
|
console.log(' remits-cli stop');
|
|
5578
|
-
console.log(' remits-cli status');
|
|
5782
|
+
console.log(' remits-cli status [--base-url URL] [--account-id ID] [--data-mode test|prod] [--json]');
|
|
5783
|
+
console.log(' remits-cli whoami [--base-url URL] [--account-id ID] [--data-mode test|prod] [--json]');
|
|
5579
5784
|
console.log(' remits-cli listen [stop|status] [--foreground true] # compatibility alias');
|
|
5580
5785
|
console.log(' remits-cli data-mode [set test|prod]');
|
|
5581
5786
|
console.log(' remits-cli install --skills [--target codex|claude|gemini|all] [--overwrite true]');
|
|
@@ -5594,6 +5799,7 @@ async function main() {
|
|
|
5594
5799
|
console.log(' remits-cli components branch <name> --retire [--force] # delete the branch\'s overlays');
|
|
5595
5800
|
console.log(' remits-cli test run --test <id|name> [--base-url URL] [--names name1,name2] [--watch true|false] [--data-mode test|prod] [--as-account ID] [--variant-branch NAME]');
|
|
5596
5801
|
console.log(' remits-cli token [--base-url URL] [--branch BRANCH] [--path embeddable/path] [--data-mode test|prod] [--as-account ID] [--variant-branch NAME|none]');
|
|
5802
|
+
console.log(' remits-cli token inspect --token <token|tokenKey|URL> [--base-url URL] [--account-id ID]');
|
|
5597
5803
|
console.log('');
|
|
5598
5804
|
console.log(' --as-account <ID> verifies AS a descendant subscriber account, so its relationship edge');
|
|
5599
5805
|
console.log(' selects the component branch and the run resolves exactly what production will.');
|
|
@@ -5686,7 +5892,7 @@ async function main() {
|
|
|
5686
5892
|
return;
|
|
5687
5893
|
}
|
|
5688
5894
|
if (subcommand === 'status') {
|
|
5689
|
-
await listenStatusCommand();
|
|
5895
|
+
await listenStatusCommand(args);
|
|
5690
5896
|
return;
|
|
5691
5897
|
}
|
|
5692
5898
|
await listenCommand(args);
|
|
@@ -5704,7 +5910,12 @@ async function main() {
|
|
|
5704
5910
|
}
|
|
5705
5911
|
|
|
5706
5912
|
if (command === 'status') {
|
|
5707
|
-
await listenStatusCommand();
|
|
5913
|
+
await listenStatusCommand(args);
|
|
5914
|
+
return;
|
|
5915
|
+
}
|
|
5916
|
+
|
|
5917
|
+
if (command === 'whoami') {
|
|
5918
|
+
await whoamiCommand(args);
|
|
5708
5919
|
return;
|
|
5709
5920
|
}
|
|
5710
5921
|
|
package/package.json
CHANGED
|
@@ -166,6 +166,31 @@ For access questions — "who can see this client account?", "why does this user
|
|
|
166
166
|
`mcp_sql_query` only when you need raw join-table investigation. Remember a user's custom fields are stored
|
|
167
167
|
**per bound account**, so the same person can differ per account.
|
|
168
168
|
|
|
169
|
+
**Test-data flags are first-class safety evidence.** `Account.testAccount` and `User.testUser` are set for
|
|
170
|
+
accounts/users created while the execution data lane is `test` (including `mcp_account_user_admin`
|
|
171
|
+
creation paths); prod-data creates leave them false. Updating an existing real account/user in test mode
|
|
172
|
+
does not convert it into test data, but its Firestore extension-field writes still go to the test lane.
|
|
173
|
+
`Object.testMode` / `Event.testMode` / `Alert.testMode` identify lifecycle rows in the test data lane.
|
|
174
|
+
Agent-facing surfaces expose these fields:
|
|
175
|
+
|
|
176
|
+
- `account-info.json`, `account-hierarchy.json`, and `mcp_account_view`: `resolution.testAccount` for the
|
|
177
|
+
described account, and `testAccount` on returned hierarchy nodes.
|
|
178
|
+
- `mcp_account_user_admin`: `testAccount` on `hierarchy`, `account`, and `account_create` results;
|
|
179
|
+
`testUser` on `users` / `user` results.
|
|
180
|
+
- `mcp_record_listing` / `mcp_record_view`: `testMode` on `object`, `event`, and `alert` records.
|
|
181
|
+
- `mcp_object_activity`: top-level `dataMode`, `object.testMode`, and `testMode` on Event/Alert
|
|
182
|
+
timeline entries.
|
|
183
|
+
- `mcp_event_diagnostics`: top-level `dataMode` and `result.event.testMode`.
|
|
184
|
+
- `remits-cli token inspect`: owner `account.testAccount` and `user.testUser`, plus token `dataMode`.
|
|
185
|
+
|
|
186
|
+
Persisted AI session rows currently do **not** store durable per-grouping `testMode` / `dataMode`; use
|
|
187
|
+
`mcp_ai_session_search.dataMode` only as the current tool execution lane, not as proof of the historical
|
|
188
|
+
grouping's data lane.
|
|
189
|
+
|
|
190
|
+
If any of those fields contradict the lane you intended, stop and rerun the command with an explicit
|
|
191
|
+
`--data-mode test` or `--data-mode prod`. Never infer prod/test from an account name, URL, branch name, or
|
|
192
|
+
the mere existence of a created account.
|
|
193
|
+
|
|
169
194
|
Use `remits-cli` for everything else: staging changes, running tests, generating embeddable tokens,
|
|
170
195
|
committing work, and diagnosing production issues.
|
|
171
196
|
|
|
@@ -400,6 +425,7 @@ Use `mcp_support_ticket` to manage lifecycle:
|
|
|
400
425
|
- `complete` — resolve the ticket with a summary of what was done
|
|
401
426
|
- `release` — unassign if you cannot continue
|
|
402
427
|
- **Pulling a ticket by number is enough — you do NOT need to know its account first.** The `ticketId` IS the ticket's globally-unique anchor id, and `mcp_support_ticket` resolves the owning account from it for every action that operates on an existing ticket (`read`, `accept`, `update_status`, `complete`, `release`, `record_progress`, `add_artifact`, `get_attachment`). So when a user says "pull ticket 19463", just call `read` with `{ "action": "read", "ticketId": "19463" }` from any authenticated **prod** session (the ticket lives in prod data) — omit `accountId` entirely. The response returns the resolved `accountId`/`accountName` (and `implementationAccountId` when set); use those for any follow-up work. **Only `create` requires an explicit `accountId`** (a brand-new ticket has no anchor to resolve from). If a bare `ticketId` returns "Support ticket not found", double-check you are in `--data-mode prod`, then fall back to passing an explicit `accountId`.
|
|
428
|
+
- `read` can return `documentState:'missing_or_empty'` with `ticket.mirrorOnly:true` and `ticket.canMutate:false`. That means the support-ticket anchor exists and the queue row is real, but the backing Firestore `support_tickets/{ticketId}` document is missing or metadata-only. Treat this as a degraded ticket, not as "ticket not found"; run or request the System Account action `Restore Support Ticket Documents From Anchor Mirrors` for the owning account before lifecycle mutations. The restore recovers mirrored scalar fields, but document-only arrays such as alert snapshots, email threads, attachments, worklog entries, and artifacts may be lost.
|
|
403
429
|
- If a ticket is part of the request, manage the lifecycle proactively. Do not wait for the human user to remind you to read, accept, update, complete, or release it.
|
|
404
430
|
|
|
405
431
|
Ticket-routing context:
|
|
@@ -1602,15 +1628,16 @@ account).
|
|
|
1602
1628
|
|
|
1603
1629
|
- `action: 'hierarchy'` (default) — the descendant tree trimmed to `depth` (1-10, default 2; nodes cut off
|
|
1604
1630
|
are marked `truncated` and still report their child count) and/or the anchored ancestor chain plus every
|
|
1605
|
-
edge (`direction: 'down'|'up'|'both'`). **This is how you get the deep tree
|
|
1606
|
-
-
|
|
1607
|
-
|
|
1631
|
+
edge (`direction: 'down'|'up'|'both'`). Nodes include `testAccount`. **This is how you get the deep tree
|
|
1632
|
+
account-info.json omits.**
|
|
1633
|
+
- `action: 'account'` — one account's `resolution` block, including `testAccount`, plus masked
|
|
1634
|
+
configuration fields, without paying for the component inventory.
|
|
1608
1635
|
- `action: 'users'` — an account's users at a hierarchy `scope` (`self`/`children`/`parents`/`hierarchy`),
|
|
1609
|
-
optional `email` substring filter. Extension fields are omitted here on purpose:
|
|
1610
|
-
account** and these users are bound to their own.
|
|
1636
|
+
optional `email` substring filter. Rows include `testUser`. Extension fields are omitted here on purpose:
|
|
1637
|
+
they are stored **per account** and these users are bound to their own.
|
|
1611
1638
|
- `action: 'user'` — one user (`userId` or `email`) with roles, account memberships, and extension fields
|
|
1612
|
-
**correctly scoped to the requested account
|
|
1613
|
-
tells you when the fields shown belong to a different account.
|
|
1639
|
+
**correctly scoped to the requested account**, plus `testUser`. It never grants membership as a side
|
|
1640
|
+
effect of a read, and tells you when the fields shown belong to a different account.
|
|
1614
1641
|
- `action: 'account_update'` — write Account-schema configuration `fields`, and/or `name`/`status`
|
|
1615
1642
|
(`ACTIVE`/`ON_HOLD`/`PENDING`).
|
|
1616
1643
|
- `action: 'user_update'` — write User-schema `fields` under the named account, plus `name`/`enabled` and
|
|
@@ -1621,12 +1648,35 @@ account).
|
|
|
1621
1648
|
**Building an account hierarchy** (the structural writes — this is how a coding agent provisions accounts
|
|
1622
1649
|
without a browser):
|
|
1623
1650
|
|
|
1651
|
+
**Data-lane rule for provisioning:** `remits-cli tool` defaults to `--data-mode test`. That is correct for
|
|
1652
|
+
fixtures and rehearsals, but it means `mcp_account_user_admin` `action:'account_create'` creates test-lane
|
|
1653
|
+
accounts unless the command explicitly passes `--data-mode prod`. For real platform/product/customer
|
|
1654
|
+
provisioning, always dry-run in prod first, check the response's `dataMode`, then run the write in prod:
|
|
1655
|
+
|
|
1656
|
+
```bash
|
|
1657
|
+
remits-cli tool --name mcp_account_user_admin --data-mode prod --input '{"action":"account_create","parentAccountId":4,"name":"Freto","type":"PRODUCT","dryRun":true}'
|
|
1658
|
+
```
|
|
1659
|
+
|
|
1660
|
+
After the real write, verify the response (or re-read `action:'account'`) shows the command `dataMode` you
|
|
1661
|
+
intended and `testAccount:false` for real provisioning. `testAccount:true` means you created a test-data
|
|
1662
|
+
account, even if the name and structure look correct.
|
|
1663
|
+
|
|
1664
|
+
For a test rehearsal, make the opposite assertion explicit: the response should show `dataMode:'test'` and
|
|
1665
|
+
`testAccount:true` for created accounts (or `testUser:true` for created users). A false test flag in a
|
|
1666
|
+
test rehearsal means the data lane or the returned entity is not the one you intended.
|
|
1667
|
+
|
|
1668
|
+
Account/User schema `fields` are Firestore-backed extension fields. Their physical storage follows the
|
|
1669
|
+
same data lane as the tool call: `--data-mode test` writes under `testing/<resolvedDatabaseName>/...`, while
|
|
1670
|
+
`--data-mode prod` writes under `accounts/<resolvedDatabaseName>/...` (for modern segmented accounts). If a
|
|
1671
|
+
test-lane rehearsal should become real provisioning, rerun the create/update in prod mode; do not assume the
|
|
1672
|
+
test-lane Firestore fields moved.
|
|
1673
|
+
|
|
1624
1674
|
- `action: 'account_create'` — create a child under `parentAccountId`, with its **primary relationship edge**,
|
|
1625
1675
|
applying `type` / `databaseName` / `domainName` / `authPath` / `targetPath` / `code` /
|
|
1626
1676
|
`repositoryNameOverride` / `branchName` / `editMode` / … **at birth**. That ordering matters: the storage
|
|
1627
1677
|
namespace is resolved from those properties, and the parent's cascaded schema fields are written into it
|
|
1628
1678
|
during creation. Idempotent — an existing same-name account under that parent comes back with
|
|
1629
|
-
`reusedExisting: true`, unchanged.
|
|
1679
|
+
`reusedExisting: true`, unchanged. The response includes `testAccount`.
|
|
1630
1680
|
- `action: 'account_structure'` — change those properties on an existing account, including account-level
|
|
1631
1681
|
custom host, login path, and landing path.
|
|
1632
1682
|
- `action: 'edge_add'` / `'edge_update'` / `'edge_remove'` — manage a membership `AccountRelationship` edge to
|
|
@@ -1752,8 +1802,11 @@ Object lifecycle timeline — metadata + recent activity.
|
|
|
1752
1802
|
|-----------|----------|-------------|
|
|
1753
1803
|
| `accountId` | yes | Account ID |
|
|
1754
1804
|
| `objectId` | yes | Object ID (from `object_id` in documents) |
|
|
1805
|
+
| `dataMode` | no | Explicit lane: `prod` or `test`. Response echoes `dataMode`. |
|
|
1755
1806
|
| `activityOptions` | no | `{limit, offset, types, start, end, order}`. Default: limit=5, order=desc. Types: `OBJECT_LOG`, `EVENT`, `ALERT`. |
|
|
1756
1807
|
|
|
1808
|
+
Response fields include `dataMode`, `object.testMode`, and `testMode` on Event/Alert timeline entries.
|
|
1809
|
+
|
|
1757
1810
|
### `mcp_record_listing`
|
|
1758
1811
|
List and search lifecycle records when you do not already know the record ID.
|
|
1759
1812
|
|
|
@@ -1807,6 +1860,7 @@ Front-stage references:
|
|
|
1807
1860
|
| Parameter | Required | Description |
|
|
1808
1861
|
|-----------|----------|-------------|
|
|
1809
1862
|
| `action` | no | `search` (default), `detail`, or session control `pause`/`unpause`/`interrupt` |
|
|
1863
|
+
| `dataMode` | no | Explicit execution lane: `prod` or `test`. Response echoes `dataMode`, but persisted groupings do not have durable per-row lane flags. |
|
|
1810
1864
|
| `search` | no | Broad text match against session IDs and grouping IDs |
|
|
1811
1865
|
| `sessionId` | no | Session ID filter in search mode, or grouping/session key in detail mode |
|
|
1812
1866
|
| `groupingId` | no | Grouping ID filter in search mode, or grouping key in detail mode |
|
|
@@ -2191,6 +2245,7 @@ This keeps file retrieval self-contained — no separate download endpoint is ne
|
|
|
2191
2245
|
|
|
2192
2246
|
**Recommended flow** — `accountId` is optional throughout; `ticketId` resolves the owning account:
|
|
2193
2247
|
1. `read` — check ticket state and any attachments
|
|
2248
|
+
- If `read` returns `ticket.mirrorOnly:true`, use the mirrored fields for triage context, restore the backing document first, then claim/update/complete it.
|
|
2194
2249
|
2. **`accept`** (with `assignee`) — this is a **hard precondition for `update_status`**, not just etiquette
|
|
2195
2250
|
3. `get_attachment` if attachments are present and relevant to the investigation
|
|
2196
2251
|
4. `update_status` — `in_progress` while working, `pending_review` when the fix is done but not yet deployed
|
|
@@ -2356,6 +2411,10 @@ optional class-histogram sampling, **no heap dump and no JFR**.
|
|
|
2356
2411
|
List and filter tickets **across an account and its descendants** so you can choose what to work on. Lifecycle
|
|
2357
2412
|
actions stay on `mcp_support_ticket`.
|
|
2358
2413
|
|
|
2414
|
+
Queue rows are built from support-ticket anchor mirrors by default. A row in this list means a
|
|
2415
|
+
support-ticket anchor exists; it does not guarantee the full Firestore document is healthy. Open the
|
|
2416
|
+
ticket with `mcp_support_ticket read` before lifecycle work and honor `mirrorOnly` / `documentState` if present.
|
|
2417
|
+
|
|
2359
2418
|
| Parameter | Required | Description |
|
|
2360
2419
|
|-----------|----------|-------------|
|
|
2361
2420
|
| `action` | no | `list` (default) |
|
|
@@ -2389,6 +2448,11 @@ remits-cli sessions remove --account-id 42 --base-url http://localhost:8080
|
|
|
2389
2448
|
|
|
2390
2449
|
You can work in multiple account repos simultaneously across different terminal windows — each uses its own session. You can also be authenticated against different base URLs (e.g., localhost for development and production) for the same account at the same time.
|
|
2391
2450
|
|
|
2451
|
+
Use `remits-cli whoami` when you need a compact proof of the active target before a sensitive operation.
|
|
2452
|
+
It prints the resolved Account ID, User ID, current git branch, data mode, and base URL. `remits-cli status`
|
|
2453
|
+
prints the same session tuple after the service/dashboard status. Pass `--base-url`, `--account-id`, and
|
|
2454
|
+
`--data-mode` when host or lane matters; do not infer those values from the repo directory or account name.
|
|
2455
|
+
|
|
2392
2456
|
## Persistent Service, Control Center, and Agent Dispatch
|
|
2393
2457
|
|
|
2394
2458
|
The current mental model is a **background remits-cli service**, not just a listener.
|
|
@@ -2404,6 +2468,7 @@ That service does three jobs:
|
|
|
2404
2468
|
remits-cli start
|
|
2405
2469
|
remits-cli start --foreground true
|
|
2406
2470
|
remits-cli status
|
|
2471
|
+
remits-cli whoami
|
|
2407
2472
|
remits-cli stop
|
|
2408
2473
|
```
|
|
2409
2474
|
|
|
@@ -2527,7 +2592,8 @@ remits-cli sessions [list|remove] [--account-id ID]
|
|
|
2527
2592
|
remits-cli config [set] [--agent claude|codex|gemini]
|
|
2528
2593
|
remits-cli start [--foreground true] [--port 8787]
|
|
2529
2594
|
remits-cli stop
|
|
2530
|
-
remits-cli status
|
|
2595
|
+
remits-cli status [--base-url URL] [--account-id ID] [--data-mode test|prod] [--json]
|
|
2596
|
+
remits-cli whoami [--base-url URL] [--account-id ID] [--data-mode test|prod] [--json]
|
|
2531
2597
|
remits-cli listen [stop|status] [--foreground true] # compatibility alias
|
|
2532
2598
|
remits-cli data-mode [set test|prod]
|
|
2533
2599
|
remits-cli components stage [--branch <name>] [--data-mode test|prod] [--json|--verbose]
|
|
@@ -2544,6 +2610,7 @@ remits-cli components branch <name> --unsubscribe <accountId>
|
|
|
2544
2610
|
remits-cli components branch <name> --retire [--force] # delete the branch's overlays
|
|
2545
2611
|
remits-cli test run --test <id|name> [--names "a,b"] [--watch true|false] [--data-mode test|prod] [--as-account <ID>] [--variant-branch <name|none>]
|
|
2546
2612
|
remits-cli token [--path <embeddablePathOrId>] [--data-mode test|prod] [--as-account <ID>] [--variant-branch <name|none>]
|
|
2613
|
+
remits-cli token inspect --token <token|tokenKey|URL> # inspect token metadata, safety/dataMode evidence, and full context
|
|
2547
2614
|
remits-cli tools [--branch <name>] [--data-mode test|prod] [--variant-branch <name|none>]
|
|
2548
2615
|
remits-cli tool --name <toolName> [--branch <name>] [--input "{...}"] [--data-mode test|prod] [--variant-branch <name|none>] [--timeout-ms 60000] [--async true --wait true]
|
|
2549
2616
|
remits-cli tool status --call-id <callId> [--data-mode test|prod]
|
|
@@ -2584,7 +2651,10 @@ For tests specifically:
|
|
|
2584
2651
|
|
|
2585
2652
|
- Every command that can touch production (`tool`, `test run`, `components sync`) prints a `PROD DATA`
|
|
2586
2653
|
banner naming the operation, the resolved account, and the host — and distinguishes a live **WRITE**
|
|
2587
|
-
from a live **READ** and from a **DRY RUN**.
|
|
2654
|
+
from a live **READ** and from a **DRY RUN**. `remits-cli tool` also prints an explicit **TEST DATA WRITE**
|
|
2655
|
+
banner for mutating tool calls in the test lane, including multi-action tools such as
|
|
2656
|
+
`mcp_account_user_admin` where the write is signaled by `input.action` (`account_create`, `user_update`,
|
|
2657
|
+
`edge_update`, etc.). If you see a WRITE banner you did not intend, stop.
|
|
2588
2658
|
- A tool call that fails **in the platform runtime** rather than in the tool (Groovy reflective dispatch
|
|
2589
2659
|
of a runtime-compiled component, an empty connection pool, a Redis reconnect, a lock-wait timeout)
|
|
2590
2660
|
now comes back as HTTP `503` with `failureClass: "transient_infrastructure"` and `retryable: true`,
|
|
@@ -2612,6 +2682,7 @@ For tests specifically:
|
|
|
2612
2682
|
| Staged change has no effect in a live (non-CLI) run | Staged overrides resolve only under a CLI TestMode (`branchName`+`cliUserId`). Live webhooks and other non-CLI runtime paths still use the DB (trunk, or the account's subscribed variant). `commit` to make it durable. See "Component Resolution". |
|
|
2613
2683
|
| Need to know an account's shape (role, type, parents, namespace, branch, host/login routes) | Read `resolution` — from the repo's `account-info.json`, or `mcp_account_user_admin` `action:'account'` (cheap), or `mcp_account_view` (full inventory): `role`/`summary`, `type`, `resolvedDatabaseName`, `domainName`/`resolvedDomainName`, `authPath`/`targetPath`, `relationships`, `componentBranch`, plus top-level `componentBranches`. Never infer structure from the account's name. |
|
|
2614
2684
|
| Need the account tree below an account, or its users | In a local repo, read `account-hierarchy.json` for the generated tree. For live data, use `mcp_account_user_admin` (`action:'hierarchy'` with a `depth`, or `action:'users'`). `account-info.json` deliberately omits the tree. |
|
|
2685
|
+
| Need to prove whether an account/user/record is test data | Check explicit flags: `resolution.testAccount` / hierarchy `testAccount`, `mcp_account_user_admin` `testAccount` / `testUser`, token inspect owner flags, and `mcp_record_listing` / `mcp_record_view` `testMode` for object/event/alert rows. Do not infer from names or branch labels. |
|
|
2615
2686
|
| Need account configuration values | In a local repo, read `account-configurations.json`. For live data, use `mcp_account_user_admin` (`action:'account'`) or `mcp_account_view`. `account-info.json` deliberately omits configurations. |
|
|
2616
2687
|
| An account has two parents and you don't know which one a run used | `resolution.relationships` lists every link with its own `branchName`/`databaseName`/`domainName`. A membership-only account with SEVERAL edges resolves **trunk and inherits nothing** until a path is named (`--as-account`, `--variant-branch`, or an edge host) — that is by design, not a bug. With exactly ONE membership edge it inherits normally, descendants included. |
|
|
2617
2688
|
| Documents missing / written to the wrong place | Compare `resolution.databaseName` (the account's own override) with `resolution.resolvedDatabaseName` (what is actually in effect), and check for a `databaseName` on one of the `relationships` edges. Data does not inherit; components do. |
|