@sayknow-cli/coding-agent 0.3.5 → 0.3.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +76 -0
- package/dist/types/capability/mcp.d.ts +2 -0
- package/dist/types/cli/local-provider-smoke.d.ts +59 -0
- package/dist/types/cli/notify-cli.d.ts +1 -1
- package/dist/types/cli/telegram-cli.d.ts +13 -0
- package/dist/types/cli/update-cli.d.ts +24 -1
- package/dist/types/commands/local-provider.d.ts +34 -0
- package/dist/types/commands/telegram.d.ts +22 -0
- package/dist/types/config/keybindings.d.ts +12 -6
- package/dist/types/config/model-registry.d.ts +5 -0
- package/dist/types/config/models-config-schema.d.ts +5 -0
- package/dist/types/config/settings-schema.d.ts +284 -4
- package/dist/types/config/settings.d.ts +7 -0
- package/dist/types/config/telegram-autostart.d.ts +14 -0
- package/dist/types/config/telegram-env-bridge.d.ts +18 -0
- package/dist/types/coordinator-mcp/server.d.ts +8 -0
- package/dist/types/defaults/skc-defaults.d.ts +7 -0
- package/dist/types/discovery/cursor.d.ts +4 -1
- package/dist/types/discovery/index.d.ts +0 -1
- package/dist/types/discovery/windsurf.d.ts +3 -1
- package/dist/types/extensibility/extensions/types.d.ts +11 -0
- package/dist/types/i18n/messages/en.d.ts +1 -0
- package/dist/types/modes/components/custom-editor.d.ts +6 -1
- package/dist/types/modes/components/custom-model-preset-wizard.d.ts +1 -1
- package/dist/types/modes/components/hook-selector.d.ts +5 -0
- package/dist/types/modes/components/model-selector.d.ts +2 -0
- package/dist/types/modes/components/settings-selector.d.ts +5 -1
- package/dist/types/modes/components/skill-message.d.ts +1 -0
- package/dist/types/modes/components/status-line/types.d.ts +5 -7
- package/dist/types/modes/components/status-line.d.ts +15 -3
- package/dist/types/modes/components/two-column-body.d.ts +9 -0
- package/dist/types/modes/components/welcome.d.ts +9 -1
- package/dist/types/modes/controllers/command-controller.d.ts +1 -0
- package/dist/types/modes/controllers/input-controller.d.ts +6 -1
- package/dist/types/modes/controllers/selector-controller.d.ts +10 -1
- package/dist/types/modes/interactive-mode.d.ts +2 -0
- package/dist/types/modes/prompt-action-autocomplete.d.ts +3 -0
- package/dist/types/modes/rpc/rpc-types.d.ts +1 -0
- package/dist/types/modes/shared/agent-wire/deep-interview-gate.d.ts +41 -7
- package/dist/types/modes/slash-command-visibility.d.ts +1 -0
- package/dist/types/modes/theme/theme.d.ts +1 -1
- package/dist/types/modes/tmux-scroll.d.ts +18 -0
- package/dist/types/modes/types.d.ts +5 -1
- package/dist/types/modes/utils/terminal-bell.d.ts +3 -0
- package/dist/types/notifications/daemon-paths.d.ts +9 -0
- package/dist/types/notifications/html-format.d.ts +2 -0
- package/dist/types/notifications/recent-activity.d.ts +5 -1
- package/dist/types/notifications/telegram-daemon-cli.d.ts +21 -4
- package/dist/types/notifications/telegram-daemon-control.d.ts +8 -0
- package/dist/types/notifications/telegram-daemon.d.ts +4 -10
- package/dist/types/notifications/telegram-reference.d.ts +4 -0
- package/dist/types/notifications/threaded-render.d.ts +1 -0
- package/dist/types/runtime-mcp/config-writer.d.ts +9 -0
- package/dist/types/runtime-mcp/config.d.ts +2 -0
- package/dist/types/runtime-mcp/loader.d.ts +2 -0
- package/dist/types/runtime-mcp/manager.d.ts +2 -0
- package/dist/types/runtime-mcp/types.d.ts +6 -0
- package/dist/types/sdk.d.ts +1 -1
- package/dist/types/skc-runtime/launch-tmux.d.ts +2 -2
- package/dist/types/skc-runtime/ralplan-runtime.d.ts +3 -2
- package/dist/types/skc-runtime/restricted-role-agent-bash.d.ts +1 -0
- package/dist/types/skc-runtime/session-state-sidecar.d.ts +5 -0
- package/dist/types/skc-runtime/state-writer.d.ts +1 -0
- package/dist/types/skc-runtime/team-runtime.d.ts +1 -1
- package/dist/types/skc-runtime/tmux-common.d.ts +12 -0
- package/dist/types/skc-runtime/ultragoal-guard.d.ts +7 -1
- package/dist/types/skc-runtime/ultragoal-runtime.d.ts +0 -4
- package/dist/types/slash-commands/types.d.ts +2 -0
- package/dist/types/system-prompt.d.ts +7 -0
- package/dist/types/tools/skill.d.ts +1 -1
- package/dist/types/utils/cmux-workspace.d.ts +52 -0
- package/dist/types/web/search/index.d.ts +4 -2
- package/dist/types/web/search/provider.d.ts +14 -0
- package/dist/types/web/search/providers/utils.d.ts +42 -13
- package/dist/types/web/search/render.d.ts +1 -0
- package/dist/types/workflow/workflow-intent-diff.d.ts +19 -0
- package/package.json +8 -8
- package/scripts/build-binary.ts +3 -0
- package/scripts/verify-insane-vendor.ts +0 -5
- package/src/capability/mcp.ts +2 -0
- package/src/cli/local-provider-smoke.ts +632 -0
- package/src/cli/notify-cli.ts +4 -2
- package/src/cli/telegram-cli.ts +156 -0
- package/src/cli/update-cli.ts +179 -13
- package/src/cli/web-search-cli.ts +5 -7
- package/src/cli.ts +20 -1
- package/src/commands/launch.ts +47 -2
- package/src/commands/local-provider.ts +62 -0
- package/src/commands/notify.ts +1 -1
- package/src/commands/ralplan.ts +1 -0
- package/src/commands/telegram.ts +47 -0
- package/src/config/keybindings.ts +15 -6
- package/src/config/mcp-schema.json +4 -0
- package/src/config/model-registry.ts +55 -1
- package/src/config/models-config-schema.ts +9 -0
- package/src/config/settings-schema.ts +256 -4
- package/src/config/settings.ts +11 -0
- package/src/config/telegram-autostart.ts +107 -0
- package/src/config/telegram-env-bridge.ts +92 -0
- package/src/coordinator-mcp/server.ts +276 -34
- package/src/defaults/skc/skills/ralplan/SKILL.md +29 -20
- package/src/defaults/skc/skills/ultragoal/SKILL.md +37 -112
- package/src/defaults/skc-defaults.ts +16 -0
- package/src/discovery/builtin.ts +12 -0
- package/src/discovery/cursor.ts +5 -84
- package/src/discovery/gemini.ts +3 -89
- package/src/discovery/index.ts +4 -1
- package/src/discovery/mcp-json.ts +11 -0
- package/src/discovery/opencode.ts +5 -122
- package/src/discovery/windsurf.ts +4 -82
- package/src/edit/read-file.ts +9 -1
- package/src/extensibility/extensions/runner.ts +2 -1
- package/src/extensibility/extensions/types.ts +11 -0
- package/src/extensibility/skills.ts +30 -2
- package/src/hooks/skill-state.ts +33 -73
- package/src/i18n/messages/de.ts +1 -0
- package/src/i18n/messages/en.ts +1 -0
- package/src/i18n/messages/es.ts +1 -0
- package/src/i18n/messages/fr.ts +1 -0
- package/src/i18n/messages/ja.settings.ts +28 -0
- package/src/i18n/messages/ja.ts +1 -0
- package/src/i18n/messages/ko.settings.ts +28 -0
- package/src/i18n/messages/ko.ts +1 -0
- package/src/i18n/messages/zh.settings.ts +28 -0
- package/src/i18n/messages/zh.ts +1 -0
- package/src/internal-urls/docs-index.generated.ts +12 -7
- package/src/main.ts +5 -2
- package/src/migrate/executor.ts +23 -6
- package/src/modes/acp/acp-agent.ts +7 -6
- package/src/modes/components/agent-dashboard.ts +3 -35
- package/src/modes/components/custom-editor.ts +26 -0
- package/src/modes/components/custom-model-preset-wizard.ts +30 -189
- package/src/modes/components/extensions/extension-dashboard.ts +2 -48
- package/src/modes/components/hook-selector.ts +21 -3
- package/src/modes/components/model-selector.ts +246 -11
- package/src/modes/components/session-observer-overlay.ts +3 -3
- package/src/modes/components/session-selector.ts +19 -9
- package/src/modes/components/settings-selector.ts +551 -11
- package/src/modes/components/skill-message.ts +18 -10
- package/src/modes/components/status-line/presets.ts +11 -0
- package/src/modes/components/status-line/segments.ts +16 -19
- package/src/modes/components/status-line/types.ts +6 -2
- package/src/modes/components/status-line.ts +281 -117
- package/src/modes/components/tree-selector.ts +28 -8
- package/src/modes/components/two-column-body.ts +36 -0
- package/src/modes/components/welcome.ts +165 -30
- package/src/modes/controllers/command-controller.ts +26 -2
- package/src/modes/controllers/event-controller.ts +102 -4
- package/src/modes/controllers/extension-ui-controller.ts +21 -2
- package/src/modes/controllers/input-controller.ts +214 -47
- package/src/modes/controllers/selector-controller.ts +35 -33
- package/src/modes/interactive-mode.ts +36 -30
- package/src/modes/prompt-action-autocomplete.ts +48 -8
- package/src/modes/rpc/rpc-types.ts +1 -0
- package/src/modes/shared/agent-wire/deep-interview-gate.ts +72 -15
- package/src/modes/shared/agent-wire/workflow-gate-schema.ts +20 -0
- package/src/modes/slash-command-visibility.ts +9 -0
- package/src/modes/theme/theme.ts +5 -1
- package/src/modes/tmux-scroll.ts +92 -0
- package/src/modes/types.ts +8 -1
- package/src/modes/utils/hotkeys-markdown.ts +5 -2
- package/src/modes/utils/terminal-bell.ts +48 -0
- package/src/modes/utils/ui-helpers.ts +76 -3
- package/src/notifications/daemon-paths.ts +22 -0
- package/src/notifications/html-format.ts +75 -1
- package/src/notifications/index.ts +32 -2
- package/src/notifications/lifecycle-control-runtime.ts +12 -3
- package/src/notifications/recent-activity.ts +32 -8
- package/src/notifications/telegram-daemon-cli.ts +125 -13
- package/src/notifications/telegram-daemon-control.ts +9 -0
- package/src/notifications/telegram-daemon.ts +88 -46
- package/src/notifications/telegram-reference.ts +36 -14
- package/src/notifications/threaded-render.ts +14 -6
- package/src/prompts/agents/architect.md +7 -4
- package/src/prompts/agents/critic.md +15 -6
- package/src/prompts/agents/planner.md +8 -3
- package/src/prompts/system/system-prompt.md +20 -2
- package/src/prompts/tools/bash.md +1 -1
- package/src/runtime-mcp/config-writer.ts +28 -0
- package/src/runtime-mcp/config.ts +7 -0
- package/src/runtime-mcp/loader.ts +3 -0
- package/src/runtime-mcp/manager.ts +3 -0
- package/src/runtime-mcp/types.ts +6 -0
- package/src/sdk.ts +10 -11
- package/src/session/agent-session.ts +103 -13
- package/src/session/session-manager.ts +58 -18
- package/src/skc-runtime/launch-tmux.ts +34 -8
- package/src/skc-runtime/launch-worktree.ts +16 -2
- package/src/skc-runtime/ralplan-runtime.ts +22 -6
- package/src/skc-runtime/restricted-role-agent-bash.ts +1 -0
- package/src/skc-runtime/session-state-sidecar.ts +167 -24
- package/src/skc-runtime/state-runtime.ts +8 -5
- package/src/skc-runtime/state-writer.ts +5 -5
- package/src/skc-runtime/team-runtime.ts +21 -6
- package/src/skc-runtime/tmux-common.ts +16 -0
- package/src/skc-runtime/tmux-sessions.ts +13 -9
- package/src/skc-runtime/ultragoal-guard.ts +150 -24
- package/src/skc-runtime/ultragoal-runtime.ts +16 -124
- package/src/skc-runtime/workflow-manifest.generated.json +0 -8
- package/src/skc-runtime/workflow-manifest.ts +0 -1
- package/src/slash-commands/builtin-registry.ts +136 -23
- package/src/slash-commands/types.ts +2 -0
- package/src/system-prompt.ts +8 -0
- package/src/task/executor.ts +5 -0
- package/src/tools/ast-grep.ts +10 -7
- package/src/tools/bash.ts +39 -14
- package/src/tools/index.ts +8 -1
- package/src/tools/skill.ts +1 -1
- package/src/utils/cmux-workspace.ts +233 -0
- package/src/utils/title-generator.ts +2 -0
- package/src/web/kagi.ts +1 -1
- package/src/web/parallel.ts +2 -2
- package/src/web/search/index.ts +137 -52
- package/src/web/search/provider.ts +80 -0
- package/src/web/search/providers/anthropic.ts +1 -1
- package/src/web/search/providers/brave.ts +1 -1
- package/src/web/search/providers/codex.ts +1 -1
- package/src/web/search/providers/duckduckgo.ts +1 -1
- package/src/web/search/providers/exa.ts +1 -1
- package/src/web/search/providers/gemini.ts +7 -3
- package/src/web/search/providers/jina.ts +1 -1
- package/src/web/search/providers/kimi.ts +6 -1
- package/src/web/search/providers/openai-compatible.ts +1 -1
- package/src/web/search/providers/parallel.ts +1 -1
- package/src/web/search/providers/perplexity.ts +2 -2
- package/src/web/search/providers/searxng.ts +1 -1
- package/src/web/search/providers/synthetic.ts +1 -1
- package/src/web/search/providers/tavily.ts +1 -1
- package/src/web/search/providers/utils.ts +67 -17
- package/src/web/search/providers/xai.ts +1 -1
- package/src/web/search/providers/zai.ts +1 -1
- package/src/web/search/render.ts +3 -0
- package/src/workflow/workflow-intent-diff.ts +199 -0
- package/vendor/insane-search/MANIFEST.json +1 -3
- package/vendor/insane-search/engine/tests/test_u1.py +0 -28
- package/vendor/insane-search/engine/transport.py +2 -3
- package/vendor/insane-search/engine/validators.py +0 -14
- package/src/defaults/skc/rules/ponytail.md +0 -68
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `skc telegram` command handlers — start/status/env for the Telegram Remote
|
|
3
|
+
* integration, driven entirely by the `telegram.*` settings group.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { logger } from "@sayknow-cli/utils";
|
|
7
|
+
import { resolveSkcRuntimeSpawnInfo } from "../daemon/runtime";
|
|
8
|
+
import { isSettingsInitialized, Settings, settings } from "../config/settings";
|
|
9
|
+
import type { TelegramSettings } from "../config/settings-schema";
|
|
10
|
+
import { telegramSettingsToEnv, validateTelegramSettings } from "../config/telegram-env-bridge";
|
|
11
|
+
|
|
12
|
+
export type TelegramAction = "start" | "status" | "env" | "__gateway";
|
|
13
|
+
|
|
14
|
+
export interface TelegramCommandArgs {
|
|
15
|
+
action: TelegramAction;
|
|
16
|
+
flags: {
|
|
17
|
+
json?: boolean;
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export const TELEGRAM_ACTIONS: readonly TelegramAction[] = ["start", "status", "env", "__gateway"];
|
|
22
|
+
|
|
23
|
+
/** Read the full telegram settings group from the settings singleton. */
|
|
24
|
+
function readTelegramSettings(): TelegramSettings {
|
|
25
|
+
return {
|
|
26
|
+
enabled: settings.get("telegram.enabled"),
|
|
27
|
+
botToken: settings.get("telegram.botToken"),
|
|
28
|
+
allowedUserIds: settings.get("telegram.allowedUserIds"),
|
|
29
|
+
allowedChatIds: settings.get("telegram.allowedChatIds"),
|
|
30
|
+
backend: settings.get("telegram.backend"),
|
|
31
|
+
enableStop: settings.get("telegram.enableStop"),
|
|
32
|
+
enableRich: settings.get("telegram.enableRich"),
|
|
33
|
+
enablePush: settings.get("telegram.enablePush"),
|
|
34
|
+
registerCommands: settings.get("telegram.registerCommands"),
|
|
35
|
+
presets: settings.get("telegram.presets"),
|
|
36
|
+
rpcSocket: settings.get("telegram.rpcSocket"),
|
|
37
|
+
stateDir: settings.get("telegram.stateDir"),
|
|
38
|
+
pollTimeoutSec: settings.get("telegram.pollTimeoutSec"),
|
|
39
|
+
apiBase: settings.get("telegram.apiBase"),
|
|
40
|
+
coordinatorCommand: settings.get("telegram.coordinatorCommand") ?? "skc",
|
|
41
|
+
coordinatorArgs: settings.get("telegram.coordinatorArgs") ?? "mcp-serve,coordinator",
|
|
42
|
+
enableEditMessageText: settings.get("telegram.enableEditMessageText"),
|
|
43
|
+
richCallbackTtlMs: settings.get("telegram.richCallbackTtlMs"),
|
|
44
|
+
richCallbackMaxTokens: settings.get("telegram.richCallbackMaxTokens"),
|
|
45
|
+
defaultTaskMaxLen: settings.get("telegram.defaultTaskMaxLen"),
|
|
46
|
+
livenessMs: settings.get("telegram.livenessMs"),
|
|
47
|
+
followTtlMs: settings.get("telegram.followTtlMs"),
|
|
48
|
+
subscriptionsMax: settings.get("telegram.subscriptionsMax"),
|
|
49
|
+
longPollMs: settings.get("telegram.longPollMs"),
|
|
50
|
+
digestThreshold: settings.get("telegram.digestThreshold"),
|
|
51
|
+
allowAttachSocketArg: settings.get("telegram.allowAttachSocketArg"),
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Resolve the gateway spawn command by re-invoking skc's own hidden
|
|
56
|
+
* `telegram __gateway` subcommand: a compiled binary self-spawns the bundled
|
|
57
|
+
* gateway entrypoint, and a source run re-executes cli.ts under bun. */
|
|
58
|
+
function resolveGatewayCommand(): { cmd: string; args: string[] } {
|
|
59
|
+
const rt = resolveSkcRuntimeSpawnInfo();
|
|
60
|
+
return { cmd: rt.execPath, args: [...rt.argsPrefix, "telegram", "__gateway"] };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async function runStart(): Promise<void> {
|
|
64
|
+
const config = readTelegramSettings();
|
|
65
|
+
|
|
66
|
+
if (!config.enabled) {
|
|
67
|
+
process.stderr.write(
|
|
68
|
+
"Telegram Remote is disabled. Enable it in Settings → Integrations, or set telegram.enabled.\n",
|
|
69
|
+
);
|
|
70
|
+
process.exit(1);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const errors = validateTelegramSettings(config);
|
|
74
|
+
if (errors.length > 0) {
|
|
75
|
+
process.stderr.write("Telegram configuration is incomplete:\n");
|
|
76
|
+
for (const err of errors) process.stderr.write(` • ${err}\n`);
|
|
77
|
+
process.exit(1);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const env = telegramSettingsToEnv(config);
|
|
81
|
+
const { cmd, args } = resolveGatewayCommand();
|
|
82
|
+
|
|
83
|
+
logger.info("telegram-remote starting", { cmd });
|
|
84
|
+
|
|
85
|
+
const proc = Bun.spawn([cmd, ...args], {
|
|
86
|
+
env: { ...process.env, ...env },
|
|
87
|
+
stdio: ["inherit", "inherit", "inherit"],
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
// Forward termination signals to the child.
|
|
91
|
+
const forward = (sig: NodeJS.Signals): void => void proc.kill(sig);
|
|
92
|
+
process.on("SIGINT", forward);
|
|
93
|
+
process.on("SIGTERM", forward);
|
|
94
|
+
|
|
95
|
+
const code = await proc.exited;
|
|
96
|
+
process.off("SIGINT", forward);
|
|
97
|
+
process.off("SIGTERM", forward);
|
|
98
|
+
process.exit(code);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async function runStatus(flags: TelegramCommandArgs["flags"]): Promise<void> {
|
|
102
|
+
const config = readTelegramSettings();
|
|
103
|
+
const errors = config.enabled ? validateTelegramSettings(config) : [];
|
|
104
|
+
|
|
105
|
+
if (flags.json) {
|
|
106
|
+
process.stdout.write(`${JSON.stringify({ enabled: config.enabled, valid: errors.length === 0, errors })}\n`);
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
process.stdout.write(`Telegram Remote: ${config.enabled ? "enabled" : "disabled"}\n`);
|
|
111
|
+
process.stdout.write(`Backend: ${config.backend}\n`);
|
|
112
|
+
|
|
113
|
+
if (config.botToken) process.stdout.write(`Bot Token: ✓ set\n`);
|
|
114
|
+
else process.stdout.write(`Bot Token: ✗ missing\n`);
|
|
115
|
+
|
|
116
|
+
if (config.allowedUserIds || config.allowedChatIds) {
|
|
117
|
+
process.stdout.write(`Allowlist: ✓ configured\n`);
|
|
118
|
+
} else {
|
|
119
|
+
process.stdout.write(`Allowlist: ✗ missing\n`);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (errors.length > 0) {
|
|
123
|
+
process.stdout.write("\nConfiguration issues:\n");
|
|
124
|
+
for (const err of errors) process.stdout.write(` • ${err}\n`);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
async function runEnv(): Promise<void> {
|
|
129
|
+
const config = readTelegramSettings();
|
|
130
|
+
const env = telegramSettingsToEnv(config);
|
|
131
|
+
for (const [key, value] of Object.entries(env)) {
|
|
132
|
+
process.stdout.write(`${key}=${value}\n`);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export async function runTelegramCommand(cmd: TelegramCommandArgs): Promise<void> {
|
|
137
|
+
if (!isSettingsInitialized()) await Settings.init();
|
|
138
|
+
switch (cmd.action) {
|
|
139
|
+
case "start":
|
|
140
|
+
await runStart();
|
|
141
|
+
return;
|
|
142
|
+
case "status":
|
|
143
|
+
await runStatus(cmd.flags);
|
|
144
|
+
return;
|
|
145
|
+
case "env":
|
|
146
|
+
await runEnv();
|
|
147
|
+
return;
|
|
148
|
+
case "__gateway": {
|
|
149
|
+
// Hidden entrypoint: run the Telegram Remote gateway in-process. Reached
|
|
150
|
+
// only via the self-spawn from runStart()/autostart, never by users.
|
|
151
|
+
const { loadConfigFromEnv, runService } = await import("../../../telegram-remote/src/index");
|
|
152
|
+
await runService(loadConfigFromEnv(process.env));
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
package/src/cli/update-cli.ts
CHANGED
|
@@ -10,10 +10,13 @@ import { pipeline } from "node:stream/promises";
|
|
|
10
10
|
import { $which, APP_NAME, isEnoent, VERSION } from "@sayknow-cli/utils";
|
|
11
11
|
import { $ } from "bun";
|
|
12
12
|
import chalk from "chalk";
|
|
13
|
+
import { installDefaultSkcDefinitions } from "../defaults/skc-defaults";
|
|
13
14
|
import { theme } from "../modes/theme/theme";
|
|
14
15
|
|
|
15
16
|
const RELEASE_REPO = "jaybeyond/Sayknow_CLI";
|
|
16
17
|
const PACKAGE = "@sayknow-cli/coding-agent";
|
|
18
|
+
const NPM_WRAPPER_PACKAGE = "sayknow-cli";
|
|
19
|
+
const NPM_MANAGED_PACKAGES = [NPM_WRAPPER_PACKAGE, PACKAGE] as const;
|
|
17
20
|
|
|
18
21
|
interface ReleaseInfo {
|
|
19
22
|
tag: string;
|
|
@@ -27,6 +30,23 @@ export interface InstalledVersionVerification {
|
|
|
27
30
|
path?: string;
|
|
28
31
|
smokeTestFailed?: boolean;
|
|
29
32
|
smokeTestOutput?: string;
|
|
33
|
+
cleanupWarning?: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface PackageManagerUpdateResult {
|
|
37
|
+
exitCode: number | null;
|
|
38
|
+
text: () => string;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export type PackageManagerUpdateRunner = (expectedVersion: string) => Promise<PackageManagerUpdateResult>;
|
|
42
|
+
|
|
43
|
+
export interface PackageManagerUpdateOptions {
|
|
44
|
+
managerName: string;
|
|
45
|
+
expectedVersion: string;
|
|
46
|
+
runInstall: PackageManagerUpdateRunner;
|
|
47
|
+
verifyInstalledRuntime: (expectedVersion: string) => Promise<InstalledVersionVerification>;
|
|
48
|
+
printVerificationResult?: (expectedVersion: string) => Promise<void>;
|
|
49
|
+
printRecoveredVerification?: (expectedVersion: string) => void;
|
|
30
50
|
}
|
|
31
51
|
|
|
32
52
|
/** Paths and verifier used while replacing a downloaded binary update. */
|
|
@@ -102,23 +122,76 @@ function isPathInDirectory(filePath: string, directoryPath: string): boolean {
|
|
|
102
122
|
return isPathInDirectoryLexical(resolvedFile, dirReal);
|
|
103
123
|
}
|
|
104
124
|
|
|
105
|
-
type
|
|
125
|
+
type PackageManagerTarget = { manager: "npm"; packageName: string };
|
|
126
|
+
type UpdateTarget = { method: "bun" } | { method: "npm"; packageName: string } | { method: "binary"; path: string };
|
|
127
|
+
|
|
128
|
+
type PathPlatform = NodeJS.Platform;
|
|
129
|
+
type PackageExists = (packageName: string, packageRoot: string) => boolean;
|
|
130
|
+
|
|
131
|
+
function pathApiForPlatform(platform: PathPlatform): typeof path.posix | typeof path.win32 {
|
|
132
|
+
return platform === "win32" ? path.win32 : path.posix;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function defaultPackageExists(_packageName: string, packageRoot: string): boolean {
|
|
136
|
+
return fs.existsSync(path.join(packageRoot, "package.json"));
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function npmPackageRootForBinPath(binPath: string, packageName: string, platform: PathPlatform): string {
|
|
140
|
+
const pathApi = pathApiForPlatform(platform);
|
|
141
|
+
const segments = packageName.split("/");
|
|
142
|
+
return pathApi.join(pathApi.dirname(binPath), "node_modules", ...segments);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function resolveNpmManagedTarget(
|
|
146
|
+
ompPath: string,
|
|
147
|
+
platform: PathPlatform = process.platform,
|
|
148
|
+
packageExists: PackageExists = defaultPackageExists,
|
|
149
|
+
): PackageManagerTarget | undefined {
|
|
150
|
+
if (platform !== "win32") return undefined;
|
|
151
|
+
const pathApi = pathApiForPlatform(platform);
|
|
152
|
+
const extension = pathApi.extname(ompPath).toLowerCase();
|
|
153
|
+
if (extension !== ".cmd" && extension !== ".ps1") return undefined;
|
|
154
|
+
const basename = pathApi.basename(ompPath, extension).toLowerCase();
|
|
155
|
+
if (basename !== APP_NAME.toLowerCase()) return undefined;
|
|
156
|
+
|
|
157
|
+
for (const packageName of NPM_MANAGED_PACKAGES) {
|
|
158
|
+
const packageRoot = npmPackageRootForBinPath(ompPath, packageName, platform);
|
|
159
|
+
if (packageExists(packageName, packageRoot)) return { manager: "npm", packageName };
|
|
160
|
+
}
|
|
161
|
+
return undefined;
|
|
162
|
+
}
|
|
106
163
|
|
|
107
|
-
function resolveUpdateMethod(ompPath: string, bunBinDir: string | undefined): "bun" | "binary" {
|
|
164
|
+
function resolveUpdateMethod(ompPath: string, bunBinDir: string | undefined): "bun" | "npm" | "binary" {
|
|
165
|
+
if (resolveNpmManagedTarget(ompPath)) return "npm";
|
|
108
166
|
if (!bunBinDir) return "binary";
|
|
109
167
|
return isPathInDirectory(ompPath, bunBinDir) ? "bun" : "binary";
|
|
110
168
|
}
|
|
111
169
|
|
|
112
|
-
export function resolveUpdateMethodForTest(ompPath: string, bunBinDir: string | undefined): "bun" | "binary" {
|
|
170
|
+
export function resolveUpdateMethodForTest(ompPath: string, bunBinDir: string | undefined): "bun" | "npm" | "binary" {
|
|
113
171
|
return resolveUpdateMethod(ompPath, bunBinDir);
|
|
114
172
|
}
|
|
173
|
+
|
|
174
|
+
export function resolveNpmManagedTargetForTest(
|
|
175
|
+
ompPath: string,
|
|
176
|
+
platform: PathPlatform,
|
|
177
|
+
packageExists: PackageExists,
|
|
178
|
+
): PackageManagerTarget | undefined {
|
|
179
|
+
return resolveNpmManagedTarget(ompPath, platform, packageExists);
|
|
180
|
+
}
|
|
115
181
|
async function resolveUpdateTarget(): Promise<UpdateTarget> {
|
|
116
182
|
const bunBinDir = await getBunGlobalBinDir();
|
|
117
183
|
const ompPath = resolveSkcPath();
|
|
118
184
|
|
|
119
185
|
if (ompPath) {
|
|
186
|
+
const npmTarget = resolveNpmManagedTarget(ompPath);
|
|
187
|
+
if (npmTarget) return { method: "npm", packageName: npmTarget.packageName };
|
|
120
188
|
const method = resolveUpdateMethod(ompPath, bunBinDir);
|
|
121
189
|
if (method === "bun") return { method };
|
|
190
|
+
if (method === "npm") {
|
|
191
|
+
throw new Error(
|
|
192
|
+
formatUnsupportedTargetMessage(`Could not resolve npm package root for ${APP_NAME} shim ${ompPath}`),
|
|
193
|
+
);
|
|
194
|
+
}
|
|
122
195
|
return { method, path: ompPath };
|
|
123
196
|
}
|
|
124
197
|
|
|
@@ -262,6 +335,11 @@ function printVerifiedVersion(expectedVersion: string): void {
|
|
|
262
335
|
console.log(chalk.green(`\n${theme.status.success} Updated to ${expectedVersion}`));
|
|
263
336
|
}
|
|
264
337
|
|
|
338
|
+
function printSuccessfulVerification(expectedVersion: string): void {
|
|
339
|
+
printVerifiedVersion(expectedVersion);
|
|
340
|
+
printRestartGuidance();
|
|
341
|
+
}
|
|
342
|
+
|
|
265
343
|
function formatBinaryInstallInstruction(platform: NodeJS.Platform = process.platform): string {
|
|
266
344
|
if (platform === "win32") {
|
|
267
345
|
return `For a supported binary install, reinstall with PowerShell: irm https://raw.githubusercontent.com/${RELEASE_REPO}/main/scripts/install.ps1 | iex`;
|
|
@@ -351,8 +429,7 @@ export function formatVerificationFailureForTest(
|
|
|
351
429
|
async function printVerification(expectedVersion: string): Promise<void> {
|
|
352
430
|
const result = await verifyInstalledRuntime(expectedVersion);
|
|
353
431
|
if (result.ok) {
|
|
354
|
-
|
|
355
|
-
printRestartGuidance();
|
|
432
|
+
printSuccessfulVerification(expectedVersion);
|
|
356
433
|
return;
|
|
357
434
|
}
|
|
358
435
|
console.log(chalk.yellow(`\nWarning: ${formatVerificationFailure(result, expectedVersion)}`));
|
|
@@ -367,6 +444,19 @@ async function unlinkIfExists(filePath: string): Promise<void> {
|
|
|
367
444
|
}
|
|
368
445
|
}
|
|
369
446
|
|
|
447
|
+
function formatBackupCleanupWarning(backupPath: string, err: unknown): string {
|
|
448
|
+
return `Installed update, but could not remove backup file ${backupPath}: ${err}. You can delete it manually after closing shells or antivirus processes that may still hold it.`;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
async function cleanupVerifiedBackup(backupPath: string): Promise<string | undefined> {
|
|
452
|
+
try {
|
|
453
|
+
await unlinkIfExists(backupPath);
|
|
454
|
+
return undefined;
|
|
455
|
+
} catch (err) {
|
|
456
|
+
return formatBackupCleanupWarning(backupPath, err);
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
|
|
370
460
|
/**
|
|
371
461
|
* Atomically replace the installed binary and roll back if version verification fails.
|
|
372
462
|
*/
|
|
@@ -386,8 +476,8 @@ export async function replaceBinaryForUpdate(options: BinaryReplacementOptions):
|
|
|
386
476
|
}
|
|
387
477
|
|
|
388
478
|
backupReady = false;
|
|
389
|
-
await
|
|
390
|
-
return verification;
|
|
479
|
+
const cleanupWarning = await cleanupVerifiedBackup(options.backupPath);
|
|
480
|
+
return cleanupWarning ? { ...verification, cleanupWarning } : verification;
|
|
391
481
|
} catch (err) {
|
|
392
482
|
if (backupReady) {
|
|
393
483
|
await unlinkIfExists(options.targetPath);
|
|
@@ -398,17 +488,67 @@ export async function replaceBinaryForUpdate(options: BinaryReplacementOptions):
|
|
|
398
488
|
}
|
|
399
489
|
}
|
|
400
490
|
|
|
491
|
+
function formatPackageManagerInstallFailure(
|
|
492
|
+
managerName: string,
|
|
493
|
+
result: PackageManagerUpdateResult,
|
|
494
|
+
verification: InstalledVersionVerification,
|
|
495
|
+
expectedVersion: string,
|
|
496
|
+
): string {
|
|
497
|
+
const output = normalizeVerificationOutput(result.text());
|
|
498
|
+
const outputSuffix = output ? `: ${output}` : "";
|
|
499
|
+
return `${managerName} install failed with exit code ${result.exitCode ?? "unknown"}${outputSuffix}. ${formatVerificationFailure(verification, expectedVersion)}`;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
export async function runPackageManagerUpdateForTest(
|
|
503
|
+
options: PackageManagerUpdateOptions,
|
|
504
|
+
): Promise<InstalledVersionVerification> {
|
|
505
|
+
return updateViaPackageManager(options);
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
async function updateViaPackageManager(options: PackageManagerUpdateOptions): Promise<InstalledVersionVerification> {
|
|
509
|
+
const result = await options.runInstall(options.expectedVersion);
|
|
510
|
+
if (result.exitCode === 0) {
|
|
511
|
+
await (options.printVerificationResult ?? printVerification)(options.expectedVersion);
|
|
512
|
+
return await options.verifyInstalledRuntime(options.expectedVersion);
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
const verification = await options.verifyInstalledRuntime(options.expectedVersion);
|
|
516
|
+
if (verification.ok) {
|
|
517
|
+
console.warn(
|
|
518
|
+
chalk.yellow(
|
|
519
|
+
`${options.managerName} exited with ${result.exitCode ?? "unknown"}, but ${APP_NAME} now verifies as ${options.expectedVersion}. Treating the update as installed.`,
|
|
520
|
+
),
|
|
521
|
+
);
|
|
522
|
+
(options.printRecoveredVerification ?? printSuccessfulVerification)(options.expectedVersion);
|
|
523
|
+
return verification;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
throw new Error(
|
|
527
|
+
formatPackageManagerInstallFailure(options.managerName, result, verification, options.expectedVersion),
|
|
528
|
+
);
|
|
529
|
+
}
|
|
530
|
+
|
|
401
531
|
/**
|
|
402
532
|
* Update via bun package manager.
|
|
403
533
|
*/
|
|
404
534
|
async function updateViaBun(expectedVersion: string): Promise<void> {
|
|
405
535
|
console.log(chalk.dim("Updating via bun..."));
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
536
|
+
await updateViaPackageManager({
|
|
537
|
+
managerName: "bun",
|
|
538
|
+
expectedVersion,
|
|
539
|
+
runInstall: async version => await $`bun install -g ${PACKAGE}@${version}`.nothrow(),
|
|
540
|
+
verifyInstalledRuntime,
|
|
541
|
+
});
|
|
542
|
+
}
|
|
410
543
|
|
|
411
|
-
|
|
544
|
+
async function updateViaNpm(packageName: string, expectedVersion: string): Promise<void> {
|
|
545
|
+
console.log(chalk.dim(`Updating npm-managed install via npm (${packageName})...`));
|
|
546
|
+
await updateViaPackageManager({
|
|
547
|
+
managerName: "npm",
|
|
548
|
+
expectedVersion,
|
|
549
|
+
runInstall: async version => await $`npm install -g ${packageName}@${version}`.nothrow(),
|
|
550
|
+
verifyInstalledRuntime,
|
|
551
|
+
});
|
|
412
552
|
}
|
|
413
553
|
|
|
414
554
|
/**
|
|
@@ -430,7 +570,7 @@ async function updateViaBinaryAt(targetPath: string, expectedVersion: string): P
|
|
|
430
570
|
await pipeline(response.body, fileStream);
|
|
431
571
|
|
|
432
572
|
console.log(chalk.dim("Installing update..."));
|
|
433
|
-
await replaceBinaryForUpdate({
|
|
573
|
+
const verification = await replaceBinaryForUpdate({
|
|
434
574
|
targetPath,
|
|
435
575
|
tempPath,
|
|
436
576
|
backupPath,
|
|
@@ -438,6 +578,7 @@ async function updateViaBinaryAt(targetPath: string, expectedVersion: string): P
|
|
|
438
578
|
verifyInstalledVersion: verifyInstalledRuntime,
|
|
439
579
|
});
|
|
440
580
|
printVerifiedVersion(expectedVersion);
|
|
581
|
+
if (verification.cleanupWarning) console.warn(chalk.yellow(verification.cleanupWarning));
|
|
441
582
|
printRestartGuidance();
|
|
442
583
|
}
|
|
443
584
|
|
|
@@ -479,6 +620,8 @@ export async function runUpdateCommand(opts: { force: boolean; check: boolean })
|
|
|
479
620
|
const target = await resolveUpdateTarget();
|
|
480
621
|
if (target.method === "bun") {
|
|
481
622
|
await updateViaBun(release.version);
|
|
623
|
+
} else if (target.method === "npm") {
|
|
624
|
+
await updateViaNpm(target.packageName, release.version);
|
|
482
625
|
} else {
|
|
483
626
|
await updateViaBinaryAt(target.path, release.version);
|
|
484
627
|
}
|
|
@@ -486,6 +629,29 @@ export async function runUpdateCommand(opts: { force: boolean; check: boolean })
|
|
|
486
629
|
console.error(chalk.red(`Update failed: ${err}`));
|
|
487
630
|
process.exit(1);
|
|
488
631
|
}
|
|
632
|
+
|
|
633
|
+
await refreshInstalledDefaultSkills();
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
/**
|
|
637
|
+
* Refresh opted-in on-disk default workflow skill copies after a successful
|
|
638
|
+
* update. The four default skills ship embedded in the binary, so most users
|
|
639
|
+
* need nothing here. But users who ran `skc setup defaults` have on-disk copies
|
|
640
|
+
* under the agent dir that shadow the embedded defaults; those would otherwise
|
|
641
|
+
* go stale after an update. Only rewrite files that already exist and differ —
|
|
642
|
+
* never materialize new copies for users who never opted in.
|
|
643
|
+
*/
|
|
644
|
+
async function refreshInstalledDefaultSkills(): Promise<void> {
|
|
645
|
+
try {
|
|
646
|
+
const result = await installDefaultSkcDefinitions({ refreshOnly: true });
|
|
647
|
+
if (result.written > 0) {
|
|
648
|
+
console.log(
|
|
649
|
+
chalk.dim(`Refreshed ${result.written} local default workflow skill file(s) at ${result.targetRoot}`),
|
|
650
|
+
);
|
|
651
|
+
}
|
|
652
|
+
} catch (err) {
|
|
653
|
+
console.error(chalk.yellow(`Warning: failed to refresh local default workflow skills: ${err}`));
|
|
654
|
+
}
|
|
489
655
|
}
|
|
490
656
|
|
|
491
657
|
/**
|
|
@@ -9,13 +9,14 @@ import chalk from "chalk";
|
|
|
9
9
|
import { Settings } from "../config/settings";
|
|
10
10
|
import { initTheme, theme } from "../modes/theme/theme";
|
|
11
11
|
import {
|
|
12
|
+
getConfiguredSearchProviderPreference,
|
|
12
13
|
isConfigurableSearchProviderId,
|
|
13
14
|
isSearchProviderPreference,
|
|
14
15
|
runSearchQuery,
|
|
15
16
|
type SearchQueryParams,
|
|
16
17
|
} from "../web/search/index";
|
|
17
18
|
import { SEARCH_PROVIDER_ORDER, setPreferredSearchProvider, setSearchFallbackProviders } from "../web/search/provider";
|
|
18
|
-
import {
|
|
19
|
+
import { applyConfiguredSearchTimeout } from "../web/search/providers/utils";
|
|
19
20
|
import { renderSearchResult } from "../web/search/render";
|
|
20
21
|
import type { SearchProviderId } from "../web/search/types";
|
|
21
22
|
|
|
@@ -170,8 +171,8 @@ export async function runSearchCommand(cmd: SearchCommandArgs): Promise<void> {
|
|
|
170
171
|
|
|
171
172
|
await initTheme();
|
|
172
173
|
const settings = await Settings.init();
|
|
173
|
-
const configuredProvider = settings
|
|
174
|
-
if (
|
|
174
|
+
const configuredProvider = getConfiguredSearchProviderPreference(settings);
|
|
175
|
+
if (isSearchProviderPreference(configuredProvider)) {
|
|
175
176
|
setPreferredSearchProvider(configuredProvider);
|
|
176
177
|
}
|
|
177
178
|
const configuredFallback = settings.get("web_search.fallback");
|
|
@@ -180,10 +181,7 @@ export async function runSearchCommand(cmd: SearchCommandArgs): Promise<void> {
|
|
|
180
181
|
configuredFallback.filter(value => typeof value === "string" && isConfigurableSearchProviderId(value)),
|
|
181
182
|
);
|
|
182
183
|
}
|
|
183
|
-
|
|
184
|
-
if (typeof configuredTimeout === "number" && Number.isFinite(configuredTimeout) && configuredTimeout > 0) {
|
|
185
|
-
setSearchHardTimeoutMs(configuredTimeout * 1000);
|
|
186
|
-
}
|
|
184
|
+
applyConfiguredSearchTimeout(settings);
|
|
187
185
|
|
|
188
186
|
const params: SearchQueryParams = {
|
|
189
187
|
query: cmd.query,
|
package/src/cli.ts
CHANGED
|
@@ -38,6 +38,7 @@ export const commands: CommandEntry[] = [
|
|
|
38
38
|
{ name: "notify", load: () => import("./commands/notify").then(m => m.default) },
|
|
39
39
|
{ name: "daemon", load: () => import("./commands/daemon").then(m => m.default) },
|
|
40
40
|
{ name: "web-search", aliases: ["q"], load: () => import("./commands/web-search").then(m => m.default) },
|
|
41
|
+
{ name: "local-provider", load: () => import("./commands/local-provider").then(m => m.default) },
|
|
41
42
|
{ name: "mcp-serve", load: () => import("./commands/mcp-serve").then(m => m.default) },
|
|
42
43
|
{ name: "mcp", load: () => import("./commands/mcp").then(m => m.default) },
|
|
43
44
|
{
|
|
@@ -51,6 +52,7 @@ export const commands: CommandEntry[] = [
|
|
|
51
52
|
{ name: "update", load: () => import("./commands/update").then(m => m.default) },
|
|
52
53
|
{ name: "plugin", load: () => import("./commands/plugin").then(m => m.default) },
|
|
53
54
|
{ name: "launch", load: () => import("./commands/launch").then(m => m.default) },
|
|
55
|
+
{ name: "telegram", load: () => import("./commands/telegram").then(m => m.default) },
|
|
54
56
|
];
|
|
55
57
|
|
|
56
58
|
async function showHelp(config: CliConfig): Promise<void> {
|
|
@@ -79,6 +81,19 @@ async function installRuntimeGlobals(): Promise<void> {
|
|
|
79
81
|
delete process.env.MallocStackLoggingNoCompact;
|
|
80
82
|
}
|
|
81
83
|
|
|
84
|
+
function isNotifyDaemonInternalFastPath(argv: string[]): boolean {
|
|
85
|
+
return argv[0] === "notify" && argv[1] === "daemon-internal";
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async function runNotifyDaemonInternalFastPath(argv: string[]): Promise<void> {
|
|
89
|
+
const { parseNotifyArgs, runNotifyCommand } = await import("./cli/notify-cli");
|
|
90
|
+
const cmd = parseNotifyArgs(argv);
|
|
91
|
+
if (cmd?.action !== "daemon-internal") {
|
|
92
|
+
throw new Error("invalid notify daemon-internal fast path");
|
|
93
|
+
}
|
|
94
|
+
await runNotifyCommand(cmd);
|
|
95
|
+
}
|
|
96
|
+
|
|
82
97
|
function hasRootFastFlag(argv: string[], flags: readonly string[]): boolean {
|
|
83
98
|
for (const arg of argv) {
|
|
84
99
|
if (isSubcommand(arg)) return false;
|
|
@@ -126,7 +141,7 @@ class RootHelpCommand extends Command {
|
|
|
126
141
|
resume: Flags.string({ char: "r", description: "Resume a session (by ID prefix, path, or picker if omitted)" }),
|
|
127
142
|
"session-dir": Flags.string({ description: "Directory for session storage and lookup" }),
|
|
128
143
|
"no-session": Flags.boolean({ description: "Don't save session (ephemeral)" }),
|
|
129
|
-
models: Flags.string({ description: "Comma-separated model patterns for
|
|
144
|
+
models: Flags.string({ description: "Comma-separated model patterns for Alt+N cycling" }),
|
|
130
145
|
"no-tools": Flags.boolean({ description: "Disable all built-in tools" }),
|
|
131
146
|
"no-lsp": Flags.boolean({ description: "Disable LSP tools, formatting, and diagnostics" }),
|
|
132
147
|
"no-pty": Flags.boolean({ description: "Disable PTY-based interactive bash execution" }),
|
|
@@ -208,6 +223,10 @@ async function runSmokeTest(): Promise<void> {
|
|
|
208
223
|
|
|
209
224
|
/** Run the CLI with the given argv (no `process.argv` prefix). */
|
|
210
225
|
export async function runCli(argv: string[]): Promise<void> {
|
|
226
|
+
if (isNotifyDaemonInternalFastPath(argv)) {
|
|
227
|
+
await runNotifyDaemonInternalFastPath(argv);
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
211
230
|
if (argv[0] === "--smoke-test") {
|
|
212
231
|
await runSmokeTest();
|
|
213
232
|
return;
|
package/src/commands/launch.ts
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
* Root command for the coding agent CLI.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
+
import * as fs from "node:fs/promises";
|
|
6
|
+
import * as path from "node:path";
|
|
5
7
|
import { THINKING_EFFORTS } from "@sayknow-cli/ai";
|
|
6
8
|
import { APP_NAME, setProjectDir } from "@sayknow-cli/utils";
|
|
7
9
|
import { Args, Command, Flags } from "@sayknow-cli/utils/cli";
|
|
@@ -10,6 +12,43 @@ import { runRootCommand } from "../main";
|
|
|
10
12
|
import { prepareAcpTerminalAuthArgs } from "../modes/acp/terminal-auth";
|
|
11
13
|
import { launchDefaultTmuxIfNeeded } from "../skc-runtime/launch-tmux";
|
|
12
14
|
import { prepareLaunchWorktree } from "../skc-runtime/launch-worktree";
|
|
15
|
+
import {
|
|
16
|
+
SKC_COORDINATOR_SESSION_ID_ENV,
|
|
17
|
+
SKC_COORDINATOR_SESSION_STATE_FILE_ENV,
|
|
18
|
+
} from "../skc-runtime/session-state-sidecar";
|
|
19
|
+
|
|
20
|
+
async function persistCoordinatorLaunchFailure(error: unknown, cwd: string): Promise<void> {
|
|
21
|
+
const stateFile = process.env[SKC_COORDINATOR_SESSION_STATE_FILE_ENV]?.trim();
|
|
22
|
+
if (!stateFile) return;
|
|
23
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
24
|
+
const code = message.split(":", 1)[0] || "launch_failed";
|
|
25
|
+
const now = new Date().toISOString();
|
|
26
|
+
const payload = {
|
|
27
|
+
schema_version: 1,
|
|
28
|
+
session_id: process.env[SKC_COORDINATOR_SESSION_ID_ENV]?.trim() || null,
|
|
29
|
+
state: "errored",
|
|
30
|
+
ready_for_input: false,
|
|
31
|
+
updated_at: now,
|
|
32
|
+
current_turn_id: null,
|
|
33
|
+
last_turn_id: null,
|
|
34
|
+
live: false,
|
|
35
|
+
reason: code,
|
|
36
|
+
source: "agent_session_event",
|
|
37
|
+
event: "launch_error",
|
|
38
|
+
cwd,
|
|
39
|
+
session_file: null,
|
|
40
|
+
final_response: {
|
|
41
|
+
text: message,
|
|
42
|
+
format: "markdown",
|
|
43
|
+
source: "launch_error",
|
|
44
|
+
artifact_path: null,
|
|
45
|
+
truncated: false,
|
|
46
|
+
},
|
|
47
|
+
error: { code, message, recoverable: true },
|
|
48
|
+
};
|
|
49
|
+
await fs.mkdir(path.dirname(stateFile), { recursive: true });
|
|
50
|
+
await Bun.write(stateFile, `${JSON.stringify(payload, null, 2)}\n`);
|
|
51
|
+
}
|
|
13
52
|
|
|
14
53
|
export default class Index extends Command {
|
|
15
54
|
static description = "Sayknow-CLI — an AI coding assistant";
|
|
@@ -80,7 +119,7 @@ export default class Index extends Command {
|
|
|
80
119
|
description: "Don't save session (ephemeral)",
|
|
81
120
|
}),
|
|
82
121
|
models: Flags.string({
|
|
83
|
-
description: "Comma-separated model patterns for
|
|
122
|
+
description: "Comma-separated model patterns for Alt+N cycling",
|
|
84
123
|
}),
|
|
85
124
|
"no-tools": Flags.boolean({
|
|
86
125
|
description: "Disable all built-in tools",
|
|
@@ -157,7 +196,13 @@ export default class Index extends Command {
|
|
|
157
196
|
return;
|
|
158
197
|
}
|
|
159
198
|
|
|
160
|
-
|
|
199
|
+
let launch: ReturnType<typeof prepareLaunchWorktree>;
|
|
200
|
+
try {
|
|
201
|
+
launch = prepareLaunchWorktree(process.cwd(), args);
|
|
202
|
+
} catch (error) {
|
|
203
|
+
await persistCoordinatorLaunchFailure(error, process.cwd());
|
|
204
|
+
throw error;
|
|
205
|
+
}
|
|
161
206
|
if (launch.worktree.enabled) {
|
|
162
207
|
process.chdir(launch.cwd);
|
|
163
208
|
setProjectDir(launch.cwd);
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test configured local OpenAI-compatible providers.
|
|
3
|
+
*/
|
|
4
|
+
import { Args, Command, Flags } from "@sayknow-cli/utils/cli";
|
|
5
|
+
import {
|
|
6
|
+
runLocalProviderDiscoverCommand,
|
|
7
|
+
runLocalProviderSmokeCommand,
|
|
8
|
+
runLocalProviderStatusCommand,
|
|
9
|
+
} from "../cli/local-provider-smoke";
|
|
10
|
+
|
|
11
|
+
export const LOCAL_PROVIDER_ACTIONS = ["status", "diagnose", "discover", "models", "smoke"] as const;
|
|
12
|
+
export const LOCAL_PROVIDER_DEFAULT_ACTION = "status";
|
|
13
|
+
|
|
14
|
+
export default class LocalProvider extends Command {
|
|
15
|
+
static description = "Diagnose configured local OpenAI-compatible providers";
|
|
16
|
+
|
|
17
|
+
static args = {
|
|
18
|
+
action: Args.string({ description: "Action", required: false, options: LOCAL_PROVIDER_ACTIONS }),
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
static flags = {
|
|
22
|
+
model: Flags.string({ description: "Model id to use for smoke (otherwise uses the first /models id)" }),
|
|
23
|
+
"models-path": Flags.string({ description: "Override models config path" }),
|
|
24
|
+
"timeout-ms": Flags.integer({ description: "Request timeout in milliseconds" }),
|
|
25
|
+
smoke: Flags.boolean({ description: "Run optional streaming chat smoke during status/diagnose" }),
|
|
26
|
+
json: Flags.boolean({ description: "Output JSON" }),
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
async run(): Promise<void> {
|
|
30
|
+
const { args, flags } = await this.parse(LocalProvider);
|
|
31
|
+
const action = args.action ?? LOCAL_PROVIDER_DEFAULT_ACTION;
|
|
32
|
+
if (action === "status" || action === "diagnose") {
|
|
33
|
+
await runLocalProviderStatusCommand({
|
|
34
|
+
model: flags.model,
|
|
35
|
+
modelsPath: flags["models-path"],
|
|
36
|
+
timeoutMs: flags["timeout-ms"],
|
|
37
|
+
smoke: flags.smoke,
|
|
38
|
+
json: flags.json,
|
|
39
|
+
});
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
if (action === "discover" || action === "models") {
|
|
43
|
+
await runLocalProviderDiscoverCommand({
|
|
44
|
+
modelsPath: flags["models-path"],
|
|
45
|
+
timeoutMs: flags["timeout-ms"],
|
|
46
|
+
json: flags.json,
|
|
47
|
+
});
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
if (action === "smoke") {
|
|
51
|
+
await runLocalProviderSmokeCommand({
|
|
52
|
+
model: flags.model,
|
|
53
|
+
modelsPath: flags["models-path"],
|
|
54
|
+
timeoutMs: flags["timeout-ms"],
|
|
55
|
+
json: flags.json,
|
|
56
|
+
});
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
process.stderr.write(`Unsupported local-provider action: ${action}\n`);
|
|
60
|
+
process.exitCode = 1;
|
|
61
|
+
}
|
|
62
|
+
}
|