@phnx-labs/agents-cli 1.20.15 → 1.20.17
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/CHANGELOG.md +9 -0
- package/dist/commands/secrets.js +53 -1
- package/dist/commands/sessions-sync.d.ts +13 -0
- package/dist/commands/sessions-sync.js +73 -0
- package/dist/commands/sessions.js +2 -0
- package/dist/commands/sync.d.ts +10 -3
- package/dist/commands/sync.js +72 -9
- package/dist/commands/view.js +11 -3
- package/dist/index.js +1 -1
- package/dist/lib/agents.d.ts +11 -0
- package/dist/lib/agents.js +11 -9
- package/dist/lib/daemon.d.ts +19 -0
- package/dist/lib/daemon.js +97 -2
- package/dist/lib/hooks.js +12 -0
- package/dist/lib/migrate.d.ts +22 -0
- package/dist/lib/migrate.js +99 -1
- package/dist/lib/plugin-marketplace.d.ts +15 -0
- package/dist/lib/plugin-marketplace.js +54 -0
- package/dist/lib/secrets/drivers/rush.d.ts +14 -0
- package/dist/lib/secrets/drivers/rush.js +84 -0
- package/dist/lib/secrets/index.js +20 -0
- package/dist/lib/secrets/linux.js +88 -10
- package/dist/lib/secrets/sync-backend.d.ts +48 -0
- package/dist/lib/secrets/sync-backend.js +13 -0
- package/dist/lib/secrets/sync.d.ts +15 -23
- package/dist/lib/secrets/sync.js +31 -66
- package/dist/lib/session/parse.d.ts +2 -0
- package/dist/lib/session/parse.js +168 -2
- package/dist/lib/session/sync/agents.d.ts +46 -0
- package/dist/lib/session/sync/agents.js +94 -0
- package/dist/lib/session/sync/config.d.ts +30 -0
- package/dist/lib/session/sync/config.js +58 -0
- package/dist/lib/session/sync/crdt.d.ts +44 -0
- package/dist/lib/session/sync/crdt.js +119 -0
- package/dist/lib/session/sync/manifest.d.ts +51 -0
- package/dist/lib/session/sync/manifest.js +96 -0
- package/dist/lib/session/sync/r2.d.ts +32 -0
- package/dist/lib/session/sync/r2.js +121 -0
- package/dist/lib/session/sync/sync.d.ts +82 -0
- package/dist/lib/session/sync/sync.js +251 -0
- package/dist/lib/shims.d.ts +1 -1
- package/dist/lib/shims.js +17 -1
- package/dist/lib/sync-umbrella.d.ts +76 -0
- package/dist/lib/sync-umbrella.js +125 -0
- package/dist/lib/teams/parsers.js +159 -1
- package/dist/lib/usage.d.ts +18 -0
- package/dist/lib/usage.js +25 -0
- package/dist/lib/versions.js +30 -13
- package/package.json +2 -1
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Agent event stream parsers.
|
|
3
3
|
*
|
|
4
4
|
* Normalizes the heterogeneous JSON event formats emitted by each agent CLI
|
|
5
|
-
* (Claude, Codex, Gemini, Cursor, OpenCode, Grok, Antigravity) into a unified
|
|
5
|
+
* (Claude, Codex, Gemini, Cursor, OpenCode, Grok, Antigravity, Kimi) into a unified
|
|
6
6
|
* event schema with consistent types: init, message, tool_use, bash,
|
|
7
7
|
* file_read, file_write, file_create, file_delete, result, error, and others.
|
|
8
8
|
*/
|
|
@@ -31,6 +31,9 @@ export function normalizeEvents(agentType, raw) {
|
|
|
31
31
|
else if (agentType === 'antigravity') {
|
|
32
32
|
return normalizeAntigravity(raw);
|
|
33
33
|
}
|
|
34
|
+
else if (agentType === 'kimi') {
|
|
35
|
+
return normalizeKimi(raw);
|
|
36
|
+
}
|
|
34
37
|
// droid (Factory AI) intentionally falls through to the generic normalizer
|
|
35
38
|
// below: its `-o stream-json` JSONL event schema is not yet verified against
|
|
36
39
|
// a live run (the documented `debug` format differs from stream-json). Events
|
|
@@ -908,6 +911,161 @@ function normalizeGrok(raw) {
|
|
|
908
911
|
timestamp: timestamp,
|
|
909
912
|
}];
|
|
910
913
|
}
|
|
914
|
+
// --- Kimi parsing ---
|
|
915
|
+
// Kimi's `--output-format stream-json` emits one JSON object per line with a
|
|
916
|
+
// simple `role`-based schema:
|
|
917
|
+
// - {"role":"assistant","content":"..."} → final message
|
|
918
|
+
// - {"role":"assistant","tool_calls":[{"function":{"name":"Bash","arguments":"<json>"}}]} → tool use
|
|
919
|
+
// - {"role":"tool","tool_call_id":"...","content":"..."} → tool result
|
|
920
|
+
// - {"role":"meta","type":"session.resume_hint","session_id":"..."} → init / session id
|
|
921
|
+
// Tool arguments are JSON-stringified inside `function.arguments` and must be
|
|
922
|
+
// parsed before extracting paths/commands. Verified against live `kimi` runs.
|
|
923
|
+
function normalizeKimi(raw) {
|
|
924
|
+
const timestamp = new Date().toISOString();
|
|
925
|
+
if (!raw || typeof raw !== 'object') {
|
|
926
|
+
return [{
|
|
927
|
+
type: 'unknown',
|
|
928
|
+
agent: 'kimi',
|
|
929
|
+
raw: raw,
|
|
930
|
+
timestamp: timestamp,
|
|
931
|
+
}];
|
|
932
|
+
}
|
|
933
|
+
const role = typeof raw.role === 'string' ? raw.role : '';
|
|
934
|
+
// Assistant message (final answer or tool-call request).
|
|
935
|
+
if (role === 'assistant') {
|
|
936
|
+
const events = [];
|
|
937
|
+
if (typeof raw.content === 'string' && raw.content) {
|
|
938
|
+
events.push({
|
|
939
|
+
type: 'message',
|
|
940
|
+
agent: 'kimi',
|
|
941
|
+
content: raw.content,
|
|
942
|
+
complete: true,
|
|
943
|
+
timestamp: timestamp,
|
|
944
|
+
});
|
|
945
|
+
}
|
|
946
|
+
const toolCalls = Array.isArray(raw.tool_calls) ? raw.tool_calls : [];
|
|
947
|
+
for (const toolCall of toolCalls) {
|
|
948
|
+
const fn = toolCall?.function || {};
|
|
949
|
+
const toolName = typeof fn.name === 'string' ? fn.name : 'unknown';
|
|
950
|
+
let toolArgs = {};
|
|
951
|
+
if (typeof fn.arguments === 'string') {
|
|
952
|
+
try {
|
|
953
|
+
toolArgs = JSON.parse(fn.arguments);
|
|
954
|
+
}
|
|
955
|
+
catch {
|
|
956
|
+
toolArgs = { _raw: fn.arguments };
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
else if (fn.arguments && typeof fn.arguments === 'object') {
|
|
960
|
+
toolArgs = fn.arguments;
|
|
961
|
+
}
|
|
962
|
+
const filePath = toolArgs?.path || toolArgs?.file_path || '';
|
|
963
|
+
const command = toolArgs?.command || '';
|
|
964
|
+
// Map known tools to structured events. If a known tool is missing the
|
|
965
|
+
// fields we need (e.g. unparseable arguments), fall back to tool_use so
|
|
966
|
+
// the event is still visible in summaries rather than dropped.
|
|
967
|
+
let normalized = null;
|
|
968
|
+
if (toolName === 'Bash' && command) {
|
|
969
|
+
const bashEvents = [{
|
|
970
|
+
type: 'bash',
|
|
971
|
+
agent: 'kimi',
|
|
972
|
+
tool: toolName,
|
|
973
|
+
command: command,
|
|
974
|
+
timestamp: timestamp,
|
|
975
|
+
}];
|
|
976
|
+
const [filesRead, filesWritten, filesDeleted] = extractFileOpsFromBash(command);
|
|
977
|
+
for (const p of filesRead) {
|
|
978
|
+
bashEvents.push({ type: 'file_read', agent: 'kimi', tool: 'bash', path: p, command, timestamp });
|
|
979
|
+
}
|
|
980
|
+
for (const p of filesWritten) {
|
|
981
|
+
bashEvents.push({ type: 'file_write', agent: 'kimi', tool: 'bash', path: p, command, timestamp });
|
|
982
|
+
}
|
|
983
|
+
for (const p of filesDeleted) {
|
|
984
|
+
bashEvents.push({ type: 'file_delete', agent: 'kimi', tool: 'bash', path: p, command, timestamp });
|
|
985
|
+
}
|
|
986
|
+
normalized = bashEvents;
|
|
987
|
+
}
|
|
988
|
+
else if (toolName === 'Read' && filePath) {
|
|
989
|
+
normalized = [{
|
|
990
|
+
type: 'file_read',
|
|
991
|
+
agent: 'kimi',
|
|
992
|
+
tool: toolName,
|
|
993
|
+
path: filePath,
|
|
994
|
+
timestamp: timestamp,
|
|
995
|
+
}];
|
|
996
|
+
}
|
|
997
|
+
else if (toolName === 'Edit' && filePath) {
|
|
998
|
+
normalized = [{
|
|
999
|
+
type: 'file_write',
|
|
1000
|
+
agent: 'kimi',
|
|
1001
|
+
tool: toolName,
|
|
1002
|
+
path: filePath,
|
|
1003
|
+
timestamp: timestamp,
|
|
1004
|
+
}];
|
|
1005
|
+
}
|
|
1006
|
+
else if (toolName === 'Write' && filePath) {
|
|
1007
|
+
normalized = [{
|
|
1008
|
+
type: 'file_create',
|
|
1009
|
+
agent: 'kimi',
|
|
1010
|
+
tool: toolName,
|
|
1011
|
+
path: filePath,
|
|
1012
|
+
timestamp: timestamp,
|
|
1013
|
+
}];
|
|
1014
|
+
}
|
|
1015
|
+
if (normalized) {
|
|
1016
|
+
events.push(...normalized);
|
|
1017
|
+
}
|
|
1018
|
+
else {
|
|
1019
|
+
events.push({
|
|
1020
|
+
type: 'tool_use',
|
|
1021
|
+
agent: 'kimi',
|
|
1022
|
+
tool: toolName,
|
|
1023
|
+
args: toolArgs,
|
|
1024
|
+
timestamp: timestamp,
|
|
1025
|
+
});
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
return events.length > 0 ? events : [];
|
|
1029
|
+
}
|
|
1030
|
+
// Tool result (response to an assistant tool_call).
|
|
1031
|
+
if (role === 'tool') {
|
|
1032
|
+
const content = typeof raw.content === 'string' ? raw.content : '';
|
|
1033
|
+
const success = raw.isError !== true && !(content && content.startsWith('Error:'));
|
|
1034
|
+
return [{
|
|
1035
|
+
type: 'tool_result',
|
|
1036
|
+
agent: 'kimi',
|
|
1037
|
+
tool_call_id: typeof raw.tool_call_id === 'string' ? raw.tool_call_id : null,
|
|
1038
|
+
success: success,
|
|
1039
|
+
content: content,
|
|
1040
|
+
timestamp: timestamp,
|
|
1041
|
+
}];
|
|
1042
|
+
}
|
|
1043
|
+
// Meta events (session lifecycle).
|
|
1044
|
+
if (role === 'meta') {
|
|
1045
|
+
const metaType = typeof raw.type === 'string' ? raw.type : '';
|
|
1046
|
+
if (metaType === 'session.resume_hint') {
|
|
1047
|
+
return [{
|
|
1048
|
+
type: 'init',
|
|
1049
|
+
agent: 'kimi',
|
|
1050
|
+
session_id: typeof raw.session_id === 'string' ? raw.session_id : null,
|
|
1051
|
+
timestamp: timestamp,
|
|
1052
|
+
}];
|
|
1053
|
+
}
|
|
1054
|
+
return [{
|
|
1055
|
+
type: 'meta',
|
|
1056
|
+
agent: 'kimi',
|
|
1057
|
+
meta_type: metaType,
|
|
1058
|
+
raw: raw,
|
|
1059
|
+
timestamp: timestamp,
|
|
1060
|
+
}];
|
|
1061
|
+
}
|
|
1062
|
+
return [{
|
|
1063
|
+
type: raw.type || 'unknown',
|
|
1064
|
+
agent: 'kimi',
|
|
1065
|
+
raw: raw,
|
|
1066
|
+
timestamp: timestamp,
|
|
1067
|
+
}];
|
|
1068
|
+
}
|
|
911
1069
|
// --- Antigravity parsing ---
|
|
912
1070
|
// Intentionally conservative. Antigravity's `agy` binary advertises an
|
|
913
1071
|
// `--output-format json` flag in its docs, but the released binary errors with
|
package/dist/lib/usage.d.ts
CHANGED
|
@@ -85,6 +85,24 @@ export declare function getUsageInfoByIdentity(inputs: UsageIdentityInput[]): Pr
|
|
|
85
85
|
export declare function getUsageInfoForIdentity(input: UsageIdentityInput): Promise<UsageInfo>;
|
|
86
86
|
/** Format a one-line usage summary with compact bars for inline display. */
|
|
87
87
|
export declare function formatUsageSummary(plan: string | null, snapshot: UsageSnapshot | null, planWidth?: number): string;
|
|
88
|
+
/**
|
|
89
|
+
* Derive an account's real throttle state from its live usage windows — the
|
|
90
|
+
* same signal `agents usage` shows and balanced rotation trusts
|
|
91
|
+
* (`getRoutingUsedPercent` in rotate.ts). A window at 100% utilization means
|
|
92
|
+
* the account is throttled until that window resets.
|
|
93
|
+
*
|
|
94
|
+
* Returns `null` when there is no snapshot, so callers render no badge rather
|
|
95
|
+
* than a misleading one. This deliberately never consults
|
|
96
|
+
* `cachedExtraUsageDisabledReason`: that field describes why pay-as-you-go
|
|
97
|
+
* overage is disabled (`out_of_credits` = no overage credits purchased,
|
|
98
|
+
* `org_level_disabled` = an admin turned overage off), NOT whether the account
|
|
99
|
+
* can do work right now. A Pro account at 5% weekly usage with overage disabled
|
|
100
|
+
* is fully usable, yet that flag would mislabel it "out of credits".
|
|
101
|
+
*
|
|
102
|
+
* The model-specific `sonnet_week` sub-limit is excluded: hitting it throttles
|
|
103
|
+
* one model, not the account, so it shouldn't flip the whole row to throttled.
|
|
104
|
+
*/
|
|
105
|
+
export declare function deriveUsageStatusFromSnapshot(snapshot: UsageSnapshot | null | undefined): 'available' | 'rate_limited' | null;
|
|
88
106
|
/**
|
|
89
107
|
* Compact colored badge for the account's overall usage status. Renders only
|
|
90
108
|
* when the account is throttled — `available` and `null` return ''.
|
package/dist/lib/usage.js
CHANGED
|
@@ -201,6 +201,31 @@ export function formatUsageSummary(plan, snapshot, planWidth = 3) {
|
|
|
201
201
|
}
|
|
202
202
|
return parts.join(' ');
|
|
203
203
|
}
|
|
204
|
+
/**
|
|
205
|
+
* Derive an account's real throttle state from its live usage windows — the
|
|
206
|
+
* same signal `agents usage` shows and balanced rotation trusts
|
|
207
|
+
* (`getRoutingUsedPercent` in rotate.ts). A window at 100% utilization means
|
|
208
|
+
* the account is throttled until that window resets.
|
|
209
|
+
*
|
|
210
|
+
* Returns `null` when there is no snapshot, so callers render no badge rather
|
|
211
|
+
* than a misleading one. This deliberately never consults
|
|
212
|
+
* `cachedExtraUsageDisabledReason`: that field describes why pay-as-you-go
|
|
213
|
+
* overage is disabled (`out_of_credits` = no overage credits purchased,
|
|
214
|
+
* `org_level_disabled` = an admin turned overage off), NOT whether the account
|
|
215
|
+
* can do work right now. A Pro account at 5% weekly usage with overage disabled
|
|
216
|
+
* is fully usable, yet that flag would mislabel it "out of credits".
|
|
217
|
+
*
|
|
218
|
+
* The model-specific `sonnet_week` sub-limit is excluded: hitting it throttles
|
|
219
|
+
* one model, not the account, so it shouldn't flip the whole row to throttled.
|
|
220
|
+
*/
|
|
221
|
+
export function deriveUsageStatusFromSnapshot(snapshot) {
|
|
222
|
+
if (!snapshot || snapshot.windows.length === 0)
|
|
223
|
+
return null;
|
|
224
|
+
const blocking = snapshot.windows.filter((window) => window.key !== 'sonnet_week');
|
|
225
|
+
const windows = blocking.length > 0 ? blocking : snapshot.windows;
|
|
226
|
+
const maxUsed = Math.max(...windows.map((window) => window.usedPercent));
|
|
227
|
+
return maxUsed >= 100 ? 'rate_limited' : 'available';
|
|
228
|
+
}
|
|
204
229
|
/**
|
|
205
230
|
* Compact colored badge for the account's overall usage status. Renders only
|
|
206
231
|
* when the account is throttled — `available` and `null` return ''.
|
package/dist/lib/versions.js
CHANGED
|
@@ -25,7 +25,7 @@ import { checkbox, select } from '@inquirer/prompts';
|
|
|
25
25
|
import { getVersionsDir, ensureAgentsDir, readMeta, writeMeta, getCommandsDir, getSkillsDir, getHooksDir, getResolvedRulesDir, getUserRulesDir, getVersionResources, ensureVersionResourcePatterns, getProjectAgentsDir, getPromptcutsPath, getUserPromptcutsPath, getEnabledExtraRepos, getAgentsDir, getUserAgentsDir, getTrashVersionsDir, getActiveRulesPreset } from './state.js';
|
|
26
26
|
import { defaultPatterns, expandPatterns } from './resource-patterns.js';
|
|
27
27
|
import { listResources } from './resources.js';
|
|
28
|
-
import { AGENTS, agentConfigDirName, getAccountEmail, resolveAgentName, formatAgentError } from './agents.js';
|
|
28
|
+
import { AGENTS, agentConfigDirName, getAccountEmail, resolveAgentName, formatAgentError, findInPath } from './agents.js';
|
|
29
29
|
import { discoverPermissionGroups, getActivePermissionPresetName, readPermissionPresetRecipe, PERMISSION_PRESET_ENV_VAR } from './permissions.js';
|
|
30
30
|
import { parseMcpServerConfig } from './mcp.js';
|
|
31
31
|
import { createVersionedAlias, removeVersionedAlias, getConfigSymlinkVersion, ensureClaudeInsideSymlink } from './shims.js';
|
|
@@ -818,6 +818,14 @@ export function getBinaryPath(agent, version) {
|
|
|
818
818
|
catch { }
|
|
819
819
|
return path.join(grokDownloads, `grok-${version}`);
|
|
820
820
|
}
|
|
821
|
+
if (agent === 'droid') {
|
|
822
|
+
// Factory's installer drops a standalone native binary at ~/.local/bin/droid
|
|
823
|
+
// (no npm package, nothing in node_modules/.bin). The binary is global, not
|
|
824
|
+
// per-version — config isolation rides the ~/.factory symlink switch, not a
|
|
825
|
+
// separate binary per version. Mirror the shim's `droid` branch so
|
|
826
|
+
// isVersionInstalled/`agents view` agree with what actually executes.
|
|
827
|
+
return path.join(os.homedir(), '.local', 'bin', 'droid');
|
|
828
|
+
}
|
|
821
829
|
const versionDir = getVersionDir(agent, version);
|
|
822
830
|
return path.join(versionDir, 'node_modules', '.bin', agentConfig.cliCommand);
|
|
823
831
|
}
|
|
@@ -1005,19 +1013,28 @@ export async function installVersion(agent, version, onProgress) {
|
|
|
1005
1013
|
// but `agents view` shows the agent under "Not Managed" because
|
|
1006
1014
|
// listInstalledVersions returns [] — the installer drops the binary in
|
|
1007
1015
|
// ~/.local/bin (or similar) rather than the version's node_modules/.bin.
|
|
1008
|
-
//
|
|
1009
|
-
//
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1016
|
+
//
|
|
1017
|
+
// Agents whose binary is special-cased in getBinaryPath (grok ->
|
|
1018
|
+
// ~/.grok/downloads, droid -> ~/.local/bin/droid) need no symlink — and
|
|
1019
|
+
// creating one is actively harmful: `which <cli>` can resolve to OUR OWN
|
|
1020
|
+
// dispatcher shim, because ~/.agents/.cache/shims sits ahead of ~/.local/bin
|
|
1021
|
+
// on PATH. Symlinking node_modules/.bin/<cli> at the shim makes the shim
|
|
1022
|
+
// exec itself forever. So we skip the resolver-backed agents here AND, for
|
|
1023
|
+
// everyone else, filter the shims dir out of the `which` candidates so the
|
|
1024
|
+
// same race can't bite a non-special-cased installScript agent.
|
|
1025
|
+
if (agent !== 'grok' && agent !== 'droid') {
|
|
1026
|
+
// findInPath is a pure-Node PATH scan that already skips our own shims
|
|
1027
|
+
// dir — so it returns the genuine install, never our dispatcher shim
|
|
1028
|
+
// (which sits ahead of ~/.local/bin on PATH and would otherwise be
|
|
1029
|
+
// captured, producing a self-referential node_modules/.bin/<cli> link
|
|
1030
|
+
// that exec-loops forever).
|
|
1031
|
+
const installedBinary = findInPath(agentConfig.cliCommand);
|
|
1032
|
+
if (installedBinary) {
|
|
1033
|
+
importInstallScriptBinary({ agentId: agent, npmPackage: agentConfig.npmPackage, cliCommand: agentConfig.cliCommand }, installedVersion, installedBinary, versionDir);
|
|
1020
1034
|
}
|
|
1035
|
+
/* If null: binary missing from PATH (install script failed silently) or
|
|
1036
|
+
only our shim is present. Leave the version dir empty so getBinaryPath
|
|
1037
|
+
correctly reports it uninstalled. */
|
|
1021
1038
|
}
|
|
1022
1039
|
createVersionedAlias(agent, installedVersion);
|
|
1023
1040
|
emit('version.install', { agent, version: installedVersion });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@phnx-labs/agents-cli",
|
|
3
|
-
"version": "1.20.
|
|
3
|
+
"version": "1.20.17",
|
|
4
4
|
"description": "One CLI for all your AI coding agents - versions, config, cloud dispatch, sessions, and teams (now with first-class Grok Build CLI support)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -82,6 +82,7 @@
|
|
|
82
82
|
"@types/proper-lockfile": "4.1.4",
|
|
83
83
|
"@xterm/headless": "6.0.0",
|
|
84
84
|
"@zed-industries/agent-client-protocol": "0.4.5",
|
|
85
|
+
"aws4fetch": "1.0.20",
|
|
85
86
|
"chalk": "5.6.2",
|
|
86
87
|
"commander": "15.0.0",
|
|
87
88
|
"croner": "10.0.1",
|