@intent-systems/nexus 2026.1.5-3 → 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 +245 -0
- package/dist/capabilities/registry.js +99 -0
- package/dist/channels/location.js +44 -0
- package/dist/channels/web/index.js +2 -0
- 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 +1022 -0
- package/dist/control-plane/compaction.js +282 -0
- package/dist/control-plane/factory.js +31 -0
- package/dist/control-plane/index.js +10 -0
- package/dist/control-plane/odu/agents.js +192 -0
- package/dist/control-plane/odu/interaction-tools.js +208 -0
- package/dist/control-plane/odu/prompt-loader.js +95 -0
- package/dist/control-plane/odu/runtime.js +479 -0
- package/dist/control-plane/odu/types.js +6 -0
- package/dist/control-plane/odu-control-plane.js +316 -0
- package/dist/control-plane/single-agent.js +249 -0
- package/dist/control-plane/types.js +11 -0
- package/dist/credentials/store.js +449 -0
- 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/logging/redact.js +109 -0
- package/dist/markdown/fences.js +58 -0
- package/dist/media/image-ops.js +3 -1
- package/dist/memory/embeddings.js +146 -0
- package/dist/memory/index.js +3 -0
- package/dist/memory/internal.js +163 -0
- package/dist/pairing/pairing-store.js +218 -0
- package/dist/plugins/cli.js +42 -0
- package/dist/plugins/discovery.js +253 -0
- package/dist/plugins/install.js +181 -0
- package/dist/plugins/loader.js +290 -0
- package/dist/plugins/registry.js +105 -0
- package/dist/plugins/status.js +29 -0
- package/dist/plugins/tools.js +39 -0
- package/dist/plugins/types.js +1 -0
- package/dist/providers/github-copilot-auth.js +1 -1
- package/dist/routing/resolve-route.js +144 -0
- package/dist/routing/session-key.js +65 -0
- 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/provider-utils.js +28 -0
- package/dist/utils.js +4 -3
- package/dist/wizard/onboarding.js +29 -7
- package/package.json +4 -29
- package/patches/@mariozechner__pi-ai.patch +215 -0
- package/patches/playwright-core@1.57.0.patch +13 -0
- package/patches/qrcode-terminal.patch +12 -0
- package/scripts/postinstall.js +202 -0
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { normalizeElevatedLevel, normalizeThinkLevel, normalizeVerboseLevel, } from "../thinking.js";
|
|
1
|
+
import { normalizeElevatedLevel, normalizeReasoningLevel, normalizeThinkLevel, normalizeVerboseLevel, } from "../thinking.js";
|
|
2
2
|
export function extractThinkDirective(body) {
|
|
3
3
|
if (!body)
|
|
4
4
|
return { cleaned: "", hasDirective: false };
|
|
5
5
|
// Match the longest keyword first to avoid partial captures (e.g. "/think:high")
|
|
6
|
-
const match = body.match(/(?:^|\s)\/(?:thinking|think|t)\s*:?\s*([a-zA-Z-]+)
|
|
6
|
+
const match = body.match(/(?:^|\s)\/(?:thinking|think|t)(?=$|\s|:)\s*:?\s*([a-zA-Z-]+)?\b/i);
|
|
7
7
|
const thinkLevel = normalizeThinkLevel(match?.[1]);
|
|
8
8
|
const cleaned = match
|
|
9
9
|
? body.replace(match[0], "").replace(/\s+/g, " ").trim()
|
|
@@ -45,6 +45,21 @@ export function extractElevatedDirective(body) {
|
|
|
45
45
|
hasDirective: !!match,
|
|
46
46
|
};
|
|
47
47
|
}
|
|
48
|
+
export function extractReasoningDirective(body) {
|
|
49
|
+
if (!body)
|
|
50
|
+
return { cleaned: "", hasDirective: false };
|
|
51
|
+
const match = body.match(/(?:^|\s)\/(?:reasoning|reason)(?=$|\s|:)\s*:?\s*([a-zA-Z-]+)?\b/i);
|
|
52
|
+
const reasoningLevel = normalizeReasoningLevel(match?.[1]);
|
|
53
|
+
const cleaned = match
|
|
54
|
+
? body.replace(match[0], "").replace(/\s+/g, " ").trim()
|
|
55
|
+
: body.trim();
|
|
56
|
+
return {
|
|
57
|
+
cleaned,
|
|
58
|
+
reasoningLevel,
|
|
59
|
+
rawLevel: match?.[1],
|
|
60
|
+
hasDirective: !!match,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
48
63
|
export function extractStatusDirective(body) {
|
|
49
64
|
if (!body)
|
|
50
65
|
return { cleaned: "", hasDirective: false };
|
|
@@ -122,7 +122,8 @@ export function resolveModelDirectiveSelection(params) {
|
|
|
122
122
|
if (params.provider && provider !== normalizeProviderId(params.provider))
|
|
123
123
|
continue;
|
|
124
124
|
const haystack = `${provider}/${model}`.toLowerCase();
|
|
125
|
-
if (haystack.includes(fragment) ||
|
|
125
|
+
if (haystack.includes(fragment) ||
|
|
126
|
+
model.toLowerCase().includes(fragment)) {
|
|
126
127
|
candidates.push({ provider, model });
|
|
127
128
|
}
|
|
128
129
|
}
|
|
@@ -132,7 +133,10 @@ export function resolveModelDirectiveSelection(params) {
|
|
|
132
133
|
for (const [aliasKey, entry] of aliasIndex.byAlias.entries()) {
|
|
133
134
|
if (!aliasKey.includes(fragment))
|
|
134
135
|
continue;
|
|
135
|
-
aliasMatches.push({
|
|
136
|
+
aliasMatches.push({
|
|
137
|
+
provider: entry.ref.provider,
|
|
138
|
+
model: entry.ref.model,
|
|
139
|
+
});
|
|
136
140
|
}
|
|
137
141
|
for (const match of aliasMatches) {
|
|
138
142
|
const key = modelKey(match.provider, match.model);
|
|
@@ -176,7 +180,8 @@ export function resolveModelDirectiveSelection(params) {
|
|
|
176
180
|
}
|
|
177
181
|
const resolvedKey = modelKey(resolved.ref.provider, resolved.ref.model);
|
|
178
182
|
if (allowedModelKeys.size === 0 || allowedModelKeys.has(resolvedKey)) {
|
|
179
|
-
const alias = resolved.alias ??
|
|
183
|
+
const alias = resolved.alias ??
|
|
184
|
+
pickAliasForKey(resolved.ref.provider, resolved.ref.model);
|
|
180
185
|
return {
|
|
181
186
|
selection: {
|
|
182
187
|
provider: resolved.ref.provider,
|
|
@@ -370,8 +370,8 @@ function defaultQueueModeForProvider(provider) {
|
|
|
370
370
|
export function resolveQueueSettings(params) {
|
|
371
371
|
const providerKey = params.provider?.trim().toLowerCase();
|
|
372
372
|
const queueCfg = params.cfg.routing?.queue;
|
|
373
|
-
const providerModeRaw = providerKey && queueCfg?.
|
|
374
|
-
? queueCfg.
|
|
373
|
+
const providerModeRaw = providerKey && queueCfg?.byChannel
|
|
374
|
+
? queueCfg.byChannel[providerKey]
|
|
375
375
|
: undefined;
|
|
376
376
|
const resolvedMode = params.inlineMode ??
|
|
377
377
|
normalizeQueueMode(params.sessionEntry?.queueMode) ??
|
package/dist/auto-reply/reply.js
CHANGED
|
@@ -35,7 +35,7 @@ export { extractElevatedDirective, extractThinkDirective, extractVerboseDirectiv
|
|
|
35
35
|
export { extractQueueDirective } from "./reply/queue.js";
|
|
36
36
|
export { extractReplyToTag } from "./reply/reply-tags.js";
|
|
37
37
|
const BARE_SESSION_RESET_PROMPT = "A new session was started via /new or /reset. Say hi briefly (1-2 sentences) and ask what the user wants to do next. Do not mention internal steps, files, tools, or reasoning.";
|
|
38
|
-
const CONTROL_COMMAND_PREFIX_RE = /^\/(?:status|help|thinking|think|t|verbose|v|elevated|elev|model|queue|activation|send|restart|reset|new|compact)\b/i;
|
|
38
|
+
const CONTROL_COMMAND_PREFIX_RE = /^\/(?:status|help|thinking|think|t|reasoning|reason|verbose|v|elevated|elev|model|queue|activation|send|restart|reset|new|compact)\b/i;
|
|
39
39
|
function normalizeAllowToken(value) {
|
|
40
40
|
if (!value)
|
|
41
41
|
return "";
|
|
@@ -21,6 +21,8 @@ export function normalizeThinkLevel(raw) {
|
|
|
21
21
|
"max",
|
|
22
22
|
].includes(key))
|
|
23
23
|
return "high";
|
|
24
|
+
if (["xhigh", "x-high", "extra-high"].includes(key))
|
|
25
|
+
return "xhigh";
|
|
24
26
|
if (["think"].includes(key))
|
|
25
27
|
return "minimal";
|
|
26
28
|
return undefined;
|
|
@@ -47,3 +49,16 @@ export function normalizeElevatedLevel(raw) {
|
|
|
47
49
|
return "on";
|
|
48
50
|
return undefined;
|
|
49
51
|
}
|
|
52
|
+
// Normalize reasoning visibility flags used to control <think> output.
|
|
53
|
+
export function normalizeReasoningLevel(raw) {
|
|
54
|
+
if (!raw)
|
|
55
|
+
return undefined;
|
|
56
|
+
const key = raw.toLowerCase();
|
|
57
|
+
if (["off", "false", "no", "0"].includes(key))
|
|
58
|
+
return "off";
|
|
59
|
+
if (["on", "true", "yes", "1"].includes(key))
|
|
60
|
+
return "on";
|
|
61
|
+
if (["stream", "live"].includes(key))
|
|
62
|
+
return "stream";
|
|
63
|
+
return undefined;
|
|
64
|
+
}
|
package/dist/browser/chrome.js
CHANGED
|
@@ -7,7 +7,7 @@ import { ensurePortAvailable } from "../infra/ports.js";
|
|
|
7
7
|
import { createSubsystemLogger } from "../logging.js";
|
|
8
8
|
import { CONFIG_DIR } from "../utils.js";
|
|
9
9
|
import { normalizeCdpWsUrl } from "./cdp.js";
|
|
10
|
-
import { DEFAULT_NEXUS_BROWSER_COLOR, DEFAULT_NEXUS_BROWSER_PROFILE_NAME } from "./constants.js";
|
|
10
|
+
import { DEFAULT_NEXUS_BROWSER_COLOR, DEFAULT_NEXUS_BROWSER_PROFILE_NAME, } from "./constants.js";
|
|
11
11
|
const log = createSubsystemLogger("browser").child("chrome");
|
|
12
12
|
function exists(filePath) {
|
|
13
13
|
try {
|
package/dist/browser/client.js
CHANGED
|
@@ -96,6 +96,8 @@ export async function browserSnapshot(baseUrl, opts) {
|
|
|
96
96
|
q.set("targetId", opts.targetId);
|
|
97
97
|
if (typeof opts.limit === "number")
|
|
98
98
|
q.set("limit", String(opts.limit));
|
|
99
|
+
if (typeof opts.maxChars === "number")
|
|
100
|
+
q.set("maxChars", String(opts.maxChars));
|
|
99
101
|
if (opts.profile)
|
|
100
102
|
q.set("profile", opts.profile);
|
|
101
103
|
return await fetchBrowserJson(`${baseUrl}/snapshot?${q.toString()}`, {
|
package/dist/browser/config.js
CHANGED
|
@@ -56,7 +56,8 @@ function ensureDefaultProfile(profiles, defaultColor, legacyCdpPort, derivedDefa
|
|
|
56
56
|
}
|
|
57
57
|
export function resolveBrowserConfig(cfg) {
|
|
58
58
|
const enabled = cfg?.enabled ?? DEFAULT_NEXUS_BROWSER_ENABLED;
|
|
59
|
-
const envControlUrl = (process.env.NEXUS_BROWSER_CONTROL_URL ??
|
|
59
|
+
const envControlUrl = (process.env.NEXUS_BROWSER_CONTROL_URL ??
|
|
60
|
+
process.env.NEXUS_BROWSER_CONTROL_URL)?.trim();
|
|
60
61
|
const derivedControlPort = (() => {
|
|
61
62
|
const raw = (process.env.NEXUS_GATEWAY_PORT ?? process.env.NEXUS_GATEWAY_PORT)?.trim();
|
|
62
63
|
if (!raw)
|
|
@@ -69,7 +70,10 @@ export function resolveBrowserConfig(cfg) {
|
|
|
69
70
|
const derivedControlUrl = derivedControlPort
|
|
70
71
|
? `http://127.0.0.1:${derivedControlPort}`
|
|
71
72
|
: null;
|
|
72
|
-
const controlInfo = parseHttpUrl(cfg?.controlUrl ??
|
|
73
|
+
const controlInfo = parseHttpUrl(cfg?.controlUrl ??
|
|
74
|
+
envControlUrl ??
|
|
75
|
+
derivedControlUrl ??
|
|
76
|
+
DEFAULT_NEXUS_BROWSER_CONTROL_URL, "browser.controlUrl");
|
|
73
77
|
const controlPort = controlInfo.port;
|
|
74
78
|
const defaultColor = normalizeHexColor(cfg?.color);
|
|
75
79
|
const derivedCdpRange = deriveDefaultBrowserCdpPortRange(controlPort);
|
|
@@ -20,6 +20,9 @@ export async function snapshotAiViaPlaywright(opts) {
|
|
|
20
20
|
const result = await maybe._snapshotForAI({
|
|
21
21
|
timeout: Math.max(500, Math.min(60_000, Math.floor(opts.timeoutMs ?? 5000))),
|
|
22
22
|
track: "response",
|
|
23
|
+
...(typeof opts.maxChars === "number" && Number.isFinite(opts.maxChars)
|
|
24
|
+
? { maxChars: Math.max(1, Math.floor(opts.maxChars)) }
|
|
25
|
+
: {}),
|
|
23
26
|
});
|
|
24
27
|
return { snapshot: String(result?.full ?? "") };
|
|
25
28
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import { ensureMediaDir, saveMediaBuffer } from "../../media/store.js";
|
|
3
3
|
import { captureScreenshot, snapshotAria } from "../cdp.js";
|
|
4
|
+
import { DEFAULT_AI_SNAPSHOT_MAX_CHARS } from "../constants.js";
|
|
4
5
|
import { DEFAULT_BROWSER_SCREENSHOT_MAX_BYTES, DEFAULT_BROWSER_SCREENSHOT_MAX_SIDE, normalizeBrowserScreenshot, } from "../screenshot.js";
|
|
5
6
|
import { getProfileContext, jsonError, toBoolean, toNumber, toStringArray, toStringOrEmpty, } from "./utils.js";
|
|
6
7
|
const SELECTOR_UNSUPPORTED_MESSAGE = [
|
|
@@ -498,6 +499,18 @@ export function registerBrowserAgentRoutes(app, ctx) {
|
|
|
498
499
|
? "ai"
|
|
499
500
|
: "aria";
|
|
500
501
|
const limit = typeof req.query.limit === "string" ? Number(req.query.limit) : undefined;
|
|
502
|
+
const maxCharsRaw = typeof req.query.maxChars === "string" ||
|
|
503
|
+
typeof req.query.maxChars === "number"
|
|
504
|
+
? Number(req.query.maxChars)
|
|
505
|
+
: undefined;
|
|
506
|
+
const maxChars = format === "ai" &&
|
|
507
|
+
typeof maxCharsRaw === "number" &&
|
|
508
|
+
Number.isFinite(maxCharsRaw) &&
|
|
509
|
+
maxCharsRaw > 0
|
|
510
|
+
? Math.floor(maxCharsRaw)
|
|
511
|
+
: format === "ai"
|
|
512
|
+
? DEFAULT_AI_SNAPSHOT_MAX_CHARS
|
|
513
|
+
: undefined;
|
|
501
514
|
try {
|
|
502
515
|
const tab = await profileCtx.ensureTabAvailable(targetId || undefined);
|
|
503
516
|
if (format === "ai") {
|
|
@@ -507,6 +520,7 @@ export function registerBrowserAgentRoutes(app, ctx) {
|
|
|
507
520
|
const snap = await pw.snapshotAiViaPlaywright({
|
|
508
521
|
cdpUrl: profileCtx.profile.cdpUrl,
|
|
509
522
|
targetId: tab.targetId,
|
|
523
|
+
maxChars,
|
|
510
524
|
});
|
|
511
525
|
return res.json({
|
|
512
526
|
ok: true,
|
|
@@ -126,7 +126,7 @@ async function resolveFilePath(rootReal, urlPath) {
|
|
|
126
126
|
}
|
|
127
127
|
}
|
|
128
128
|
function isDisabledByEnv() {
|
|
129
|
-
if (process.env.NEXUS_SKIP_CANVAS_HOST === "1"
|
|
129
|
+
if (process.env.NEXUS_SKIP_CANVAS_HOST === "1")
|
|
130
130
|
return true;
|
|
131
131
|
if (process.env.NODE_ENV === "test")
|
|
132
132
|
return true;
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { getSkillMetadata, isConfigPathTruthy, loadWorkspaceSkillEntries, resolveRuntimePlatform, resolveSkillConfig, } from "../agents/skills.js";
|
|
4
|
+
import { loadConfig } from "../config/config.js";
|
|
5
|
+
import { ensureCredentialIndexSync } from "../credentials/store.js";
|
|
6
|
+
import { MANAGED_SKILLS_DIR, NEXUS_ROOT } from "../utils.js";
|
|
7
|
+
import { loadCapabilityRegistry } from "./registry.js";
|
|
8
|
+
const STATUS_PRIORITY = [
|
|
9
|
+
"active",
|
|
10
|
+
"ready",
|
|
11
|
+
"needs_setup",
|
|
12
|
+
"needs_install",
|
|
13
|
+
"broken",
|
|
14
|
+
"unavailable",
|
|
15
|
+
];
|
|
16
|
+
function hasBinary(bin) {
|
|
17
|
+
const pathEnv = process.env.PATH ?? "";
|
|
18
|
+
const parts = pathEnv.split(path.delimiter).filter(Boolean);
|
|
19
|
+
for (const part of parts) {
|
|
20
|
+
const candidate = path.join(part, bin);
|
|
21
|
+
try {
|
|
22
|
+
fs.accessSync(candidate, fs.constants.X_OK);
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
// keep scanning
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
function normalizeProviderId(raw) {
|
|
32
|
+
return raw.trim().toLowerCase();
|
|
33
|
+
}
|
|
34
|
+
function resolveCredentialAliases(providerId) {
|
|
35
|
+
const base = providerId.replace(/-oauth$/, "");
|
|
36
|
+
const aliases = new Set([providerId, base]);
|
|
37
|
+
if (providerId === "gog") {
|
|
38
|
+
aliases.add("google");
|
|
39
|
+
aliases.add("google-oauth");
|
|
40
|
+
}
|
|
41
|
+
if (providerId === "google-oauth") {
|
|
42
|
+
aliases.add("google");
|
|
43
|
+
aliases.add("gog");
|
|
44
|
+
}
|
|
45
|
+
return Array.from(aliases).filter(Boolean);
|
|
46
|
+
}
|
|
47
|
+
function setupLooksCredentialed(raw) {
|
|
48
|
+
const lowered = raw.toLowerCase();
|
|
49
|
+
if (!lowered || lowered === "-" || lowered === "none")
|
|
50
|
+
return false;
|
|
51
|
+
return (lowered.includes("oauth") ||
|
|
52
|
+
lowered.includes("api key") ||
|
|
53
|
+
lowered.includes("apikey") ||
|
|
54
|
+
lowered.includes("token") ||
|
|
55
|
+
lowered.includes("account") ||
|
|
56
|
+
lowered.includes("bot") ||
|
|
57
|
+
lowered.includes("key"));
|
|
58
|
+
}
|
|
59
|
+
function detectSkillUsageActive(skillName) {
|
|
60
|
+
const usageLog = path.join(MANAGED_SKILLS_DIR, skillName, "usage.log");
|
|
61
|
+
try {
|
|
62
|
+
const stat = fs.statSync(usageLog);
|
|
63
|
+
return stat.isFile() && stat.size > 0;
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
function resolveSkillType(_skillName, metadata) {
|
|
70
|
+
if (metadata?.type)
|
|
71
|
+
return metadata.type;
|
|
72
|
+
const requires = metadata?.requires;
|
|
73
|
+
if (requires?.env?.length || requires?.config?.length)
|
|
74
|
+
return "connector";
|
|
75
|
+
if (requires?.bins?.length)
|
|
76
|
+
return "tool";
|
|
77
|
+
return "guide";
|
|
78
|
+
}
|
|
79
|
+
function resolveSkillProviderStatus(params) {
|
|
80
|
+
const { skillName, providerId, config, credentialServices, metadata, setupHint, } = params;
|
|
81
|
+
const platform = resolveRuntimePlatform();
|
|
82
|
+
const skillType = resolveSkillType(skillName, metadata);
|
|
83
|
+
if (metadata?.os?.length && !metadata.os.includes(platform)) {
|
|
84
|
+
return {
|
|
85
|
+
id: providerId,
|
|
86
|
+
kind: "skill",
|
|
87
|
+
status: "unavailable",
|
|
88
|
+
reason: "os",
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
const skillKey = metadata?.skillKey ?? skillName;
|
|
92
|
+
const skillConfig = resolveSkillConfig(config, skillKey);
|
|
93
|
+
const requires = metadata?.requires;
|
|
94
|
+
const missingBins = (requires?.bins ?? []).filter((bin) => !hasBinary(bin));
|
|
95
|
+
const anyBins = requires?.anyBins ?? [];
|
|
96
|
+
const anyBinsOk = anyBins.length === 0 || anyBins.some((bin) => hasBinary(bin));
|
|
97
|
+
if (missingBins.length > 0 || !anyBinsOk) {
|
|
98
|
+
return {
|
|
99
|
+
id: providerId,
|
|
100
|
+
kind: "skill",
|
|
101
|
+
status: "needs_install",
|
|
102
|
+
reason: missingBins.length > 0 ? "missing_bins" : "missing_any_bins",
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
const missingEnv = (requires?.env ?? []).filter((envName) => {
|
|
106
|
+
if (process.env[envName])
|
|
107
|
+
return false;
|
|
108
|
+
if (skillConfig?.env?.[envName])
|
|
109
|
+
return false;
|
|
110
|
+
if (skillConfig?.apiKey && metadata?.primaryEnv === envName)
|
|
111
|
+
return false;
|
|
112
|
+
return true;
|
|
113
|
+
});
|
|
114
|
+
if (missingEnv.length > 0) {
|
|
115
|
+
return {
|
|
116
|
+
id: providerId,
|
|
117
|
+
kind: "skill",
|
|
118
|
+
status: "needs_setup",
|
|
119
|
+
reason: "missing_env",
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
const missingConfig = (requires?.config ?? []).filter((configPath) => !isConfigPathTruthy(config, configPath));
|
|
123
|
+
if (missingConfig.length > 0) {
|
|
124
|
+
return {
|
|
125
|
+
id: providerId,
|
|
126
|
+
kind: "skill",
|
|
127
|
+
status: "needs_setup",
|
|
128
|
+
reason: "missing_config",
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
const credentialHints = requires?.credentials ?? [];
|
|
132
|
+
const requiresCredential = setupLooksCredentialed(setupHint ?? "");
|
|
133
|
+
if (skillType === "connector" ||
|
|
134
|
+
credentialHints.length > 0 ||
|
|
135
|
+
requiresCredential) {
|
|
136
|
+
const aliases = new Set();
|
|
137
|
+
for (const entry of [providerId, ...credentialHints]) {
|
|
138
|
+
for (const alias of resolveCredentialAliases(entry))
|
|
139
|
+
aliases.add(alias);
|
|
140
|
+
}
|
|
141
|
+
const hasCred = Array.from(aliases).some((alias) => credentialServices.has(alias));
|
|
142
|
+
if (!hasCred) {
|
|
143
|
+
return {
|
|
144
|
+
id: providerId,
|
|
145
|
+
kind: "skill",
|
|
146
|
+
status: "needs_setup",
|
|
147
|
+
reason: "missing_credentials",
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
if (detectSkillUsageActive(skillName)) {
|
|
152
|
+
return { id: providerId, kind: "skill", status: "active" };
|
|
153
|
+
}
|
|
154
|
+
return { id: providerId, kind: "skill", status: "ready" };
|
|
155
|
+
}
|
|
156
|
+
function resolveCredentialProviderStatus(providerId, credentialServices) {
|
|
157
|
+
const aliases = resolveCredentialAliases(providerId);
|
|
158
|
+
const hasCredential = aliases.some((alias) => credentialServices.has(alias));
|
|
159
|
+
return {
|
|
160
|
+
id: providerId,
|
|
161
|
+
kind: "credential",
|
|
162
|
+
status: hasCredential ? "ready" : "needs_setup",
|
|
163
|
+
reason: hasCredential ? undefined : "missing_credentials",
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
function pickBestStatus(statuses) {
|
|
167
|
+
for (const status of STATUS_PRIORITY) {
|
|
168
|
+
if (statuses.includes(status))
|
|
169
|
+
return status;
|
|
170
|
+
}
|
|
171
|
+
return "unavailable";
|
|
172
|
+
}
|
|
173
|
+
export function detectCapabilities(params) {
|
|
174
|
+
const config = params?.config ?? loadConfig();
|
|
175
|
+
const workspaceDir = params?.workspaceDir ?? path.join(NEXUS_ROOT, "home");
|
|
176
|
+
const registry = params?.registry ?? loadCapabilityRegistry();
|
|
177
|
+
const credentialIndex = ensureCredentialIndexSync();
|
|
178
|
+
const credentialServices = new Set(Object.keys(credentialIndex.services ?? {}).map((name) => normalizeProviderId(name)));
|
|
179
|
+
const skillEntries = loadWorkspaceSkillEntries(workspaceDir, { config });
|
|
180
|
+
const skillMap = new Map();
|
|
181
|
+
const providesMap = new Map();
|
|
182
|
+
for (const entry of skillEntries) {
|
|
183
|
+
const metadata = getSkillMetadata(entry);
|
|
184
|
+
const key = normalizeProviderId(metadata?.skillKey ?? entry.skill.name);
|
|
185
|
+
skillMap.set(key, { name: entry.skill.name, metadata });
|
|
186
|
+
const provides = (metadata?.provides ?? [])
|
|
187
|
+
.map((cap) => cap.trim())
|
|
188
|
+
.filter(Boolean);
|
|
189
|
+
for (const capId of provides) {
|
|
190
|
+
const normalizedCap = capId.trim();
|
|
191
|
+
if (!normalizedCap)
|
|
192
|
+
continue;
|
|
193
|
+
const existing = providesMap.get(normalizedCap) ?? [];
|
|
194
|
+
if (!existing.includes(key))
|
|
195
|
+
existing.push(key);
|
|
196
|
+
providesMap.set(normalizedCap, existing);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
const capabilities = [];
|
|
200
|
+
for (const capability of registry.all) {
|
|
201
|
+
const extraProviders = providesMap.get(capability.id) ?? [];
|
|
202
|
+
const providerList = Array.from(new Set([
|
|
203
|
+
...capability.providers.map(normalizeProviderId),
|
|
204
|
+
...extraProviders,
|
|
205
|
+
]));
|
|
206
|
+
const providerStatuses = [];
|
|
207
|
+
for (const provider of providerList) {
|
|
208
|
+
const normalized = normalizeProviderId(provider);
|
|
209
|
+
const skillEntry = skillMap.get(normalized);
|
|
210
|
+
if (skillEntry) {
|
|
211
|
+
providerStatuses.push(resolveSkillProviderStatus({
|
|
212
|
+
skillName: skillEntry.name,
|
|
213
|
+
providerId: normalized,
|
|
214
|
+
config,
|
|
215
|
+
credentialServices,
|
|
216
|
+
metadata: skillEntry.metadata,
|
|
217
|
+
setupHint: capability.setup,
|
|
218
|
+
}));
|
|
219
|
+
}
|
|
220
|
+
else if (resolveCredentialAliases(normalized).some((alias) => credentialServices.has(alias))) {
|
|
221
|
+
providerStatuses.push(resolveCredentialProviderStatus(normalized, credentialServices));
|
|
222
|
+
}
|
|
223
|
+
else {
|
|
224
|
+
providerStatuses.push({
|
|
225
|
+
id: normalized,
|
|
226
|
+
kind: "unknown",
|
|
227
|
+
status: "unavailable",
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
const status = pickBestStatus(providerStatuses.map((p) => p.status));
|
|
232
|
+
capabilities.push({ ...capability, status, providers: providerStatuses });
|
|
233
|
+
}
|
|
234
|
+
const summary = {
|
|
235
|
+
total: capabilities.length,
|
|
236
|
+
active: capabilities.filter((c) => c.status === "active").length,
|
|
237
|
+
ready: capabilities.filter((c) => c.status === "ready").length,
|
|
238
|
+
needs_setup: capabilities.filter((c) => c.status === "needs_setup").length,
|
|
239
|
+
needs_install: capabilities.filter((c) => c.status === "needs_install")
|
|
240
|
+
.length,
|
|
241
|
+
unavailable: capabilities.filter((c) => c.status === "unavailable").length,
|
|
242
|
+
broken: capabilities.filter((c) => c.status === "broken").length,
|
|
243
|
+
};
|
|
244
|
+
return { registry, capabilities, summary };
|
|
245
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { resolveUserPath } from "../utils.js";
|
|
5
|
+
const CAPABILITIES_PATH = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../../docs/CAPABILITIES.md");
|
|
6
|
+
function resolveCapabilitiesPath() {
|
|
7
|
+
const candidates = [];
|
|
8
|
+
const override = process.env.NEXUS_CAPABILITIES_PATH?.trim();
|
|
9
|
+
if (override) {
|
|
10
|
+
candidates.push(resolveUserPath(override));
|
|
11
|
+
}
|
|
12
|
+
candidates.push(CAPABILITIES_PATH);
|
|
13
|
+
for (const candidate of candidates) {
|
|
14
|
+
if (!candidate)
|
|
15
|
+
continue;
|
|
16
|
+
try {
|
|
17
|
+
if (fs.existsSync(candidate))
|
|
18
|
+
return candidate;
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
// ignore and keep searching
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
function normalizeProviderToken(raw) {
|
|
27
|
+
const normalized = raw
|
|
28
|
+
.replace(/\(.*?\)/g, "")
|
|
29
|
+
.replace(/\s+/g, " ")
|
|
30
|
+
.trim()
|
|
31
|
+
.toLowerCase();
|
|
32
|
+
if (normalized.endsWith(" guide")) {
|
|
33
|
+
return normalized.slice(0, -" guide".length).trim();
|
|
34
|
+
}
|
|
35
|
+
return normalized;
|
|
36
|
+
}
|
|
37
|
+
function parseProviders(raw) {
|
|
38
|
+
const cleaned = raw.replace(/\(.*?\)/g, "").trim();
|
|
39
|
+
if (!cleaned)
|
|
40
|
+
return [];
|
|
41
|
+
const parts = cleaned.split(/[,;+]/g).map((p) => p.trim());
|
|
42
|
+
const out = [];
|
|
43
|
+
for (const part of parts) {
|
|
44
|
+
if (!part)
|
|
45
|
+
continue;
|
|
46
|
+
const normalized = normalizeProviderToken(part);
|
|
47
|
+
if (!normalized)
|
|
48
|
+
continue;
|
|
49
|
+
if (!out.includes(normalized))
|
|
50
|
+
out.push(normalized);
|
|
51
|
+
}
|
|
52
|
+
return out;
|
|
53
|
+
}
|
|
54
|
+
export function loadCapabilityRegistry() {
|
|
55
|
+
const capabilitiesPath = resolveCapabilitiesPath();
|
|
56
|
+
if (!capabilitiesPath) {
|
|
57
|
+
return { categories: {}, all: [] };
|
|
58
|
+
}
|
|
59
|
+
let raw;
|
|
60
|
+
try {
|
|
61
|
+
raw = fs.readFileSync(capabilitiesPath, "utf-8");
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
return { categories: {}, all: [] };
|
|
65
|
+
}
|
|
66
|
+
const lines = raw.replace(/\r\n/g, "\n").split("\n");
|
|
67
|
+
let currentCategory = "Uncategorized";
|
|
68
|
+
const categories = {};
|
|
69
|
+
const all = [];
|
|
70
|
+
for (const line of lines) {
|
|
71
|
+
const headingMatch = line.match(/^###\s+(.+)$/);
|
|
72
|
+
if (headingMatch) {
|
|
73
|
+
currentCategory =
|
|
74
|
+
headingMatch[1]?.replace(/^[-–]\s*/, "").trim() || currentCategory;
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
const tableMatch = line.match(/^\|\s*`([^`]+)`\s*\|\s*([^|]+)\|\s*([^|]+)\|\s*([^|]+)\|/);
|
|
78
|
+
if (!tableMatch)
|
|
79
|
+
continue;
|
|
80
|
+
const [, idRaw, descriptionRaw, providersRaw, setupRaw] = tableMatch;
|
|
81
|
+
const id = (idRaw ?? "").trim();
|
|
82
|
+
if (!id)
|
|
83
|
+
continue;
|
|
84
|
+
const providersRawTrimmed = (providersRaw ?? "").trim();
|
|
85
|
+
const providers = parseProviders(providersRawTrimmed);
|
|
86
|
+
const capability = {
|
|
87
|
+
id,
|
|
88
|
+
description: (descriptionRaw ?? "").trim(),
|
|
89
|
+
providersRaw: providersRawTrimmed,
|
|
90
|
+
providers,
|
|
91
|
+
setup: (setupRaw ?? "").trim(),
|
|
92
|
+
category: currentCategory,
|
|
93
|
+
};
|
|
94
|
+
categories[currentCategory] = categories[currentCategory] ?? [];
|
|
95
|
+
categories[currentCategory].push(capability);
|
|
96
|
+
all.push(capability);
|
|
97
|
+
}
|
|
98
|
+
return { categories, all };
|
|
99
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
function resolveLocation(location) {
|
|
2
|
+
const source = location.source ??
|
|
3
|
+
(location.isLive ? "live" : location.name || location.address ? "place" : "pin");
|
|
4
|
+
const isLive = Boolean(location.isLive ?? source === "live");
|
|
5
|
+
return { ...location, source, isLive };
|
|
6
|
+
}
|
|
7
|
+
function formatAccuracy(accuracy) {
|
|
8
|
+
if (!Number.isFinite(accuracy))
|
|
9
|
+
return "";
|
|
10
|
+
return ` ±${Math.round(accuracy ?? 0)}m`;
|
|
11
|
+
}
|
|
12
|
+
function formatCoords(latitude, longitude) {
|
|
13
|
+
return `${latitude.toFixed(6)}, ${longitude.toFixed(6)}`;
|
|
14
|
+
}
|
|
15
|
+
export function formatLocationText(location) {
|
|
16
|
+
const resolved = resolveLocation(location);
|
|
17
|
+
const coords = formatCoords(resolved.latitude, resolved.longitude);
|
|
18
|
+
const accuracy = formatAccuracy(resolved.accuracy);
|
|
19
|
+
const caption = resolved.caption?.trim();
|
|
20
|
+
let header = "";
|
|
21
|
+
if (resolved.source === "live" || resolved.isLive) {
|
|
22
|
+
header = `🛰 Live location: ${coords}${accuracy}`;
|
|
23
|
+
}
|
|
24
|
+
else if (resolved.name || resolved.address) {
|
|
25
|
+
const label = [resolved.name, resolved.address].filter(Boolean).join(" — ");
|
|
26
|
+
header = `📍 ${label} (${coords}${accuracy})`;
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
header = `📍 ${coords}${accuracy}`;
|
|
30
|
+
}
|
|
31
|
+
return caption ? `${header}\n${caption}` : header;
|
|
32
|
+
}
|
|
33
|
+
export function toLocationContext(location) {
|
|
34
|
+
const resolved = resolveLocation(location);
|
|
35
|
+
return {
|
|
36
|
+
LocationLat: resolved.latitude,
|
|
37
|
+
LocationLon: resolved.longitude,
|
|
38
|
+
LocationAccuracy: resolved.accuracy,
|
|
39
|
+
LocationName: resolved.name,
|
|
40
|
+
LocationAddress: resolved.address,
|
|
41
|
+
LocationSource: resolved.source,
|
|
42
|
+
LocationIsLive: resolved.isLive,
|
|
43
|
+
};
|
|
44
|
+
}
|
package/dist/cli/cloud-cli.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import crypto from "node:crypto";
|
|
1
3
|
import fs from "node:fs";
|
|
2
4
|
import path from "node:path";
|
|
3
|
-
import crypto from "node:crypto";
|
|
4
|
-
import { spawn } from "node:child_process";
|
|
5
5
|
import { scanCredentials } from "../commands/credential.js";
|
|
6
6
|
import { openUrl } from "../commands/onboard-helpers.js";
|
|
7
|
-
import { storeKeychainSecret, writeCredentialRecord, } from "../credentials/store.js";
|
|
7
|
+
import { resolveDefaultEnvVar, storeKeychainSecret, writeCredentialRecord, } from "../credentials/store.js";
|
|
8
8
|
import { NEXUS_ROOT, sleep } from "../utils.js";
|
|
9
9
|
const DEFAULT_CLOUD_TIMEOUT_MS = 120_000;
|
|
10
10
|
const DEFAULT_CLOUD_POLL_MS = 2_000;
|
|
@@ -77,6 +77,7 @@ async function storeHubToken(params) {
|
|
|
77
77
|
value: params.token,
|
|
78
78
|
});
|
|
79
79
|
}
|
|
80
|
+
let envVar;
|
|
80
81
|
if (storedInKeychain) {
|
|
81
82
|
record = {
|
|
82
83
|
owner: "user",
|
|
@@ -86,17 +87,18 @@ async function storeHubToken(params) {
|
|
|
86
87
|
};
|
|
87
88
|
}
|
|
88
89
|
else {
|
|
90
|
+
envVar = resolveDefaultEnvVar({ service, type: "token" });
|
|
91
|
+
process.env[envVar] = params.token;
|
|
89
92
|
record = {
|
|
90
93
|
owner: "user",
|
|
91
94
|
type: "token",
|
|
92
95
|
configuredAt: now,
|
|
93
|
-
storage: { provider: "
|
|
94
|
-
token: params.token,
|
|
96
|
+
storage: { provider: "env", var: envVar },
|
|
95
97
|
};
|
|
96
98
|
}
|
|
97
99
|
await writeCredentialRecord(service, account, authId, record);
|
|
98
100
|
await scanCredentials();
|
|
99
|
-
return { storedInKeychain, account };
|
|
101
|
+
return { storedInKeychain, account, envVar };
|
|
100
102
|
}
|
|
101
103
|
function parseCloudLoginArgs(args) {
|
|
102
104
|
let baseUrl = getHubBaseUrl();
|
|
@@ -296,7 +298,10 @@ async function handleCloudLogin(rawArgs) {
|
|
|
296
298
|
}
|
|
297
299
|
console.log(stored.storedInKeychain
|
|
298
300
|
? " Token stored in keychain"
|
|
299
|
-
:
|
|
301
|
+
: ` Token stored in env var ${stored.envVar ?? ""}`.trim());
|
|
302
|
+
if (!stored.storedInKeychain && stored.envVar) {
|
|
303
|
+
console.log(` Persist it in your shell: export ${stored.envVar}=...`);
|
|
304
|
+
}
|
|
300
305
|
if (cloudUrl) {
|
|
301
306
|
console.log(` Cloud URL: ${cloudUrl}`);
|
|
302
307
|
}
|