salmon-loop 0.2.3 → 0.2.13

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.
Files changed (42) hide show
  1. package/dist/cli/chat.js +1 -0
  2. package/dist/cli/commands/chat.js +17 -18
  3. package/dist/cli/commands/context.js +15 -3
  4. package/dist/cli/commands/help-format.js +12 -0
  5. package/dist/cli/commands/registry.js +4 -7
  6. package/dist/cli/commands/run/config-resolution.js +30 -24
  7. package/dist/cli/commands/run/handler.js +16 -17
  8. package/dist/cli/commands/run/loop-params.js +1 -0
  9. package/dist/cli/commands/run/parse-options.js +2 -2
  10. package/dist/cli/commands/run/validate-options.js +0 -5
  11. package/dist/cli/commands/run/verbose.js +2 -7
  12. package/dist/cli/commands/serve.js +29 -22
  13. package/dist/cli/locales/en.js +2 -0
  14. package/dist/cli/program-bootstrap.js +6 -1
  15. package/dist/cli/program-commands.js +4 -0
  16. package/dist/cli/program-options.js +1 -0
  17. package/dist/cli/slash/runtime.js +3 -3
  18. package/dist/cli/utils/output-format.js +6 -0
  19. package/dist/cli/utils/resolve-cli-config.js +98 -0
  20. package/dist/cli/utils/verbose-level.js +8 -0
  21. package/dist/core/config/load.js +22 -8
  22. package/dist/core/config/merge.js +27 -0
  23. package/dist/core/config/paths.js +24 -5
  24. package/dist/core/config/resolve.js +7 -5
  25. package/dist/core/config/validate.js +21 -0
  26. package/dist/core/facades/cli-command-chat.js +1 -1
  27. package/dist/core/facades/cli-context.js +1 -0
  28. package/dist/core/grizzco/engine/transaction/transaction-runner.js +8 -0
  29. package/dist/core/grizzco/steps/preflight.js +4 -1
  30. package/dist/core/intent/chat-intent.js +0 -4
  31. package/dist/core/llm/ai-sdk/request-params.js +1 -1
  32. package/dist/core/protocols/a2a/sdk/executor.js +6 -5
  33. package/dist/core/protocols/acp/formal-agent.js +163 -20
  34. package/dist/core/protocols/acp/permission-provider.js +20 -0
  35. package/dist/core/protocols/shared/execution-request.js +24 -0
  36. package/dist/core/session/compression.js +4 -4
  37. package/dist/core/session/manager.js +3 -2
  38. package/dist/core/strata/layers/worktree.js +4 -4
  39. package/dist/core/tools/builtin/fs.js +4 -4
  40. package/dist/interfaces/cli/task-runner.js +4 -3
  41. package/dist/locales/en.js +52 -0
  42. package/package.json +3 -3
package/dist/cli/chat.js CHANGED
@@ -266,6 +266,7 @@ export async function startChatMode(options) {
266
266
  authorizationMode: 'deferred',
267
267
  userInputProvider,
268
268
  subAgentController,
269
+ permissionMode: options.permissionMode,
269
270
  });
270
271
  return { kind: 'flow', mode: intentDecision.intent, result };
271
272
  })(), CHAT_QUEUE_CONFIG.TASK_TIMEOUT_MS, () => timeoutAbort.abort()).finally(() => {
@@ -1,13 +1,25 @@
1
- import { resolve } from 'path';
2
- import { createPluginRegistry, createPromptRegistry, createRuntimeLlm, setPluginRegistry, setPromptRegistry, ExtensionConfigError, getLogger, normalizePermissionMode, PluginLoader, resolveConfig, resolveExtensions, } from '../../core/facades/cli-command-chat.js';
1
+ import { createPluginRegistry, createPromptRegistry, createRuntimeLlm, setPluginRegistry, setPromptRegistry, ExtensionConfigError, getLogger, normalizePermissionMode, PluginLoader, resolveExtensions, } from '../../core/facades/cli-command-chat.js';
3
2
  import { text } from '../locales/index.js';
4
- import { resolveAuditScope } from '../utils/audit-scope.js';
5
3
  import { resolveLlmOutputPolicyFromCli } from '../utils/llm-output.js';
6
4
  import { createOutcomeReporter } from '../utils/outcome-reporter.js';
5
+ import { resolveCliConfig } from '../utils/resolve-cli-config.js';
7
6
  import { resolveVerifyOption } from '../utils/verify-resolver.js';
8
7
  export async function handleChatCommand(options, command) {
9
8
  const allOptions = command.optsWithGlobals();
10
- const runPath = resolve(allOptions.repo || process.cwd());
9
+ const configResult = await resolveCliConfig({
10
+ repo: allOptions.repo,
11
+ cwd: process.cwd(),
12
+ configPath: allOptions.config,
13
+ enableConfigFile: allOptions.configFile !== false,
14
+ auditScope: allOptions.auditScope,
15
+ verbose: allOptions.verbose,
16
+ logMode: allOptions.logMode,
17
+ });
18
+ if (!configResult.ok) {
19
+ getLogger().error(configResult.message, true);
20
+ process.exit(1);
21
+ }
22
+ const { resolvedConfig, auditScope, repoPath: runPath, verboseLevel } = configResult;
11
23
  const printInstruction = typeof allOptions.print === 'string'
12
24
  ? allOptions.print
13
25
  : undefined;
@@ -28,19 +40,6 @@ export async function handleChatCommand(options, command) {
28
40
  setPluginRegistry(languagePlugins);
29
41
  setPromptRegistry(createPromptRegistry());
30
42
  await PluginLoader.loadPlugins(languagePlugins, runPath);
31
- const resolvedConfig = await resolveConfig({
32
- repoRoot: runPath,
33
- enableConfigFile: true,
34
- });
35
- const auditScopeResolution = resolveAuditScope({
36
- cliValue: allOptions.auditScope,
37
- configValue: resolvedConfig.observability.audit.scope,
38
- });
39
- if (!auditScopeResolution.ok) {
40
- getLogger().error(text.cli.invalidAuditScope(auditScopeResolution.invalid), true);
41
- process.exit(1);
42
- }
43
- const auditScope = auditScopeResolution.value;
44
43
  const rawPermissionMode = allOptions.mode ?? resolvedConfig.permissionMode ?? 'interactive';
45
44
  const permissionMode = normalizePermissionMode(rawPermissionMode);
46
45
  if (!permissionMode) {
@@ -94,7 +93,7 @@ export async function handleChatCommand(options, command) {
94
93
  : allOptions.checkpointStrategy || 'worktree',
95
94
  continue: continueSession,
96
95
  resumeSessionId,
97
- verbose: allOptions.verbose,
96
+ verbose: verboseLevel,
98
97
  llmOutput,
99
98
  markdownTheme: resolvedConfig.markdownTheme,
100
99
  markdownRenderMode: resolvedConfig.markdownRenderMode,
@@ -1,8 +1,21 @@
1
- import { createContextCacheStore, createDefaultPermissionGate, ContextService, defaultPathAdapter, getLogger, resolveConfig, setChurnRankingPolicy, } from '../../core/facades/cli-context.js';
1
+ import { createContextCacheStore, createDefaultPermissionGate, ContextService, getLogger, setChurnRankingPolicy, } from '../../core/facades/cli-context.js';
2
2
  import { text } from '../locales/index.js';
3
+ import { resolveCliConfig } from '../utils/resolve-cli-config.js';
3
4
  export async function handleContextCommand(options, command) {
4
5
  const allOptions = command.optsWithGlobals();
5
- const repoPath = defaultPathAdapter.resolve(allOptions.repo || process.cwd());
6
+ const configResult = await resolveCliConfig({
7
+ repo: allOptions.repo,
8
+ cwd: process.cwd(),
9
+ configPath: allOptions.config,
10
+ enableConfigFile: allOptions.configFile !== false,
11
+ auditScope: allOptions.auditScope,
12
+ logMode: allOptions.logMode,
13
+ });
14
+ if (!configResult.ok) {
15
+ getLogger().error(configResult.message, true);
16
+ process.exit(1);
17
+ }
18
+ const { resolvedConfig, repoPath } = configResult;
6
19
  if (options.file && options.selection) {
7
20
  getLogger().error(text.cli.fileSelectionConflict, true);
8
21
  process.exit(1);
@@ -26,7 +39,6 @@ export async function handleContextCommand(options, command) {
26
39
  }
27
40
  budgetChars = parsed;
28
41
  }
29
- const resolvedConfig = await resolveConfig({ repoRoot: repoPath });
30
42
  setChurnRankingPolicy({
31
43
  primaryBoost: resolvedConfig.raw?.context?.churn?.weight?.primary,
32
44
  rerankWeight: resolvedConfig.raw?.context?.churn?.weight?.rerank,
@@ -0,0 +1,12 @@
1
+ export function formatHelpRows(items) {
2
+ const rows = items.map((item) => {
3
+ const aliases = item.aliases?.length ? ` (${item.aliases.join(', ')})` : '';
4
+ return {
5
+ label: `${item.name}${aliases}`,
6
+ description: item.description,
7
+ };
8
+ });
9
+ const maxName = Math.max(...rows.map((row) => row.label.length), 0);
10
+ return rows.map((row) => `${row.label}`.padEnd(maxName + 2) + row.description).join('\n');
11
+ }
12
+ //# sourceMappingURL=help-format.js.map
@@ -1,6 +1,8 @@
1
+ import { text } from '../locales/index.js';
1
2
  import { allowlistCommand } from './allowlist.js';
2
3
  import { configCommand } from './config.js';
3
4
  import { exitCommand } from './exit.js';
5
+ import { formatHelpRows } from './help-format.js';
4
6
  import { llmOutputCommand } from './llm-output.js';
5
7
  import { logModeCommand } from './log-mode.js';
6
8
  import { modeCommand } from './mode.js';
@@ -29,16 +31,11 @@ const baseCommands = [
29
31
  order: 80,
30
32
  execute: ({ emit }) => {
31
33
  const visible = commands.filter((c) => !c.hidden);
32
- const maxName = Math.max(...visible.map((cmd) => cmd.name.length), 0);
33
- const rows = visible.map((cmd) => {
34
- const paddedName = `${cmd.name}`.padEnd(maxName + 2);
35
- return `${paddedName}${cmd.description}`;
36
- });
37
- const helpMsg = rows.join('\n');
34
+ const helpMsg = formatHelpRows(visible);
38
35
  emit({
39
36
  type: 'log',
40
37
  level: 'info',
41
- message: `Available Commands:\n${helpMsg}`,
38
+ message: text.cli.helpAvailableCommands(helpMsg),
42
39
  timestamp: new Date(),
43
40
  });
44
41
  },
@@ -1,37 +1,43 @@
1
- import { redactConfigForPrint, resolveConfig, ConfigError } from '../../../core/config/index.js';
1
+ import { redactConfigForPrint } from '../../../core/config/index.js';
2
2
  import { getLogger } from '../../../core/facades/cli-observability.js';
3
- import { text } from '../../locales/index.js';
3
+ import { resolveCliConfig } from '../../utils/resolve-cli-config.js';
4
4
  export async function resolveRunConfig(params) {
5
- let resolvedConfig;
6
- try {
7
- resolvedConfig = await resolveConfig({
8
- repoRoot: params.repoPath,
9
- configFilePath: params.cliOptions.config,
10
- enableConfigFile: params.cliOptions.configFile !== false,
11
- });
12
- }
13
- catch (err) {
14
- if (err instanceof ConfigError) {
15
- const msg = text.config.error(err.code || err.message, err.details);
16
- getLogger().error(msg);
17
- if (params.outputFormat === 'json') {
18
- params.writeJsonFailure({ message: msg, errorCode: err.code, repoPath: params.repoPath });
19
- }
20
- return { ok: false, exitCode: 1 };
21
- }
22
- const msg = err instanceof Error ? err.message : String(err);
23
- getLogger().error(text.config.loadFailed(msg));
5
+ const resolved = await resolveCliConfig({
6
+ repoPath: params.repoPath,
7
+ configPath: params.cliOptions.config,
8
+ enableConfigFile: params.cliOptions.configFile !== false,
9
+ auditScope: params.cliOptions.auditScope,
10
+ verbose: params.cliOptions.verbose,
11
+ outputFormat: params.cliOptions.outputFormat,
12
+ logMode: params.cliOptions.logMode,
13
+ });
14
+ if (!resolved.ok) {
15
+ getLogger().error(resolved.message);
24
16
  if (params.outputFormat === 'json') {
25
- params.writeJsonFailure({ message: text.config.loadFailed(msg), repoPath: params.repoPath });
17
+ params.writeJsonFailure({
18
+ message: resolved.message,
19
+ errorCode: resolved.errorCode,
20
+ repoPath: params.repoPath,
21
+ });
26
22
  }
27
23
  return { ok: false, exitCode: 1 };
28
24
  }
29
25
  if (params.cliOptions.printConfig) {
30
- const raw = resolvedConfig.raw || { version: 1 };
26
+ const raw = resolved.resolvedConfig.raw || { version: 1 };
31
27
  const redacted = redactConfigForPrint(raw);
32
28
  process.stdout.write(JSON.stringify(redacted, null, 2) + '\n');
33
29
  return { ok: true, printedConfig: true };
34
30
  }
35
- return { ok: true, resolvedConfig };
31
+ return {
32
+ ok: true,
33
+ resolvedConfig: {
34
+ repoPath: resolved.repoPath,
35
+ verboseLevel: resolved.verboseLevel,
36
+ outputFormat: resolved.outputFormat,
37
+ headlessOutput: resolved.headlessOutput,
38
+ resolvedConfig: resolved.resolvedConfig,
39
+ auditScope: resolved.auditScope,
40
+ },
41
+ };
36
42
  }
37
43
  //# sourceMappingURL=config-resolution.js.map
@@ -3,8 +3,9 @@ import { buildSessionConversationContext, createPluginRegistry, createPromptRegi
3
3
  import { createStdoutWriter } from '../../headless/stdout-writer.js';
4
4
  import { text } from '../../locales/index.js';
5
5
  import { StderrLogReporter } from '../../reporters/stderr-log-reporter.js';
6
- import { resolveAuditScope } from '../../utils/audit-scope.js';
7
6
  import { createOutcomeReporter } from '../../utils/outcome-reporter.js';
7
+ import { resolveOutputFormat } from '../../utils/output-format.js';
8
+ import { resolveCliCommonOptions } from '../../utils/resolve-cli-config.js';
8
9
  import { buildRunAssistantMessage } from './assistant-message.js';
9
10
  import { resolveRunConfig } from './config-resolution.js';
10
11
  import { handleEarlyRunCommandErrors } from './early-errors.js';
@@ -22,7 +23,7 @@ import { createRuntimeLlmAndWarn } from './runtime-llm.js';
22
23
  import { resolveRunRuntimeOptions } from './runtime-options.js';
23
24
  import { initializeSession } from './session.js';
24
25
  import { buildStructuredOutputState } from './structured-output.js';
25
- import { logRunVerboseSummary, resolveVerboseLevel } from './verbose.js';
26
+ import { logRunVerboseSummary } from './verbose.js';
26
27
  export async function handleRunCommand(options, command) {
27
28
  const parsed = parseRunCommandOptions(command);
28
29
  const allOptions = parsed.allOptions;
@@ -36,13 +37,20 @@ export async function handleRunCommand(options, command) {
36
37
  const explicitInstruction = parsed.explicitInstruction;
37
38
  const jsonSchemaSpec = parsed.jsonSchemaSpec;
38
39
  const rawOutputFormat = parsed.rawOutputFormat;
39
- if (rawOutputFormat !== 'text' &&
40
- rawOutputFormat !== 'stream-json' &&
41
- rawOutputFormat !== 'json') {
40
+ const commonOptions = resolveCliCommonOptions({
41
+ repoPath: runPath,
42
+ verbose: allOptions.verbose,
43
+ outputFormat: rawOutputFormat,
44
+ });
45
+ if (!commonOptions.ok) {
46
+ getLogger().error(commonOptions.message, true);
47
+ process.exit(1);
48
+ }
49
+ const outputFormat = commonOptions.options.outputFormat ?? resolveOutputFormat(rawOutputFormat);
50
+ if (!outputFormat) {
42
51
  getLogger().error(text.cli.invalidOutputFormat(rawOutputFormat), true);
43
52
  process.exit(1);
44
53
  }
45
- const outputFormat = rawOutputFormat;
46
54
  const headlessOutput = outputFormat !== 'text';
47
55
  const rawOutputProfile = parsed.rawOutputProfile;
48
56
  const outputProfileForStreamJson = parsed.outputProfileForStreamJson;
@@ -167,7 +175,7 @@ export async function handleRunCommand(options, command) {
167
175
  }
168
176
  if ('printedConfig' in configResult)
169
177
  return;
170
- const resolvedConfig = configResult.resolvedConfig;
178
+ const { resolvedConfig, auditScope } = configResult.resolvedConfig;
171
179
  const runtimeOptions = await resolveRunRuntimeOptions({
172
180
  repoPath: runPath,
173
181
  resolvedConfig,
@@ -253,7 +261,7 @@ export async function handleRunCommand(options, command) {
253
261
  if (!effectiveVerify) {
254
262
  getLogger().warn(text.verify.noCommandFound);
255
263
  }
256
- const verboseLevel = resolveVerboseLevel(allOptions.verbose);
264
+ const verboseLevel = commonOptions.options.verboseLevel;
257
265
  logRunVerboseSummary({
258
266
  verboseLevel,
259
267
  instruction: instructionText,
@@ -267,15 +275,6 @@ export async function handleRunCommand(options, command) {
267
275
  configPath: resolvedConfig.source.used ? resolvedConfig.source.path || '' : undefined,
268
276
  });
269
277
  try {
270
- const auditScopeResolution = resolveAuditScope({
271
- cliValue: allOptions.auditScope,
272
- configValue: resolvedConfig.observability.audit.scope,
273
- });
274
- if (!auditScopeResolution.ok) {
275
- getLogger().error(text.cli.invalidAuditScope(auditScopeResolution.invalid), true);
276
- process.exit(1);
277
- }
278
- const auditScope = auditScopeResolution.value;
279
278
  const { llm } = createRuntimeLlmAndWarn({
280
279
  llmConfig: resolvedConfig.llm,
281
280
  langfuseEnabled: resolvedConfig.observability.langfuse.enabled,
@@ -30,6 +30,7 @@ export function buildRunLoopParams(params) {
30
30
  forceNonInteractive: params.headlessOutput || params.printMode,
31
31
  permissionMode: params.permissionMode,
32
32
  }),
33
+ permissionMode: params.permissionMode,
33
34
  extensions: params.extensions,
34
35
  permissionRules: params.permissionRules,
35
36
  eventPayload: params.headlessIncludeToolInput ||
@@ -1,4 +1,4 @@
1
- import { resolve } from 'path';
1
+ import { resolveRepoPath } from '../../utils/resolve-cli-config.js';
2
2
  function splitToolRules(raw) {
3
3
  const parts = [];
4
4
  const push = (s) => {
@@ -20,7 +20,7 @@ function splitToolRules(raw) {
20
20
  }
21
21
  export function parseRunCommandOptions(command) {
22
22
  const allOptions = command.optsWithGlobals();
23
- const repoPath = resolve(allOptions.repo || process.cwd());
23
+ const repoPath = resolveRepoPath({ repo: allOptions.repo, cwd: process.cwd() });
24
24
  const continueSession = Boolean(allOptions.continue);
25
25
  const resumeSessionId = typeof allOptions.resume === 'string'
26
26
  ? allOptions.resume
@@ -1,8 +1,3 @@
1
- export function resolveOutputFormat(raw) {
2
- if (raw === 'text' || raw === 'stream-json' || raw === 'json')
3
- return raw;
4
- return undefined;
5
- }
6
1
  export function validateRunCommandOptions(params) {
7
2
  const { parsed } = params;
8
3
  if (parsed.explicitInstruction && parsed.printInstruction) {
@@ -1,12 +1,7 @@
1
1
  import { getLogger } from '../../../core/facades/cli-observability.js';
2
2
  import { text } from '../../locales/index.js';
3
- export function resolveVerboseLevel(raw) {
4
- if (raw === true)
5
- return 'basic';
6
- if (typeof raw === 'string')
7
- return raw;
8
- return undefined;
9
- }
3
+ import { resolveVerboseLevel } from '../../utils/verbose-level.js';
4
+ export { resolveVerboseLevel };
10
5
  export function logRunVerboseSummary(params) {
11
6
  if (!params.verboseLevel)
12
7
  return;
@@ -1,8 +1,8 @@
1
- import { buildA2AAgentCard, buildSidecarRouteDescriptors, createAcpFormalAgent, createAgentServerRuntime, createInteractionFacade, createSalmonTaskExecutor, createTaskEventBus, createPluginRegistry, createPromptRegistry, defaultSidecarRouteCatalog, defaultPathAdapter, getSidecarListenOptions, getUserAcpSessionStorePath, GitSnapshotCheckpointService, getLogger, mkdir, PlainReporter, PluginLoader, resolveConfig, resolveExtensions, runSalmonLoop, setPluginRegistry, setPromptRegistry, startAcpStdioServer, StderrReporter, } from '../../core/facades/cli-serve.js';
1
+ import { buildA2AAgentCard, buildSidecarRouteDescriptors, createAcpFormalAgent, createAgentServerRuntime, createInteractionFacade, createSalmonTaskExecutor, createTaskEventBus, createPluginRegistry, createPromptRegistry, defaultPathAdapter, defaultSidecarRouteCatalog, getSidecarListenOptions, getUserAcpSessionStorePath, GitSnapshotCheckpointService, getLogger, mkdir, PlainReporter, PluginLoader, resolveExtensions, runSalmonLoop, setPluginRegistry, setPromptRegistry, startAcpStdioServer, StderrReporter, } from '../../core/facades/cli-serve.js';
2
2
  import { createTerminalAuthorizationProvider } from '../authorization/provider.js';
3
3
  import { text } from '../locales/index.js';
4
- import { resolveAuditScope } from '../utils/audit-scope.js';
5
4
  import { createOutcomeReporter } from '../utils/outcome-reporter.js';
5
+ import { resolveCliConfig } from '../utils/resolve-cli-config.js';
6
6
  import { createRuntimeLlmAndWarn } from './run/runtime-llm.js';
7
7
  function parsePort(value, fallback) {
8
8
  if (value === undefined || value === null || value === '')
@@ -45,18 +45,20 @@ export function registerServeCommands(program) {
45
45
  }
46
46
  export async function handleServeCommand(_options, command) {
47
47
  const allOptions = command.optsWithGlobals();
48
- const defaultRepoPath = defaultPathAdapter.resolve(allOptions.repo || process.cwd());
49
- const resolvedConfig = await resolveConfig({ repoRoot: defaultRepoPath });
50
- const serverConfig = resolvedConfig.server;
51
- const auditScopeResolution = resolveAuditScope({
52
- cliValue: allOptions.auditScope,
53
- configValue: resolvedConfig.observability.audit.scope,
48
+ const configResult = await resolveCliConfig({
49
+ repo: allOptions.repo,
50
+ cwd: process.cwd(),
51
+ configPath: allOptions.config,
52
+ enableConfigFile: allOptions.configFile !== false,
53
+ auditScope: allOptions.auditScope,
54
+ logMode: allOptions.logMode,
54
55
  });
55
- if (!auditScopeResolution.ok) {
56
- getLogger().error(text.cli.invalidAuditScope(auditScopeResolution.invalid), true);
56
+ if (!configResult.ok) {
57
+ getLogger().error(configResult.message, true);
57
58
  process.exit(1);
58
59
  }
59
- const auditScope = auditScopeResolution.value;
60
+ const { resolvedConfig, auditScope, repoPath: defaultRepoPath } = configResult;
61
+ const serverConfig = resolvedConfig.server;
60
62
  const rawA2aHost = allOptions.a2aHost ?? serverConfig?.a2a?.host;
61
63
  const a2aHost = String(rawA2aHost ?? '127.0.0.1');
62
64
  const rawA2aPort = allOptions.a2aPort ?? serverConfig?.a2a?.port;
@@ -113,6 +115,7 @@ export async function handleServeCommand(_options, command) {
113
115
  langfuseSessionId: resolvedConfig.observability.langfuse.sessionId,
114
116
  langfuseUserId: resolvedConfig.observability.langfuse.userId,
115
117
  auditScope,
118
+ permissionMode: resolvedConfig.permissionMode,
116
119
  languagePlugins,
117
120
  fileSystemOverride,
118
121
  authorizationProvider: authorizationProvider ?? defaultAuthorizationProvider,
@@ -228,8 +231,19 @@ export async function handleServeCommand(_options, command) {
228
231
  }
229
232
  export async function handleServeAcpCommand(_options, command) {
230
233
  const allOptions = command.optsWithGlobals();
231
- const defaultRepoPath = defaultPathAdapter.resolve(allOptions.repo || process.cwd());
232
- const resolvedConfig = await resolveConfig({ repoRoot: defaultRepoPath });
234
+ const configResult = await resolveCliConfig({
235
+ repo: allOptions.repo,
236
+ cwd: process.cwd(),
237
+ configPath: allOptions.config,
238
+ enableConfigFile: allOptions.configFile !== false,
239
+ auditScope: allOptions.auditScope,
240
+ logMode: allOptions.logMode,
241
+ });
242
+ if (!configResult.ok) {
243
+ getLogger().error(configResult.message, true);
244
+ process.exit(1);
245
+ }
246
+ const { resolvedConfig, auditScope, repoPath: defaultRepoPath } = configResult;
233
247
  getLogger().setReporter(allOptions.color === false ? new StderrReporter() : new PlainReporter());
234
248
  const languagePlugins = createPluginRegistry();
235
249
  setPluginRegistry(languagePlugins);
@@ -240,14 +254,6 @@ export async function handleServeAcpCommand(_options, command) {
240
254
  llmConfig: resolvedConfig.llm,
241
255
  langfuseEnabled: resolvedConfig.observability.langfuse.enabled,
242
256
  });
243
- const auditScopeResolution = resolveAuditScope({
244
- cliValue: allOptions.auditScope,
245
- configValue: resolvedConfig.observability.audit.scope,
246
- });
247
- if (!auditScopeResolution.ok) {
248
- getLogger().error(text.cli.invalidAuditScope(auditScopeResolution.invalid), true);
249
- process.exit(1);
250
- }
251
257
  const outcomeReporter = createOutcomeReporter({
252
258
  enabled: resolvedConfig.observability.langfuse.outcome,
253
259
  endpoint: resolvedConfig.observability.langfuse.endpoint,
@@ -275,7 +281,8 @@ export async function handleServeAcpCommand(_options, command) {
275
281
  outcomeReporter,
276
282
  langfuseSessionId: resolvedConfig.observability.langfuse.sessionId,
277
283
  langfuseUserId: resolvedConfig.observability.langfuse.userId,
278
- auditScope: auditScopeResolution.value,
284
+ auditScope,
285
+ permissionMode: resolvedConfig.permissionMode,
279
286
  languagePlugins,
280
287
  authorizationProvider: authorizationProvider ?? defaultAuthorizationProvider,
281
288
  authorizationMode,
@@ -35,6 +35,7 @@ export const en = {
35
35
  chatAnswerEmpty: 'No answer produced.',
36
36
  unknownCommand: (cmd) => `Unknown command: ${cmd}. Type /help for available commands.`,
37
37
  helpAvailableCommands: (rows) => `Available Commands:\n${rows}`,
38
+ programHelpFooter: '\nTips:\n Use "s8p <command> --help" to see command-specific options.\n In chat, type /help to list slash commands.',
38
39
  slashHandlerUnavailable: 'Command handler unavailable',
39
40
  slashInternalError: 'Internal error',
40
41
  skillNoPrompt: (id) => `Skill ${id} did not produce a prompt`,
@@ -234,6 +235,7 @@ export const en = {
234
235
  preflightPolicyOption: 'Preflight policy (lenient: continue on test failure, strict: fail on test failure)',
235
236
  checkpointStrategyOption: 'Checkpoint strategy to use (direct, worktree)',
236
237
  permissionModeOption: 'Permission mode (interactive, yolo)',
238
+ logModeOption: 'UI log mode (quiet, normal, debug)',
237
239
  environmentModeOption: 'Worktree environment mode (strict, parity)',
238
240
  applyBackOnDirtyOption: 'Behavior when apply-back detects a dirty workspace (3way, abort)',
239
241
  worktreePrepareOption: 'Optional setup command to run inside worktree',
@@ -8,7 +8,12 @@ export function bootstrapProgram() {
8
8
  chalk.level = 3;
9
9
  const program = new Command();
10
10
  program.exitOverride();
11
- program.name('s8p').alias('salmonloop').description(text.cli.programDescription).version('0.2.0');
11
+ program
12
+ .name('s8p')
13
+ .alias('salmonloop')
14
+ .description(text.cli.programDescription)
15
+ .version('0.2.0')
16
+ .addHelpText('after', text.cli.programHelpFooter);
12
17
  return program;
13
18
  }
14
19
  //# sourceMappingURL=program-bootstrap.js.map
@@ -90,6 +90,8 @@ export function registerProgramCommands(program) {
90
90
  .command('context')
91
91
  .description(text.cli.contextDescription)
92
92
  .option('-i, --instruction <instruction>', text.cli.instructionOption)
93
+ .option('--config <path>', text.cli.configOption)
94
+ .option('--no-config-file', text.cli.noConfigFileOption)
93
95
  .option('-f, --file <path>', text.cli.fileOption)
94
96
  .option('-s, --selection <text>', text.cli.selectionOption)
95
97
  .option('--diff-scope <scope>', text.cli.contextDiffScopeOption, 'primary')
@@ -100,6 +102,8 @@ export function registerProgramCommands(program) {
100
102
  program
101
103
  .command('chat', { isDefault: true })
102
104
  .description('Enter interactive chat mode (default)')
105
+ .option('--config <path>', text.cli.configOption)
106
+ .option('--no-config-file', text.cli.noConfigFileOption)
103
107
  .option('--verbose [level]', text.cli.verboseOption)
104
108
  .action(handleChatCommand);
105
109
  }
@@ -7,6 +7,7 @@ export function configureGlobalProgramOptions(program) {
7
7
  .option('--resume <sessionId>', text.cli.resumeOption)
8
8
  .option('-v, --verify <command>', text.cli.verifyOption)
9
9
  .option('--no-verify', 'Disable verification')
10
+ .option('--log-mode <mode>', text.cli.logModeOption)
10
11
  .option('-cs, --checkpoint-strategy <type>', text.cli.checkpointStrategyOption, 'worktree')
11
12
  .option('--mode <mode>', text.cli.permissionModeOption, 'interactive')
12
13
  .option('--llm-output <kinds>', text.cli.llmOutputOption)
@@ -1,4 +1,5 @@
1
1
  import { createSlashRegistry, createStandardToolstack, executeSkill, logIgnoredError, getLogger, RuntimeEnvironment, SkillLoader, SlashRouter, } from '../../core/facades/cli-slash-runtime.js';
2
+ import { formatHelpRows } from '../commands/help-format.js';
2
3
  import { suggestSubcommands } from '../commands/subcommand-suggestions.js';
3
4
  import { text } from '../locales/index.js';
4
5
  function isSafeSkillId(id) {
@@ -63,12 +64,11 @@ export async function createCliSlashRuntime(options) {
63
64
  const handler = {
64
65
  execute: async (_req) => {
65
66
  const visible = registry.list().filter((c) => !c.hidden);
66
- const maxName = Math.max(...visible.map((c) => c.name.length), 0);
67
- const rows = visible.map((c) => `${c.name}`.padEnd(maxName + 2) + c.description);
67
+ const rows = formatHelpRows(visible);
68
68
  options.emit({
69
69
  type: 'log',
70
70
  level: 'info',
71
- message: text.cli.helpAvailableCommands(rows.join('\n')),
71
+ message: text.cli.helpAvailableCommands(rows),
72
72
  timestamp: new Date(),
73
73
  });
74
74
  return { kind: 'consumed' };
@@ -0,0 +1,6 @@
1
+ export function resolveOutputFormat(raw) {
2
+ if (raw === 'text' || raw === 'stream-json' || raw === 'json')
3
+ return raw;
4
+ return undefined;
5
+ }
6
+ //# sourceMappingURL=output-format.js.map
@@ -0,0 +1,98 @@
1
+ import { defaultPathAdapter } from '../../core/adapters/path/path-adapter.js';
2
+ import { ConfigError, normalizeUiLogMode, resolveConfig } from '../../core/config/index.js';
3
+ import { text } from '../locales/index.js';
4
+ import { resolveAuditScope } from './audit-scope.js';
5
+ import { resolveOutputFormat } from './output-format.js';
6
+ import { resolveVerboseLevel } from './verbose-level.js';
7
+ export async function resolveCliConfig(params) {
8
+ const common = resolveCliCommonOptions({
9
+ repoPath: params.repoPath,
10
+ repo: params.repo,
11
+ cwd: params.cwd,
12
+ verbose: params.verbose,
13
+ outputFormat: params.outputFormat,
14
+ });
15
+ if (!common.ok) {
16
+ return { ok: false, message: common.message };
17
+ }
18
+ const { repoPath, verboseLevel, outputFormat, headlessOutput } = common.options;
19
+ let resolvedConfig;
20
+ try {
21
+ resolvedConfig = await resolveConfig({
22
+ repoRoot: repoPath,
23
+ configFilePath: params.configPath,
24
+ enableConfigFile: params.enableConfigFile !== false,
25
+ });
26
+ }
27
+ catch (err) {
28
+ if (err instanceof ConfigError) {
29
+ return {
30
+ ok: false,
31
+ message: text.config.error(err.code || err.message, err.details),
32
+ errorCode: err.code,
33
+ };
34
+ }
35
+ const msg = err instanceof Error ? err.message : String(err);
36
+ return { ok: false, message: text.config.loadFailed(msg) };
37
+ }
38
+ const auditScopeResolution = resolveAuditScope({
39
+ cliValue: params.auditScope,
40
+ configValue: resolvedConfig.observability.audit.scope,
41
+ });
42
+ if (!auditScopeResolution.ok) {
43
+ return {
44
+ ok: false,
45
+ message: text.cli.invalidAuditScope(auditScopeResolution.invalid),
46
+ };
47
+ }
48
+ if (params.logMode !== undefined) {
49
+ const normalized = normalizeUiLogMode(params.logMode);
50
+ if (!normalized) {
51
+ return { ok: false, message: text.cli.logModeInvalid(String(params.logMode)) };
52
+ }
53
+ resolvedConfig = {
54
+ ...resolvedConfig,
55
+ ui: {
56
+ ...resolvedConfig.ui,
57
+ logMode: normalized,
58
+ },
59
+ };
60
+ }
61
+ return {
62
+ ok: true,
63
+ repoPath,
64
+ verboseLevel,
65
+ outputFormat,
66
+ headlessOutput,
67
+ resolvedConfig,
68
+ auditScope: auditScopeResolution.value,
69
+ };
70
+ }
71
+ export function resolveRepoPath(params) {
72
+ return defaultPathAdapter.resolve(params.repo || params.cwd || process.cwd());
73
+ }
74
+ export function resolveCliCommonOptions(params) {
75
+ const repoPath = params.repoPath ?? resolveRepoPath({ repo: params.repo, cwd: params.cwd });
76
+ const verboseLevel = resolveVerboseLevel(params.verbose);
77
+ let outputFormat;
78
+ let headlessOutput;
79
+ if (params.outputFormat !== undefined) {
80
+ const raw = String(params.outputFormat || '');
81
+ const resolved = resolveOutputFormat(raw);
82
+ if (!resolved) {
83
+ return { ok: false, message: text.cli.invalidOutputFormat(raw) };
84
+ }
85
+ outputFormat = resolved;
86
+ headlessOutput = resolved !== 'text';
87
+ }
88
+ return {
89
+ ok: true,
90
+ options: {
91
+ repoPath,
92
+ verboseLevel,
93
+ outputFormat,
94
+ headlessOutput,
95
+ },
96
+ };
97
+ }
98
+ //# sourceMappingURL=resolve-cli-config.js.map
@@ -0,0 +1,8 @@
1
+ export function resolveVerboseLevel(raw) {
2
+ if (raw === true)
3
+ return 'basic';
4
+ if (typeof raw === 'string')
5
+ return raw;
6
+ return undefined;
7
+ }
8
+ //# sourceMappingURL=verbose-level.js.map