@sayknow-cli/coding-agent 0.3.1 → 0.3.4
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/bin/skc.js +4 -0
- package/dist/types/cli/mcp-cli.d.ts +25 -0
- package/dist/types/cli/plugin-cli.d.ts +2 -0
- package/dist/types/cli.d.ts +6 -0
- package/dist/types/commands/mcp.d.ts +70 -0
- package/dist/types/commands/plugin.d.ts +6 -0
- package/dist/types/commands/session.d.ts +6 -0
- package/dist/types/config/keybindings.d.ts +2 -2
- package/dist/types/config/model-profile-activation.d.ts +8 -1
- package/dist/types/config/model-profiles.d.ts +2 -2
- package/dist/types/config/model-registry.d.ts +3 -3
- package/dist/types/config/models-config-schema.d.ts +0 -5
- package/dist/types/config/settings-schema.d.ts +79 -68
- package/dist/types/deep-interview/plaintext-gate-guard.d.ts +11 -0
- package/dist/types/export/html/template.generated.d.ts +1 -1
- package/dist/types/extensibility/skc-plugins/compiler.d.ts +19 -0
- package/dist/types/extensibility/skc-plugins/constrained-hooks.d.ts +29 -0
- package/dist/types/extensibility/skc-plugins/index.d.ts +9 -0
- package/dist/types/extensibility/skc-plugins/injection.d.ts +9 -0
- package/dist/types/extensibility/skc-plugins/installer.d.ts +13 -0
- package/dist/types/extensibility/skc-plugins/mcp-policy.d.ts +26 -0
- package/dist/types/extensibility/skc-plugins/observability.d.ts +27 -0
- package/dist/types/extensibility/skc-plugins/prompt-appendix.d.ts +16 -0
- package/dist/types/extensibility/skc-plugins/registry.d.ts +32 -0
- package/dist/types/extensibility/skc-plugins/runtime-adapters.d.ts +64 -0
- package/dist/types/extensibility/skc-plugins/session-validation.d.ts +42 -0
- package/dist/types/extensibility/skc-plugins/types.d.ts +158 -2
- package/dist/types/extensibility/skc-plugins/validation.d.ts +8 -1
- package/dist/types/i18n/messages/en.d.ts +0 -1
- package/dist/types/main.d.ts +2 -0
- package/dist/types/modes/components/custom-editor.d.ts +1 -1
- package/dist/types/modes/components/model-selector.d.ts +8 -0
- package/dist/types/modes/components/settings-defs.d.ts +0 -1
- package/dist/types/modes/components/status-line/git-utils.d.ts +6 -0
- package/dist/types/modes/theme/defaults/index.d.ts +99 -0
- package/dist/types/modes/theme/theme.d.ts +1 -1
- package/dist/types/notifications/html-format.d.ts +11 -0
- package/dist/types/notifications/index.d.ts +149 -1
- package/dist/types/notifications/lifecycle-commands.d.ts +60 -0
- package/dist/types/notifications/lifecycle-control-runtime.d.ts +98 -0
- package/dist/types/notifications/lifecycle-orchestrator.d.ts +144 -0
- package/dist/types/notifications/operator-runtime.d.ts +52 -0
- package/dist/types/notifications/rate-limit-pool.d.ts +2 -0
- package/dist/types/notifications/recent-activity.d.ts +35 -0
- package/dist/types/notifications/telegram-daemon.d.ts +114 -16
- package/dist/types/notifications/telegram-reference.d.ts +3 -1
- package/dist/types/notifications/topic-registry.d.ts +12 -9
- package/dist/types/runtime-mcp/types.d.ts +7 -0
- package/dist/types/sdk.d.ts +2 -0
- package/dist/types/session/agent-session.d.ts +14 -4
- package/dist/types/session/blob-store.d.ts +25 -0
- package/dist/types/session/session-manager.d.ts +57 -0
- package/dist/types/skc-runtime/launch-tmux.d.ts +1 -0
- package/dist/types/skc-runtime/psmux-detect.d.ts +78 -0
- package/dist/types/skc-runtime/state-renderer.d.ts +5 -0
- package/dist/types/skc-runtime/team-runtime.d.ts +2 -0
- package/dist/types/skc-runtime/tmux-common.d.ts +30 -2
- package/dist/types/skc-runtime/tmux-sessions.d.ts +18 -0
- package/dist/types/skc-runtime/ultragoal-guard.d.ts +37 -1
- package/dist/types/skc-runtime/ultragoal-runtime.d.ts +80 -0
- package/dist/types/slash-commands/helpers/fast-status-report.d.ts +6 -0
- package/dist/types/system-prompt.d.ts +2 -0
- package/dist/types/task/executor.d.ts +9 -1
- package/dist/types/tools/browser/tab-supervisor.d.ts +31 -0
- package/dist/types/tools/composer-bash-policy.d.ts +14 -0
- package/dist/types/tools/computer-gc.d.ts +23 -0
- package/dist/types/tools/cron.d.ts +36 -61
- package/dist/types/tools/index.d.ts +3 -2
- package/dist/types/tools/resource-gc.d.ts +54 -0
- package/dist/types/utils/changelog.d.ts +1 -0
- package/dist/types/web/insane/url-guard.d.ts +6 -3
- package/dist/types/web/scrapers/types.d.ts +5 -0
- package/dist/types/web/scrapers/utils.d.ts +7 -1
- package/dist/types/web/search/index.d.ts +1 -0
- package/dist/types/web/search/providers/utils.d.ts +11 -4
- package/package.json +11 -9
- package/scripts/g004-tmux-smoke.ts +100 -0
- package/scripts/g005-daemon-smoke.ts +180 -0
- package/scripts/g011-daemon-path-smoke.ts +153 -0
- package/src/cli/args.ts +0 -1
- package/src/cli/fast-help.ts +29 -2
- package/src/cli/mcp-cli.ts +272 -0
- package/src/cli/plugin-cli.ts +66 -3
- package/src/cli/web-search-cli.ts +5 -0
- package/src/cli.ts +30 -12
- package/src/commands/mcp.ts +117 -0
- package/src/commands/plugin.ts +4 -0
- package/src/commands/session.ts +18 -0
- package/src/config/keybindings.ts +2 -2
- package/src/config/model-profile-activation.ts +62 -8
- package/src/config/model-profiles.ts +3 -4
- package/src/config/model-registry.ts +3 -6
- package/src/config/models-config-schema.ts +1 -1
- package/src/config/settings-schema.ts +88 -103
- package/src/deep-interview/plaintext-gate-guard.ts +94 -0
- package/src/defaults/skc/extensions/grok-cli-vendor/biome.json +1 -1
- package/src/defaults/skc/skills/deep-interview/SKILL.md +7 -6
- package/src/defaults/skc/skills/team/SKILL.md +5 -3
- package/src/defaults/skc/skills/ultragoal/SKILL.md +41 -13
- package/src/export/html/index.ts +2 -2
- package/src/export/html/template.generated.ts +1 -1
- package/src/export/html/template.js +0 -12
- package/src/extensibility/extensions/runner.ts +1 -0
- package/src/extensibility/skc-plugins/compiler.ts +351 -0
- package/src/extensibility/skc-plugins/constrained-hooks.ts +170 -0
- package/src/extensibility/skc-plugins/index.ts +9 -0
- package/src/extensibility/skc-plugins/injection.ts +109 -0
- package/src/extensibility/skc-plugins/installer.ts +434 -0
- package/src/extensibility/skc-plugins/loader.ts +3 -1
- package/src/extensibility/skc-plugins/mcp-policy.ts +239 -0
- package/src/extensibility/skc-plugins/observability.ts +84 -0
- package/src/extensibility/skc-plugins/paths.ts +1 -1
- package/src/extensibility/skc-plugins/prompt-appendix.ts +109 -0
- package/src/extensibility/skc-plugins/registry.ts +180 -0
- package/src/extensibility/skc-plugins/runtime-adapters.ts +234 -0
- package/src/extensibility/skc-plugins/schema.ts +250 -20
- package/src/extensibility/skc-plugins/session-validation.ts +147 -0
- package/src/extensibility/skc-plugins/types.ts +199 -3
- package/src/extensibility/skc-plugins/validation.ts +80 -0
- package/src/extensibility/skills.ts +15 -0
- package/src/goals/tools/goal-tool.ts +14 -1
- package/src/hooks/skill-state.ts +57 -0
- package/src/i18n/messages/de.ts +0 -1
- package/src/i18n/messages/en.ts +0 -1
- package/src/i18n/messages/es.ts +0 -1
- package/src/i18n/messages/fr.ts +0 -1
- package/src/i18n/messages/ja.ts +0 -1
- package/src/i18n/messages/ko.ts +0 -1
- package/src/i18n/messages/zh.ts +0 -1
- package/src/internal-urls/docs-index.generated.ts +14 -11
- package/src/main.ts +14 -3
- package/src/modes/bridge/bridge-mode.ts +11 -0
- package/src/modes/components/assistant-message.ts +49 -1
- package/src/modes/components/custom-editor.ts +2 -0
- package/src/modes/components/footer.ts +2 -3
- package/src/modes/components/hook-editor.ts +1 -1
- package/src/modes/components/hook-selector.ts +67 -43
- package/src/modes/components/model-selector.ts +65 -12
- package/src/modes/components/settings-defs.ts +1 -2
- package/src/modes/components/settings-selector.ts +2 -7
- package/src/modes/components/status-line/git-utils.ts +25 -0
- package/src/modes/components/status-line.ts +10 -11
- package/src/modes/components/welcome.ts +2 -3
- package/src/modes/controllers/extension-ui-controller.ts +0 -27
- package/src/modes/controllers/selector-controller.ts +59 -11
- package/src/modes/interactive-mode.ts +4 -1
- package/src/modes/shared/agent-wire/scopes.ts +1 -1
- package/src/modes/theme/defaults/gruvbox-dark.json +99 -0
- package/src/modes/theme/defaults/index.ts +2 -0
- package/src/modes/theme/theme.ts +0 -4
- package/src/modes/utils/hotkeys-markdown.ts +1 -1
- package/src/notifications/html-format.ts +38 -0
- package/src/notifications/index.ts +242 -12
- package/src/notifications/lifecycle-commands.ts +238 -0
- package/src/notifications/lifecycle-control-runtime.ts +405 -0
- package/src/notifications/lifecycle-orchestrator.ts +358 -0
- package/src/notifications/operator-runtime.ts +171 -0
- package/src/notifications/rate-limit-pool.ts +19 -0
- package/src/notifications/recent-activity.ts +132 -0
- package/src/notifications/telegram-daemon.ts +778 -257
- package/src/notifications/telegram-reference.ts +25 -7
- package/src/notifications/topic-registry.ts +23 -9
- package/src/prompts/agents/executor.md +2 -2
- package/src/prompts/system/system-prompt.md +2 -2
- package/src/prompts/tools/cron.md +5 -3
- package/src/prompts/tools/read.md +1 -1
- package/src/runtime-mcp/transports/stdio.ts +38 -4
- package/src/runtime-mcp/types.ts +7 -0
- package/src/sdk.ts +162 -10
- package/src/session/agent-session.ts +210 -74
- package/src/session/blob-store.ts +196 -8
- package/src/session/session-manager.ts +762 -12
- package/src/skc-runtime/launch-tmux.ts +68 -19
- package/src/skc-runtime/psmux-detect.ts +239 -0
- package/src/skc-runtime/state-renderer.ts +13 -0
- package/src/skc-runtime/team-runtime.ts +56 -23
- package/src/skc-runtime/tmux-common.ts +88 -4
- package/src/skc-runtime/tmux-sessions.ts +111 -9
- package/src/skc-runtime/ultragoal-guard.ts +192 -8
- package/src/skc-runtime/ultragoal-runtime.ts +296 -16
- package/src/slash-commands/builtin-registry.ts +23 -3
- package/src/slash-commands/helpers/fast-status-report.ts +13 -3
- package/src/slash-commands/helpers/parse.ts +2 -1
- package/src/system-prompt.ts +9 -0
- package/src/task/executor.ts +31 -7
- package/src/task/index.ts +2 -0
- package/src/tools/ask.ts +5 -1
- package/src/tools/bash.ts +9 -0
- package/src/tools/browser/tab-supervisor.ts +86 -2
- package/src/tools/composer-bash-policy.ts +96 -0
- package/src/tools/computer-gc.ts +66 -0
- package/src/tools/computer.ts +2 -0
- package/src/tools/cron.ts +75 -112
- package/src/tools/fetch.ts +18 -2
- package/src/tools/index.ts +5 -9
- package/src/tools/read.ts +25 -55
- package/src/tools/renderers.ts +0 -2
- package/src/tools/resource-gc.ts +291 -0
- package/src/tools/ultragoal-ask-guard.ts +7 -1
- package/src/utils/changelog.ts +8 -0
- package/src/web/insane/url-guard.ts +18 -14
- package/src/web/scrapers/types.ts +143 -45
- package/src/web/scrapers/utils.ts +70 -19
- package/src/web/search/index.ts +1 -0
- package/src/web/search/providers/utils.ts +22 -5
- package/vendor/insane-search/MANIFEST.json +3 -1
- package/vendor/insane-search/engine/__init__.py +14 -0
- package/vendor/insane-search/engine/content_safety.py +151 -0
- package/vendor/insane-search/engine/fetch_chain.py +32 -0
- package/vendor/insane-search/engine/tests/test_u8.py +216 -0
- package/dist/types/tools/inspect-image-renderer.d.ts +0 -26
- package/dist/types/tools/inspect-image.d.ts +0 -31
- package/src/prompts/tools/inspect-image-system.md +0 -20
- package/src/prompts/tools/inspect-image.md +0 -32
- package/src/tools/inspect-image-renderer.ts +0 -103
- package/src/tools/inspect-image.ts +0 -172
|
@@ -111,5 +111,6 @@ export declare class WebSearchTool implements AgentTool<typeof webSearchSchema,
|
|
|
111
111
|
export declare const webSearchCustomTool: CustomTool<typeof webSearchSchema, SearchRenderDetails>;
|
|
112
112
|
export declare function getSearchTools(): CustomTool<any, any>[];
|
|
113
113
|
export { getSearchProvider, setPreferredSearchProvider, setSearchFallbackProviders } from "./provider";
|
|
114
|
+
export { setSearchHardTimeoutMs } from "./providers/utils";
|
|
114
115
|
export type { SearchProviderId as SearchProvider, SearchResponse } from "./types";
|
|
115
116
|
export { isConfigurableSearchProviderId, isSearchProviderPreference } from "./types";
|
|
@@ -15,16 +15,23 @@ import { SearchProviderError, type SearchProviderId, type SearchSource } from ".
|
|
|
15
15
|
*/
|
|
16
16
|
export declare function findCredential(storage: AgentStorage | null | undefined, envKey: string | null | undefined, ...storageProviders: string[]): string | null;
|
|
17
17
|
/**
|
|
18
|
-
* Default hard ceiling for a single web-search round-trip.
|
|
18
|
+
* Default hard ceiling for a single web-search round-trip. 300s tolerates
|
|
19
19
|
* legitimate slow LLM-mediated responses (anthropic web_search_20250305,
|
|
20
20
|
* perplexity, gemini, OpenAI code backend) while still guaranteeing the session unfreezes
|
|
21
|
-
*
|
|
21
|
+
* if Bun's `AbortSignal` fails to propagate on Windows.
|
|
22
22
|
*
|
|
23
23
|
* Pure search APIs (brave, exa, jina, tavily, searxng, synthetic, zai)
|
|
24
24
|
* settle far faster in practice; reusing the same ceiling keeps the wiring
|
|
25
25
|
* uniform without compromising correctness.
|
|
26
26
|
*/
|
|
27
|
-
export declare const SEARCH_HARD_TIMEOUT_MS =
|
|
27
|
+
export declare const SEARCH_HARD_TIMEOUT_MS = 300000;
|
|
28
|
+
/**
|
|
29
|
+
* Override the hard timeout applied to every web-search round-trip.
|
|
30
|
+
*
|
|
31
|
+
* @param ms - Hard timeout in milliseconds. Non-finite or non-positive
|
|
32
|
+
* values reset the timeout to {@link SEARCH_HARD_TIMEOUT_MS}.
|
|
33
|
+
*/
|
|
34
|
+
export declare function setSearchHardTimeoutMs(ms: number | undefined): void;
|
|
28
35
|
/**
|
|
29
36
|
* Compose a caller-supplied {@link AbortSignal} with a hard timeout so an
|
|
30
37
|
* outbound `fetch()` is guaranteed to settle within `ms` even when the
|
|
@@ -36,7 +43,7 @@ export declare const SEARCH_HARD_TIMEOUT_MS = 60000;
|
|
|
36
43
|
* because the user's Esc is never delivered to the native layer.
|
|
37
44
|
*
|
|
38
45
|
* @param signal - Caller cancellation signal, if any.
|
|
39
|
-
* @param ms - Hard timeout in milliseconds. Defaults to
|
|
46
|
+
* @param ms - Hard timeout in milliseconds. Defaults to the configured value.
|
|
40
47
|
*/
|
|
41
48
|
export declare function withHardTimeout(signal: AbortSignal | undefined, ms?: number): AbortSignal;
|
|
42
49
|
/**
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@sayknow-cli/coding-agent",
|
|
4
|
-
"version": "0.3.
|
|
4
|
+
"version": "0.3.4",
|
|
5
5
|
"description": "Sayknow-CLI CLI with read, bash, edit, write tools and session management",
|
|
6
6
|
"homepage": "https://github.com/jaybeyond/Sayknow_CLI",
|
|
7
7
|
"author": "jaybeyond",
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"main": "./src/index.ts",
|
|
29
29
|
"types": "./dist/types/index.d.ts",
|
|
30
30
|
"bin": {
|
|
31
|
-
"skc": "
|
|
31
|
+
"skc": "bin/skc.js"
|
|
32
32
|
},
|
|
33
33
|
"scripts": {
|
|
34
34
|
"build": "bun scripts/build-binary.ts",
|
|
@@ -52,12 +52,12 @@
|
|
|
52
52
|
"@agentclientprotocol/sdk": "0.21.0",
|
|
53
53
|
"@babel/parser": "^7.29.3",
|
|
54
54
|
"@mozilla/readability": "^0.6.0",
|
|
55
|
-
"@sayknow-cli/stats": "0.3.
|
|
56
|
-
"@sayknow-cli/agent-core": "0.3.
|
|
57
|
-
"@sayknow-cli/ai": "0.3.
|
|
58
|
-
"@sayknow-cli/natives": "0.3.
|
|
59
|
-
"@sayknow-cli/tui": "0.3.
|
|
60
|
-
"@sayknow-cli/utils": "0.3.
|
|
55
|
+
"@sayknow-cli/stats": "0.3.4",
|
|
56
|
+
"@sayknow-cli/agent-core": "0.3.4",
|
|
57
|
+
"@sayknow-cli/ai": "0.3.4",
|
|
58
|
+
"@sayknow-cli/natives": "0.3.4",
|
|
59
|
+
"@sayknow-cli/tui": "0.3.4",
|
|
60
|
+
"@sayknow-cli/utils": "0.3.4",
|
|
61
61
|
"@puppeteer/browsers": "^2.13.0",
|
|
62
62
|
"@types/turndown": "5.0.6",
|
|
63
63
|
"@xterm/headless": "^6.0.0",
|
|
@@ -74,13 +74,15 @@
|
|
|
74
74
|
"zod": "4.4.3"
|
|
75
75
|
},
|
|
76
76
|
"devDependencies": {
|
|
77
|
-
"@types/bun": "^1.3.14"
|
|
77
|
+
"@types/bun": "^1.3.14",
|
|
78
|
+
"@types/ws": "^8.5.13"
|
|
78
79
|
},
|
|
79
80
|
"engines": {
|
|
80
81
|
"bun": ">=1.3.14"
|
|
81
82
|
},
|
|
82
83
|
"files": [
|
|
83
84
|
"src",
|
|
85
|
+
"bin",
|
|
84
86
|
"scripts",
|
|
85
87
|
"examples",
|
|
86
88
|
"README.md",
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
// G004 real-tmux smoke: exercises forceCloseSkcTmuxSession against LIVE tmux
|
|
2
|
+
// sessions (tmux 3.6a). Proves the wrapper hard-kills SKC-managed live panes
|
|
3
|
+
// (where remove refuses), refuses non-SKC sessions, and enforces session-id
|
|
4
|
+
// matching. Produces durable evidence; not part of the unit suite.
|
|
5
|
+
import assert from "node:assert";
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
buildSkcTmuxExactOptionTarget,
|
|
9
|
+
buildSkcTmuxProfileCommands,
|
|
10
|
+
resolveSkcTmuxCommand,
|
|
11
|
+
} from "../src/skc-runtime/tmux-common";
|
|
12
|
+
import { forceCloseSkcTmuxSession, removeSkcTmuxSession, statusSkcTmuxSession } from "../src/skc-runtime/tmux-sessions";
|
|
13
|
+
|
|
14
|
+
const tmux = resolveSkcTmuxCommand(process.env);
|
|
15
|
+
|
|
16
|
+
function sh(args: string[]): { code: number; err: string } {
|
|
17
|
+
const r = Bun.spawnSync([tmux, ...args], { stdout: "pipe", stderr: "pipe" });
|
|
18
|
+
return { code: r.exitCode, err: r.stderr.toString().trim() };
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function makeRawSession(name: string): void {
|
|
22
|
+
const r = sh(["new-session", "-d", "-s", name, "sleep 600"]);
|
|
23
|
+
if (r.code !== 0) throw new Error(`failed to create tmux session ${name}: ${r.err}`);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function tagAsSkc(name: string, sessionId?: string): void {
|
|
27
|
+
const target = buildSkcTmuxExactOptionTarget(name);
|
|
28
|
+
for (const cmd of buildSkcTmuxProfileCommands(target, process.env, { sessionId })) {
|
|
29
|
+
const r = sh(cmd.args);
|
|
30
|
+
if (r.code !== 0) throw new Error(`failed to tag ${name} (${cmd.description}): ${r.err}`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function exists(name: string): boolean {
|
|
35
|
+
return sh(["has-session", "-t", `=${name}`]).code === 0;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function killQuiet(name: string): void {
|
|
39
|
+
sh(["kill-session", "-t", `=${name}`]);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const suffix = `${process.pid}-${Date.now()}`;
|
|
43
|
+
const live = `skc_g004live_${suffix}`;
|
|
44
|
+
const raw = `g004raw_${suffix}`;
|
|
45
|
+
const mism = `skc_g004mism_${suffix}`;
|
|
46
|
+
const cleanup: string[] = [live, raw, mism];
|
|
47
|
+
|
|
48
|
+
try {
|
|
49
|
+
// 1. SKC-managed LIVE session: remove refuses, force-close hard-kills.
|
|
50
|
+
makeRawSession(live);
|
|
51
|
+
tagAsSkc(live, "sess-g004");
|
|
52
|
+
const status = statusSkcTmuxSession(live);
|
|
53
|
+
assert.equal(status.profile, "1", "session must be recognized as SKC-managed");
|
|
54
|
+
assert.ok(status.panePids.length > 0, "session must have a live pane (sleep)");
|
|
55
|
+
console.log(`[g004] live SKC session up: ${live} panePids=${status.panePids.length}`);
|
|
56
|
+
|
|
57
|
+
let removeRefused = false;
|
|
58
|
+
try {
|
|
59
|
+
removeSkcTmuxSession(live);
|
|
60
|
+
} catch (e) {
|
|
61
|
+
removeRefused = /skc_tmux_session_live/.test(String(e));
|
|
62
|
+
}
|
|
63
|
+
assert.ok(removeRefused, "removeSkcTmuxSession must REFUSE a live pane");
|
|
64
|
+
console.log("[g004] remove refused live session (expected)");
|
|
65
|
+
|
|
66
|
+
const closed = forceCloseSkcTmuxSession(live, process.env, "sess-g004");
|
|
67
|
+
assert.equal(closed.name, live);
|
|
68
|
+
assert.ok(!exists(live), "force-close must hard-kill the live SKC session");
|
|
69
|
+
console.log("[g004] force-close hard-killed the live SKC session (id-matched)");
|
|
70
|
+
|
|
71
|
+
// 2. Non-SKC (untagged) session: force-close must refuse.
|
|
72
|
+
makeRawSession(raw);
|
|
73
|
+
let notManaged = false;
|
|
74
|
+
try {
|
|
75
|
+
forceCloseSkcTmuxSession(raw, process.env);
|
|
76
|
+
} catch (e) {
|
|
77
|
+
notManaged = /skc_tmux_session_(not_managed|not_found)/.test(String(e));
|
|
78
|
+
}
|
|
79
|
+
assert.ok(notManaged, "force-close must refuse a non-SKC tmux session");
|
|
80
|
+
assert.ok(exists(raw), "non-SKC session must be left untouched");
|
|
81
|
+
killQuiet(raw);
|
|
82
|
+
console.log("[g004] force-close refused + preserved non-SKC session (expected)");
|
|
83
|
+
|
|
84
|
+
// 3. SKC session with a MISMATCHED expected session id: must refuse.
|
|
85
|
+
makeRawSession(mism);
|
|
86
|
+
tagAsSkc(mism, "sess-real");
|
|
87
|
+
let idMismatch = false;
|
|
88
|
+
try {
|
|
89
|
+
forceCloseSkcTmuxSession(mism, process.env, "sess-WRONG");
|
|
90
|
+
} catch (e) {
|
|
91
|
+
idMismatch = /skc_tmux_session_id_mismatch/.test(String(e));
|
|
92
|
+
}
|
|
93
|
+
assert.ok(idMismatch, "force-close must refuse on session-id mismatch");
|
|
94
|
+
assert.ok(exists(mism), "mismatched session must be left untouched");
|
|
95
|
+
console.log("[g004] force-close refused on session-id mismatch (expected)");
|
|
96
|
+
|
|
97
|
+
console.log("[g004] PASS: forceCloseSkcTmuxSession verified against live tmux");
|
|
98
|
+
} finally {
|
|
99
|
+
for (const name of cleanup) killQuiet(name);
|
|
100
|
+
}
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
// G005 real end-to-end daemon-orchestration smoke.
|
|
2
|
+
//
|
|
3
|
+
// Drives the lifecycle orchestrator's create -> close path with REAL effects:
|
|
4
|
+
// a real fsynced file-backed idempotency ledger, a real audit JSONL, and a real
|
|
5
|
+
// tmux session spawned/closed via the actual tmux helpers. Proves the daemon
|
|
6
|
+
// orchestration turns a `session_create` frame into a genuinely-spawned,
|
|
7
|
+
// SKC-tagged tmux session and a `session_close` frame into a real hard-close,
|
|
8
|
+
// with idempotent re-ack. Not part of the unit suite.
|
|
9
|
+
import assert from "node:assert";
|
|
10
|
+
import * as crypto from "node:crypto";
|
|
11
|
+
import * as fs from "node:fs";
|
|
12
|
+
import * as os from "node:os";
|
|
13
|
+
import * as path from "node:path";
|
|
14
|
+
import type { SessionCloseFrame, SessionCreateFrame } from "../src/notifications/index";
|
|
15
|
+
import {
|
|
16
|
+
type AuditEvent,
|
|
17
|
+
handleLifecycleRequest,
|
|
18
|
+
type LedgerDoc,
|
|
19
|
+
type LedgerStore,
|
|
20
|
+
type OrchestratorDeps,
|
|
21
|
+
} from "../src/notifications/lifecycle-orchestrator";
|
|
22
|
+
import {
|
|
23
|
+
buildSkcTmuxExactOptionTarget,
|
|
24
|
+
buildSkcTmuxProfileCommands,
|
|
25
|
+
resolveSkcTmuxCommand,
|
|
26
|
+
} from "../src/skc-runtime/tmux-common";
|
|
27
|
+
import { forceCloseSkcTmuxSession, statusSkcTmuxSession } from "../src/skc-runtime/tmux-sessions";
|
|
28
|
+
|
|
29
|
+
const tmux = resolveSkcTmuxCommand(process.env);
|
|
30
|
+
const tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), "skc-g005-"));
|
|
31
|
+
const ledgerPath = path.join(tmpRoot, "idempotency.json");
|
|
32
|
+
const auditPath = path.join(tmpRoot, "audit.jsonl");
|
|
33
|
+
const created: string[] = [];
|
|
34
|
+
|
|
35
|
+
function sh(args: string[]): number {
|
|
36
|
+
return Bun.spawnSync([tmux, ...args], { stdout: "pipe", stderr: "pipe" }).exitCode;
|
|
37
|
+
}
|
|
38
|
+
function exists(name: string): boolean {
|
|
39
|
+
return sh(["has-session", "-t", `=${name}`]) === 0;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Real fsynced + atomic file ledger store.
|
|
43
|
+
const store: LedgerStore = {
|
|
44
|
+
async read(): Promise<LedgerDoc> {
|
|
45
|
+
try {
|
|
46
|
+
return JSON.parse(fs.readFileSync(ledgerPath, "utf8")) as LedgerDoc;
|
|
47
|
+
} catch {
|
|
48
|
+
return { version: 1, entries: {} };
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
async write(doc: LedgerDoc): Promise<void> {
|
|
52
|
+
const tmp = `${ledgerPath}.${process.pid}.tmp`;
|
|
53
|
+
const fd = fs.openSync(tmp, "w", 0o600);
|
|
54
|
+
fs.writeSync(fd, JSON.stringify(doc));
|
|
55
|
+
fs.fsyncSync(fd);
|
|
56
|
+
fs.closeSync(fd);
|
|
57
|
+
fs.renameSync(tmp, ledgerPath);
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const auditLines: AuditEvent[] = [];
|
|
62
|
+
const deps: OrchestratorDeps = {
|
|
63
|
+
pairedChatId: "42",
|
|
64
|
+
now: () => Date.now(),
|
|
65
|
+
store,
|
|
66
|
+
audit: e => {
|
|
67
|
+
auditLines.push(e);
|
|
68
|
+
fs.appendFileSync(auditPath, `${JSON.stringify(e)}\n`, { mode: 0o600 });
|
|
69
|
+
},
|
|
70
|
+
allowCreate: () => true,
|
|
71
|
+
writeStartupPrompt: async (requestId, prompt) => {
|
|
72
|
+
if (prompt === undefined) return undefined;
|
|
73
|
+
const ref = path.join(tmpRoot, `prompt-${requestId}`);
|
|
74
|
+
const fd = fs.openSync(ref, "w", 0o600);
|
|
75
|
+
fs.writeSync(fd, prompt);
|
|
76
|
+
fs.fsyncSync(fd);
|
|
77
|
+
fs.closeSync(fd);
|
|
78
|
+
return ref;
|
|
79
|
+
},
|
|
80
|
+
// REAL spawn: create a SKC-tagged tmux session carrying the authoritative id.
|
|
81
|
+
spawnCreate: async (_frame, ids) => {
|
|
82
|
+
const name = `skc_g005_${ids.intendedSessionId}`;
|
|
83
|
+
created.push(name);
|
|
84
|
+
if (sh(["new-session", "-d", "-s", name, "sleep 600"]) !== 0) {
|
|
85
|
+
throw new Error(`tmux spawn failed for ${name}`);
|
|
86
|
+
}
|
|
87
|
+
const target = buildSkcTmuxExactOptionTarget(name);
|
|
88
|
+
for (const cmd of buildSkcTmuxProfileCommands(target, process.env, {
|
|
89
|
+
sessionId: ids.intendedSessionId,
|
|
90
|
+
})) {
|
|
91
|
+
if (sh(cmd.args) !== 0) throw new Error(`tag failed for ${name}`);
|
|
92
|
+
}
|
|
93
|
+
return {
|
|
94
|
+
sessionId: ids.intendedSessionId,
|
|
95
|
+
tmuxSession: name,
|
|
96
|
+
endpointUrl: "ws://127.0.0.1:0",
|
|
97
|
+
topicThreadId: "1",
|
|
98
|
+
};
|
|
99
|
+
},
|
|
100
|
+
// REAL close: hard-close the SKC-managed tmux session, id-matched.
|
|
101
|
+
closeSession: async target => {
|
|
102
|
+
forceCloseSkcTmuxSession(target.tmuxSession ?? "", process.env, target.sessionId);
|
|
103
|
+
return { processGone: !exists(target.tmuxSession ?? "") };
|
|
104
|
+
},
|
|
105
|
+
resumeSession: async () => ({ ambiguous: [] }),
|
|
106
|
+
newLifecycleRequestId: () => `lc-${crypto.randomUUID()}`,
|
|
107
|
+
newSessionId: () => `s${crypto.randomUUID().slice(0, 8)}`,
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
const createFrame: SessionCreateFrame = {
|
|
111
|
+
type: "session_create",
|
|
112
|
+
requestId: "lc_g005",
|
|
113
|
+
lifecycleRequestId: "lc_g005",
|
|
114
|
+
intendedSessionId: `g005${Date.now().toString(36)}`,
|
|
115
|
+
updateId: 100,
|
|
116
|
+
chatId: "42",
|
|
117
|
+
token: "control-token",
|
|
118
|
+
target: { kind: "existing_path", path: tmpRoot },
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
async function main(): Promise<void> {
|
|
122
|
+
// 1. CREATE -> real tmux session spawned + SKC-tagged with the authoritative id.
|
|
123
|
+
const createOut = await handleLifecycleRequest(createFrame, deps);
|
|
124
|
+
assert.equal(createOut.status, "ok", "create must succeed");
|
|
125
|
+
const session = createOut.status === "ok" ? createOut.entry.tmuxSession! : "";
|
|
126
|
+
assert.ok(exists(session), "real tmux session must exist after create");
|
|
127
|
+
const status = statusSkcTmuxSession(session);
|
|
128
|
+
assert.equal(status.profile, "1", "spawned session must be SKC-managed");
|
|
129
|
+
assert.equal(status.sessionId, createFrame.intendedSessionId, "authoritative session id propagated to tmux tag");
|
|
130
|
+
console.log(`[g005] CREATE -> live tmux session ${session} (id=${status.sessionId})`);
|
|
131
|
+
|
|
132
|
+
// 2. Idempotent re-ack: same updateId+body must NOT spawn a second session.
|
|
133
|
+
const before = created.length;
|
|
134
|
+
const dupOut = await handleLifecycleRequest(createFrame, deps);
|
|
135
|
+
assert.equal(dupOut.status, "ok", "duplicate create re-acks ok");
|
|
136
|
+
assert.equal(created.length, before, "duplicate create must NOT spawn a second session");
|
|
137
|
+
console.log("[g005] DUPLICATE create re-acked, no second spawn (idempotent)");
|
|
138
|
+
|
|
139
|
+
// 3. CLOSE -> real hard-close of the SKC-managed session, id-matched.
|
|
140
|
+
const closeFrame: SessionCloseFrame = {
|
|
141
|
+
type: "session_close",
|
|
142
|
+
requestId: "lc_g005_close",
|
|
143
|
+
updateId: 101,
|
|
144
|
+
chatId: "42",
|
|
145
|
+
token: "control-token",
|
|
146
|
+
target: { sessionId: createFrame.intendedSessionId, tmuxSession: session },
|
|
147
|
+
force: true,
|
|
148
|
+
};
|
|
149
|
+
const closeOut = await handleLifecycleRequest(closeFrame, deps);
|
|
150
|
+
assert.equal(closeOut.status, "ok", "close must succeed");
|
|
151
|
+
assert.ok(!exists(session), "real tmux session must be gone after close");
|
|
152
|
+
console.log(`[g005] CLOSE -> hard-closed ${session} (id-matched)`);
|
|
153
|
+
|
|
154
|
+
// 4. Durable ledger + audit redaction.
|
|
155
|
+
const doc = JSON.parse(fs.readFileSync(ledgerPath, "utf8")) as LedgerDoc;
|
|
156
|
+
assert.equal(doc.entries["42:100"]?.state, "success");
|
|
157
|
+
assert.equal(doc.entries["42:101"]?.state, "success");
|
|
158
|
+
const auditBlob = fs.readFileSync(auditPath, "utf8");
|
|
159
|
+
assert.ok(!auditBlob.includes("control-token"), "audit must never contain the control token");
|
|
160
|
+
assert.ok(
|
|
161
|
+
auditLines.some(a => a.event === "spawn_started"),
|
|
162
|
+
"audit records spawn_started",
|
|
163
|
+
);
|
|
164
|
+
console.log("[g005] durable fsynced ledger + token-redacted audit verified");
|
|
165
|
+
|
|
166
|
+
console.log("[g005] PASS: real create->close daemon orchestration over live tmux");
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
main()
|
|
170
|
+
.then(() => {
|
|
171
|
+
for (const n of created) sh(["kill-session", "-t", `=${n}`]);
|
|
172
|
+
fs.rmSync(tmpRoot, { recursive: true, force: true });
|
|
173
|
+
process.exit(0);
|
|
174
|
+
})
|
|
175
|
+
.catch(err => {
|
|
176
|
+
for (const n of created) sh(["kill-session", "-t", `=${n}`]);
|
|
177
|
+
fs.rmSync(tmpRoot, { recursive: true, force: true });
|
|
178
|
+
console.error("[g005] FAIL", err);
|
|
179
|
+
process.exit(1);
|
|
180
|
+
});
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
// G011 real daemon-path smoke.
|
|
2
|
+
//
|
|
3
|
+
// Drives a PARSED /session_* command through the LIVE native
|
|
4
|
+
// NotificationControlServer + a real loopback WebSocket client (NO injected/fake
|
|
5
|
+
// seams), wired to the real orchestrator (with stubbed spawn/close effects so the
|
|
6
|
+
// focus is the authenticated wire path, not tmux). Asserts:
|
|
7
|
+
// - a wrong-token handshake is rejected (control token gates the endpoint);
|
|
8
|
+
// - a valid frame is forwarded with the control token STRIPPED from payloadJson;
|
|
9
|
+
// - the host response is routed back to the client by requestId;
|
|
10
|
+
// - control discovery exists while running and is removed after stop.
|
|
11
|
+
import assert from "node:assert";
|
|
12
|
+
import * as crypto from "node:crypto";
|
|
13
|
+
import * as fs from "node:fs";
|
|
14
|
+
import * as os from "node:os";
|
|
15
|
+
import * as path from "node:path";
|
|
16
|
+
import WebSocket from "ws";
|
|
17
|
+
|
|
18
|
+
// Import the WORKTREE native build directly (not @sayknow-cli/natives, which can
|
|
19
|
+
// resolve to a different checkout in this dev environment).
|
|
20
|
+
import { NotificationControlServer } from "../../natives/native/index.js";
|
|
21
|
+
import { parseLifecycleCommand } from "../src/notifications/lifecycle-commands";
|
|
22
|
+
import { attachLifecycleControl, fileAudit, fileLedgerStore } from "../src/notifications/lifecycle-control-runtime";
|
|
23
|
+
import type { OrchestratorDeps } from "../src/notifications/lifecycle-orchestrator";
|
|
24
|
+
|
|
25
|
+
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "skc-g011-"));
|
|
26
|
+
const token = crypto.randomBytes(32).toString("base64url");
|
|
27
|
+
const ownerId = "daemon-g011";
|
|
28
|
+
const closed: string[] = [];
|
|
29
|
+
|
|
30
|
+
function deps(): OrchestratorDeps {
|
|
31
|
+
return {
|
|
32
|
+
pairedChatId: "42",
|
|
33
|
+
now: () => Date.now(),
|
|
34
|
+
store: fileLedgerStore(path.join(tmp, "idempotency.json")),
|
|
35
|
+
audit: fileAudit(path.join(tmp, "audit.jsonl")),
|
|
36
|
+
allowCreate: () => true,
|
|
37
|
+
writeStartupPrompt: async () => undefined,
|
|
38
|
+
// Stubbed effects: this smoke proves the WIRE path, not tmux (covered by g005).
|
|
39
|
+
spawnCreate: async (_f, ids) => ({
|
|
40
|
+
sessionId: ids.intendedSessionId,
|
|
41
|
+
tmuxSession: `skc-${ids.intendedSessionId}`,
|
|
42
|
+
endpointUrl: "ws://127.0.0.1:0",
|
|
43
|
+
topicThreadId: "1",
|
|
44
|
+
}),
|
|
45
|
+
closeSession: async t => {
|
|
46
|
+
closed.push(t.sessionId);
|
|
47
|
+
return { processGone: true };
|
|
48
|
+
},
|
|
49
|
+
resumeSession: async () => ({ ambiguous: [] }),
|
|
50
|
+
newLifecycleRequestId: () => `lc-${crypto.randomUUID()}`,
|
|
51
|
+
newSessionId: () => `s${crypto.randomUUID().slice(0, 8)}`,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function send(ws: WebSocket, frame: unknown): void {
|
|
56
|
+
ws.send(JSON.stringify(frame));
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async function main(): Promise<void> {
|
|
60
|
+
const server = new NotificationControlServer(token, ownerId, tmp);
|
|
61
|
+
|
|
62
|
+
// Wire the real orchestrator to the real native control server.
|
|
63
|
+
const d = deps();
|
|
64
|
+
attachLifecycleControl(server as never, d);
|
|
65
|
+
|
|
66
|
+
const ep = (await server.start()) as { url: string };
|
|
67
|
+
assert.ok(ep.url.startsWith("ws://127.0.0.1:"), `loopback url, got ${ep.url}`);
|
|
68
|
+
const controlJson = path.join(tmp, "notifications", "control.json");
|
|
69
|
+
assert.ok(fs.existsSync(controlJson), "control discovery must exist while running");
|
|
70
|
+
// The control discovery file MUST NOT persist the privileged control token:
|
|
71
|
+
// the daemon holds it in memory and is the only client.
|
|
72
|
+
const controlRaw = fs.readFileSync(controlJson, "utf8");
|
|
73
|
+
assert.ok(!controlRaw.includes(token), "control token MUST NOT be persisted in control.json");
|
|
74
|
+
assert.ok(
|
|
75
|
+
!("token" in (JSON.parse(controlRaw) as Record<string, unknown>)),
|
|
76
|
+
"control.json must omit any token field",
|
|
77
|
+
);
|
|
78
|
+
console.log(`[g011] live control endpoint ${ep.url}; discovery present, no persisted token`);
|
|
79
|
+
|
|
80
|
+
// 1. Wrong-token handshake must be rejected.
|
|
81
|
+
await new Promise<void>(resolve => {
|
|
82
|
+
const bad = new WebSocket(`${ep.url}/?token=wrong`);
|
|
83
|
+
bad.on("open", () => {
|
|
84
|
+
throw new Error("wrong-token handshake must NOT open");
|
|
85
|
+
});
|
|
86
|
+
bad.on("error", () => {
|
|
87
|
+
console.log("[g011] wrong-token handshake rejected (expected)");
|
|
88
|
+
resolve();
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
// 2. Valid client: send a PARSED /session_close command through the real wire.
|
|
93
|
+
const parsed = parseLifecycleCommand("/session_close sess-g011");
|
|
94
|
+
assert.equal(parsed.kind, "close", "parser must yield a close command");
|
|
95
|
+
const requestId = "lc-g011-1";
|
|
96
|
+
const frame = {
|
|
97
|
+
type: "session_close",
|
|
98
|
+
requestId,
|
|
99
|
+
updateId: 1,
|
|
100
|
+
chatId: "42",
|
|
101
|
+
token,
|
|
102
|
+
target: parsed.kind === "close" ? parsed.target : { sessionId: "sess-g011" },
|
|
103
|
+
force: true,
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
const response = await new Promise<Record<string, unknown>>((resolve, reject) => {
|
|
107
|
+
const ws = new WebSocket(`${ep.url}/?token=${token}`);
|
|
108
|
+
const timer = setTimeout(() => reject(new Error("timed out")), 5000);
|
|
109
|
+
ws.on("open", () => send(ws, frame));
|
|
110
|
+
ws.on("message", data => {
|
|
111
|
+
clearTimeout(timer);
|
|
112
|
+
ws.close();
|
|
113
|
+
resolve(JSON.parse(String(data)) as Record<string, unknown>);
|
|
114
|
+
});
|
|
115
|
+
ws.on("error", reject);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
assert.equal(response.type, "session_close_response", "response routed back to client");
|
|
119
|
+
assert.equal(response.requestId, requestId, "response correlated by requestId");
|
|
120
|
+
assert.equal(response.sessionId, "sess-g011");
|
|
121
|
+
assert.deepEqual(closed, ["sess-g011"], "orchestrator close effect ran exactly once");
|
|
122
|
+
console.log("[g011] parsed command -> real wire -> orchestrator -> routed response OK");
|
|
123
|
+
|
|
124
|
+
// 3. The real orchestrator path must NOT leak the control token into its
|
|
125
|
+
// durable audit log or idempotency ledger (g002 separately proves the native
|
|
126
|
+
// payloadJson boundary is token-stripped).
|
|
127
|
+
for (const f of ["audit.jsonl", "idempotency.json"]) {
|
|
128
|
+
const p = path.join(tmp, f);
|
|
129
|
+
if (fs.existsSync(p)) {
|
|
130
|
+
assert.ok(!fs.readFileSync(p, "utf8").includes(token), `control token leaked into ${f}`);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
console.log("[g011] no control-token leak in audit/ledger on the real path");
|
|
134
|
+
|
|
135
|
+
// 4. Stop removes control discovery.
|
|
136
|
+
server.stop();
|
|
137
|
+
await new Promise(r => setTimeout(r, 50));
|
|
138
|
+
assert.ok(!fs.existsSync(controlJson), "control discovery removed after stop");
|
|
139
|
+
console.log("[g011] control discovery removed after stop");
|
|
140
|
+
|
|
141
|
+
console.log("[g011] PASS: real daemon-path (parse -> native control endpoint -> orchestrator -> reply)");
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
main()
|
|
145
|
+
.then(() => {
|
|
146
|
+
fs.rmSync(tmp, { recursive: true, force: true });
|
|
147
|
+
process.exit(0);
|
|
148
|
+
})
|
|
149
|
+
.catch(err => {
|
|
150
|
+
fs.rmSync(tmp, { recursive: true, force: true });
|
|
151
|
+
console.error("[g011] FAIL", err);
|
|
152
|
+
process.exit(1);
|
|
153
|
+
});
|
package/src/cli/args.ts
CHANGED
|
@@ -284,7 +284,6 @@ ${chalk.bold("Available Tools (default-enabled unless noted):")}
|
|
|
284
284
|
lsp - Language server protocol (code intelligence)
|
|
285
285
|
python - Execute Python code (requires: ${APP_NAME} setup python)
|
|
286
286
|
notebook - Edit Jupyter notebooks
|
|
287
|
-
inspect_image - Analyze images with a vision model
|
|
288
287
|
browser - Browser automation (Puppeteer)
|
|
289
288
|
task - Launch sub-agents for parallel tasks
|
|
290
289
|
todo_write - Manage todo/task lists
|
package/src/cli/fast-help.ts
CHANGED
|
@@ -1,7 +1,35 @@
|
|
|
1
1
|
import { APP_NAME, CONFIG_DIR_NAME } from "@sayknow-cli/utils/dirs";
|
|
2
2
|
|
|
3
3
|
export function getExtraHelpText(): string {
|
|
4
|
-
return `
|
|
4
|
+
return `Commands:
|
|
5
|
+
${APP_NAME} [prompt] - Start an interactive coding session (default launch command)
|
|
6
|
+
${APP_NAME} launch - Start an explicit launch/session workflow
|
|
7
|
+
${APP_NAME} setup - Install SKC defaults or optional dependencies
|
|
8
|
+
${APP_NAME} session - List, inspect, create, remove, or attach sessions
|
|
9
|
+
${APP_NAME} state - Inspect or manage persisted SKC state
|
|
10
|
+
${APP_NAME} harness - Run harness control-plane commands
|
|
11
|
+
${APP_NAME} coordinator - Manage coordinator/runtime coordination helpers
|
|
12
|
+
${APP_NAME} team - Run tmux-backed coordinated execution
|
|
13
|
+
${APP_NAME} ultragoal - Run durable goal execution workflow
|
|
14
|
+
${APP_NAME} ralplan - Run consensus planning workflow
|
|
15
|
+
${APP_NAME} deep-interview - Run requirements interview workflow
|
|
16
|
+
${APP_NAME} skills - List/read embedded workflow skills
|
|
17
|
+
${APP_NAME} config - List, get, and set configuration values
|
|
18
|
+
${APP_NAME} notify - Send or test notifications
|
|
19
|
+
${APP_NAME} daemon - Manage background daemon helpers
|
|
20
|
+
${APP_NAME} mcp - Manage MCP server registrations
|
|
21
|
+
${APP_NAME} mcp-serve - Serve the MCP integration endpoint
|
|
22
|
+
${APP_NAME} contribute-pr - Prepare contribution/PR workflow artifacts
|
|
23
|
+
${APP_NAME} migrate - Run migration helpers
|
|
24
|
+
${APP_NAME} rlm - Run RLM helpers
|
|
25
|
+
${APP_NAME} update - Update SKC installation artifacts
|
|
26
|
+
${APP_NAME} plugin - Install, remove, and list plugins
|
|
27
|
+
${APP_NAME} web-search - Search the web from the CLI (alias: q)
|
|
28
|
+
${APP_NAME} codex-native-hook - Run Codex native hook integration
|
|
29
|
+
${APP_NAME} gc - Run garbage-collection/cleanup helpers
|
|
30
|
+
${APP_NAME} <command> --help - Show command-specific help
|
|
31
|
+
|
|
32
|
+
Environment Variables:
|
|
5
33
|
# Core Providers
|
|
6
34
|
ANTHROPIC_API_KEY - Anthropic Claude models
|
|
7
35
|
ANTHROPIC_OAUTH_TOKEN - Anthropic OAuth (takes precedence over API key)
|
|
@@ -69,7 +97,6 @@ Available Tools (default-enabled unless noted):
|
|
|
69
97
|
lsp - Language server protocol (code intelligence)
|
|
70
98
|
python - Execute Python code (requires: ${APP_NAME} setup python)
|
|
71
99
|
notebook - Edit Jupyter notebooks
|
|
72
|
-
inspect_image - Analyze images with a vision model
|
|
73
100
|
browser - Browser automation (Puppeteer)
|
|
74
101
|
task - Launch sub-agents for parallel tasks
|
|
75
102
|
todo_write - Manage todo/task lists
|