@sayknow-cli/coding-agent 0.3.7 → 0.3.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/types/append-only-mode.d.ts +31 -0
- package/dist/types/capability/rule.d.ts +8 -0
- package/dist/types/cli/completion-cli.d.ts +41 -0
- package/dist/types/cli/config-cli.d.ts +1 -0
- package/dist/types/cli/fixture-report.d.ts +20 -0
- package/dist/types/cli/fixture-report.test.d.ts +1 -0
- package/dist/types/cli/nofile-limit.d.ts +11 -0
- package/dist/types/cli/notify-cli.d.ts +7 -0
- package/dist/types/cli/update-cli.d.ts +22 -0
- package/dist/types/cli.d.ts +124 -1
- package/dist/types/commands/completion.d.ts +29 -0
- package/dist/types/commands/config.d.ts +3 -0
- package/dist/types/commands/deep-interview.d.ts +3 -0
- package/dist/types/config/file-lock.d.ts +7 -6
- package/dist/types/config/keybindings.d.ts +2 -2
- package/dist/types/config/model-profile-activation.d.ts +6 -0
- package/dist/types/config/model-profiles.d.ts +1 -0
- package/dist/types/config/settings-schema.d.ts +105 -1
- package/dist/types/config/settings.d.ts +8 -0
- package/dist/types/discovery/index.d.ts +1 -0
- package/dist/types/export/html/template.generated.d.ts +1 -1
- package/dist/types/export/ttsr.d.ts +14 -3
- package/dist/types/extensibility/extensions/runner.d.ts +2 -1
- package/dist/types/extensibility/extensions/types.d.ts +8 -0
- package/dist/types/goals/runtime.d.ts +1 -0
- package/dist/types/hooks/native-skill-hook.d.ts +12 -0
- package/dist/types/main.d.ts +4 -1
- package/dist/types/modes/components/assistant-message.d.ts +5 -1
- package/dist/types/modes/components/model-selector.d.ts +4 -0
- package/dist/types/modes/components/queued-message-selector.d.ts +12 -0
- package/dist/types/modes/components/settings-selector.d.ts +2 -0
- package/dist/types/modes/components/status-line.d.ts +9 -0
- package/dist/types/modes/controllers/command-controller.d.ts +1 -0
- package/dist/types/modes/controllers/event-controller.d.ts +5 -0
- package/dist/types/modes/controllers/input-controller.d.ts +1 -3
- package/dist/types/modes/interactive-mode.d.ts +2 -0
- package/dist/types/modes/print-mode.d.ts +13 -1
- package/dist/types/modes/prompt-action-autocomplete.d.ts +1 -1
- package/dist/types/modes/rpc/rpc-types.d.ts +14 -0
- package/dist/types/modes/shared/agent-wire/event-contract.d.ts +19 -0
- package/dist/types/modes/shared/agent-wire/event-envelope.d.ts +23 -1
- package/dist/types/modes/types.d.ts +1 -0
- package/dist/types/modes/utils/ui-helpers.d.ts +1 -0
- package/dist/types/notifications/config.d.ts +14 -1
- package/dist/types/notifications/daemon-paths.d.ts +1 -0
- package/dist/types/notifications/index.d.ts +6 -3
- package/dist/types/notifications/lifecycle-commands.d.ts +11 -3
- package/dist/types/notifications/telegram-daemon.d.ts +23 -3
- package/dist/types/notifications/telegram-reference.d.ts +0 -5
- package/dist/types/notifications/threaded-render.d.ts +6 -0
- package/dist/types/runtime-api-key.d.ts +7 -0
- package/dist/types/sdk.d.ts +3 -0
- package/dist/types/session/agent-session.d.ts +61 -6
- package/dist/types/session/agent-storage.d.ts +1 -0
- package/dist/types/session/file-mention-pruning.d.ts +8 -0
- package/dist/types/session/messages.d.ts +4 -0
- package/dist/types/session/session-manager.d.ts +17 -1
- package/dist/types/skc-runtime/deep-interview-ambiguity.d.ts +39 -0
- package/dist/types/skc-runtime/deep-interview-recorder.d.ts +10 -1
- package/dist/types/skc-runtime/deep-interview-state.d.ts +17 -2
- package/dist/types/skc-runtime/session-state-sidecar.d.ts +7 -0
- package/dist/types/skc-runtime/state-writer.d.ts +3 -0
- package/dist/types/skc-runtime/ultragoal-receipt-freshness.d.ts +42 -0
- package/dist/types/skc-runtime/ultragoal-runtime.d.ts +120 -13
- package/dist/types/skill-state/active-state.d.ts +18 -1
- package/dist/types/system-prompt.d.ts +18 -2
- package/dist/types/task/executor.d.ts +3 -0
- package/dist/types/task/fork-context-budget.d.ts +7 -0
- package/dist/types/task/token-log.d.ts +30 -0
- package/dist/types/task/token-log.test.d.ts +1 -0
- package/dist/types/tools/bisect.d.ts +81 -0
- package/dist/types/tools/computer.d.ts +2 -2
- package/dist/types/tools/index.d.ts +1 -0
- package/dist/types/tools/search.d.ts +2 -2
- package/dist/types/tools/ultragoal-ask-guard.d.ts +9 -2
- package/dist/types/utils/file-mentions.d.ts +14 -0
- package/dist/types/utils/git.d.ts +32 -0
- package/dist/types/web/scrapers/youtube.d.ts +8 -0
- package/dist/types/workflow/workflow-intent-diff.d.ts +8 -2
- package/dist/types/workflow/workflow-intent-report-consumer.d.ts +50 -0
- package/dist/types/workflow/workflow-intent-report-schema.d.ts +2 -0
- package/dist/types/workflow/workflow-intent-report.d.ts +48 -0
- package/package.json +8 -8
- package/src/append-only-mode.ts +58 -0
- package/src/autoresearch/prompt.md +1 -1
- package/src/autoresearch/tools/log-experiment.ts +1 -1
- package/src/capability/rule.ts +9 -0
- package/src/cli/completion-cli.ts +234 -0
- package/src/cli/config-cli.ts +61 -9
- package/src/cli/fast-help.ts +1 -0
- package/src/cli/fixture-report.test.ts +230 -0
- package/src/cli/fixture-report.ts +171 -0
- package/src/cli/nofile-limit.ts +48 -0
- package/src/cli/notify-cli.ts +92 -8
- package/src/cli/telegram-cli.ts +1 -1
- package/src/cli/update-cli.ts +85 -9
- package/src/cli.ts +29 -1
- package/src/commands/completion.ts +84 -0
- package/src/commands/config.ts +2 -0
- package/src/commands/contribution-prep.ts +1 -1
- package/src/commands/deep-interview.ts +2 -1
- package/src/config/file-lock.ts +73 -34
- package/src/config/keybindings.ts +2 -2
- package/src/config/model-profile-activation.ts +65 -0
- package/src/config/model-profiles.ts +31 -2
- package/src/config/settings-schema.ts +68 -1
- package/src/config/settings.ts +22 -0
- package/src/defaults/skc/skills/team/SKILL.md +17 -14
- package/src/defaults/skc/skills/ultragoal/SKILL.md +59 -4
- package/src/defaults/skc/skills/ultragoal/pipeline-validation-contracts.md +30 -0
- package/src/defaults/skc-defaults.ts +9 -0
- package/src/discovery/helpers.ts +10 -0
- package/src/discovery/index.ts +1 -0
- package/src/export/html/template.generated.ts +1 -1
- package/src/export/html/template.js +60 -2
- package/src/export/ttsr.ts +96 -18
- package/src/extensibility/extensions/runner.ts +183 -219
- package/src/extensibility/extensions/types.ts +9 -0
- package/src/extensibility/skc-plugins/runtime-adapters.ts +156 -44
- package/src/goals/runtime.ts +10 -2
- package/src/hindsight/backend.ts +1 -3
- package/src/hooks/native-skill-hook.ts +124 -4
- package/src/internal-urls/docs-index.generated.ts +14 -5
- package/src/main.ts +95 -43
- package/src/memories/index.ts +12 -1
- package/src/modes/components/assistant-message.ts +241 -82
- package/src/modes/components/custom-editor.ts +1 -0
- package/src/modes/components/model-selector.ts +54 -11
- package/src/modes/components/queued-message-selector.ts +92 -0
- package/src/modes/components/settings-selector.ts +4 -0
- package/src/modes/components/status-line.ts +167 -54
- package/src/modes/components/tool-execution.ts +44 -19
- package/src/modes/components/welcome.ts +181 -44
- package/src/modes/controllers/command-controller.ts +40 -4
- package/src/modes/controllers/event-controller.ts +66 -47
- package/src/modes/controllers/extension-ui-controller.ts +4 -10
- package/src/modes/controllers/input-controller.ts +180 -18
- package/src/modes/controllers/selector-controller.ts +127 -24
- package/src/modes/interactive-mode.ts +51 -16
- package/src/modes/print-mode.ts +43 -4
- package/src/modes/prompt-action-autocomplete.ts +21 -20
- package/src/modes/rpc/rpc-mode.ts +29 -3
- package/src/modes/rpc/rpc-types.ts +9 -0
- package/src/modes/shared/agent-wire/command-validation.ts +4 -0
- package/src/modes/shared/agent-wire/event-contract.ts +22 -0
- package/src/modes/shared/agent-wire/event-envelope.ts +82 -0
- package/src/modes/shared/agent-wire/scopes.ts +1 -0
- package/src/modes/types.ts +1 -0
- package/src/modes/utils/hotkeys-markdown.ts +1 -0
- package/src/modes/utils/ui-helpers.ts +20 -7
- package/src/notifications/config.ts +25 -5
- package/src/notifications/daemon-paths.ts +2 -0
- package/src/notifications/index.ts +117 -14
- package/src/notifications/lifecycle-commands.ts +37 -7
- package/src/notifications/lifecycle-orchestrator.ts +19 -6
- package/src/notifications/telegram-daemon-control.ts +5 -3
- package/src/notifications/telegram-daemon.ts +302 -51
- package/src/notifications/telegram-reference.ts +0 -16
- package/src/notifications/threaded-render.ts +17 -1
- package/src/prompts/agents/architect.md +19 -10
- package/src/prompts/agents/critic.md +16 -18
- package/src/prompts/agents/executor.md +14 -11
- package/src/prompts/agents/planner.md +18 -13
- package/src/prompts/ci-green-request.md +3 -8
- package/src/prompts/goals/goal-continuation.md +0 -5
- package/src/prompts/goals/goal-mode-active.md +0 -5
- package/src/prompts/system/agent-creation-architect.md +2 -2
- package/src/prompts/system/plan-mode-active.md +1 -1
- package/src/prompts/system/plan-mode-subagent.md +3 -3
- package/src/prompts/system/project-prompt.md +0 -13
- package/src/prompts/system/subagent-system-prompt.md +3 -2
- package/src/prompts/system/system-prompt.md +29 -17
- package/src/prompts/system/volatile-project-context.md +10 -0
- package/src/prompts/system/web-search.md +4 -7
- package/src/prompts/tools/apply-patch.md +6 -0
- package/src/prompts/tools/ast-edit.md +1 -0
- package/src/prompts/tools/bash.md +1 -1
- package/src/prompts/tools/bisect.md +22 -0
- package/src/prompts/tools/browser.md +1 -1
- package/src/prompts/tools/computer.md +2 -2
- package/src/prompts/tools/cron.md +0 -2
- package/src/prompts/tools/eval.md +2 -2
- package/src/prompts/tools/find.md +1 -1
- package/src/prompts/tools/github.md +6 -6
- package/src/prompts/tools/goal.md +1 -3
- package/src/prompts/tools/image-gen.md +12 -5
- package/src/prompts/tools/irc.md +5 -4
- package/src/prompts/tools/job.md +2 -2
- package/src/prompts/tools/lsp.md +1 -1
- package/src/prompts/tools/monitor.md +2 -2
- package/src/prompts/tools/patch.md +2 -2
- package/src/prompts/tools/replace.md +2 -21
- package/src/prompts/tools/rewind.md +1 -1
- package/src/prompts/tools/search-tool-bm25.md +1 -11
- package/src/prompts/tools/search.md +1 -1
- package/src/prompts/tools/skill.md +3 -2
- package/src/prompts/tools/ssh.md +2 -1
- package/src/prompts/tools/subagent.md +2 -2
- package/src/prompts/tools/task.md +4 -5
- package/src/prompts/tools/todo-write.md +1 -1
- package/src/prompts/tools/vim.md +1 -0
- package/src/prompts/tools/web-search.md +2 -0
- package/src/prompts/tools/write.md +8 -2
- package/src/runtime-api-key.ts +16 -0
- package/src/sdk.ts +52 -28
- package/src/session/agent-session.ts +1011 -141
- package/src/session/agent-storage.ts +4 -0
- package/src/session/file-mention-pruning.ts +64 -0
- package/src/session/messages.ts +4 -0
- package/src/session/session-manager.ts +103 -4
- package/src/skc-runtime/deep-interview-ambiguity.ts +206 -0
- package/src/skc-runtime/deep-interview-recorder.ts +62 -8
- package/src/skc-runtime/deep-interview-state.ts +44 -2
- package/src/skc-runtime/session-state-sidecar.ts +263 -20
- package/src/skc-runtime/state-runtime.ts +12 -2
- package/src/skc-runtime/state-writer.ts +15 -0
- package/src/skc-runtime/ultragoal-guard.ts +55 -66
- package/src/skc-runtime/ultragoal-receipt-freshness.ts +315 -0
- package/src/skc-runtime/ultragoal-runtime.ts +1663 -159
- package/src/skc-runtime/workflow-manifest.generated.json +113 -2
- package/src/skc-runtime/workflow-manifest.ts +74 -1
- package/src/skill-state/active-state.ts +141 -5
- package/src/skill-state/deep-interview-mutation-guard.ts +67 -3
- package/src/slash-commands/builtin-registry.ts +218 -71
- package/src/system-prompt.ts +57 -16
- package/src/task/executor.ts +132 -24
- package/src/task/fork-context-advisory.ts +2 -7
- package/src/task/fork-context-budget.ts +9 -0
- package/src/task/index.ts +13 -6
- package/src/task/output-manager.ts +20 -8
- package/src/task/receipt.ts +26 -7
- package/src/task/token-log.test.ts +188 -0
- package/src/task/token-log.ts +179 -0
- package/src/tools/bisect.ts +367 -0
- package/src/tools/computer.ts +246 -43
- package/src/tools/index.ts +3 -0
- package/src/tools/output-meta.ts +116 -4
- package/src/tools/search.ts +8 -2
- package/src/tools/skill.ts +15 -0
- package/src/tools/telegram-send.ts +6 -0
- package/src/tools/ultragoal-ask-guard.ts +39 -5
- package/src/utils/file-mentions.ts +39 -7
- package/src/utils/git.ts +73 -0
- package/src/web/scrapers/youtube.ts +24 -12
- package/src/workflow/workflow-intent-diff.ts +29 -6
- package/src/workflow/workflow-intent-report-consumer.ts +221 -0
- package/src/workflow/workflow-intent-report-schema.ts +129 -0
- package/src/workflow/workflow-intent-report.ts +135 -0
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import * as fs from "node:fs/promises";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import type { SkcSessionContext } from "../skc-runtime/session-layout";
|
|
4
|
+
import { resolveSkcSessionForRead, SessionResolutionError } from "../skc-runtime/session-resolution";
|
|
5
|
+
import { computeTaskTokenMetrics, readTaskTokenLogs } from "../task/token-log";
|
|
6
|
+
import type { TaskTokenLog } from "../task/types";
|
|
7
|
+
|
|
8
|
+
const LIVE_RUNNER_SCHEMA_VERSION = 1;
|
|
9
|
+
const BINARY_ID = "skc";
|
|
10
|
+
|
|
11
|
+
function deterministicLog(
|
|
12
|
+
input: number,
|
|
13
|
+
output: number,
|
|
14
|
+
cacheRead: number,
|
|
15
|
+
cacheWrite: number,
|
|
16
|
+
totalTokens: number,
|
|
17
|
+
): TaskTokenLog {
|
|
18
|
+
return {
|
|
19
|
+
subagentId: "root",
|
|
20
|
+
agent: "main",
|
|
21
|
+
turn: 1,
|
|
22
|
+
at: "2026-01-01T00:00:00.000Z",
|
|
23
|
+
input,
|
|
24
|
+
output,
|
|
25
|
+
cacheRead,
|
|
26
|
+
cacheWrite,
|
|
27
|
+
totalTokens,
|
|
28
|
+
model: "fixture-model",
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface LiveRunReportShape {
|
|
33
|
+
schemaVersion: 1;
|
|
34
|
+
binaryId: string;
|
|
35
|
+
fixtureId: string;
|
|
36
|
+
totals: {
|
|
37
|
+
turns: number;
|
|
38
|
+
inputTokens: number;
|
|
39
|
+
outputTokens: number;
|
|
40
|
+
cacheReadTokens: number;
|
|
41
|
+
cacheWriteTokens: number;
|
|
42
|
+
totalTokens: number;
|
|
43
|
+
};
|
|
44
|
+
cacheHitRate: number | null;
|
|
45
|
+
receiptArtifactRatio: number | null;
|
|
46
|
+
spawnDecisions: number | null;
|
|
47
|
+
roi: number | null;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const DETERMINISTIC_FIXTURES: Record<string, readonly TaskTokenLog[]> = {
|
|
51
|
+
"fixed-fixture": [
|
|
52
|
+
{
|
|
53
|
+
subagentId: "root",
|
|
54
|
+
agent: "main",
|
|
55
|
+
turn: 1,
|
|
56
|
+
at: "2026-01-01T00:00:00.000Z",
|
|
57
|
+
input: 100,
|
|
58
|
+
output: 20,
|
|
59
|
+
cacheRead: 40,
|
|
60
|
+
cacheWrite: 10,
|
|
61
|
+
totalTokens: 170,
|
|
62
|
+
model: "fixture-model",
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
subagentId: "1-executor",
|
|
66
|
+
agent: "executor",
|
|
67
|
+
turn: 1,
|
|
68
|
+
at: "2026-01-01T00:00:01.000Z",
|
|
69
|
+
input: 60,
|
|
70
|
+
output: 15,
|
|
71
|
+
cacheRead: 20,
|
|
72
|
+
cacheWrite: 5,
|
|
73
|
+
totalTokens: 100,
|
|
74
|
+
model: "fixture-model",
|
|
75
|
+
},
|
|
76
|
+
],
|
|
77
|
+
};
|
|
78
|
+
const DEFAULT_REDUCTION_FIXTURE_LOGS: Record<string, readonly TaskTokenLog[]> = {
|
|
79
|
+
"pr9.task-recursion.before": [deterministicLog(2400, 420, 600, 200, 3620)],
|
|
80
|
+
"pr9.task-recursion.after": [deterministicLog(1800, 360, 450, 150, 2760)],
|
|
81
|
+
"pr9.output-caps.before": [deterministicLog(22_000, 3_200, 0, 0, 25_200)],
|
|
82
|
+
"pr9.output-caps.after": [deterministicLog(18_000, 2_400, 0, 0, 20_400)],
|
|
83
|
+
"pr9.max-inline-result-bytes.before": [deterministicLog(48_000, 2_000, 0, 0, 50_000)],
|
|
84
|
+
"pr9.max-inline-result-bytes.after": [deterministicLog(12_000, 1_600, 0, 0, 13_600)],
|
|
85
|
+
"pr9.read-artifact-spill-threshold.before": [deterministicLog(96_000, 2_500, 0, 0, 98_500)],
|
|
86
|
+
"pr9.read-artifact-spill-threshold.after": [deterministicLog(18_000, 2_100, 0, 0, 20_100)],
|
|
87
|
+
"pr9.maintenance-pruning.before": [deterministicLog(40_000, 2_000, 18_000, 6_000, 66_000)],
|
|
88
|
+
"pr9.maintenance-pruning.after": [deterministicLog(30_000, 1_800, 10_000, 3_000, 44_800)],
|
|
89
|
+
"pr9.append-only-provider-expansion.before": [deterministicLog(28_000, 1_700, 8_000, 2_500, 40_200)],
|
|
90
|
+
"pr9.append-only-provider-expansion.after": [deterministicLog(20_000, 1_700, 14_000, 2_500, 38_200)],
|
|
91
|
+
"pr9.rpc-compact-deltas.before": [deterministicLog(14_000, 1_200, 6_000, 1_500, 22_700)],
|
|
92
|
+
"pr9.rpc-compact-deltas.after": [deterministicLog(10_000, 900, 6_200, 1_500, 18_600)],
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
export function buildFixtureReport(fixtureId: string, logs: readonly TaskTokenLog[]): LiveRunReportShape {
|
|
96
|
+
const metrics = computeTaskTokenMetrics(logs);
|
|
97
|
+
return {
|
|
98
|
+
schemaVersion: LIVE_RUNNER_SCHEMA_VERSION,
|
|
99
|
+
binaryId: BINARY_ID,
|
|
100
|
+
fixtureId,
|
|
101
|
+
totals: {
|
|
102
|
+
turns: metrics.turns,
|
|
103
|
+
inputTokens: metrics.inputTokens,
|
|
104
|
+
outputTokens: metrics.outputTokens,
|
|
105
|
+
cacheReadTokens: metrics.cacheReadTokens,
|
|
106
|
+
cacheWriteTokens: metrics.cacheWriteTokens,
|
|
107
|
+
totalTokens: metrics.totalTokens,
|
|
108
|
+
},
|
|
109
|
+
cacheHitRate: logs.length === 0 ? null : metrics.cacheHitRate,
|
|
110
|
+
receiptArtifactRatio: null,
|
|
111
|
+
spawnDecisions: null,
|
|
112
|
+
roi: null,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export async function runFixtureReport(fixtureId: string): Promise<number> {
|
|
117
|
+
let resolved: ResolvedFixtureLogs;
|
|
118
|
+
try {
|
|
119
|
+
resolved = await resolveFixtureLogs(fixtureId);
|
|
120
|
+
} catch (error) {
|
|
121
|
+
// e.g. a corrupt token-log. Fail loudly rather than emitting an all-zero report.
|
|
122
|
+
process.stderr.write(
|
|
123
|
+
`failed to build fixture report for ${fixtureId}: ${error instanceof Error ? error.message : String(error)}\n`,
|
|
124
|
+
);
|
|
125
|
+
return 1;
|
|
126
|
+
}
|
|
127
|
+
if (resolved.kind === "unknown") {
|
|
128
|
+
// Neither a known deterministic fixture nor a resolvable SKC session. Emit a
|
|
129
|
+
// bounded error on stderr with a non-zero exit rather than a schema-valid
|
|
130
|
+
// all-zero report, which a before/after benchmark would misread as "0 tokens".
|
|
131
|
+
process.stderr.write(`unknown fixture id and no matching SKC session: ${fixtureId}\n`);
|
|
132
|
+
return 1;
|
|
133
|
+
}
|
|
134
|
+
process.stdout.write(JSON.stringify(buildFixtureReport(fixtureId, resolved.logs)));
|
|
135
|
+
return 0;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
type ResolvedFixtureLogs =
|
|
139
|
+
| { readonly kind: "logs"; readonly logs: readonly TaskTokenLog[] }
|
|
140
|
+
| { readonly kind: "unknown" };
|
|
141
|
+
|
|
142
|
+
async function resolveFixtureLogs(fixtureId: string): Promise<ResolvedFixtureLogs> {
|
|
143
|
+
const deterministic = DETERMINISTIC_FIXTURES[fixtureId] ?? DEFAULT_REDUCTION_FIXTURE_LOGS[fixtureId];
|
|
144
|
+
if (deterministic) return { kind: "logs", logs: deterministic };
|
|
145
|
+
let session: SkcSessionContext;
|
|
146
|
+
try {
|
|
147
|
+
session = await resolveSkcSessionForRead(process.cwd(), {
|
|
148
|
+
flagValue: fixtureId,
|
|
149
|
+
envSessionId: process.env.SKC_SESSION_ID,
|
|
150
|
+
});
|
|
151
|
+
} catch (error) {
|
|
152
|
+
if (error instanceof SessionResolutionError) return { kind: "unknown" };
|
|
153
|
+
throw error;
|
|
154
|
+
}
|
|
155
|
+
// resolveSkcSessionForRead accepts any explicit flagValue as a session id
|
|
156
|
+
// without checking the dir exists, so a typo would otherwise yield a
|
|
157
|
+
// schema-valid all-zero report. Require the session root to exist; a real
|
|
158
|
+
// session with no turns yet still reads as a legitimate empty log set.
|
|
159
|
+
if (!(await directoryExists(session.sessionRoot))) return { kind: "unknown" };
|
|
160
|
+
const logs = await readTaskTokenLogs(path.join(session.sessionRoot, "token-logs"));
|
|
161
|
+
return { kind: "logs", logs };
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
async function directoryExists(dir: string): Promise<boolean> {
|
|
165
|
+
try {
|
|
166
|
+
const stat = await fs.stat(dir);
|
|
167
|
+
return stat.isDirectory();
|
|
168
|
+
} catch {
|
|
169
|
+
return false;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { execFileSync } from "node:child_process";
|
|
2
|
+
|
|
3
|
+
export const RECOMMENDED_MACOS_NOFILE_LIMIT = 4096;
|
|
4
|
+
|
|
5
|
+
export function parseNoFileLimit(text: string): number | undefined {
|
|
6
|
+
const trimmed = text.trim();
|
|
7
|
+
if (!trimmed || trimmed === "unlimited") return undefined;
|
|
8
|
+
const value = Number(trimmed);
|
|
9
|
+
return Number.isSafeInteger(value) && value > 0 ? value : undefined;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function buildMacOSNoFileLimitWarning(currentLimit: number): string {
|
|
13
|
+
return [
|
|
14
|
+
`Warning: macOS file descriptor limit is low (ulimit -n = ${currentLimit}).`,
|
|
15
|
+
'SKC and project dev servers can hit EMFILE / "too many open files" while scanning or watching repositories.',
|
|
16
|
+
"For this terminal session, run:",
|
|
17
|
+
` ulimit -n ${RECOMMENDED_MACOS_NOFILE_LIMIT}`,
|
|
18
|
+
"If your shell refuses that value, raise the per-user launchd limit and restart the terminal:",
|
|
19
|
+
` sudo launchctl limit maxfiles ${RECOMMENDED_MACOS_NOFILE_LIMIT} 65536`,
|
|
20
|
+
"Avoid using huge values such as 2147483646 on macOS; they are commonly rejected or clamped.",
|
|
21
|
+
"Set SKC_SKIP_NOFILE_CHECK=1 to silence this preflight warning.",
|
|
22
|
+
].join("\n");
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface WarnIfMacOSNoFileLimitTooLowDeps {
|
|
26
|
+
platform?: NodeJS.Platform;
|
|
27
|
+
env?: NodeJS.ProcessEnv;
|
|
28
|
+
execFileSync?: typeof execFileSync;
|
|
29
|
+
writeStderr?: (text: string) => void;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function warnIfMacOSNoFileLimitTooLow(deps: WarnIfMacOSNoFileLimitTooLowDeps = {}): void {
|
|
33
|
+
const platform = deps.platform ?? process.platform;
|
|
34
|
+
if (platform !== "darwin") return;
|
|
35
|
+
const env = deps.env ?? process.env;
|
|
36
|
+
if (env.SKC_SKIP_NOFILE_CHECK === "1" || env.SKC_SKIP_NOFILE_CHECK === "true") return;
|
|
37
|
+
|
|
38
|
+
const run = deps.execFileSync ?? execFileSync;
|
|
39
|
+
let currentLimit: number | undefined;
|
|
40
|
+
try {
|
|
41
|
+
currentLimit = parseNoFileLimit(run("/bin/sh", ["-lc", "ulimit -n"], { encoding: "utf8" }));
|
|
42
|
+
} catch {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
if (currentLimit === undefined || currentLimit >= RECOMMENDED_MACOS_NOFILE_LIMIT) return;
|
|
46
|
+
const write = deps.writeStderr ?? (text => process.stderr.write(text));
|
|
47
|
+
write(`${buildMacOSNoFileLimitWarning(currentLimit)}\n`);
|
|
48
|
+
}
|
package/src/cli/notify-cli.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Handles `skc notify` setup/status and the hidden daemon entrypoint.
|
|
5
5
|
*/
|
|
6
6
|
import { createInterface } from "node:readline/promises";
|
|
7
|
-
import { APP_NAME } from "@sayknow-cli/utils";
|
|
7
|
+
import { APP_NAME } from "@sayknow-cli/utils/dirs";
|
|
8
8
|
import chalk from "chalk";
|
|
9
9
|
import type { Settings } from "../config/settings";
|
|
10
10
|
import { getNotificationConfig, maskToken } from "../notifications/config";
|
|
@@ -58,6 +58,11 @@ interface TelegramUser {
|
|
|
58
58
|
allows_users_to_create_topics?: boolean;
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
+
interface TelegramChat {
|
|
62
|
+
id?: number | string;
|
|
63
|
+
type?: string;
|
|
64
|
+
}
|
|
65
|
+
|
|
61
66
|
type ThreadedModeState = "enabled" | "disabled" | "unknown";
|
|
62
67
|
type ThreadedModeFinalLabel = "verified" | "unverified" | "unknown";
|
|
63
68
|
|
|
@@ -148,6 +153,7 @@ async function runSetup(deps: NotifyCommandDeps): Promise<void> {
|
|
|
148
153
|
let chatId: string;
|
|
149
154
|
if (deps.setupChatId?.trim()) {
|
|
150
155
|
chatId = deps.setupChatId.trim();
|
|
156
|
+
await verifyPrivateChatId(fetchImpl, apiBase, token, chatId);
|
|
151
157
|
process.stdout.write(`Using provided chat id ${chatId} (non-interactive).\n`);
|
|
152
158
|
} else {
|
|
153
159
|
const stale = await getUpdates(fetchImpl, apiBase, token, { timeout: 0, allowed_updates: ["message"] });
|
|
@@ -170,16 +176,76 @@ async function runSetup(deps: NotifyCommandDeps): Promise<void> {
|
|
|
170
176
|
);
|
|
171
177
|
}
|
|
172
178
|
|
|
173
|
-
|
|
174
|
-
|
|
179
|
+
type TokenPromptInput = NodeJS.ReadStream & {
|
|
180
|
+
isRaw?: boolean;
|
|
181
|
+
setRawMode?: (mode: boolean) => unknown;
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
type TokenPromptOutput = Pick<NodeJS.WriteStream, "write">;
|
|
185
|
+
|
|
186
|
+
export async function promptForToken(
|
|
187
|
+
input: TokenPromptInput = process.stdin,
|
|
188
|
+
output: TokenPromptOutput = process.stdout,
|
|
189
|
+
): Promise<string> {
|
|
190
|
+
if (!input.isTTY) {
|
|
175
191
|
throw new Error("notify setup requires an interactive TTY unless setupToken is injected.");
|
|
176
192
|
}
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
return (await rl.question("Telegram BotFather token: ")).trim();
|
|
180
|
-
} finally {
|
|
181
|
-
rl.close();
|
|
193
|
+
if (typeof input.setRawMode !== "function") {
|
|
194
|
+
throw new Error("notify setup requires a TTY with raw input support unless setupToken is injected.");
|
|
182
195
|
}
|
|
196
|
+
|
|
197
|
+
output.write("Telegram BotFather token: ");
|
|
198
|
+
const wasRaw = input.isRaw === true;
|
|
199
|
+
input.setRawMode(true);
|
|
200
|
+
|
|
201
|
+
return await new Promise<string>((resolve, reject) => {
|
|
202
|
+
let value = "";
|
|
203
|
+
let settled = false;
|
|
204
|
+
|
|
205
|
+
const cleanup = () => {
|
|
206
|
+
input.off("data", onData);
|
|
207
|
+
input.off("error", onError);
|
|
208
|
+
input.setRawMode?.(wasRaw);
|
|
209
|
+
output.write("\n");
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
const finish = (callback: () => void) => {
|
|
213
|
+
if (settled) return;
|
|
214
|
+
settled = true;
|
|
215
|
+
cleanup();
|
|
216
|
+
callback();
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
const accept = () => finish(() => resolve(value.trim()));
|
|
220
|
+
const cancel = () => finish(() => reject(new Error("Telegram bot token prompt cancelled.")));
|
|
221
|
+
const onError = (error: Error) => finish(() => reject(error));
|
|
222
|
+
const onData = (chunk: Buffer | string) => {
|
|
223
|
+
for (const char of String(chunk)) {
|
|
224
|
+
if (char === "\r" || char === "\n") {
|
|
225
|
+
accept();
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
if (char === "\u0003") {
|
|
229
|
+
cancel();
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
if (char === "\u0004") {
|
|
233
|
+
if (value) accept();
|
|
234
|
+
else cancel();
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
if (char === "\u007f" || char === "\b") {
|
|
238
|
+
value = value.slice(0, -1);
|
|
239
|
+
continue;
|
|
240
|
+
}
|
|
241
|
+
if (char >= " ") value += char;
|
|
242
|
+
}
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
input.on("data", onData);
|
|
246
|
+
input.once("error", onError);
|
|
247
|
+
input.resume();
|
|
248
|
+
});
|
|
183
249
|
}
|
|
184
250
|
|
|
185
251
|
const THREADED_ENABLED_SUCCESS =
|
|
@@ -223,6 +289,24 @@ async function getMe(fetchImpl: typeof fetch, apiBase: string, token: string): P
|
|
|
223
289
|
return user;
|
|
224
290
|
}
|
|
225
291
|
|
|
292
|
+
async function verifyPrivateChatId(
|
|
293
|
+
fetchImpl: typeof fetch,
|
|
294
|
+
apiBase: string,
|
|
295
|
+
token: string,
|
|
296
|
+
chatId: string,
|
|
297
|
+
): Promise<void> {
|
|
298
|
+
const chat = (await callTelegram<unknown>(fetchImpl, apiBase, token, "getChat", { chat_id: chatId })) as
|
|
299
|
+
| TelegramChat
|
|
300
|
+
| undefined;
|
|
301
|
+
if (!chat || typeof chat !== "object") {
|
|
302
|
+
throw new Error("Telegram getChat returned invalid Telegram response: missing valid Chat result.");
|
|
303
|
+
}
|
|
304
|
+
if (chat.type !== "private") {
|
|
305
|
+
const type = typeof chat.type === "string" && chat.type ? chat.type : "unknown";
|
|
306
|
+
throw new Error(`Provided chat id ${chatId} is a ${type} chat; pairing requires a private Telegram chat.`);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
226
310
|
function threadedModeState(user: TelegramUser): ThreadedModeState {
|
|
227
311
|
if (user.has_topics_enabled === true) return "enabled";
|
|
228
312
|
if (user.has_topics_enabled === false) return "disabled";
|
package/src/cli/telegram-cli.ts
CHANGED
|
@@ -4,10 +4,10 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { logger } from "@sayknow-cli/utils";
|
|
7
|
-
import { resolveSkcRuntimeSpawnInfo } from "../daemon/runtime";
|
|
8
7
|
import { isSettingsInitialized, Settings, settings } from "../config/settings";
|
|
9
8
|
import type { TelegramSettings } from "../config/settings-schema";
|
|
10
9
|
import { telegramSettingsToEnv, validateTelegramSettings } from "../config/telegram-env-bridge";
|
|
10
|
+
import { resolveSkcRuntimeSpawnInfo } from "../daemon/runtime";
|
|
11
11
|
|
|
12
12
|
export type TelegramAction = "start" | "status" | "env" | "__gateway";
|
|
13
13
|
|
package/src/cli/update-cli.ts
CHANGED
|
@@ -552,31 +552,107 @@ async function updateViaNpm(packageName: string, expectedVersion: string): Promi
|
|
|
552
552
|
}
|
|
553
553
|
|
|
554
554
|
/**
|
|
555
|
-
*
|
|
555
|
+
* Flush a freshly written file's data to stable storage.
|
|
556
|
+
*
|
|
557
|
+
* Critical on network filesystems (e.g. NFS home directories): `pipeline`
|
|
558
|
+
* resolving does not guarantee the downloaded bytes are durable on the
|
|
559
|
+
* server, so the post-install `skc --version` check can exec a binary whose
|
|
560
|
+
* pages are not yet consistent. The child then faults, the version check
|
|
561
|
+
* fails, and the update is rolled back with "could not verify updated
|
|
562
|
+
* version" even though the download itself succeeded. Explicitly fsyncing
|
|
563
|
+
* before the rename/exec avoids the race.
|
|
556
564
|
*/
|
|
557
|
-
async function
|
|
558
|
-
const
|
|
559
|
-
|
|
565
|
+
async function fsyncFile(filePath: string): Promise<void> {
|
|
566
|
+
const handle = await fs.promises.open(filePath, "r+");
|
|
567
|
+
try {
|
|
568
|
+
await handle.sync();
|
|
569
|
+
} finally {
|
|
570
|
+
await handle.close();
|
|
571
|
+
}
|
|
572
|
+
}
|
|
560
573
|
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
574
|
+
export async function fsyncFileForTest(filePath: string): Promise<void> {
|
|
575
|
+
return fsyncFile(filePath);
|
|
576
|
+
}
|
|
564
577
|
|
|
578
|
+
/**
|
|
579
|
+
* Download a release binary to a temp path, throwing a friendly error when the
|
|
580
|
+
* release asset cannot be fetched.
|
|
581
|
+
*/
|
|
582
|
+
async function downloadBinaryTo(url: string, tempPath: string, binaryName: string): Promise<void> {
|
|
565
583
|
const response = await fetch(url, { redirect: "follow" });
|
|
566
584
|
if (!response.ok || !response.body) {
|
|
567
585
|
throw new Error(formatBinaryDownloadFailureMessage(binaryName, url, response.statusText || response.status));
|
|
568
586
|
}
|
|
569
587
|
const fileStream = fs.createWriteStream(tempPath, { mode: 0o755 });
|
|
570
588
|
await pipeline(response.body, fileStream);
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
/** Injectable steps of the binary update flow (seams for testing ordering). */
|
|
592
|
+
export interface BinaryUpdateFlow {
|
|
593
|
+
download(url: string, tempPath: string): Promise<void>;
|
|
594
|
+
fsync(filePath: string): Promise<void>;
|
|
595
|
+
replace(options: BinaryReplacementOptions): Promise<InstalledVersionVerification>;
|
|
596
|
+
verifyInstalledVersion(expectedVersion: string): Promise<InstalledVersionVerification>;
|
|
597
|
+
/** Best-effort cleanup of the temp file when the flow aborts before replace. */
|
|
598
|
+
removeTemp?(filePath: string): Promise<void>;
|
|
599
|
+
/** Called once fsync has succeeded, right before replacement begins. */
|
|
600
|
+
beforeReplace?(): void;
|
|
601
|
+
}
|
|
571
602
|
|
|
572
|
-
|
|
573
|
-
|
|
603
|
+
/**
|
|
604
|
+
* Orchestrate download → fsync → replace → verify with a strict ordering
|
|
605
|
+
* contract: the downloaded temp binary MUST be flushed to stable storage
|
|
606
|
+
* before it is published (renamed into place) or exec'd for verification.
|
|
607
|
+
*
|
|
608
|
+
* If fsync fails the temp bytes are not durable, so we abort before
|
|
609
|
+
* replacement/verification and clean up the temp file rather than installing a
|
|
610
|
+
* possibly-truncated binary.
|
|
611
|
+
*/
|
|
612
|
+
export async function runBinaryUpdateFlow(
|
|
613
|
+
targetPath: string,
|
|
614
|
+
url: string,
|
|
615
|
+
expectedVersion: string,
|
|
616
|
+
flow: BinaryUpdateFlow,
|
|
617
|
+
): Promise<InstalledVersionVerification> {
|
|
618
|
+
const tempPath = `${targetPath}.new`;
|
|
619
|
+
const backupPath = `${targetPath}.bak`;
|
|
620
|
+
|
|
621
|
+
await flow.download(url, tempPath);
|
|
622
|
+
try {
|
|
623
|
+
await flow.fsync(tempPath);
|
|
624
|
+
} catch (err) {
|
|
625
|
+
if (flow.removeTemp) await flow.removeTemp(tempPath);
|
|
626
|
+
throw err;
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
flow.beforeReplace?.();
|
|
630
|
+
return flow.replace({
|
|
574
631
|
targetPath,
|
|
575
632
|
tempPath,
|
|
576
633
|
backupPath,
|
|
577
634
|
expectedVersion,
|
|
635
|
+
verifyInstalledVersion: flow.verifyInstalledVersion,
|
|
636
|
+
});
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
/**
|
|
640
|
+
* Download a release binary to a target path, replacing an existing file.
|
|
641
|
+
*/
|
|
642
|
+
async function updateViaBinaryAt(targetPath: string, expectedVersion: string): Promise<void> {
|
|
643
|
+
const binaryName = getBinaryName();
|
|
644
|
+
const url = buildReleaseBinaryUrl(expectedVersion);
|
|
645
|
+
console.log(chalk.dim(`Downloading ${binaryName}…`));
|
|
646
|
+
|
|
647
|
+
const verification = await runBinaryUpdateFlow(targetPath, url, expectedVersion, {
|
|
648
|
+
download: (downloadUrl, tempPath) => downloadBinaryTo(downloadUrl, tempPath, binaryName),
|
|
649
|
+
fsync: fsyncFile,
|
|
650
|
+
replace: replaceBinaryForUpdate,
|
|
578
651
|
verifyInstalledVersion: verifyInstalledRuntime,
|
|
652
|
+
removeTemp: unlinkIfExists,
|
|
653
|
+
beforeReplace: () => console.log(chalk.dim("Installing update...")),
|
|
579
654
|
});
|
|
655
|
+
|
|
580
656
|
printVerifiedVersion(expectedVersion);
|
|
581
657
|
if (verification.cleanupWarning) console.warn(chalk.yellow(verification.cleanupWarning));
|
|
582
658
|
printRestartGuidance();
|
package/src/cli.ts
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { Args, type CliConfig, Command, type CommandEntry, Flags, run } from "@sayknow-cli/utils/cli";
|
|
8
8
|
import { APP_NAME, formatBunRuntimeError, MIN_BUN_VERSION, VERSION } from "@sayknow-cli/utils/dirs";
|
|
9
|
+
import { runFixtureReport } from "./cli/fixture-report";
|
|
9
10
|
|
|
10
11
|
if (Bun.semver.order(Bun.version, MIN_BUN_VERSION) < 0) {
|
|
11
12
|
process.stderr.write(
|
|
@@ -35,6 +36,7 @@ export const commands: CommandEntry[] = [
|
|
|
35
36
|
{ name: "gc", load: () => import("./commands/gc").then(m => m.default) },
|
|
36
37
|
{ name: "ralplan", load: () => import("./commands/ralplan").then(m => m.default) },
|
|
37
38
|
{ name: "config", load: () => import("./commands/config").then(m => m.default) },
|
|
39
|
+
{ name: "stats", load: () => import("./commands/stats").then(m => m.default) },
|
|
38
40
|
{ name: "notify", load: () => import("./commands/notify").then(m => m.default) },
|
|
39
41
|
{ name: "daemon", load: () => import("./commands/daemon").then(m => m.default) },
|
|
40
42
|
{ name: "web-search", aliases: ["q"], load: () => import("./commands/web-search").then(m => m.default) },
|
|
@@ -51,6 +53,7 @@ export const commands: CommandEntry[] = [
|
|
|
51
53
|
{ name: "rlm", load: () => import("./commands/rlm").then(m => m.default) },
|
|
52
54
|
{ name: "update", load: () => import("./commands/update").then(m => m.default) },
|
|
53
55
|
{ name: "plugin", load: () => import("./commands/plugin").then(m => m.default) },
|
|
56
|
+
{ name: "completion", load: () => import("./commands/completion").then(m => m.default) },
|
|
54
57
|
{ name: "launch", load: () => import("./commands/launch").then(m => m.default) },
|
|
55
58
|
{ name: "telegram", load: () => import("./commands/telegram").then(m => m.default) },
|
|
56
59
|
];
|
|
@@ -74,6 +77,9 @@ async function installRuntimeGlobals(): Promise<void> {
|
|
|
74
77
|
// `HTTP2Unsupported`. See @sayknow-cli/ai/utils/h2-fetch for details.
|
|
75
78
|
installH2Fetch();
|
|
76
79
|
|
|
80
|
+
const { warnIfMacOSNoFileLimitTooLow } = await import("./cli/nofile-limit");
|
|
81
|
+
warnIfMacOSNoFileLimitTooLow();
|
|
82
|
+
|
|
77
83
|
// Strip macOS malloc-stack-logging env vars before any subprocess is spawned.
|
|
78
84
|
// Otherwise every child bun process (subagents, plugin installs, ptree spawns,
|
|
79
85
|
// etc.) prints a `MallocStackLogging: can't turn off …` warning to stderr.
|
|
@@ -94,6 +100,17 @@ async function runNotifyDaemonInternalFastPath(argv: string[]): Promise<void> {
|
|
|
94
100
|
await runNotifyCommand(cmd);
|
|
95
101
|
}
|
|
96
102
|
|
|
103
|
+
function rootFixtureArg(argv: string[]): { present: boolean; id: string | undefined } {
|
|
104
|
+
for (let i = 0; i < argv.length; i++) {
|
|
105
|
+
const arg = argv[i];
|
|
106
|
+
// Stop at the first subcommand token so a `--fixture` flag belonging to a
|
|
107
|
+
// subcommand never hijacks the root fast-path into fixture-report mode.
|
|
108
|
+
if (isSubcommand(arg)) return { present: false, id: undefined };
|
|
109
|
+
if (arg === "--fixture") return { present: true, id: argv[i + 1] };
|
|
110
|
+
}
|
|
111
|
+
return { present: false, id: undefined };
|
|
112
|
+
}
|
|
113
|
+
|
|
97
114
|
function hasRootFastFlag(argv: string[], flags: readonly string[]): boolean {
|
|
98
115
|
for (const arg of argv) {
|
|
99
116
|
if (isSubcommand(arg)) return false;
|
|
@@ -110,7 +127,7 @@ function hasRootVersionFlag(argv: string[]): boolean {
|
|
|
110
127
|
return hasRootFastFlag(argv, versionFlags);
|
|
111
128
|
}
|
|
112
129
|
|
|
113
|
-
class RootHelpCommand extends Command {
|
|
130
|
+
export class RootHelpCommand extends Command {
|
|
114
131
|
static description = "Sayknow-CLI — an AI coding assistant";
|
|
115
132
|
static hidden = true;
|
|
116
133
|
static args = {
|
|
@@ -231,6 +248,17 @@ export async function runCli(argv: string[]): Promise<void> {
|
|
|
231
248
|
await runSmokeTest();
|
|
232
249
|
return;
|
|
233
250
|
}
|
|
251
|
+
const fixtureArg = rootFixtureArg(argv);
|
|
252
|
+
if (fixtureArg.present) {
|
|
253
|
+
const id = fixtureArg.id;
|
|
254
|
+
if (!id || id.startsWith("-")) {
|
|
255
|
+
process.stderr.write(`${APP_NAME} --fixture requires a fixture id\n`);
|
|
256
|
+
process.exitCode = 1;
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
process.exitCode = await runFixtureReport(id);
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
234
262
|
if (hasRootHelpFlag(argv)) {
|
|
235
263
|
const { renderRootHelp } = await import("@sayknow-cli/utils/cli");
|
|
236
264
|
const { getExtraHelpText } = await import("./cli/fast-help");
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generate shell completion specs for external completion engines.
|
|
3
|
+
*/
|
|
4
|
+
import { Args, Command, Flags } from "@sayknow-cli/utils/cli";
|
|
5
|
+
import { commands, RootHelpCommand } from "../cli";
|
|
6
|
+
import {
|
|
7
|
+
buildSkcFigSpec,
|
|
8
|
+
defaultInshellisenseSpecDir,
|
|
9
|
+
type InstallSkcInshellisenseSpecResult,
|
|
10
|
+
installSkcInshellisenseSpec,
|
|
11
|
+
renderFigSpecModule,
|
|
12
|
+
} from "../cli/completion-cli";
|
|
13
|
+
|
|
14
|
+
const TARGETS = ["inshellisense", "fig"] as const;
|
|
15
|
+
|
|
16
|
+
type CompletionTarget = (typeof TARGETS)[number];
|
|
17
|
+
|
|
18
|
+
function normalizeTarget(target: CompletionTarget): "inshellisense" {
|
|
19
|
+
return target === "fig" ? "inshellisense" : target;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function formatInstallResult(result: InstallSkcInshellisenseSpecResult, customDir: boolean): string {
|
|
23
|
+
const lines = [
|
|
24
|
+
"Installed SKC inshellisense completion spec:",
|
|
25
|
+
` spec: ${result.specPath}`,
|
|
26
|
+
` index: ${result.indexPath} (${result.indexStatus})`,
|
|
27
|
+
];
|
|
28
|
+
if (customDir) {
|
|
29
|
+
lines.push(
|
|
30
|
+
"Add the install directory to inshellisense's [specs].path setting, then restart the inshellisense session.",
|
|
31
|
+
);
|
|
32
|
+
} else {
|
|
33
|
+
lines.push("Restart the inshellisense session, or run `is reinit` if your shell integration needs a refresh.");
|
|
34
|
+
}
|
|
35
|
+
return `${lines.join("\n")}\n`;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export default class Completion extends Command {
|
|
39
|
+
static description = "Generate shell completion specs";
|
|
40
|
+
|
|
41
|
+
static args = {
|
|
42
|
+
target: Args.string({
|
|
43
|
+
description: "Completion format",
|
|
44
|
+
required: false,
|
|
45
|
+
options: TARGETS,
|
|
46
|
+
}),
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
static flags = {
|
|
50
|
+
install: Flags.boolean({ description: "Install the generated spec for inshellisense" }),
|
|
51
|
+
dir: Flags.string({ description: "Install directory (defaults to ~/.fig/autocomplete/build)" }),
|
|
52
|
+
force: Flags.boolean({ description: "Overwrite an existing non-SKC inshellisense index.js" }),
|
|
53
|
+
json: Flags.boolean({ description: "Output JSON instead of JavaScript or status text" }),
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
async run(): Promise<void> {
|
|
57
|
+
const { args, flags } = await this.parse(Completion);
|
|
58
|
+
const target = normalizeTarget((args.target ?? "inshellisense") as CompletionTarget);
|
|
59
|
+
if (target !== "inshellisense") {
|
|
60
|
+
process.stderr.write(`Unsupported completion target: ${target}\n`);
|
|
61
|
+
process.exitCode = 1;
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
if ((flags.dir || flags.force) && !flags.install) {
|
|
65
|
+
process.stderr.write("--dir and --force only apply with --install\n");
|
|
66
|
+
process.exitCode = 1;
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const spec = await buildSkcFigSpec(commands, RootHelpCommand);
|
|
71
|
+
if (!flags.install) {
|
|
72
|
+
process.stdout.write(flags.json ? `${JSON.stringify(spec, null, 2)}\n` : renderFigSpecModule(spec));
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const directory = flags.dir ?? defaultInshellisenseSpecDir();
|
|
77
|
+
const result = await installSkcInshellisenseSpec(spec, { dir: directory, force: flags.force });
|
|
78
|
+
if (flags.json) {
|
|
79
|
+
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
process.stdout.write(formatInstallResult(result, flags.dir !== undefined));
|
|
83
|
+
}
|
|
84
|
+
}
|
package/src/commands/config.ts
CHANGED
|
@@ -29,6 +29,7 @@ export default class Config extends Command {
|
|
|
29
29
|
|
|
30
30
|
static flags = {
|
|
31
31
|
json: Flags.boolean({ description: "Output JSON" }),
|
|
32
|
+
"show-secrets": Flags.boolean({ description: "Show secret-like setting values without redaction (unsafe)" }),
|
|
32
33
|
};
|
|
33
34
|
|
|
34
35
|
async run(): Promise<void> {
|
|
@@ -42,6 +43,7 @@ export default class Config extends Command {
|
|
|
42
43
|
value,
|
|
43
44
|
flags: {
|
|
44
45
|
json: flags.json,
|
|
46
|
+
showSecrets: flags["show-secrets"],
|
|
45
47
|
},
|
|
46
48
|
};
|
|
47
49
|
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { Command, Flags } from "@sayknow-cli/utils/cli";
|
|
2
|
-
import { prepareContributionPrep } from "../session/contribution-prep";
|
|
3
2
|
|
|
4
3
|
function writeText(lines: string[]): void {
|
|
5
4
|
process.stdout.write(`${lines.join("\n")}\n`);
|
|
@@ -19,6 +18,7 @@ export default class ContributionPrep extends Command {
|
|
|
19
18
|
|
|
20
19
|
async run(): Promise<void> {
|
|
21
20
|
const { flags } = await this.parse(ContributionPrep);
|
|
21
|
+
const { prepareContributionPrep } = await import("../session/contribution-prep");
|
|
22
22
|
const cwd = process.cwd();
|
|
23
23
|
const result = await prepareContributionPrep(
|
|
24
24
|
{
|