@sns-myagent/cli 0.3.8 → 0.3.9
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 +8 -0
- package/README.md +63 -24
- package/bin/snsagent.js +3 -41
- package/dist/cli.js +3267 -3288
- package/package.json +4 -4
- package/src/adapters/telegram/handler.ts +1 -1
- package/src/agents/ensemble.ts +3 -4
- package/src/agents/executor.ts +129 -0
- package/src/async/notifier.ts +1 -1
- package/src/cli/config-cli.ts +1 -1
- package/src/cli/entry.ts +2 -2
- package/src/cli/gallery-fixtures/misc.ts +3 -3
- package/src/cli/grep-cli.ts +3 -3
- package/src/cli/grievances-cli.ts +2 -2
- package/src/cli/index.ts +186 -31
- package/src/cli/plugin-cli.ts +5 -5
- package/src/cli/setup-cli.ts +3 -3
- package/src/cli/setup-model-picker.ts +1 -1
- package/src/cli/shell-cli.ts +1 -1
- package/src/cli/ssh-cli.ts +1 -1
- package/src/cli/stats-cli.ts +1 -1
- package/src/cli/tiny-models-cli.ts +3 -3
- package/src/cli/ttsr-cli.ts +2 -2
- package/src/cli/update-cli.ts +3 -3
- package/src/cli/usage-cli.ts +1 -1
- package/src/cli-commands.ts +6 -6
- package/src/commands/acp.ts +2 -2
- package/src/commands/chat.ts +3 -3
- package/src/commands/install.ts +5 -5
- package/src/commands/say.ts +4 -4
- package/src/commands/setup.ts +1 -1
- package/src/config/settings-schema.ts +1 -1
- package/src/config/sns-config.ts +1 -1
- package/src/extensibility/plugins/loader.ts +1 -1
- package/src/extensibility/plugins/manager.ts +2 -2
- package/src/hindsight/client.ts +1 -1
- package/src/internal-urls/docs-index.generated.txt +2 -0
- package/src/main.ts +4 -0
- package/src/modes/acp/acp-agent.ts +3 -3
- package/src/modes/components/plugin-settings.ts +2 -2
- package/src/modes/components/welcome.ts +66 -160
- package/src/modes/interactive-mode.ts +2 -2
- package/src/modes/print-mode.ts +2 -2
- package/src/modes/setup-version.ts +1 -1
- package/src/modes/setup-wizard/scenes/byok-setup.ts +354 -0
- package/src/modes/setup-wizard/scenes/outro.ts +2 -2
- package/src/modes/setup-wizard/scenes/providers.ts +6 -5
- package/src/modes/setup-wizard/scenes/splash.ts +3 -3
- package/src/modes/setup-wizard/wizard-overlay.ts +2 -2
- package/src/modes/theme/dark.json +21 -20
- package/src/modes/theme/light.json +30 -29
- package/src/modes/utils/ui-helpers.ts +1 -1
- package/src/slash-commands/builtin-registry.ts +6 -3
- package/src/stt/recorder.ts +3 -3
- package/src/tts/player.ts +1 -1
- package/src/tts/tts-client.ts +1 -1
- package/src/tui/chat-blocks.ts +4 -4
- package/src/tui/chat-ui.ts +3 -3
- package/src/tui/code-cell.ts +3 -3
- package/src/tui/command-palette.ts +3 -3
- package/src/tui/splash.ts +12 -18
- package/src/ui/banner.ts +51 -66
- package/src/ui/chat-prompt.ts +2 -2
- package/src/ui/colors.ts +17 -14
- package/src/ui/error-display.ts +2 -2
- package/src/ui/gradient.ts +3 -3
- package/src/ui/memory-toast.ts +5 -5
- package/src/ui/status-bar.ts +1 -1
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@sns-myagent/cli",
|
|
4
|
-
"version": "0.3.
|
|
5
|
-
"description": "SNS-MyAgent
|
|
4
|
+
"version": "0.3.9",
|
|
5
|
+
"description": "SNS-MyAgent — BYOK coding agent CLI. Multi-agent + Telegram + self-learning memory. ~120MB binary, zero forced cost.",
|
|
6
6
|
"homepage": "https://github.com/Reihantt6/sns-myagent",
|
|
7
7
|
"author": "Reihantt6",
|
|
8
8
|
"contributors": [],
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"scripts": {
|
|
36
36
|
"fetch-binary": "node scripts/fetch-binary.mjs",
|
|
37
37
|
"postinstall": "node scripts/fetch-binary.mjs && node scripts/apply-pi-natives-patch.js",
|
|
38
|
-
"build": "bun build --compile --define process.env.PI_COMPILED='\"true\"' --define process.env.PKG_VERSION='\"0.3.
|
|
38
|
+
"build": "bun build --compile --define process.env.PI_COMPILED='\"true\"' --define process.env.PKG_VERSION='\"0.3.9\"' --no-compile-autoload-bunfig --no-compile-autoload-dotenv --no-compile-autoload-tsconfig --no-compile-autoload-package-json --keep-names --outfile bin/snsagent-linux-x64 ./src/cli/entry.ts && cp bin/snsagent-linux-x64 bin/snsagent",
|
|
39
39
|
"check": "biome check . && bun run check:types",
|
|
40
40
|
"check:types": "tsgo -p tsconfig.json --noEmit",
|
|
41
41
|
"lint": "biome lint .",
|
|
@@ -117,4 +117,4 @@
|
|
|
117
117
|
"CHANGELOG.md",
|
|
118
118
|
"dist/types"
|
|
119
119
|
]
|
|
120
|
-
}
|
|
120
|
+
}
|
|
@@ -122,7 +122,7 @@ export function parseMessage(msg: Message): ParsedMessage {
|
|
|
122
122
|
*/
|
|
123
123
|
export const COMMAND_REPLIES: Record<TelegramCommand, string> = {
|
|
124
124
|
start:
|
|
125
|
-
"*Welcome to
|
|
125
|
+
"*Welcome to snsagent* 🤖\n\n" +
|
|
126
126
|
"I'm a Telegram front-end for the SNS-MyAgent coding agent. " +
|
|
127
127
|
"Send me any task or question; I'll route it to the agent and reply here.\n\n" +
|
|
128
128
|
"Type /help for the full command list.",
|
package/src/agents/ensemble.ts
CHANGED
|
@@ -62,7 +62,7 @@ export async function executeEnsemble(
|
|
|
62
62
|
|
|
63
63
|
// Execute with resilience if enabled
|
|
64
64
|
const executeWithResilience = options.resilient !== false
|
|
65
|
-
? createResilientExecutor(config, options)
|
|
65
|
+
? createResilientExecutor(config, options, executeAgent)
|
|
66
66
|
: executeAgent;
|
|
67
67
|
|
|
68
68
|
const result = await strategy.execute(prompt, agentRoles, executeWithResilience);
|
|
@@ -147,6 +147,7 @@ function createStrategy(name: string, options: Record<string, unknown>): Ensembl
|
|
|
147
147
|
function createResilientExecutor(
|
|
148
148
|
config: ReturnType<typeof getAgentsConfig>["config"],
|
|
149
149
|
options: EnsembleOptions,
|
|
150
|
+
executeAgent: (role: string, prompt: string) => Promise<AgentResponse>,
|
|
150
151
|
) {
|
|
151
152
|
return async (role: string, prompt: string): Promise<AgentResponse> => {
|
|
152
153
|
const agentConfig = config.agents[role];
|
|
@@ -158,8 +159,7 @@ function createResilientExecutor(
|
|
|
158
159
|
async () => {
|
|
159
160
|
return resilience.withTimeout(
|
|
160
161
|
async () => {
|
|
161
|
-
|
|
162
|
-
throw new Error("executeAgent must be provided by caller");
|
|
162
|
+
return executeAgent(role, prompt);
|
|
163
163
|
},
|
|
164
164
|
options.taskTimeoutMs ?? config.task_timeout_ms ?? 120_000,
|
|
165
165
|
`ensemble:${role}`,
|
|
@@ -171,7 +171,6 @@ function createResilientExecutor(
|
|
|
171
171
|
},
|
|
172
172
|
);
|
|
173
173
|
if (!result.success) throw result.error ?? new Error("resilient executor failed");
|
|
174
|
-
// Type guard: caller must inject a real AgentResponse executor.
|
|
175
174
|
return result.result as unknown as AgentResponse;
|
|
176
175
|
};
|
|
177
176
|
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ensemble Agent Executor — SNS-MyAgent Phase 5.1d
|
|
3
|
+
*
|
|
4
|
+
* Bridges the ensemble orchestrator (src/agents/ensemble.ts) to the
|
|
5
|
+
* actual LLM execution (src/task/executor.ts).
|
|
6
|
+
*
|
|
7
|
+
* Provides `executeAgentForEnsemble(role, prompt) → AgentResponse`
|
|
8
|
+
* which resolves agent config from agents.yaml, builds an AgentDefinition,
|
|
9
|
+
* calls runSubprocess, and returns the typed response.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { randomBytes } from "node:crypto";
|
|
13
|
+
import type { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
|
14
|
+
import { getAgentsConfig, type AgentRoleConfig } from "./config.js";
|
|
15
|
+
import { runSubprocess, type ExecutorOptions } from "../task/executor.js";
|
|
16
|
+
import type { AgentDefinition, SingleResult } from "../task/types.js";
|
|
17
|
+
import type { AgentResponse } from "./strategies/types.js";
|
|
18
|
+
|
|
19
|
+
// ─── Default agent definition ───────────────────────────────────────────────
|
|
20
|
+
|
|
21
|
+
const DEFAULT_SYSTEM_PROMPT = `You are a helpful AI assistant. Answer the user's request directly and concisely.
|
|
22
|
+
Do not use any tools. Do not spawn subagents. Just answer.`;
|
|
23
|
+
|
|
24
|
+
const DEFAULT_AGENT: AgentDefinition = {
|
|
25
|
+
name: "task",
|
|
26
|
+
description: "General-purpose agent for ensemble tasks",
|
|
27
|
+
systemPrompt: DEFAULT_SYSTEM_PROMPT,
|
|
28
|
+
source: "bundled",
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
// ─── Executor ───────────────────────────────────────────────────────────────
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Execute a single agent call for the ensemble orchestrator.
|
|
35
|
+
*
|
|
36
|
+
* @param role - Agent role name (from agents.yaml or "task")
|
|
37
|
+
* @param prompt - The user prompt to send
|
|
38
|
+
* @returns AgentResponse compatible with ensemble strategies
|
|
39
|
+
*/
|
|
40
|
+
export async function executeAgentForEnsemble(
|
|
41
|
+
role: string,
|
|
42
|
+
prompt: string,
|
|
43
|
+
): Promise<AgentResponse> {
|
|
44
|
+
const startTime = Date.now();
|
|
45
|
+
const config = getAgentsConfig();
|
|
46
|
+
const roleConfig = config.resolveRole(role);
|
|
47
|
+
|
|
48
|
+
// Build AgentDefinition from config (or use default)
|
|
49
|
+
const agentDef = buildAgentDefinition(role, roleConfig);
|
|
50
|
+
|
|
51
|
+
// Resolve model string for display
|
|
52
|
+
const modelStr = roleConfig?.model ?? "default";
|
|
53
|
+
|
|
54
|
+
// Generate unique ID for this execution
|
|
55
|
+
const execId = `ensemble-${role}-${randomBytes(4).toString("hex")}`;
|
|
56
|
+
|
|
57
|
+
// Build executor options
|
|
58
|
+
const cwd = process.cwd();
|
|
59
|
+
const options: ExecutorOptions = {
|
|
60
|
+
cwd,
|
|
61
|
+
agent: agentDef,
|
|
62
|
+
task: prompt,
|
|
63
|
+
index: 0,
|
|
64
|
+
id: execId,
|
|
65
|
+
role,
|
|
66
|
+
// Use model override from agents.yaml if specified
|
|
67
|
+
modelOverride: roleConfig?.model,
|
|
68
|
+
thinkingLevel: resolveThinkingLevel(roleConfig?.thinking_level),
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
try {
|
|
72
|
+
const result: SingleResult = await runSubprocess(options);
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
role,
|
|
76
|
+
model: result.resolvedModel ?? modelStr,
|
|
77
|
+
content: result.output || result.error || "(no output)",
|
|
78
|
+
tokens: result.usage
|
|
79
|
+
? { input: result.usage.input ?? 0, output: result.usage.output ?? 0 }
|
|
80
|
+
: undefined,
|
|
81
|
+
timeMs: Date.now() - startTime,
|
|
82
|
+
};
|
|
83
|
+
} catch (err) {
|
|
84
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
85
|
+
return {
|
|
86
|
+
role,
|
|
87
|
+
model: modelStr,
|
|
88
|
+
content: `[executor error] ${msg}`,
|
|
89
|
+
timeMs: Date.now() - startTime,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// ─── Helpers ────────────────────────────────────────────────────────────────
|
|
95
|
+
|
|
96
|
+
function buildAgentDefinition(
|
|
97
|
+
role: string,
|
|
98
|
+
roleConfig: AgentRoleConfig | null,
|
|
99
|
+
): AgentDefinition {
|
|
100
|
+
return {
|
|
101
|
+
name: role,
|
|
102
|
+
description: roleConfig?.system_prompt
|
|
103
|
+
? `Custom agent: ${role}`
|
|
104
|
+
: `Ensemble agent: ${role}`,
|
|
105
|
+
systemPrompt: roleConfig?.system_prompt ?? DEFAULT_SYSTEM_PROMPT,
|
|
106
|
+
tools: roleConfig?.tools,
|
|
107
|
+
source: roleConfig ? "user" : "bundled",
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Map a string thinking_level from agents.yaml to the actual ThinkingLevel const.
|
|
113
|
+
* Returns undefined if not set or unrecognized.
|
|
114
|
+
*/
|
|
115
|
+
function resolveThinkingLevel(
|
|
116
|
+
level: string | undefined,
|
|
117
|
+
): ThinkingLevel | undefined {
|
|
118
|
+
if (!level) return undefined;
|
|
119
|
+
const map: Record<string, ThinkingLevel> = {
|
|
120
|
+
inherit: "inherit" as ThinkingLevel,
|
|
121
|
+
off: "off" as ThinkingLevel,
|
|
122
|
+
minimal: "minimal" as ThinkingLevel,
|
|
123
|
+
low: "low" as ThinkingLevel,
|
|
124
|
+
medium: "medium" as ThinkingLevel,
|
|
125
|
+
high: "high" as ThinkingLevel,
|
|
126
|
+
xhigh: "xhigh" as ThinkingLevel,
|
|
127
|
+
};
|
|
128
|
+
return map[level.toLowerCase()] ?? undefined;
|
|
129
|
+
}
|
package/src/async/notifier.ts
CHANGED
|
@@ -10,7 +10,7 @@ const STATUS_COLORS: Record<string, (text: string) => string> = {
|
|
|
10
10
|
completed: (t) => chalk.green(t),
|
|
11
11
|
failed: (t) => chalk.red(t),
|
|
12
12
|
cancelled: (t) => chalk.yellow(t),
|
|
13
|
-
running: (t) => chalk.
|
|
13
|
+
running: (t) => chalk.hex("#F97316")(t),
|
|
14
14
|
pending: (t) => chalk.dim(t),
|
|
15
15
|
};
|
|
16
16
|
|
package/src/cli/config-cli.ts
CHANGED
|
@@ -132,7 +132,7 @@ function formatValue(value: unknown): string {
|
|
|
132
132
|
return value ? chalk.green("true") : chalk.red("false");
|
|
133
133
|
}
|
|
134
134
|
if (typeof value === "number") {
|
|
135
|
-
return chalk.
|
|
135
|
+
return chalk.hex("#F97316")(String(value));
|
|
136
136
|
}
|
|
137
137
|
if (typeof value === "string") {
|
|
138
138
|
return chalk.yellow(value);
|
package/src/cli/entry.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
// Single-file CLI entry for `bun build --compile`.
|
|
3
|
-
// Mirrors bin/
|
|
3
|
+
// Mirrors bin/snsagent (Node dev shim) but is a TS module so Bun can bundle
|
|
4
4
|
// all transitive imports into the binary.
|
|
5
5
|
|
|
6
6
|
import { ensureConfig } from "../config/loader.js";
|
|
@@ -34,7 +34,7 @@ try {
|
|
|
34
34
|
} catch (err) {
|
|
35
35
|
const e = err as Error;
|
|
36
36
|
const msg = e.message ? e.message : String(err);
|
|
37
|
-
process.stderr.write(`\u2717
|
|
37
|
+
process.stderr.write(`\u2717 snsagent crashed: ${msg}\n`);
|
|
38
38
|
if (process.env.SNS_DEBUG) {
|
|
39
39
|
process.stderr.write("\n" + (e.stack ?? "") + "\n");
|
|
40
40
|
}
|
|
@@ -119,14 +119,14 @@ export const miscFixtures: Record<string, GalleryFixture> = {
|
|
|
119
119
|
{
|
|
120
120
|
type: "text",
|
|
121
121
|
text: [
|
|
122
|
-
"●
|
|
123
|
-
" Loaded: loaded (/etc/systemd/system/
|
|
122
|
+
"● snsagent-api.service - snsagent API",
|
|
123
|
+
" Loaded: loaded (/etc/systemd/system/snsagent-api.service; enabled)",
|
|
124
124
|
" Active: active (running) since Sat 2026-06-06 09:14:02 UTC; 3h 21min ago",
|
|
125
125
|
" Main PID: 4812 (bun)",
|
|
126
126
|
" Tasks: 17 (limit: 4915)",
|
|
127
127
|
" Memory: 142.6M",
|
|
128
128
|
" CPU: 38.214s",
|
|
129
|
-
" CGroup: /system.slice/
|
|
129
|
+
" CGroup: /system.slice/snsagent-api.service",
|
|
130
130
|
" └─4812 /usr/local/bin/bun run dist/server.js",
|
|
131
131
|
].join("\n"),
|
|
132
132
|
},
|
package/src/cli/grep-cli.ts
CHANGED
|
@@ -111,7 +111,7 @@ export async function runGrepCommand(cmd: GrepCommandArgs): Promise<void> {
|
|
|
111
111
|
console.log(chalk.dim(`${displayPath}-${ctx.lineNumber}- ${ctx.line}`));
|
|
112
112
|
}
|
|
113
113
|
}
|
|
114
|
-
console.log(`${chalk.
|
|
114
|
+
console.log(`${chalk.hex("#F97316")(displayPath)}:${chalk.yellow(String(match.lineNumber))}: ${match.line}`);
|
|
115
115
|
if (match.contextAfter) {
|
|
116
116
|
for (const ctx of match.contextAfter) {
|
|
117
117
|
console.log(chalk.dim(`${displayPath}-${ctx.lineNumber}- ${ctx.line}`));
|
|
@@ -119,9 +119,9 @@ export async function runGrepCommand(cmd: GrepCommandArgs): Promise<void> {
|
|
|
119
119
|
}
|
|
120
120
|
console.log("");
|
|
121
121
|
} else if (cmd.mode === GrepOutputMode.Count) {
|
|
122
|
-
console.log(`${chalk.
|
|
122
|
+
console.log(`${chalk.hex("#F97316")(displayPath)}: ${match.matchCount ?? 0} matches`);
|
|
123
123
|
} else {
|
|
124
|
-
console.log(chalk.
|
|
124
|
+
console.log(chalk.hex("#F97316")(displayPath));
|
|
125
125
|
}
|
|
126
126
|
}
|
|
127
127
|
} catch (err) {
|
|
@@ -71,7 +71,7 @@ export async function listGrievances(options: ListGrievancesOptions): Promise<vo
|
|
|
71
71
|
|
|
72
72
|
for (const row of rows) {
|
|
73
73
|
console.log(
|
|
74
|
-
`${chalk.dim(`#${row.id}`)} ${chalk.
|
|
74
|
+
`${chalk.dim(`#${row.id}`)} ${chalk.hex("#F97316")(row.tool)} ${chalk.dim(`(${row.model} v${row.version})`)}`,
|
|
75
75
|
);
|
|
76
76
|
console.log(` ${row.report}`);
|
|
77
77
|
console.log();
|
|
@@ -181,7 +181,7 @@ function makeProgressBar(total: number, width = 30): ProgressBar {
|
|
|
181
181
|
const pct = `${Math.floor(ratio * 100)
|
|
182
182
|
.toString()
|
|
183
183
|
.padStart(3, " ")}%`;
|
|
184
|
-
process.stdout.write(`\r${chalk.
|
|
184
|
+
process.stdout.write(`\r${chalk.hex("#F97316")("Pushing")} [${bar}] ${pct} ${done}/${total}`);
|
|
185
185
|
};
|
|
186
186
|
render(0);
|
|
187
187
|
return {
|
package/src/cli/index.ts
CHANGED
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
import { loadConfig, saveConfig, configPath, ensureConfig } from "../config/loader.js";
|
|
15
15
|
import type { Config } from "../config/schema.js";
|
|
16
16
|
import { defaultConfigFn } from "../config/loader.js";
|
|
17
|
+
import { getAgentDir } from "@oh-my-pi/pi-utils";
|
|
17
18
|
import { startTelegramAdapter, stopTelegramAdapter } from "../adapters/telegram/index.js";
|
|
18
19
|
import { createForwardToAgent, resetChatSession, getBridgeStats } from "../adapters/telegram/bridge.js";
|
|
19
20
|
import { createInterface } from "node:readline/promises";
|
|
@@ -24,8 +25,9 @@ import chalk from "chalk";
|
|
|
24
25
|
|
|
25
26
|
// Inline version — kept in sync with package.json. Read from package.json at
|
|
26
27
|
// runtime so the bin script does not need a second copy.
|
|
27
|
-
import { readFileSync } from "node:fs";
|
|
28
|
-
import {
|
|
28
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
29
|
+
import { homedir } from "node:os";
|
|
30
|
+
import { dirname, join, resolve } from "node:path";
|
|
29
31
|
import { fileURLToPath } from "node:url";
|
|
30
32
|
|
|
31
33
|
/**
|
|
@@ -64,12 +66,137 @@ function cmdVersion(): number {
|
|
|
64
66
|
return 0;
|
|
65
67
|
}
|
|
66
68
|
|
|
69
|
+
/** Derive a provider name from base URL for models.yml key. */
|
|
70
|
+
function deriveProviderName(baseUrl: string): string {
|
|
71
|
+
try {
|
|
72
|
+
const url = new URL(baseUrl);
|
|
73
|
+
return url.hostname.replace(/^api\./, "").replace(/\./g, "-") || "custom-provider";
|
|
74
|
+
} catch {
|
|
75
|
+
return "custom-provider";
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** BYOK provider setup — reusable for first-run and re-init. */
|
|
80
|
+
async function byokProviderSetup(rl: ReturnType<typeof createInterface>): Promise<void> {
|
|
81
|
+
process.stdout.write("\n");
|
|
82
|
+
process.stdout.write(` ${chalk.hex("#F97316")("●")} ${chalk.bold("AI Provider Setup")}\n`);
|
|
83
|
+
process.stdout.write(` ${chalk.dim("Connect your LLM provider (skip to set via env vars later):")}\n`);
|
|
84
|
+
|
|
85
|
+
// Base URL
|
|
86
|
+
process.stdout.write(` ${chalk.hex("#F97316")("●")} Base URL [https://api.openai.com/v1]: `);
|
|
87
|
+
const baseUrlRaw = await rl.question("");
|
|
88
|
+
const baseUrl = (baseUrlRaw.trim() || "https://api.openai.com/v1").replace(/\/+$/, "");
|
|
89
|
+
|
|
90
|
+
// API Key
|
|
91
|
+
process.stdout.write(` ${chalk.hex("#F97316")("●")} API Key: `);
|
|
92
|
+
const apiKey = await rl.question("");
|
|
93
|
+
|
|
94
|
+
// API type
|
|
95
|
+
process.stdout.write(` ${chalk.hex("#F97316")("●")} API Type:\n`);
|
|
96
|
+
process.stdout.write(` ${chalk.hex("#F97316")("1")} openai-completions (default — OpenAI, OpenRouter, Ollama, vLLM)\n`);
|
|
97
|
+
process.stdout.write(` ${chalk.hex("#F97316")("2")} openai-responses\n`);
|
|
98
|
+
process.stdout.write(` ${chalk.hex("#F97316")("3")} anthropic-messages\n`);
|
|
99
|
+
process.stdout.write(` ${chalk.hex("#F97316")("4")} google-generative-ai\n`);
|
|
100
|
+
process.stdout.write(` ${chalk.hex("#F97316")("5")} azure-openai-responses\n`);
|
|
101
|
+
process.stdout.write(` [1] `);
|
|
102
|
+
const apiTypeAnswer = await rl.question("");
|
|
103
|
+
|
|
104
|
+
const apiTypeMap: Record<string, string> = {
|
|
105
|
+
"1": "openai-completions",
|
|
106
|
+
"2": "openai-responses",
|
|
107
|
+
"3": "anthropic-messages",
|
|
108
|
+
"4": "google-generative-ai",
|
|
109
|
+
"5": "azure-openai-responses",
|
|
110
|
+
};
|
|
111
|
+
const apiType = apiTypeMap[apiTypeAnswer.trim()] || "openai-completions";
|
|
112
|
+
|
|
113
|
+
// Save provider to models.yml
|
|
114
|
+
const apiKeyTrimmed = apiKey.trim();
|
|
115
|
+
if (apiKeyTrimmed || baseUrl.includes("localhost") || baseUrl.includes("127.0.0.1")) {
|
|
116
|
+
try {
|
|
117
|
+
const { YAML } = await import("bun");
|
|
118
|
+
const agentDir = getAgentDir();
|
|
119
|
+
const modelsPath = join(agentDir, "models.yml");
|
|
120
|
+
|
|
121
|
+
let existing: Record<string, unknown> = {};
|
|
122
|
+
if (existsSync(modelsPath)) {
|
|
123
|
+
try {
|
|
124
|
+
const content = readFileSync(modelsPath, "utf-8");
|
|
125
|
+
existing = (YAML.parse(content) as Record<string, unknown>) ?? {};
|
|
126
|
+
} catch { existing = {}; }
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const providers = (existing.providers ?? {}) as Record<string, unknown>;
|
|
130
|
+
const providerName = deriveProviderName(baseUrl);
|
|
131
|
+
|
|
132
|
+
// Determine auth mode — no API key + localhost = auth "none"
|
|
133
|
+
const isNoAuth = !apiKeyTrimmed;
|
|
134
|
+
const providerConfig: Record<string, unknown> = {
|
|
135
|
+
baseUrl,
|
|
136
|
+
api: apiType,
|
|
137
|
+
auth: isNoAuth ? "none" : "apiKey",
|
|
138
|
+
};
|
|
139
|
+
if (apiKeyTrimmed) providerConfig.apiKey = apiKeyTrimmed;
|
|
140
|
+
|
|
141
|
+
// Auto-detect models for OpenAI-compatible
|
|
142
|
+
if (!isNoAuth && (apiType === "openai-completions" || apiType === "openai-responses")) {
|
|
143
|
+
process.stdout.write(` ${chalk.dim("Detecting models…")}\n`);
|
|
144
|
+
try {
|
|
145
|
+
const headers: Record<string, string> = {};
|
|
146
|
+
if (apiKeyTrimmed) headers["Authorization"] = `Bearer ${apiKeyTrimmed}`;
|
|
147
|
+
const res = await fetch(`${baseUrl}/models`, { headers, signal: AbortSignal.timeout(10_000) });
|
|
148
|
+
if (res.ok) {
|
|
149
|
+
const data = (await res.json()) as { data?: { id: string }[] };
|
|
150
|
+
if (data.data?.length) {
|
|
151
|
+
const models = data.data.map(m => m.id).filter(Boolean).sort();
|
|
152
|
+
providerConfig.models = models.map(id => ({
|
|
153
|
+
id,
|
|
154
|
+
api: apiType,
|
|
155
|
+
contextWindow: 128000,
|
|
156
|
+
supportsTools: true,
|
|
157
|
+
}));
|
|
158
|
+
process.stdout.write(` ${chalk.green("✓")} ${models.length} model${models.length !== 1 ? "s" : ""} detected\n`);
|
|
159
|
+
} else {
|
|
160
|
+
process.stdout.write(` ${chalk.dim("No models listed at /models endpoint (provider may not support it)")}\n`);
|
|
161
|
+
}
|
|
162
|
+
} else {
|
|
163
|
+
process.stdout.write(` ${chalk.dim(`Could not detect models (HTTP ${res.status}) — manual config may be needed`)}\n`);
|
|
164
|
+
}
|
|
165
|
+
} catch {
|
|
166
|
+
process.stdout.write(` ${chalk.dim("Could not reach /models endpoint — saved without model list")}\n`);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
providers[providerName] = providerConfig;
|
|
171
|
+
existing.providers = providers;
|
|
172
|
+
|
|
173
|
+
if (!existsSync(agentDir)) mkdirSync(agentDir, { recursive: true });
|
|
174
|
+
writeFileSync(modelsPath, YAML.stringify(existing), "utf-8");
|
|
175
|
+
process.stdout.write(` ${chalk.green("✓")} Provider saved to ${modelsPath}\n`);
|
|
176
|
+
} catch (err) {
|
|
177
|
+
process.stdout.write(` ${chalk.dim(`Could not save provider config: ${(err as Error).message}`)}\n`);
|
|
178
|
+
}
|
|
179
|
+
} else {
|
|
180
|
+
process.stdout.write(` ${chalk.dim("Skipped — set OPENAI_API_KEY or ANTHROPIC_API_KEY env var to connect later")}\n`);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
67
184
|
async function cmdInit(): Promise<number> {
|
|
68
185
|
const path = configPath();
|
|
69
186
|
try {
|
|
70
187
|
const existing = loadConfig();
|
|
71
188
|
if (existing) {
|
|
72
189
|
process.stdout.write(`✓ config already exists at ${path}\n`);
|
|
190
|
+
// Still allow BYOK provider re-configuration
|
|
191
|
+
process.stdout.write(` ${chalk.dim("Re-run provider setup? [y/N] ")}`);
|
|
192
|
+
const rl = createInterface({ input: stdin, output: stdout });
|
|
193
|
+
const reconfigure = await rl.question("");
|
|
194
|
+
if (reconfigure.trim().toLowerCase() !== "y") {
|
|
195
|
+
rl.close();
|
|
196
|
+
return 0;
|
|
197
|
+
}
|
|
198
|
+
await byokProviderSetup(rl);
|
|
199
|
+
rl.close();
|
|
73
200
|
return 0;
|
|
74
201
|
}
|
|
75
202
|
|
|
@@ -77,15 +204,15 @@ async function cmdInit(): Promise<number> {
|
|
|
77
204
|
const rl = createInterface({ input: stdin, output: stdout });
|
|
78
205
|
|
|
79
206
|
process.stdout.write("\n");
|
|
80
|
-
process.stdout.write(` ${chalk.
|
|
207
|
+
process.stdout.write(` ${chalk.hex("#F97316")("●")} ${chalk.bold("Memory Backend Selection")}\n`);
|
|
81
208
|
process.stdout.write(` ${chalk.dim("Select memory backend (default: mnemopi):")}\n`);
|
|
82
|
-
process.stdout.write(` ${chalk.
|
|
83
|
-
process.stdout.write(` ${chalk.
|
|
84
|
-
process.stdout.write(` ${chalk.
|
|
85
|
-
process.stdout.write(` ${chalk.
|
|
86
|
-
process.stdout.write(` ${chalk.
|
|
87
|
-
process.stdout.write(` ${chalk.
|
|
88
|
-
process.stdout.write(` ${chalk.
|
|
209
|
+
process.stdout.write(` ${chalk.hex("#F97316")("1")} mnemopi (default) — SQLite + vector + graph, offline, no config\n`);
|
|
210
|
+
process.stdout.write(` ${chalk.hex("#F97316")("2")} local — rollout summary only\n`);
|
|
211
|
+
process.stdout.write(` ${chalk.hex("#F97316")("3")} mnemosyne — advanced local (Python daemon)\n`);
|
|
212
|
+
process.stdout.write(` ${chalk.hex("#F97316")("4")} mem0 — cloud API (needs MEM0_API_KEY)\n`);
|
|
213
|
+
process.stdout.write(` ${chalk.hex("#F97316")("5")} lcm — local server (needs LCM_HOST)\n`);
|
|
214
|
+
process.stdout.write(` ${chalk.hex("#F97316")("6")} hindsight — cloud (needs HINDSIGHT_API_KEY)\n`);
|
|
215
|
+
process.stdout.write(` ${chalk.hex("#F97316")("7")} off — disabled\n`);
|
|
89
216
|
process.stdout.write(` [1] `);
|
|
90
217
|
|
|
91
218
|
const answer = await rl.question("");
|
|
@@ -121,7 +248,7 @@ async function cmdInit(): Promise<number> {
|
|
|
121
248
|
|
|
122
249
|
// Mnemosyne needs Python daemon
|
|
123
250
|
if (backend === "mnemosyne") {
|
|
124
|
-
process.stdout.write(`\n ${chalk.
|
|
251
|
+
process.stdout.write(`\n ${chalk.hex("#F97316")("●")} mnemosyne requires Python daemon.\n`);
|
|
125
252
|
process.stdout.write(` ${chalk.dim("Install now? (pip install mnemosyne) [Y/n] ")}`);
|
|
126
253
|
const yn = await rl.question("");
|
|
127
254
|
if (!yn || yn.toLowerCase() === "y") {
|
|
@@ -130,6 +257,9 @@ async function cmdInit(): Promise<number> {
|
|
|
130
257
|
}
|
|
131
258
|
}
|
|
132
259
|
|
|
260
|
+
// ── BYOK Provider Setup ─────────────────────────────────────
|
|
261
|
+
await byokProviderSetup(rl);
|
|
262
|
+
|
|
133
263
|
rl.close();
|
|
134
264
|
|
|
135
265
|
const cfg = defaultConfigFn();
|
|
@@ -405,14 +535,15 @@ Commands:
|
|
|
405
535
|
(none) start full agent interactive mode (default)
|
|
406
536
|
agent alias for default
|
|
407
537
|
version print package version
|
|
408
|
-
init create .sns-myagent/config.json
|
|
538
|
+
init create .sns-myagent/config.json + connect AI provider
|
|
539
|
+
setup alias for init (BYOK provider setup)
|
|
409
540
|
chat [--stub] start interactive chat (stub for Phase 2B)
|
|
410
541
|
launch alias for default
|
|
411
542
|
config show print current config
|
|
412
543
|
config get <key> read a dot-path value (e.g. model.provider)
|
|
413
544
|
config set <key> <value> update a dot-path value
|
|
414
545
|
telegram start|stop|status manage the Telegram polling adapter
|
|
415
|
-
orchestrate <prompt> multi-agent ensemble run
|
|
546
|
+
orchestrate <prompt> multi-agent ensemble run
|
|
416
547
|
help print this help
|
|
417
548
|
`;
|
|
418
549
|
|
|
@@ -434,6 +565,7 @@ export async function runCliAsync(argv: string[]): Promise<number> {
|
|
|
434
565
|
case "-v":
|
|
435
566
|
return cmdVersion();
|
|
436
567
|
case "init":
|
|
568
|
+
case "setup":
|
|
437
569
|
return cmdInit();
|
|
438
570
|
case "chat":
|
|
439
571
|
return cmdChat(rest);
|
|
@@ -441,8 +573,6 @@ export async function runCliAsync(argv: string[]): Promise<number> {
|
|
|
441
573
|
return cmdConfig(rest);
|
|
442
574
|
case "telegram":
|
|
443
575
|
return cmdTelegram(rest);
|
|
444
|
-
case "launch":
|
|
445
|
-
return cmdLaunch(rest);
|
|
446
576
|
case "orchestrate":
|
|
447
577
|
return cmdOrchestrate(rest);
|
|
448
578
|
default:
|
|
@@ -456,30 +586,55 @@ async function cmdOrchestrate(args: string[]): Promise<number> {
|
|
|
456
586
|
if (args.length === 0) {
|
|
457
587
|
process.stderr.write("✗ orchestrate requires a prompt\n");
|
|
458
588
|
process.stderr.write(" usage: snsagent orchestrate <prompt>\n");
|
|
459
|
-
process.stderr.write(" flags: --strategy consensus|critic|best_of_n\n");
|
|
589
|
+
process.stderr.write(" flags: --strategy consensus|critic|best_of_n|single\n");
|
|
460
590
|
process.stderr.write(" --agents role1,role2\n");
|
|
461
591
|
process.stderr.write(" --ensemble <name> (from agents.yaml)\n");
|
|
462
592
|
return 1;
|
|
463
593
|
}
|
|
464
|
-
|
|
465
|
-
|
|
594
|
+
|
|
595
|
+
// Parse flags and build prompt
|
|
596
|
+
const promptParts: string[] = [];
|
|
597
|
+
let strategy: string | undefined;
|
|
598
|
+
let agents: string[] | undefined;
|
|
599
|
+
let ensemble: string | undefined;
|
|
600
|
+
|
|
466
601
|
for (let i = 0; i < args.length; i++) {
|
|
467
602
|
const a = args[i];
|
|
468
|
-
if (a === "--strategy")
|
|
469
|
-
else if (a === "--agents")
|
|
470
|
-
else if (a === "--ensemble")
|
|
603
|
+
if (a === "--strategy") { strategy = args[++i]; }
|
|
604
|
+
else if (a === "--agents") { agents = (args[++i] ?? "").split(",").filter(Boolean); }
|
|
605
|
+
else if (a === "--ensemble") { ensemble = args[++i]; }
|
|
606
|
+
else { promptParts.push(a); }
|
|
471
607
|
}
|
|
472
|
-
|
|
608
|
+
|
|
609
|
+
const prompt = promptParts.join(" ");
|
|
610
|
+
if (!prompt) {
|
|
611
|
+
process.stderr.write("✗ orchestrate requires a prompt\n");
|
|
612
|
+
return 1;
|
|
613
|
+
}
|
|
614
|
+
|
|
473
615
|
try {
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
);
|
|
482
|
-
|
|
616
|
+
const { executeEnsemble } = await import("../agents/ensemble.js");
|
|
617
|
+
const { executeAgentForEnsemble } = await import("../agents/executor.js");
|
|
618
|
+
|
|
619
|
+
const result = await executeEnsemble(prompt, executeAgentForEnsemble, {
|
|
620
|
+
strategy: strategy as "consensus" | "critic" | "best_of_n" | undefined,
|
|
621
|
+
agents,
|
|
622
|
+
ensemble,
|
|
623
|
+
});
|
|
624
|
+
|
|
625
|
+
// Output result
|
|
626
|
+
console.log(result.final);
|
|
627
|
+
|
|
628
|
+
// Cost summary to stderr
|
|
629
|
+
if (result.costBreakdown.length > 0) {
|
|
630
|
+
process.stderr.write(`\n── ensemble: ${result.strategy} | ${result.rounds} round(s) | ${result.totalTimeMs}ms ──\n`);
|
|
631
|
+
for (const row of result.costBreakdown) {
|
|
632
|
+
const tokens = row.inputTokens + row.outputTokens;
|
|
633
|
+
process.stderr.write(` ${row.role}: ${row.model} (${tokens} tokens)\n`);
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
return 0;
|
|
483
638
|
} catch (err) {
|
|
484
639
|
process.stderr.write(`✗ orchestrate failed: ${(err as Error).message}\n`);
|
|
485
640
|
return 1;
|
package/src/cli/plugin-cli.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Plugin CLI command handlers.
|
|
3
3
|
*
|
|
4
|
-
* Handles `
|
|
4
|
+
* Handles `snsagent plugin <command>` subcommands for plugin lifecycle management.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import { APP_NAME, getProjectDir } from "@oh-my-pi/pi-utils";
|
|
@@ -270,7 +270,7 @@ async function handleMarketplace(args: string[], _flags: PluginCommandArgs["flag
|
|
|
270
270
|
}
|
|
271
271
|
console.log(chalk.bold("Configured Marketplaces:\n"));
|
|
272
272
|
for (const mp of marketplaces) {
|
|
273
|
-
console.log(` ${chalk.
|
|
273
|
+
console.log(` ${chalk.hex("#F97316")(mp.name)} ${chalk.dim(mp.sourceUri)}`);
|
|
274
274
|
}
|
|
275
275
|
} catch (err) {
|
|
276
276
|
console.error(chalk.red(`${theme.status.error} Failed to list marketplaces: ${err}`));
|
|
@@ -294,7 +294,7 @@ async function handleDiscover(args: string[], _flags: PluginCommandArgs["flags"]
|
|
|
294
294
|
|
|
295
295
|
console.log(chalk.bold(`Available Plugins${marketplace ? ` (${marketplace})` : ""}:\n`));
|
|
296
296
|
for (const plugin of plugins) {
|
|
297
|
-
console.log(` ${chalk.
|
|
297
|
+
console.log(` ${chalk.hex("#F97316")(plugin.name)}${plugin.version ? `@${plugin.version}` : ""}`);
|
|
298
298
|
if (plugin.description) {
|
|
299
299
|
console.log(chalk.dim(` ${plugin.description}`));
|
|
300
300
|
}
|
|
@@ -323,7 +323,7 @@ async function handleUpgrade(args: string[], flags: PluginCommandArgs["flags"]):
|
|
|
323
323
|
if (flags.scope) {
|
|
324
324
|
console.error(
|
|
325
325
|
chalk.yellow(
|
|
326
|
-
`Warning: --scope is ignored when upgrading all plugins. Use '
|
|
326
|
+
`Warning: --scope is ignored when upgrading all plugins. Use 'snsagent plugin upgrade <id> --scope ${flags.scope}' to target a specific plugin and scope.`,
|
|
327
327
|
),
|
|
328
328
|
);
|
|
329
329
|
}
|
|
@@ -386,7 +386,7 @@ async function handleInstall(
|
|
|
386
386
|
if (target.type === "local") {
|
|
387
387
|
// Local paths route to link(): symlink the directory into the plugins
|
|
388
388
|
// node_modules tree so source edits show up without a reinstall. Matches
|
|
389
|
-
// `
|
|
389
|
+
// `snsagent plugin link <path>` so users can use either verb interchangeably.
|
|
390
390
|
if (flags.scope) {
|
|
391
391
|
console.error(
|
|
392
392
|
chalk.yellow(
|