@intent-systems/nexus 2026.1.5-4 → 2026.1.5-5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agents/agent-id.js +41 -0
- package/dist/agents/auth-profiles.js +114 -25
- package/dist/agents/identity-state.js +79 -0
- package/dist/agents/model-auth.js +1 -0
- package/dist/agents/model-fallback.js +15 -9
- package/dist/agents/model-selection.js +1 -1
- package/dist/agents/models-config.js +17 -11
- package/dist/agents/pi-embedded-runner.js +101 -9
- package/dist/agents/sandbox.js +12 -3
- package/dist/agents/skill-runner.js +29 -4
- package/dist/agents/skill-usage.js +114 -11
- package/dist/agents/skills-status.js +4 -4
- package/dist/agents/skills.js +18 -7
- package/dist/agents/subagent-registry.js +25 -11
- package/dist/agents/system-prompt.js +16 -0
- package/dist/agents/tool-policy.js +19 -3
- package/dist/agents/tools/browser-tool.js +5 -2
- package/dist/agents/tools/image-tool.js +93 -8
- package/dist/agents/tools/sessions-announce-target.js +5 -1
- package/dist/agents/workspace.js +55 -46
- package/dist/auto-reply/command-detection.js +2 -1
- package/dist/auto-reply/reply/directive-handling.js +153 -28
- package/dist/auto-reply/reply/directives.js +17 -2
- package/dist/auto-reply/reply/model-selection.js +8 -3
- package/dist/auto-reply/reply/queue.js +2 -2
- package/dist/auto-reply/reply.js +1 -1
- package/dist/auto-reply/thinking.js +15 -0
- package/dist/browser/chrome.js +1 -1
- package/dist/browser/client.js +2 -0
- package/dist/browser/config.js +6 -2
- package/dist/browser/pw-tools-core.js +3 -0
- package/dist/browser/routes/agent.js +14 -0
- package/dist/canvas-host/server.js +1 -1
- package/dist/capabilities/detector.js +46 -15
- package/dist/capabilities/registry.js +2 -1
- package/dist/cli/cloud-cli.js +12 -7
- package/dist/cli/credential-cli.js +139 -17
- package/dist/cli/gateway-cli.js +1 -1
- package/dist/cli/log-cli.js +25 -0
- package/dist/cli/pairing-cli.js +1 -1
- package/dist/cli/program.js +58 -6
- package/dist/cli/run-main.js +1 -1
- package/dist/cli/skills-cli.js +144 -21
- package/dist/cli/skills-hub-cli.js +59 -29
- package/dist/cli/tool-connector-cli.js +99 -24
- package/dist/cli/upstream-sync-cli.js +253 -96
- package/dist/cli/usage-cli.js +14 -0
- package/dist/commands/auth-choice-options.js +6 -1
- package/dist/commands/auth-choice.js +157 -5
- package/dist/commands/bootstrap-preset.js +10 -6
- package/dist/commands/capabilities.js +33 -6
- package/dist/commands/claude-md.js +3 -2
- package/dist/commands/config-view.js +1 -1
- package/dist/commands/configure.js +4 -4
- package/dist/commands/credential.js +497 -36
- package/dist/commands/cursor-rules.js +39 -19
- package/dist/commands/doctor.js +5 -4
- package/dist/commands/identity.js +28 -31
- package/dist/commands/init.js +15 -18
- package/dist/commands/log.js +134 -0
- package/dist/commands/models/fallbacks.js +1 -1
- package/dist/commands/models/image-fallbacks.js +1 -1
- package/dist/commands/models/list.js +1 -1
- package/dist/commands/models/scan.js +1 -1
- package/dist/commands/onboard-auth.js +27 -2
- package/dist/commands/onboard-eve-identity.js +7 -8
- package/dist/commands/onboard-non-interactive.js +4 -2
- package/dist/commands/onboard-quickstart.js +18 -11
- package/dist/commands/quest-state.js +271 -0
- package/dist/commands/quest.js +53 -13
- package/dist/commands/reset.js +1 -1
- package/dist/commands/sessions-ingest.js +5 -4
- package/dist/commands/setup.js +4 -2
- package/dist/commands/skills-manifest.js +2 -2
- package/dist/commands/status.js +179 -61
- package/dist/commands/suggestions.js +1 -1
- package/dist/commands/usage-tracking.js +32 -0
- package/dist/commands/usage-upload.js +6 -1
- package/dist/config/defaults.js +1 -3
- package/dist/config/includes.js +5 -7
- package/dist/config/io.js +88 -16
- package/dist/config/legacy.js +4 -2
- package/dist/config/paths.js +16 -0
- package/dist/config/sessions.js +9 -5
- package/dist/config/zod-schema.js +4 -3
- package/dist/control-plane/broker/broker.js +131 -78
- package/dist/control-plane/compaction.js +3 -5
- package/dist/control-plane/factory.js +2 -2
- package/dist/control-plane/index.js +2 -2
- package/dist/control-plane/odu/agents.js +28 -23
- package/dist/control-plane/odu/interaction-tools.js +62 -50
- package/dist/control-plane/odu/prompt-loader.js +8 -8
- package/dist/control-plane/odu/runtime.js +87 -75
- package/dist/control-plane/odu-control-plane.js +14 -12
- package/dist/control-plane/single-agent.js +13 -13
- package/dist/credentials/store.js +133 -7
- package/dist/gateway/server-browser.js +5 -4
- package/dist/gateway/server-methods/cron.js +11 -1
- package/dist/gateway/server.js +14 -7
- package/dist/infra/bonjour.js +1 -1
- package/dist/infra/event-log.js +8 -2
- package/dist/infra/path-env.js +1 -2
- package/dist/infra/provider-usage.auth.js +5 -3
- package/dist/infra/provider-usage.fetch.claude.js +16 -6
- package/dist/infra/provider-usage.fetch.minimax.js +8 -3
- package/dist/infra/provider-usage.js +9 -5
- package/dist/infra/restart.js +2 -2
- package/dist/infra/usage-settings.js +78 -0
- package/dist/infra/usage-suggestions.js +17 -5
- package/dist/infra/usage-upload.js +38 -1
- package/dist/infra/voicewake.js +2 -2
- package/dist/media/image-ops.js +3 -1
- package/dist/memory/index.js +2 -381
- package/dist/pairing/pairing-store.js +24 -0
- package/dist/providers/github-copilot-auth.js +1 -1
- package/dist/routing/resolve-route.js +6 -6
- package/dist/routing/session-key.js +3 -1
- package/dist/sessions/send-policy.js +5 -5
- package/dist/slack/monitor.js +22 -1
- package/dist/telegram/reaction-level.js +2 -1
- package/dist/utils.js +4 -3
- package/dist/wizard/onboarding.js +29 -7
- package/package.json +1 -1
package/dist/commands/status.js
CHANGED
|
@@ -1,109 +1,184 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
|
-
import
|
|
2
|
+
import { resolveIdentitySnapshot } from "../agents/identity-state.js";
|
|
3
|
+
import { getAggregateStats } from "../agents/skill-usage.js";
|
|
3
4
|
import { detectCapabilities } from "../capabilities/detector.js";
|
|
4
|
-
import { listCredentials } from "./credential.js";
|
|
5
|
-
import { resolveStateDir } from "../config/paths.js";
|
|
6
5
|
import { defaultRuntime } from "../runtime.js";
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
return undefined;
|
|
16
|
-
}
|
|
6
|
+
import { listCredentials } from "./credential.js";
|
|
7
|
+
import { resolveQuestState } from "./quest-state.js";
|
|
8
|
+
const STATUS_BOX_WIDTH = 62;
|
|
9
|
+
const POWER_BOX_WIDTH = 62;
|
|
10
|
+
function clampText(text, width) {
|
|
11
|
+
if (text.length <= width)
|
|
12
|
+
return text;
|
|
13
|
+
return text.slice(0, width);
|
|
17
14
|
}
|
|
18
|
-
function
|
|
19
|
-
const
|
|
20
|
-
const
|
|
21
|
-
const
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
const
|
|
27
|
-
const
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
const agentName = readField(agentIdentityPath, "Name");
|
|
31
|
-
const userName = readField(userProfilePath, "Name");
|
|
32
|
-
return {
|
|
33
|
-
agentId,
|
|
34
|
-
agentName,
|
|
35
|
-
userName,
|
|
36
|
-
agentIdentityPath,
|
|
37
|
-
agentSoulPath,
|
|
38
|
-
agentMemoryPath,
|
|
39
|
-
userProfilePath,
|
|
40
|
-
bootstrapPath,
|
|
41
|
-
hasIdentity,
|
|
42
|
-
};
|
|
15
|
+
function centerText(text, width) {
|
|
16
|
+
const clamped = clampText(text, width);
|
|
17
|
+
const pad = Math.max(0, width - clamped.length);
|
|
18
|
+
const left = Math.floor(pad / 2);
|
|
19
|
+
const right = pad - left;
|
|
20
|
+
return `${" ".repeat(left)}${clamped}${" ".repeat(right)}`;
|
|
21
|
+
}
|
|
22
|
+
function renderBox(lines, width) {
|
|
23
|
+
const top = `╔${"═".repeat(width)}╗`;
|
|
24
|
+
const bottom = `╚${"═".repeat(width)}╝`;
|
|
25
|
+
const body = lines.map((line) => `║${centerText(line, width)}║`);
|
|
26
|
+
return [top, ...body, bottom];
|
|
43
27
|
}
|
|
28
|
+
const FALLBACK_BOOTSTRAP_PROMPT = `# BOOTSTRAP.md - Welcome to Nexus
|
|
29
|
+
|
|
30
|
+
Identity setup is required.
|
|
31
|
+
|
|
32
|
+
For humans: start a short conversation and share your name, goals, and preferences.
|
|
33
|
+
For agents: write PROFILE.md, IDENTITY.md, SOUL.md, and MEMORY.md as you learn them.
|
|
34
|
+
`;
|
|
44
35
|
function readBootstrapPrompt(pathname) {
|
|
45
36
|
try {
|
|
46
37
|
return fs.readFileSync(pathname, "utf-8");
|
|
47
38
|
}
|
|
48
39
|
catch {
|
|
49
|
-
return
|
|
40
|
+
return FALLBACK_BOOTSTRAP_PROMPT;
|
|
50
41
|
}
|
|
51
42
|
}
|
|
52
43
|
function formatSuggestions(capabilities) {
|
|
53
44
|
const picks = capabilities.filter((cap) => ["ready", "needs_setup", "needs_install", "broken"].includes(cap.status));
|
|
54
|
-
const
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
45
|
+
const priority = ["broken", "ready", "needs_setup", "needs_install"];
|
|
46
|
+
const sorted = picks.sort((a, b) => priority.indexOf(a.status) - priority.indexOf(b.status));
|
|
47
|
+
return sorted.slice(0, 3).map((cap, idx) => {
|
|
48
|
+
const action = cap.status === "broken"
|
|
49
|
+
? "nexus credential list"
|
|
50
|
+
: `nexus capabilities --status ${cap.status.replace("_", "-")}`;
|
|
51
|
+
const title = cap.status === "ready"
|
|
52
|
+
? `Try ${cap.id} (ready, never used)`
|
|
53
|
+
: cap.status === "needs_setup"
|
|
54
|
+
? `Set up ${cap.id}`
|
|
55
|
+
: cap.status === "needs_install"
|
|
56
|
+
? `Install ${cap.id}`
|
|
57
|
+
: `Fix ${cap.id}`;
|
|
58
|
+
return {
|
|
59
|
+
id: cap.id,
|
|
60
|
+
status: cap.status,
|
|
61
|
+
index: idx + 1,
|
|
62
|
+
title,
|
|
63
|
+
action,
|
|
64
|
+
};
|
|
65
|
+
});
|
|
60
66
|
}
|
|
61
67
|
export async function statusCommand(opts, runtime = defaultRuntime) {
|
|
62
|
-
const
|
|
68
|
+
const identityResolution = resolveIdentitySnapshot();
|
|
69
|
+
if (!identityResolution.ok) {
|
|
70
|
+
const payload = {
|
|
71
|
+
ok: false,
|
|
72
|
+
error: "multiple_agents",
|
|
73
|
+
agents: identityResolution.agentOptions,
|
|
74
|
+
};
|
|
75
|
+
if (opts.json) {
|
|
76
|
+
runtime.log(JSON.stringify(payload, null, 2));
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
runtime.error("Multiple agents detected in state/agents.");
|
|
80
|
+
runtime.error(`Set NEXUS_AGENT_ID to one of: ${identityResolution.agentOptions.join(", ")}`);
|
|
81
|
+
}
|
|
82
|
+
runtime.exit(2);
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
const identity = identityResolution.snapshot;
|
|
63
86
|
const bootstrapPrompt = !identity.hasIdentity
|
|
64
87
|
? readBootstrapPrompt(identity.bootstrapPath)
|
|
65
88
|
: null;
|
|
66
89
|
const showBrief = opts.brief === true;
|
|
67
90
|
const showCredentials = opts.credentials === true || (!opts.capabilities && !showBrief);
|
|
68
91
|
const showCapabilities = opts.capabilities === true || (!opts.credentials && !showBrief);
|
|
69
|
-
const
|
|
70
|
-
|
|
71
|
-
const
|
|
92
|
+
const showUsage = opts.usage === true ||
|
|
93
|
+
(!showBrief && !opts.capabilities && !opts.credentials);
|
|
94
|
+
const shouldLoadCredentials = showCredentials || !showBrief;
|
|
95
|
+
const shouldLoadCapabilities = showCapabilities || !showBrief;
|
|
96
|
+
const credentials = shouldLoadCredentials ? await listCredentials() : null;
|
|
97
|
+
const capabilitySnapshot = shouldLoadCapabilities
|
|
98
|
+
? detectCapabilities()
|
|
99
|
+
: null;
|
|
100
|
+
const suggestions = capabilitySnapshot
|
|
101
|
+
? formatSuggestions(capabilitySnapshot.capabilities)
|
|
102
|
+
: [];
|
|
103
|
+
const usage = showUsage
|
|
104
|
+
? await getAggregateStats({ windowDays: 7, limit: 3 })
|
|
105
|
+
: null;
|
|
106
|
+
const questState = showBrief
|
|
107
|
+
? null
|
|
108
|
+
: await resolveQuestState({
|
|
109
|
+
identityConfigured: identity.hasIdentity,
|
|
110
|
+
capabilities: capabilitySnapshot?.capabilities ?? [],
|
|
111
|
+
credentials: credentials ?? {
|
|
112
|
+
version: 1,
|
|
113
|
+
lastUpdated: "",
|
|
114
|
+
services: {},
|
|
115
|
+
},
|
|
116
|
+
includeSecrets: true,
|
|
117
|
+
});
|
|
118
|
+
if (opts.quiet) {
|
|
119
|
+
runtime.exit(bootstrapPrompt ? 2 : 0);
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
72
122
|
if (opts.json) {
|
|
73
123
|
runtime.log(JSON.stringify({
|
|
74
124
|
platform: `${process.platform}/${process.arch}`,
|
|
75
125
|
identity,
|
|
76
|
-
bootstrap: bootstrapPrompt
|
|
126
|
+
bootstrap: bootstrapPrompt
|
|
127
|
+
? { required: true, prompt: bootstrapPrompt }
|
|
128
|
+
: undefined,
|
|
77
129
|
credentials: credentials ?? undefined,
|
|
78
130
|
capabilities: capabilitySnapshot ?? undefined,
|
|
79
131
|
suggestions,
|
|
132
|
+
usage: usage ?? undefined,
|
|
133
|
+
power: questState?.power ?? undefined,
|
|
80
134
|
}, null, 2));
|
|
81
135
|
return;
|
|
82
136
|
}
|
|
83
|
-
|
|
137
|
+
for (const line of renderBox(["Nexus Status", `${process.platform}/${process.arch}`], STATUS_BOX_WIDTH)) {
|
|
138
|
+
runtime.log(line);
|
|
139
|
+
}
|
|
84
140
|
if (bootstrapPrompt) {
|
|
85
|
-
runtime.log("
|
|
141
|
+
runtime.log("");
|
|
142
|
+
for (const line of renderBox(["Welcome to Nexus!", "Identity setup required."], STATUS_BOX_WIDTH)) {
|
|
143
|
+
runtime.log(line);
|
|
144
|
+
}
|
|
86
145
|
runtime.log(`\nBootstrap prompt (${identity.bootstrapPath}):\n`);
|
|
87
146
|
runtime.log(bootstrapPrompt.trimEnd());
|
|
88
147
|
return;
|
|
89
148
|
}
|
|
90
|
-
runtime.log("\
|
|
149
|
+
runtime.log("\n👤 Identity");
|
|
91
150
|
runtime.log(` User: ${identity.userName ?? "(unknown)"} → ${identity.userProfilePath}`);
|
|
92
151
|
runtime.log(` Agent: ${identity.agentName ?? "(unknown)"} (${identity.agentId}) → ${identity.agentIdentityPath}`);
|
|
93
152
|
runtime.log(` → ${identity.agentSoulPath}`);
|
|
94
153
|
runtime.log(` → ${identity.agentMemoryPath}`);
|
|
95
154
|
if (showCredentials && credentials) {
|
|
96
155
|
const services = Object.entries(credentials.services ?? {});
|
|
97
|
-
|
|
156
|
+
const accountCount = services.reduce((sum, [, info]) => sum + (info.accounts?.length ?? 0), 0);
|
|
157
|
+
runtime.log(`\n🔑 Credentials (${accountCount} configured)`);
|
|
158
|
+
const statusIcon = {
|
|
159
|
+
active: "✅",
|
|
160
|
+
ready: "⭐",
|
|
161
|
+
broken: "❌",
|
|
162
|
+
};
|
|
98
163
|
for (const [service, info] of services) {
|
|
99
164
|
const accounts = info.accounts ?? [];
|
|
100
|
-
|
|
101
|
-
|
|
165
|
+
if (accounts.length === 0) {
|
|
166
|
+
runtime.log(` ${service}: none`);
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
runtime.log(` ${service}`);
|
|
170
|
+
for (const account of accounts) {
|
|
171
|
+
const status = account.status ?? "ready";
|
|
172
|
+
const icon = statusIcon[status] ?? "•";
|
|
173
|
+
const suffix = account.lastError ? ` - ${account.lastError}` : "";
|
|
174
|
+
runtime.log(` ${icon} ${account.id} (${status})${suffix}`);
|
|
175
|
+
}
|
|
102
176
|
}
|
|
177
|
+
runtime.log(` Run 'nexus credential list' for full details`);
|
|
103
178
|
}
|
|
104
179
|
if (showCapabilities && capabilitySnapshot) {
|
|
105
|
-
runtime.log(`\
|
|
106
|
-
for (const [category,
|
|
180
|
+
runtime.log(`\n🎯 Capabilities (${capabilitySnapshot.summary.active}/${capabilitySnapshot.summary.total} active)`);
|
|
181
|
+
for (const [category, _entries] of Object.entries(capabilitySnapshot.registry.categories)) {
|
|
107
182
|
const categoryCaps = capabilitySnapshot.capabilities.filter((cap) => cap.category === category);
|
|
108
183
|
const activeCount = categoryCaps.filter((cap) => cap.status === "active").length;
|
|
109
184
|
if (categoryCaps.length === 0)
|
|
@@ -116,16 +191,59 @@ export async function statusCommand(opts, runtime = defaultRuntime) {
|
|
|
116
191
|
}
|
|
117
192
|
}
|
|
118
193
|
}
|
|
194
|
+
if (showUsage && usage) {
|
|
195
|
+
runtime.log("\n📊 Usage (last 7 days)");
|
|
196
|
+
const mostUsed = usage.topUsed && usage.topUsed.length > 0
|
|
197
|
+
? usage.topUsed
|
|
198
|
+
.map((item) => `${item.name} (${item.runs})`)
|
|
199
|
+
.join(", ")
|
|
200
|
+
: usage.mostUsed
|
|
201
|
+
? `${usage.mostUsed.name} (${usage.mostUsed.runs})`
|
|
202
|
+
: "none";
|
|
203
|
+
const readyUnused = usage.readyButUnused && usage.readyButUnused.length > 0
|
|
204
|
+
? usage.readyButUnused.join(", ")
|
|
205
|
+
: "none";
|
|
206
|
+
runtime.log(` Most used: ${mostUsed}`);
|
|
207
|
+
runtime.log(` Ready but unused: ${readyUnused}`);
|
|
208
|
+
}
|
|
119
209
|
if (suggestions.length > 0) {
|
|
120
|
-
runtime.log("\
|
|
210
|
+
runtime.log("\n🎯 Suggestions");
|
|
121
211
|
for (const suggestion of suggestions) {
|
|
122
|
-
runtime.log(` ${suggestion.index}. ${suggestion.
|
|
212
|
+
runtime.log(` ${suggestion.index}. ${suggestion.title}`);
|
|
213
|
+
runtime.log(` → ${suggestion.action}`);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
if (questState?.power) {
|
|
217
|
+
const width = 10;
|
|
218
|
+
const filled = Math.round((questState.power.percent / 100) * width);
|
|
219
|
+
const bar = `${"#".repeat(filled)}${"-".repeat(width - filled)}`;
|
|
220
|
+
const lines = [
|
|
221
|
+
`Power Level: ${bar} ${questState.power.percent}%`,
|
|
222
|
+
questState.power.nextQuest
|
|
223
|
+
? `Next unlock: ${questState.power.nextQuest.title} (+${questState.power.nextQuest.weight}%)`
|
|
224
|
+
: "Next unlock: none",
|
|
225
|
+
"Complete the core path to reach 100%",
|
|
226
|
+
];
|
|
227
|
+
runtime.log("");
|
|
228
|
+
for (const line of renderBox(lines, POWER_BOX_WIDTH)) {
|
|
229
|
+
runtime.log(line);
|
|
123
230
|
}
|
|
124
231
|
}
|
|
125
232
|
runtime.log(`\nNext: run 'nexus capabilities' or 'nexus credential list' for details.`);
|
|
126
233
|
}
|
|
127
234
|
export async function getStatusSummary() {
|
|
128
|
-
const
|
|
235
|
+
const resolution = resolveIdentitySnapshot();
|
|
236
|
+
if (!resolution.ok) {
|
|
237
|
+
return {
|
|
238
|
+
ok: false,
|
|
239
|
+
platform: `${process.platform}/${process.arch}`,
|
|
240
|
+
identity: {
|
|
241
|
+
agentId: "unknown",
|
|
242
|
+
hasIdentity: false,
|
|
243
|
+
},
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
const identity = resolution.snapshot;
|
|
129
247
|
return {
|
|
130
248
|
ok: true,
|
|
131
249
|
platform: `${process.platform}/${process.arch}`,
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { fetchUsageTrackingSettings, updateUsageTrackingSettings, } from "../infra/usage-settings.js";
|
|
2
|
+
import { defaultRuntime } from "../runtime.js";
|
|
3
|
+
export async function usageTrackingCommand(opts, runtime = defaultRuntime) {
|
|
4
|
+
if (opts.enable && opts.disable) {
|
|
5
|
+
runtime.error("Choose either --enable or --disable.");
|
|
6
|
+
runtime.exit(1);
|
|
7
|
+
}
|
|
8
|
+
const requestedOptOut = opts.disable ? true : opts.enable ? false : null;
|
|
9
|
+
const result = requestedOptOut === null
|
|
10
|
+
? await fetchUsageTrackingSettings()
|
|
11
|
+
: await updateUsageTrackingSettings(requestedOptOut);
|
|
12
|
+
if (!result) {
|
|
13
|
+
runtime.error("Usage tracking settings are unavailable (missing URL or token).");
|
|
14
|
+
runtime.exit(1);
|
|
15
|
+
}
|
|
16
|
+
if (opts.json) {
|
|
17
|
+
runtime.log(JSON.stringify(result, null, 2));
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
if (!result.ok) {
|
|
21
|
+
const errorMessage = result.error === "not_allowed"
|
|
22
|
+
? "Usage tracking is required for free accounts."
|
|
23
|
+
: result.error || "Failed to update usage tracking settings.";
|
|
24
|
+
runtime.error(errorMessage);
|
|
25
|
+
runtime.exit(1);
|
|
26
|
+
}
|
|
27
|
+
if (!result.canOptOut) {
|
|
28
|
+
runtime.log("Usage tracking is required for free accounts.");
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
runtime.log(`Usage tracking ${result.optOut ? "disabled" : "enabled"}${result.planName ? ` (${result.planName})` : ""}`);
|
|
32
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { defaultRuntime } from "../runtime.js";
|
|
2
1
|
import { runUsageUpload } from "../infra/usage-upload.js";
|
|
2
|
+
import { defaultRuntime } from "../runtime.js";
|
|
3
3
|
export async function usageUploadCommand(opts, runtime = defaultRuntime) {
|
|
4
4
|
const result = await runUsageUpload({
|
|
5
5
|
limit: opts.limit,
|
|
@@ -23,5 +23,10 @@ export async function usageUploadCommand(opts, runtime = defaultRuntime) {
|
|
|
23
23
|
runtime.log(` uploaded: ${result.uploaded ?? 0}`);
|
|
24
24
|
return;
|
|
25
25
|
}
|
|
26
|
+
if (result.status === "disabled") {
|
|
27
|
+
runtime.log(`\nUsage upload disabled`);
|
|
28
|
+
runtime.log(` cleared_outbox: ${result.cleared ?? 0}`);
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
26
31
|
runtime.log("\nUsage upload complete");
|
|
27
32
|
}
|
package/dist/config/defaults.js
CHANGED
|
@@ -99,9 +99,7 @@ export function applyModelDefaults(cfg) {
|
|
|
99
99
|
if (Object.keys(existingModels).length === 0)
|
|
100
100
|
return cfg;
|
|
101
101
|
let mutated = false;
|
|
102
|
-
const nextModels = {
|
|
103
|
-
...existingModels,
|
|
104
|
-
};
|
|
102
|
+
const nextModels = { ...existingModels };
|
|
105
103
|
for (const [alias, target] of Object.entries(DEFAULT_MODEL_ALIASES)) {
|
|
106
104
|
const entry = nextModels[target];
|
|
107
105
|
if (!entry)
|
package/dist/config/includes.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* @example
|
|
5
5
|
* ```json5
|
|
6
6
|
* {
|
|
7
|
-
* "$include": "./base.json5",
|
|
7
|
+
* "$include": "./base.json5", // single file
|
|
8
8
|
* "$include": ["./a.json5", "./b.json5"] // merge multiple
|
|
9
9
|
* }
|
|
10
10
|
* ```
|
|
@@ -52,7 +52,8 @@ export function deepMerge(target, source) {
|
|
|
52
52
|
if (isPlainObject(target) && isPlainObject(source)) {
|
|
53
53
|
const result = { ...target };
|
|
54
54
|
for (const key of Object.keys(source)) {
|
|
55
|
-
result[key] =
|
|
55
|
+
result[key] =
|
|
56
|
+
key in result ? deepMerge(result[key], source[key]) : source[key];
|
|
56
57
|
}
|
|
57
58
|
return result;
|
|
58
59
|
}
|
|
@@ -92,14 +93,11 @@ class IncludeProcessor {
|
|
|
92
93
|
}
|
|
93
94
|
processInclude(obj) {
|
|
94
95
|
const includeValue = obj[INCLUDE_KEY];
|
|
95
|
-
const otherKeys = Object.keys(obj).filter((
|
|
96
|
+
const otherKeys = Object.keys(obj).filter((key) => key !== INCLUDE_KEY);
|
|
96
97
|
const included = this.resolveInclude(includeValue);
|
|
97
98
|
if (otherKeys.length === 0) {
|
|
98
99
|
return included;
|
|
99
100
|
}
|
|
100
|
-
if (!isPlainObject(included)) {
|
|
101
|
-
throw new ConfigIncludeError("Sibling keys require included content to be an object", typeof includeValue === "string" ? includeValue : INCLUDE_KEY);
|
|
102
|
-
}
|
|
103
101
|
// Merge included content with sibling keys
|
|
104
102
|
const rest = {};
|
|
105
103
|
for (const key of otherKeys) {
|
|
@@ -172,7 +170,7 @@ class IncludeProcessor {
|
|
|
172
170
|
// Public API
|
|
173
171
|
// ============================================================================
|
|
174
172
|
const defaultResolver = {
|
|
175
|
-
readFile: (
|
|
173
|
+
readFile: (filePath) => fs.readFileSync(filePath, "utf-8"),
|
|
176
174
|
parseJson: (raw) => JSON5.parse(raw),
|
|
177
175
|
};
|
|
178
176
|
/**
|
package/dist/config/io.js
CHANGED
|
@@ -4,10 +4,12 @@ import path from "node:path";
|
|
|
4
4
|
import JSON5 from "json5";
|
|
5
5
|
import { loadShellEnvFallback, resolveShellEnvFallbackTimeoutMs, shouldEnableShellEnvFallback, } from "../infra/shell-env.js";
|
|
6
6
|
import { applyIdentityDefaults, applyLoggingDefaults, applyMessageDefaults, applyModelDefaults, applySessionDefaults, applyTalkApiKey, } from "./defaults.js";
|
|
7
|
+
import { ConfigIncludeError, resolveConfigIncludes } from "./includes.js";
|
|
7
8
|
import { findLegacyConfigIssues } from "./legacy.js";
|
|
8
|
-
import {
|
|
9
|
+
import { migrateLegacyConfig } from "./legacy-migrate.js";
|
|
10
|
+
import { resolveConfigPath, resolveStateDir } from "./paths.js";
|
|
9
11
|
import { validateConfigObject } from "./validation.js";
|
|
10
|
-
|
|
12
|
+
export { CircularIncludeError, ConfigIncludeError } from "./includes.js";
|
|
11
13
|
const SHELL_ENV_EXPECTED_KEYS = [
|
|
12
14
|
"OPENAI_API_KEY",
|
|
13
15
|
"ANTHROPIC_API_KEY",
|
|
@@ -23,6 +25,22 @@ const SHELL_ENV_EXPECTED_KEYS = [
|
|
|
23
25
|
"NEXUS_GATEWAY_TOKEN",
|
|
24
26
|
"NEXUS_GATEWAY_PASSWORD",
|
|
25
27
|
];
|
|
28
|
+
function attachChannelAliases(cfg) {
|
|
29
|
+
const channels = cfg.channels ?? {};
|
|
30
|
+
const nextChannels = {
|
|
31
|
+
...channels,
|
|
32
|
+
whatsapp: channels.whatsapp ?? cfg.whatsapp,
|
|
33
|
+
telegram: channels.telegram ?? cfg.telegram,
|
|
34
|
+
discord: channels.discord ?? cfg.discord,
|
|
35
|
+
slack: channels.slack ?? cfg.slack,
|
|
36
|
+
signal: channels.signal ?? cfg.signal,
|
|
37
|
+
imessage: channels.imessage ?? cfg.imessage,
|
|
38
|
+
};
|
|
39
|
+
const hasAny = Object.values(nextChannels).some((value) => value !== undefined);
|
|
40
|
+
if (!hasAny)
|
|
41
|
+
return cfg;
|
|
42
|
+
return { ...cfg, channels: nextChannels };
|
|
43
|
+
}
|
|
26
44
|
function resolveConfigPathForDeps(deps) {
|
|
27
45
|
if (deps.configPath)
|
|
28
46
|
return deps.configPath;
|
|
@@ -48,8 +66,9 @@ export function parseConfigJson5(raw, json5 = JSON5) {
|
|
|
48
66
|
}
|
|
49
67
|
export function createConfigIO(overrides = {}) {
|
|
50
68
|
const deps = normalizeDeps(overrides);
|
|
51
|
-
const
|
|
69
|
+
const resolveConfigPath = () => resolveConfigPathForDeps(deps);
|
|
52
70
|
function loadConfig() {
|
|
71
|
+
const configPath = resolveConfigPath();
|
|
53
72
|
try {
|
|
54
73
|
if (!deps.fs.existsSync(configPath)) {
|
|
55
74
|
if (shouldEnableShellEnvFallback(deps.env)) {
|
|
@@ -65,17 +84,33 @@ export function createConfigIO(overrides = {}) {
|
|
|
65
84
|
}
|
|
66
85
|
const raw = deps.fs.readFileSync(configPath, "utf-8");
|
|
67
86
|
const parsed = deps.json5.parse(raw);
|
|
68
|
-
|
|
87
|
+
const resolved = resolveConfigIncludes(parsed, configPath, {
|
|
88
|
+
readFile: (p) => deps.fs.readFileSync(p, "utf-8"),
|
|
89
|
+
parseJson: (json) => deps.json5.parse(json),
|
|
90
|
+
});
|
|
91
|
+
if (typeof resolved !== "object" || resolved === null)
|
|
69
92
|
return {};
|
|
70
|
-
const
|
|
71
|
-
|
|
93
|
+
const migrated = migrateLegacyConfig(resolved);
|
|
94
|
+
const candidate = migrated.config ?? resolved;
|
|
95
|
+
const validated = validateConfigObject(candidate);
|
|
96
|
+
if (!validated.ok) {
|
|
72
97
|
deps.logger.error("Invalid config:");
|
|
73
|
-
for (const iss of validated.
|
|
74
|
-
deps.logger.error(`- ${iss.path
|
|
98
|
+
for (const iss of validated.issues) {
|
|
99
|
+
deps.logger.error(`- ${iss.path}: ${iss.message}`);
|
|
75
100
|
}
|
|
76
101
|
return {};
|
|
77
102
|
}
|
|
78
|
-
|
|
103
|
+
if (migrated.config && migrated.changes.length > 0) {
|
|
104
|
+
deps.logger.warn(`Auto-migrated config: ${migrated.changes.join(" ")}`);
|
|
105
|
+
try {
|
|
106
|
+
deps.fs.writeFileSync(configPath, `${JSON.stringify(migrated.config, null, 2)}\n`);
|
|
107
|
+
}
|
|
108
|
+
catch (err) {
|
|
109
|
+
deps.logger.warn(`Failed to write migrated config: ${String(err)}`);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
const cfg = applyModelDefaults(applySessionDefaults(applyLoggingDefaults(applyMessageDefaults(applyIdentityDefaults(validated.config)))));
|
|
113
|
+
const normalized = attachChannelAliases(cfg);
|
|
79
114
|
const enabled = shouldEnableShellEnvFallback(deps.env) ||
|
|
80
115
|
cfg.env?.shellEnv?.enabled === true;
|
|
81
116
|
if (enabled) {
|
|
@@ -88,7 +123,7 @@ export function createConfigIO(overrides = {}) {
|
|
|
88
123
|
resolveShellEnvFallbackTimeoutMs(deps.env),
|
|
89
124
|
});
|
|
90
125
|
}
|
|
91
|
-
return
|
|
126
|
+
return normalized;
|
|
92
127
|
}
|
|
93
128
|
catch (err) {
|
|
94
129
|
deps.logger.error(`Failed to read config at ${configPath}`, err);
|
|
@@ -96,9 +131,10 @@ export function createConfigIO(overrides = {}) {
|
|
|
96
131
|
}
|
|
97
132
|
}
|
|
98
133
|
async function readConfigFileSnapshot() {
|
|
134
|
+
const configPath = resolveConfigPath();
|
|
99
135
|
const exists = deps.fs.existsSync(configPath);
|
|
100
136
|
if (!exists) {
|
|
101
|
-
const config = applyTalkApiKey(applyModelDefaults(applySessionDefaults(applyMessageDefaults({}))));
|
|
137
|
+
const config = attachChannelAliases(applyTalkApiKey(applyModelDefaults(applySessionDefaults(applyMessageDefaults({})))));
|
|
102
138
|
const legacyIssues = [];
|
|
103
139
|
return {
|
|
104
140
|
path: configPath,
|
|
@@ -128,8 +164,34 @@ export function createConfigIO(overrides = {}) {
|
|
|
128
164
|
legacyIssues: [],
|
|
129
165
|
};
|
|
130
166
|
}
|
|
131
|
-
|
|
132
|
-
|
|
167
|
+
let resolved;
|
|
168
|
+
try {
|
|
169
|
+
resolved = resolveConfigIncludes(parsedRes.parsed, configPath, {
|
|
170
|
+
readFile: (p) => deps.fs.readFileSync(p, "utf-8"),
|
|
171
|
+
parseJson: (json) => deps.json5.parse(json),
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
catch (err) {
|
|
175
|
+
const message = err instanceof ConfigIncludeError
|
|
176
|
+
? err.message
|
|
177
|
+
: `Include resolution failed: ${String(err)}`;
|
|
178
|
+
return {
|
|
179
|
+
path: configPath,
|
|
180
|
+
exists: true,
|
|
181
|
+
raw,
|
|
182
|
+
parsed: parsedRes.parsed,
|
|
183
|
+
valid: false,
|
|
184
|
+
config: {},
|
|
185
|
+
issues: [{ path: "", message }],
|
|
186
|
+
legacyIssues: [],
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
const migrated = migrateLegacyConfig(resolved);
|
|
190
|
+
const candidate = migrated.config ?? resolved;
|
|
191
|
+
const legacyIssues = migrated.config
|
|
192
|
+
? []
|
|
193
|
+
: findLegacyConfigIssues(candidate);
|
|
194
|
+
const validated = validateConfigObject(candidate);
|
|
133
195
|
if (!validated.ok) {
|
|
134
196
|
return {
|
|
135
197
|
path: configPath,
|
|
@@ -142,13 +204,22 @@ export function createConfigIO(overrides = {}) {
|
|
|
142
204
|
legacyIssues,
|
|
143
205
|
};
|
|
144
206
|
}
|
|
207
|
+
if (migrated.config && migrated.changes.length > 0) {
|
|
208
|
+
deps.logger.warn(`Auto-migrated config: ${migrated.changes.join(" ")}`);
|
|
209
|
+
try {
|
|
210
|
+
deps.fs.writeFileSync(configPath, `${JSON.stringify(migrated.config, null, 2)}\n`);
|
|
211
|
+
}
|
|
212
|
+
catch (err) {
|
|
213
|
+
deps.logger.warn(`Failed to write migrated config: ${String(err)}`);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
145
216
|
return {
|
|
146
217
|
path: configPath,
|
|
147
218
|
exists: true,
|
|
148
219
|
raw,
|
|
149
220
|
parsed: parsedRes.parsed,
|
|
150
221
|
valid: true,
|
|
151
|
-
config: applyTalkApiKey(applyModelDefaults(applySessionDefaults(applyLoggingDefaults(applyMessageDefaults(validated.config))))),
|
|
222
|
+
config: attachChannelAliases(applyTalkApiKey(applyModelDefaults(applySessionDefaults(applyLoggingDefaults(applyMessageDefaults(validated.config)))))),
|
|
152
223
|
issues: [],
|
|
153
224
|
legacyIssues,
|
|
154
225
|
};
|
|
@@ -167,6 +238,7 @@ export function createConfigIO(overrides = {}) {
|
|
|
167
238
|
}
|
|
168
239
|
}
|
|
169
240
|
async function writeConfigFile(cfg) {
|
|
241
|
+
const configPath = resolveConfigPath();
|
|
170
242
|
await deps.fs.promises.mkdir(path.dirname(configPath), {
|
|
171
243
|
recursive: true,
|
|
172
244
|
});
|
|
@@ -176,13 +248,13 @@ export function createConfigIO(overrides = {}) {
|
|
|
176
248
|
await deps.fs.promises.writeFile(configPath, json, "utf-8");
|
|
177
249
|
}
|
|
178
250
|
return {
|
|
179
|
-
configPath,
|
|
251
|
+
configPath: resolveConfigPath(),
|
|
180
252
|
loadConfig,
|
|
181
253
|
readConfigFileSnapshot,
|
|
182
254
|
writeConfigFile,
|
|
183
255
|
};
|
|
184
256
|
}
|
|
185
|
-
const defaultIO = createConfigIO(
|
|
257
|
+
const defaultIO = createConfigIO();
|
|
186
258
|
export const loadConfig = defaultIO.loadConfig;
|
|
187
259
|
export const readConfigFileSnapshot = defaultIO.readConfigFileSnapshot;
|
|
188
260
|
export const writeConfigFile = defaultIO.writeConfigFile;
|
package/dist/config/legacy.js
CHANGED
|
@@ -138,7 +138,8 @@ const LEGACY_CONFIG_MIGRATIONS = [
|
|
|
138
138
|
if (!match || typeof match !== "object")
|
|
139
139
|
continue;
|
|
140
140
|
const matchRecord = match;
|
|
141
|
-
if (matchRecord.channel === undefined &&
|
|
141
|
+
if (matchRecord.channel === undefined &&
|
|
142
|
+
matchRecord.provider !== undefined) {
|
|
142
143
|
matchRecord.channel = matchRecord.provider;
|
|
143
144
|
delete matchRecord.provider;
|
|
144
145
|
touched = true;
|
|
@@ -202,7 +203,8 @@ const LEGACY_CONFIG_MIGRATIONS = [
|
|
|
202
203
|
if (!match || typeof match !== "object")
|
|
203
204
|
continue;
|
|
204
205
|
const matchRecord = match;
|
|
205
|
-
if (matchRecord.channel === undefined &&
|
|
206
|
+
if (matchRecord.channel === undefined &&
|
|
207
|
+
matchRecord.provider !== undefined) {
|
|
206
208
|
matchRecord.channel = matchRecord.provider;
|
|
207
209
|
delete matchRecord.provider;
|
|
208
210
|
touched = true;
|
package/dist/config/paths.js
CHANGED
|
@@ -20,6 +20,12 @@ export function resolveStateDir(env = process.env, homedir = os.homedir) {
|
|
|
20
20
|
const override = env.NEXUS_STATE_DIR?.trim();
|
|
21
21
|
if (override)
|
|
22
22
|
return resolveUserPath(override);
|
|
23
|
+
const envHome = env.HOME?.trim();
|
|
24
|
+
if (envHome)
|
|
25
|
+
return path.join(resolveUserPath(envHome), "nexus", "state");
|
|
26
|
+
const envProfile = env.USERPROFILE?.trim();
|
|
27
|
+
if (envProfile)
|
|
28
|
+
return path.join(resolveUserPath(envProfile), "nexus", "state");
|
|
23
29
|
return path.join(homedir(), "nexus", "state");
|
|
24
30
|
}
|
|
25
31
|
function resolveUserPath(input) {
|
|
@@ -54,6 +60,16 @@ export function resolveCredentialsDir(env = process.env, stateDir = resolveState
|
|
|
54
60
|
return resolveUserPath(override);
|
|
55
61
|
return path.join(stateDir, "credentials");
|
|
56
62
|
}
|
|
63
|
+
/**
|
|
64
|
+
* Bootstrap prompt path (shared across agents).
|
|
65
|
+
* Default: $NEXUS_STATE_DIR/agents/BOOTSTRAP.md
|
|
66
|
+
*/
|
|
67
|
+
export function resolveBootstrapPath(env = process.env, stateDir = resolveStateDir(env, os.homedir)) {
|
|
68
|
+
const override = env.NEXUS_BOOTSTRAP_PATH?.trim();
|
|
69
|
+
if (override)
|
|
70
|
+
return resolveUserPath(override);
|
|
71
|
+
return path.join(stateDir, "agents", "BOOTSTRAP.md");
|
|
72
|
+
}
|
|
57
73
|
const OAUTH_FILENAME = "oauth.json";
|
|
58
74
|
/** @deprecated Use resolveCredentialsDir. */
|
|
59
75
|
export function resolveOAuthDir(env = process.env, stateDir = resolveStateDir(env, os.homedir)) {
|