all-hands-cli 0.1.0
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/.allhands/README.md +75 -0
- package/.allhands/agents/compounder.yaml +15 -0
- package/.allhands/agents/coordinator.yaml +17 -0
- package/.allhands/agents/documentor.yaml +15 -0
- package/.allhands/agents/e2e-test-planner.yaml +17 -0
- package/.allhands/agents/emergent.yaml +22 -0
- package/.allhands/agents/executor.yaml +14 -0
- package/.allhands/agents/ideation.yaml +11 -0
- package/.allhands/agents/initiative-steering.yaml +19 -0
- package/.allhands/agents/judge.yaml +13 -0
- package/.allhands/agents/planner.yaml +19 -0
- package/.allhands/agents/pr-reviewer.yaml +15 -0
- package/.allhands/docs.json +5 -0
- package/.allhands/docs.local.json +26 -0
- package/.allhands/flows/COMPOUNDING.md +203 -0
- package/.allhands/flows/COORDINATION.md +89 -0
- package/.allhands/flows/CORE.md +87 -0
- package/.allhands/flows/DOCUMENTATION.md +218 -0
- package/.allhands/flows/E2E_TEST_PLAN_BUILDING.md +140 -0
- package/.allhands/flows/EMERGENT_PLANNING.md +57 -0
- package/.allhands/flows/IDEATION_SCOPING.md +154 -0
- package/.allhands/flows/INITIATIVE_STEERING.md +110 -0
- package/.allhands/flows/JUDGE_REVIEWING.md +79 -0
- package/.allhands/flows/PROMPT_TASK_EXECUTION.md +68 -0
- package/.allhands/flows/PR_REVIEWING.md +43 -0
- package/.allhands/flows/SPEC_PLANNING.md +216 -0
- package/.allhands/flows/harness/WRITING_HARNESS_FLOWS.md +27 -0
- package/.allhands/flows/harness/WRITING_HARNESS_KNOWLEDGE.md +27 -0
- package/.allhands/flows/harness/WRITING_HARNESS_ORCHESTRATION.md +27 -0
- package/.allhands/flows/harness/WRITING_HARNESS_SKILLS.md +27 -0
- package/.allhands/flows/harness/WRITING_HARNESS_TOOLS.md +27 -0
- package/.allhands/flows/harness/WRITING_HARNESS_VALIDATION_TOOLING.md +27 -0
- package/.allhands/flows/shared/CODEBASE_UNDERSTANDING.md +72 -0
- package/.allhands/flows/shared/CREATE_HARNESS_SPEC.md +48 -0
- package/.allhands/flows/shared/CREATE_SPEC.md +41 -0
- package/.allhands/flows/shared/CREATE_VALIDATION_TOOLING_SPEC.md +70 -0
- package/.allhands/flows/shared/DOCUMENTATION_DISCOVERY.md +123 -0
- package/.allhands/flows/shared/DOCUMENTATION_WRITER.md +101 -0
- package/.allhands/flows/shared/EMERGENT_REFINEMENT_ANALYSIS.md +76 -0
- package/.allhands/flows/shared/EXTERNAL_TECH_GUIDANCE.md +97 -0
- package/.allhands/flows/shared/IDEATION_CODEBASE_GROUNDING.md +49 -0
- package/.allhands/flows/shared/PLAN_DEEPENING.md +152 -0
- package/.allhands/flows/shared/PROMPT_TASKS_CURATION.md +113 -0
- package/.allhands/flows/shared/PROMPT_VALIDATION_REVIEW.MD +99 -0
- package/.allhands/flows/shared/QUICK_PREMORTEM.md +70 -0
- package/.allhands/flows/shared/RESEARCH_GUIDANCE.md +38 -0
- package/.allhands/flows/shared/REVIEW_OPTIONS_BREAKDOWN.md +68 -0
- package/.allhands/flows/shared/SKILL_EXTRACTION.md +84 -0
- package/.allhands/flows/shared/SPEC_FLOW_ANALYSIS.md +119 -0
- package/.allhands/flows/shared/TDD_WORKFLOW.md +109 -0
- package/.allhands/flows/shared/UTILIZE_VALIDATION_TOOLING.md +84 -0
- package/.allhands/flows/shared/WRITING_HARNESS_FLOWS.md +11 -0
- package/.allhands/flows/shared/WRITING_HARNESS_MCP_TOOLS.md +84 -0
- package/.allhands/flows/shared/jury/ARCHITECTURE_REVIEW.md +91 -0
- package/.allhands/flows/shared/jury/BEST_PRACTICES_REVIEW.md +80 -0
- package/.allhands/flows/shared/jury/CLAIM_VERIFICATION_REVIEW.md +101 -0
- package/.allhands/flows/shared/jury/EXPECTATIONS_FIT_REVIEW.md +78 -0
- package/.allhands/flows/shared/jury/MAINTAINABILITY_REVIEW.md +110 -0
- package/.allhands/flows/shared/jury/PROMPTS_EXPECTATIONS_FIT.md +74 -0
- package/.allhands/flows/shared/jury/PROMPTS_FLOW_ANALYSIS.md +92 -0
- package/.allhands/flows/shared/jury/PROMPTS_YAGNI.md +78 -0
- package/.allhands/flows/shared/jury/PROMPT_PREMORTEM.md +125 -0
- package/.allhands/flows/shared/jury/SECURITY_REVIEW.md +86 -0
- package/.allhands/flows/shared/jury/YAGNI_REVIEW.md +82 -0
- package/.allhands/flows/wip/DEBUG_INVESTIGATION.md +162 -0
- package/.allhands/flows/wip/MEMORY_RECALL.md +62 -0
- package/.allhands/harness/ah +131 -0
- package/.allhands/harness/package-lock.json +5292 -0
- package/.allhands/harness/package.json +52 -0
- package/.allhands/harness/src/__tests__/e2e/commands.test.ts +307 -0
- package/.allhands/harness/src/__tests__/e2e/event-loop.test.ts +539 -0
- package/.allhands/harness/src/__tests__/e2e/hooks.test.ts +427 -0
- package/.allhands/harness/src/__tests__/e2e/new-initiative-routing.test.ts +137 -0
- package/.allhands/harness/src/__tests__/e2e/run-e2e.ts +109 -0
- package/.allhands/harness/src/__tests__/e2e/specs-type.test.ts +210 -0
- package/.allhands/harness/src/__tests__/e2e/validation-hooks.test.ts +669 -0
- package/.allhands/harness/src/__tests__/e2e/validation-path-consistency.test.ts +354 -0
- package/.allhands/harness/src/__tests__/e2e/validation.test.ts +528 -0
- package/.allhands/harness/src/__tests__/harness/assertions.ts +318 -0
- package/.allhands/harness/src/__tests__/harness/cli-runner.ts +359 -0
- package/.allhands/harness/src/__tests__/harness/fixture.ts +384 -0
- package/.allhands/harness/src/__tests__/harness/hook-runner.ts +411 -0
- package/.allhands/harness/src/__tests__/harness/index.ts +122 -0
- package/.allhands/harness/src/cli.ts +36 -0
- package/.allhands/harness/src/commands/complexity.ts +177 -0
- package/.allhands/harness/src/commands/context7.ts +202 -0
- package/.allhands/harness/src/commands/docs.ts +557 -0
- package/.allhands/harness/src/commands/hooks.ts +24 -0
- package/.allhands/harness/src/commands/index.ts +51 -0
- package/.allhands/harness/src/commands/knowledge.ts +382 -0
- package/.allhands/harness/src/commands/memories.ts +302 -0
- package/.allhands/harness/src/commands/notify.ts +61 -0
- package/.allhands/harness/src/commands/oracle.ts +158 -0
- package/.allhands/harness/src/commands/perplexity.ts +220 -0
- package/.allhands/harness/src/commands/planning.ts +245 -0
- package/.allhands/harness/src/commands/schema.ts +73 -0
- package/.allhands/harness/src/commands/skills.ts +128 -0
- package/.allhands/harness/src/commands/solutions.ts +353 -0
- package/.allhands/harness/src/commands/spawn.ts +158 -0
- package/.allhands/harness/src/commands/specs.ts +532 -0
- package/.allhands/harness/src/commands/tavily.ts +226 -0
- package/.allhands/harness/src/commands/tools.ts +579 -0
- package/.allhands/harness/src/commands/trace.ts +327 -0
- package/.allhands/harness/src/commands/tui.ts +960 -0
- package/.allhands/harness/src/commands/validate.ts +143 -0
- package/.allhands/harness/src/commands/validation-tools.ts +108 -0
- package/.allhands/harness/src/hooks/context.ts +1442 -0
- package/.allhands/harness/src/hooks/enforcement.ts +170 -0
- package/.allhands/harness/src/hooks/index.ts +54 -0
- package/.allhands/harness/src/hooks/lifecycle.ts +229 -0
- package/.allhands/harness/src/hooks/notification.ts +104 -0
- package/.allhands/harness/src/hooks/observability.ts +551 -0
- package/.allhands/harness/src/hooks/session.ts +88 -0
- package/.allhands/harness/src/hooks/shared.ts +815 -0
- package/.allhands/harness/src/hooks/transcript-parser.ts +208 -0
- package/.allhands/harness/src/hooks/validation.ts +617 -0
- package/.allhands/harness/src/lib/__tests__/ctags.test.ts +244 -0
- package/.allhands/harness/src/lib/__tests__/docs-validation.test.ts +344 -0
- package/.allhands/harness/src/lib/__tests__/mcp-runtime.test.ts +190 -0
- package/.allhands/harness/src/lib/__tests__/schema.test.ts +861 -0
- package/.allhands/harness/src/lib/base-command.ts +198 -0
- package/.allhands/harness/src/lib/cli-daemon.ts +343 -0
- package/.allhands/harness/src/lib/compaction.ts +313 -0
- package/.allhands/harness/src/lib/ctags.ts +497 -0
- package/.allhands/harness/src/lib/docs-validation.ts +907 -0
- package/.allhands/harness/src/lib/event-loop.ts +662 -0
- package/.allhands/harness/src/lib/flows.ts +155 -0
- package/.allhands/harness/src/lib/git.ts +276 -0
- package/.allhands/harness/src/lib/knowledge-worker.ts +72 -0
- package/.allhands/harness/src/lib/knowledge.ts +810 -0
- package/.allhands/harness/src/lib/llm.ts +255 -0
- package/.allhands/harness/src/lib/mcp-client.ts +432 -0
- package/.allhands/harness/src/lib/mcp-daemon.ts +486 -0
- package/.allhands/harness/src/lib/mcp-runtime.ts +418 -0
- package/.allhands/harness/src/lib/notification.ts +115 -0
- package/.allhands/harness/src/lib/opencode/index.ts +70 -0
- package/.allhands/harness/src/lib/opencode/profiles.ts +300 -0
- package/.allhands/harness/src/lib/opencode/prompts/codesearch.md +98 -0
- package/.allhands/harness/src/lib/opencode/prompts/knowledge-aggregator.md +67 -0
- package/.allhands/harness/src/lib/opencode/runner.ts +281 -0
- package/.allhands/harness/src/lib/oracle.ts +926 -0
- package/.allhands/harness/src/lib/planning-utils.ts +150 -0
- package/.allhands/harness/src/lib/planning.ts +605 -0
- package/.allhands/harness/src/lib/pr-review.ts +225 -0
- package/.allhands/harness/src/lib/prompts.ts +522 -0
- package/.allhands/harness/src/lib/schema.ts +418 -0
- package/.allhands/harness/src/lib/schemas/agent-profile.ts +141 -0
- package/.allhands/harness/src/lib/schemas/template-vars.ts +138 -0
- package/.allhands/harness/src/lib/session.ts +164 -0
- package/.allhands/harness/src/lib/specs.ts +348 -0
- package/.allhands/harness/src/lib/tldr.ts +829 -0
- package/.allhands/harness/src/lib/tmux.ts +1051 -0
- package/.allhands/harness/src/lib/trace-store.ts +714 -0
- package/.allhands/harness/src/mcp/__tests__/index.test.ts +46 -0
- package/.allhands/harness/src/mcp/_template.ts +47 -0
- package/.allhands/harness/src/mcp/filesystem.ts +33 -0
- package/.allhands/harness/src/mcp/index.ts +69 -0
- package/.allhands/harness/src/mcp/playwright.ts +34 -0
- package/.allhands/harness/src/mcp/xcodebuild.ts +29 -0
- package/.allhands/harness/src/schemas/docs.schema.json +44 -0
- package/.allhands/harness/src/schemas/settings.schema.json +214 -0
- package/.allhands/harness/src/tui/actions.ts +227 -0
- package/.allhands/harness/src/tui/file-viewer-modal.ts +270 -0
- package/.allhands/harness/src/tui/index.ts +1574 -0
- package/.allhands/harness/src/tui/modal.ts +232 -0
- package/.allhands/harness/src/tui/prompts-pane.ts +186 -0
- package/.allhands/harness/src/tui/status-pane.ts +434 -0
- package/.allhands/harness/tsconfig.json +22 -0
- package/.allhands/harness/vitest.config.ts +13 -0
- package/.allhands/pillars.md +33 -0
- package/.allhands/principles.md +88 -0
- package/.allhands/schemas/alignment.yaml +51 -0
- package/.allhands/schemas/documentation.yaml +10 -0
- package/.allhands/schemas/prompt.yaml +92 -0
- package/.allhands/schemas/skill.yaml +34 -0
- package/.allhands/schemas/solution.yaml +131 -0
- package/.allhands/schemas/spec.yaml +67 -0
- package/.allhands/schemas/validation-suite.yaml +49 -0
- package/.allhands/schemas/workflow.yaml +51 -0
- package/.allhands/settings.json +57 -0
- package/.allhands/skills/claude-code-patterns/SKILL.md +60 -0
- package/.allhands/skills/claude-code-patterns/docs/context-hygiene.md +19 -0
- package/.allhands/skills/harness-maintenance/SKILL.md +449 -0
- package/.allhands/skills/harness-maintenance/references/core-architecture.md +187 -0
- package/.allhands/skills/harness-maintenance/references/harness-skills.md +87 -0
- package/.allhands/skills/harness-maintenance/references/knowledge-compounding.md +78 -0
- package/.allhands/skills/harness-maintenance/references/tools-commands-mcp-hooks.md +115 -0
- package/.allhands/skills/harness-maintenance/references/validation-tooling.md +77 -0
- package/.allhands/skills/harness-maintenance/references/writing-flows.md +84 -0
- package/.allhands/validation/browser-automation.md +109 -0
- package/.allhands/validation/xcode-automation.md +195 -0
- package/.allhands/workflows/documentation.md +86 -0
- package/.allhands/workflows/investigation.md +81 -0
- package/.allhands/workflows/milestone.md +91 -0
- package/.allhands/workflows/optimization.md +85 -0
- package/.allhands/workflows/refactor.md +99 -0
- package/.allhands/workflows/triage.md +81 -0
- package/.claude/README.md +1 -0
- package/.claude/agents/explorer.md +10 -0
- package/.claude/agents/researcher.md +11 -0
- package/.claude/agents/task-runner.md +8 -0
- package/.claude/settings.json +231 -0
- package/.env.ai.example +7 -0
- package/.github/workflows/npm-publish.yml +69 -0
- package/.internal.json +45 -0
- package/.tldr/config.json +11 -0
- package/.tldrignore +90 -0
- package/CLAUDE.md +6 -0
- package/README.md +98 -0
- package/bin/sync-cli.js +7552 -0
- package/concerns.md +7 -0
- package/docs/README.md +41 -0
- package/docs/agents/README.md +24 -0
- package/docs/agents/agent-configuration-system.md +86 -0
- package/docs/agents/execution-agents.md +50 -0
- package/docs/agents/knowledge-agents.md +61 -0
- package/docs/agents/orchestration-agent.md +57 -0
- package/docs/agents/planning-agents.md +84 -0
- package/docs/agents/quality-review-agents.md +67 -0
- package/docs/agents/workflow-agent-orchestration.md +69 -0
- package/docs/flows/README.md +44 -0
- package/docs/flows/compounding.md +126 -0
- package/docs/flows/coordination.md +72 -0
- package/docs/flows/core-harness-integration.md +63 -0
- package/docs/flows/documentation-orchestration.md +98 -0
- package/docs/flows/e2e-test-plan-building.md +83 -0
- package/docs/flows/emergent-refinement.md +104 -0
- package/docs/flows/flow-authoring-and-mcp-tools.md +89 -0
- package/docs/flows/judge-reviewing.md +112 -0
- package/docs/flows/plan-deepening-and-research.md +107 -0
- package/docs/flows/plan-review-jury.md +114 -0
- package/docs/flows/pr-reviewing.md +54 -0
- package/docs/flows/prompt-task-execution.md +119 -0
- package/docs/flows/spec-planning.md +162 -0
- package/docs/flows/type-specific-scoping-flows.md +49 -0
- package/docs/flows/validation-and-skills-integration.md +145 -0
- package/docs/flows/wip/wip-flows.md +102 -0
- package/docs/harness/README.md +23 -0
- package/docs/harness/agent-profiles.md +84 -0
- package/docs/harness/cli/README.md +24 -0
- package/docs/harness/cli/cli-entry-and-command-discovery.md +91 -0
- package/docs/harness/cli/docs-command.md +87 -0
- package/docs/harness/cli/knowledge-command.md +91 -0
- package/docs/harness/cli/minor-cli-commands.md +65 -0
- package/docs/harness/cli/oracle-command.md +113 -0
- package/docs/harness/cli/planning-command.md +95 -0
- package/docs/harness/cli/schema-and-validation-commands.md +154 -0
- package/docs/harness/cli/search-commands.md +97 -0
- package/docs/harness/cli/spawn-command.md +136 -0
- package/docs/harness/cli/specs-command.md +102 -0
- package/docs/harness/cli/tools-command.md +122 -0
- package/docs/harness/cli/trace-command.md +122 -0
- package/docs/harness/cli-daemon.md +92 -0
- package/docs/harness/event-loop.md +184 -0
- package/docs/harness/hooks/README.md +15 -0
- package/docs/harness/hooks/context-hooks.md +96 -0
- package/docs/harness/hooks/lifecycle-and-observability-hooks.md +135 -0
- package/docs/harness/hooks/validation-hooks.md +97 -0
- package/docs/harness/test-harness.md +149 -0
- package/docs/harness/tui.md +176 -0
- package/docs/memories.md +20 -0
- package/docs/solutions/agentic-issues/premature-agent-deletion-tui-action-dependency-20260130.md +49 -0
- package/docs/solutions/agentic-issues/ref-anchor-scope-mismatch-skill-references-20260131.md +55 -0
- package/docs/solutions/agentic-issues/tautological-tests-routing-20260131.md +52 -0
- package/docs/solutions/integration_issue/blocktool-output-format-mismatch-hook-runner-20260130.md +52 -0
- package/docs/solutions/integration_issue/dual-validation-path-divergence-schema-20260130.md +66 -0
- package/docs/solutions/security-issues/unsanitized-domain-path-join-20260131.md +52 -0
- package/docs/solutions/test-failures/event-loop-mock-ordering-checkAgentWindows-20260130.md +63 -0
- package/docs/sync-cli/README.md +19 -0
- package/docs/sync-cli/cli-entrypoint-and-commands.md +39 -0
- package/docs/sync-cli/commands/README.md +11 -0
- package/docs/sync-cli/commands/pull-manifest-command.md +36 -0
- package/docs/sync-cli/commands/push-command.md +84 -0
- package/docs/sync-cli/commands/sync-command.md +71 -0
- package/docs/sync-cli/systems/README.md +14 -0
- package/docs/sync-cli/systems/git-and-github-integration.md +49 -0
- package/docs/sync-cli/systems/interactive-ui.md +43 -0
- package/docs/sync-cli/systems/manifest-and-distribution.md +51 -0
- package/docs/sync-cli/systems/path-resolution.md +42 -0
- package/package.json +46 -0
- package/scripts/install-shim.sh +40 -0
- package/scripts/pre-pack.sh +25 -0
- package/specs/harness-maintenance-skill.spec.md +138 -0
- package/specs/roadmap/git-spec-lifecycle-management.spec.md +113 -0
- package/specs/sync-init-flag.spec.md +117 -0
- package/specs/unified-workflow-orchestration.spec.md +250 -0
- package/specs/validation-tooling-practice.spec.md +98 -0
- package/specs/workflow-domain-configuration.spec.md +265 -0
- package/src/commands/pull-manifest.ts +31 -0
- package/src/commands/push.ts +344 -0
- package/src/commands/sync.ts +289 -0
- package/src/lib/constants.ts +10 -0
- package/src/lib/dotfiles.ts +36 -0
- package/src/lib/fs-utils.ts +18 -0
- package/src/lib/gh.ts +40 -0
- package/src/lib/git.ts +63 -0
- package/src/lib/gitignore.ts +167 -0
- package/src/lib/manifest.ts +121 -0
- package/src/lib/marker-sync.ts +39 -0
- package/src/lib/paths.ts +38 -0
- package/src/lib/target-lines.ts +66 -0
- package/src/lib/ui.ts +78 -0
- package/src/sync-cli.ts +120 -0
- package/target-lines.json +23 -0
- package/tsconfig.json +20 -0
|
@@ -0,0 +1,579 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tools Command - MCP server integrations with session management.
|
|
3
|
+
*
|
|
4
|
+
* Commands:
|
|
5
|
+
* - ah tools list - List all available MCP servers
|
|
6
|
+
* - ah tools <server> - List tools on a server
|
|
7
|
+
* - ah tools <server>:<tool> [args] - Call a specific tool
|
|
8
|
+
* - ah tools <server> --help-tool - Show help for server
|
|
9
|
+
* - ah tools <server>:<tool> --help-tool - Show help for specific tool
|
|
10
|
+
*
|
|
11
|
+
* Session management (stateful servers only):
|
|
12
|
+
* - ah tools <server> --restart - Restart session (recovery from bad state)
|
|
13
|
+
* - ah tools --sessions - List all active sessions
|
|
14
|
+
* - ah tools --shutdown-daemon - Shutdown the daemon for this AGENT_ID
|
|
15
|
+
*
|
|
16
|
+
* Session Lifecycle:
|
|
17
|
+
* - Sessions are auto-started on first tool call (no --start needed)
|
|
18
|
+
* - Sessions auto-cleanup after inactivity timeout (no --stop needed)
|
|
19
|
+
* - Use --restart only if a server gets into a bad state
|
|
20
|
+
*
|
|
21
|
+
* Session isolation via AGENT_ID:
|
|
22
|
+
* - AGENT_ID=<id> ah tools ... - Use specific agent session
|
|
23
|
+
* - Default AGENT_ID is "default"
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
import { Command } from 'commander';
|
|
27
|
+
import {
|
|
28
|
+
callTool,
|
|
29
|
+
discoverTools,
|
|
30
|
+
getAgentId,
|
|
31
|
+
getDaemonInfo,
|
|
32
|
+
isDaemonRunning,
|
|
33
|
+
listSessions,
|
|
34
|
+
restartServer,
|
|
35
|
+
shutdownDaemon,
|
|
36
|
+
} from '../lib/mcp-client.js';
|
|
37
|
+
import { tracedAction } from '../lib/base-command.js';
|
|
38
|
+
import {
|
|
39
|
+
DAEMON_DEFAULT_MCP_TIMEOUT,
|
|
40
|
+
formatToolHelp,
|
|
41
|
+
type McpServerConfig,
|
|
42
|
+
type McpToolSchema,
|
|
43
|
+
} from '../lib/mcp-runtime.js';
|
|
44
|
+
import { discoverServers, getServer } from '../mcp/index.js';
|
|
45
|
+
|
|
46
|
+
export function register(program: Command): void {
|
|
47
|
+
program
|
|
48
|
+
.command('tools [target]')
|
|
49
|
+
.description('MCP tool integrations with session management')
|
|
50
|
+
.option('--json', 'Output as JSON')
|
|
51
|
+
.option('--help-tool', 'Show help for the target')
|
|
52
|
+
.option('--list', 'List all available MCP servers')
|
|
53
|
+
.option('--sessions', 'List all active sessions')
|
|
54
|
+
.option('--restart', 'Restart server session (recovery from bad state)')
|
|
55
|
+
.option('--shutdown-daemon', 'Shutdown the daemon for this AGENT_ID')
|
|
56
|
+
.allowUnknownOption(true)
|
|
57
|
+
.action(tracedAction('tools', async (target: string | undefined, options: {
|
|
58
|
+
json?: boolean;
|
|
59
|
+
helpTool?: boolean;
|
|
60
|
+
list?: boolean;
|
|
61
|
+
sessions?: boolean;
|
|
62
|
+
restart?: boolean;
|
|
63
|
+
shutdownDaemon?: boolean;
|
|
64
|
+
}, cmd: Command) => {
|
|
65
|
+
const agentId = getAgentId();
|
|
66
|
+
|
|
67
|
+
// Shutdown daemon
|
|
68
|
+
if (options.shutdownDaemon) {
|
|
69
|
+
await handleShutdownDaemon(agentId, options.json);
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// List all active sessions
|
|
74
|
+
if (options.sessions) {
|
|
75
|
+
await handleListSessions(agentId, options.json);
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// List all servers
|
|
80
|
+
if (options.list || !target) {
|
|
81
|
+
await handleListServers(agentId, options.json);
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Parse target: "server" or "server:tool"
|
|
86
|
+
const [serverName, toolName] = target.split(':');
|
|
87
|
+
|
|
88
|
+
const config = await getServer(serverName);
|
|
89
|
+
if (!config) {
|
|
90
|
+
const servers = await discoverServers();
|
|
91
|
+
const available = Array.from(servers.keys()).join(', ');
|
|
92
|
+
const msg = `Unknown server: ${serverName}. Available: ${available || 'none'}`;
|
|
93
|
+
if (options.json) {
|
|
94
|
+
console.log(JSON.stringify({ success: false, error: msg }));
|
|
95
|
+
} else {
|
|
96
|
+
console.error(`Error: ${msg}`);
|
|
97
|
+
}
|
|
98
|
+
process.exit(1);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Restart command (stateful servers only)
|
|
102
|
+
if (options.restart) {
|
|
103
|
+
if (!config.stateful) {
|
|
104
|
+
const msg = `Server ${serverName} is stateless. --restart only works with stateful servers.`;
|
|
105
|
+
if (options.json) {
|
|
106
|
+
console.log(JSON.stringify({ success: false, error: msg }));
|
|
107
|
+
} else {
|
|
108
|
+
console.error(`Error: ${msg}`);
|
|
109
|
+
}
|
|
110
|
+
process.exit(1);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
await handleSessionRestart(config, agentId, options.json);
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Get tools
|
|
118
|
+
let allTools: McpToolSchema[];
|
|
119
|
+
try {
|
|
120
|
+
allTools = await discoverTools(config, agentId);
|
|
121
|
+
} catch (e) {
|
|
122
|
+
const error = e instanceof Error ? e.message : String(e);
|
|
123
|
+
if (options.json) {
|
|
124
|
+
console.log(JSON.stringify({ success: false, error }));
|
|
125
|
+
} else {
|
|
126
|
+
console.error(`Error discovering tools: ${error}`);
|
|
127
|
+
}
|
|
128
|
+
process.exit(1);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// List tools on server (no tool specified)
|
|
132
|
+
if (!toolName) {
|
|
133
|
+
if (options.helpTool) {
|
|
134
|
+
// Full help with session management info
|
|
135
|
+
if (options.json) {
|
|
136
|
+
console.log(JSON.stringify({
|
|
137
|
+
success: true,
|
|
138
|
+
server: config.name,
|
|
139
|
+
description: config.description,
|
|
140
|
+
stateful: config.stateful ?? false,
|
|
141
|
+
stateful_session_timeout: config.stateful_session_timeout ?? DAEMON_DEFAULT_MCP_TIMEOUT,
|
|
142
|
+
agentId,
|
|
143
|
+
tools: allTools,
|
|
144
|
+
}, null, 2));
|
|
145
|
+
} else {
|
|
146
|
+
const helpText = await formatServerHelpWithSession(config, allTools, agentId);
|
|
147
|
+
console.log(helpText);
|
|
148
|
+
}
|
|
149
|
+
} else {
|
|
150
|
+
// Brief tool list
|
|
151
|
+
await handleListTools(config, allTools, options.json);
|
|
152
|
+
}
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Find the specific tool
|
|
157
|
+
const tool = allTools.find((t) => t.name === toolName);
|
|
158
|
+
if (!tool) {
|
|
159
|
+
const available = allTools.map((t) => t.name).join(', ');
|
|
160
|
+
const msg = `Unknown tool: ${toolName} on ${serverName}. Available: ${available}`;
|
|
161
|
+
if (options.json) {
|
|
162
|
+
console.log(JSON.stringify({ success: false, error: msg }));
|
|
163
|
+
} else {
|
|
164
|
+
console.error(`Error: ${msg}`);
|
|
165
|
+
}
|
|
166
|
+
process.exit(1);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Help for specific tool
|
|
170
|
+
if (options.helpTool) {
|
|
171
|
+
if (options.json) {
|
|
172
|
+
console.log(JSON.stringify({
|
|
173
|
+
success: true,
|
|
174
|
+
server: config.name,
|
|
175
|
+
tool,
|
|
176
|
+
hint: config.toolHints?.[toolName],
|
|
177
|
+
}, null, 2));
|
|
178
|
+
} else {
|
|
179
|
+
console.log(formatToolHelp(tool, config.toolHints?.[toolName]));
|
|
180
|
+
}
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Call the tool - parse remaining args as params
|
|
185
|
+
const params = parseToolArgs(cmd.args.slice(1)); // Skip the target arg
|
|
186
|
+
|
|
187
|
+
try {
|
|
188
|
+
const result = await callTool(config, toolName, params, agentId);
|
|
189
|
+
if (options.json) {
|
|
190
|
+
console.log(JSON.stringify({ success: true, result }, null, 2));
|
|
191
|
+
} else {
|
|
192
|
+
if (typeof result === 'string') {
|
|
193
|
+
console.log(result);
|
|
194
|
+
} else {
|
|
195
|
+
console.log(JSON.stringify(result, null, 2));
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
} catch (e) {
|
|
199
|
+
const error = e instanceof Error ? e.message : String(e);
|
|
200
|
+
if (options.json) {
|
|
201
|
+
console.log(JSON.stringify({ success: false, error }));
|
|
202
|
+
} else {
|
|
203
|
+
console.error(`Error: ${error}`);
|
|
204
|
+
}
|
|
205
|
+
process.exit(1);
|
|
206
|
+
}
|
|
207
|
+
}));
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Format server help with session management info.
|
|
212
|
+
*/
|
|
213
|
+
async function formatServerHelpWithSession(
|
|
214
|
+
config: McpServerConfig,
|
|
215
|
+
tools: McpToolSchema[],
|
|
216
|
+
agentId: string
|
|
217
|
+
): Promise<string> {
|
|
218
|
+
const lines: string[] = [];
|
|
219
|
+
|
|
220
|
+
// Header
|
|
221
|
+
lines.push(`${config.name} - ${config.description}`);
|
|
222
|
+
|
|
223
|
+
if (config.stateful) {
|
|
224
|
+
const timeoutMs = config.stateful_session_timeout ?? DAEMON_DEFAULT_MCP_TIMEOUT;
|
|
225
|
+
const timeoutSec = Math.round(timeoutMs / 1000);
|
|
226
|
+
|
|
227
|
+
lines.push('');
|
|
228
|
+
lines.push(' [STATEFUL] This server maintains session state between calls.');
|
|
229
|
+
lines.push('');
|
|
230
|
+
lines.push(' Session Lifecycle:');
|
|
231
|
+
lines.push(' - Auto-starts on first tool call');
|
|
232
|
+
lines.push(` - Auto-closes after ${timeoutSec}s of inactivity`);
|
|
233
|
+
lines.push(' - Use --restart if server gets into a bad state');
|
|
234
|
+
lines.push('');
|
|
235
|
+
lines.push(' Commands:');
|
|
236
|
+
lines.push(' --restart Restart session (for recovery)');
|
|
237
|
+
lines.push('');
|
|
238
|
+
lines.push(' Session Isolation (set AGENT_ID env var for parallel sessions):');
|
|
239
|
+
lines.push(` Current AGENT_ID: ${agentId}`);
|
|
240
|
+
|
|
241
|
+
const daemonInfo = await getDaemonInfo(agentId);
|
|
242
|
+
if (daemonInfo.running) {
|
|
243
|
+
lines.push(` Daemon: running (PID: ${daemonInfo.pid})`);
|
|
244
|
+
if (daemonInfo.sessions && daemonInfo.sessions.includes(config.name)) {
|
|
245
|
+
lines.push(` Session: active`);
|
|
246
|
+
} else {
|
|
247
|
+
lines.push(' Session: not started (will start on first call)');
|
|
248
|
+
}
|
|
249
|
+
} else {
|
|
250
|
+
lines.push(' Daemon: not running (will start on first call)');
|
|
251
|
+
}
|
|
252
|
+
} else {
|
|
253
|
+
lines.push('');
|
|
254
|
+
lines.push(' [STATELESS] Each tool call is independent.');
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
lines.push('');
|
|
258
|
+
lines.push(`Tools (${tools.length}):`);
|
|
259
|
+
|
|
260
|
+
for (const tool of tools) {
|
|
261
|
+
lines.push('');
|
|
262
|
+
lines.push(formatToolHelp(tool, config.toolHints?.[tool.name]));
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
return lines.join('\n');
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* List all available MCP servers.
|
|
270
|
+
*/
|
|
271
|
+
async function handleListServers(agentId: string, json?: boolean): Promise<void> {
|
|
272
|
+
const servers = await discoverServers();
|
|
273
|
+
|
|
274
|
+
if (servers.size === 0) {
|
|
275
|
+
if (json) {
|
|
276
|
+
console.log(JSON.stringify({ success: true, servers: [] }));
|
|
277
|
+
} else {
|
|
278
|
+
console.log('No MCP servers configured.');
|
|
279
|
+
console.log('Add servers in .allhands/harness/src/mcp/ (copy _template.ts)');
|
|
280
|
+
}
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
const daemonRunning = isDaemonRunning(agentId);
|
|
285
|
+
|
|
286
|
+
if (json) {
|
|
287
|
+
const serverList = Array.from(servers.values()).map((s) => ({
|
|
288
|
+
name: s.name,
|
|
289
|
+
description: s.description,
|
|
290
|
+
type: s.type ?? 'stdio',
|
|
291
|
+
stateful: s.stateful ?? false,
|
|
292
|
+
stateful_session_timeout: s.stateful_session_timeout ?? DAEMON_DEFAULT_MCP_TIMEOUT,
|
|
293
|
+
}));
|
|
294
|
+
console.log(JSON.stringify({
|
|
295
|
+
success: true,
|
|
296
|
+
agentId,
|
|
297
|
+
daemonRunning,
|
|
298
|
+
servers: serverList,
|
|
299
|
+
}, null, 2));
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
console.log(`Available MCP servers (AGENT_ID: ${agentId}):`);
|
|
304
|
+
console.log('');
|
|
305
|
+
for (const config of servers.values()) {
|
|
306
|
+
const stateLabel = config.stateful ? '[STATEFUL]' : '[STATELESS]';
|
|
307
|
+
console.log(` ${config.name} ${stateLabel}`);
|
|
308
|
+
console.log(` ${config.description}`);
|
|
309
|
+
}
|
|
310
|
+
console.log('');
|
|
311
|
+
console.log('Usage:');
|
|
312
|
+
console.log(' ah tools <server> List tools on server');
|
|
313
|
+
console.log(' ah tools <server>:<tool> Call a tool');
|
|
314
|
+
console.log(' ah tools <server> --help-tool Show detailed help');
|
|
315
|
+
console.log(' ah tools --sessions List active sessions');
|
|
316
|
+
console.log('');
|
|
317
|
+
console.log('Session isolation: AGENT_ID=<id> ah tools ...');
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* List all active sessions.
|
|
322
|
+
*/
|
|
323
|
+
async function handleListSessions(agentId: string, json?: boolean): Promise<void> {
|
|
324
|
+
const sessions = await listSessions(agentId);
|
|
325
|
+
const daemonInfo = await getDaemonInfo(agentId);
|
|
326
|
+
|
|
327
|
+
if (json) {
|
|
328
|
+
console.log(JSON.stringify({
|
|
329
|
+
success: true,
|
|
330
|
+
agentId,
|
|
331
|
+
daemon: daemonInfo,
|
|
332
|
+
sessions: sessions.map((s) => ({
|
|
333
|
+
server: s.serverName,
|
|
334
|
+
startedAt: s.startedAt.toISOString(),
|
|
335
|
+
lastUsedAt: s.lastUsedAt.toISOString(),
|
|
336
|
+
timeoutMs: s.timeoutMs,
|
|
337
|
+
pid: s.pid,
|
|
338
|
+
})),
|
|
339
|
+
}, null, 2));
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
console.log(`Sessions for AGENT_ID: ${agentId}`);
|
|
344
|
+
console.log('');
|
|
345
|
+
|
|
346
|
+
if (!daemonInfo.running) {
|
|
347
|
+
console.log(' Daemon: not running');
|
|
348
|
+
console.log(' No active sessions.');
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
console.log(` Daemon: running (PID: ${daemonInfo.pid})`);
|
|
353
|
+
console.log('');
|
|
354
|
+
|
|
355
|
+
if (sessions.length === 0) {
|
|
356
|
+
console.log(' No active MCP sessions.');
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
console.log(' Active sessions:');
|
|
361
|
+
for (const session of sessions) {
|
|
362
|
+
const idleMs = Date.now() - session.lastUsedAt.getTime();
|
|
363
|
+
const idleSec = Math.floor(idleMs / 1000);
|
|
364
|
+
const timeoutSec = Math.floor(session.timeoutMs / 1000);
|
|
365
|
+
const remaining = Math.max(0, timeoutSec - idleSec);
|
|
366
|
+
console.log(` ${session.serverName}`);
|
|
367
|
+
console.log(` PID: ${session.pid ?? 'unknown'}, Idle: ${idleSec}s, Timeout in: ${remaining}s`);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* List tools available on a server.
|
|
373
|
+
*/
|
|
374
|
+
async function handleListTools(
|
|
375
|
+
config: McpServerConfig,
|
|
376
|
+
tools: McpToolSchema[],
|
|
377
|
+
json?: boolean
|
|
378
|
+
): Promise<void> {
|
|
379
|
+
if (json) {
|
|
380
|
+
console.log(JSON.stringify({
|
|
381
|
+
success: true,
|
|
382
|
+
server: config.name,
|
|
383
|
+
stateful: config.stateful ?? false,
|
|
384
|
+
tools: tools.map((t) => ({
|
|
385
|
+
name: t.name,
|
|
386
|
+
description: t.description,
|
|
387
|
+
})),
|
|
388
|
+
}, null, 2));
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
const stateLabel = config.stateful ? '[STATEFUL]' : '[STATELESS]';
|
|
393
|
+
console.log(`${config.name} - ${config.description} ${stateLabel}`);
|
|
394
|
+
console.log('');
|
|
395
|
+
console.log(`Tools (${tools.length}):`);
|
|
396
|
+
|
|
397
|
+
for (const tool of tools) {
|
|
398
|
+
// Build parameter signature
|
|
399
|
+
const params: string[] = [];
|
|
400
|
+
const props = tool.inputSchema?.properties ?? {};
|
|
401
|
+
const required = new Set(tool.inputSchema?.required ?? []);
|
|
402
|
+
|
|
403
|
+
for (const [name, schema] of Object.entries(props)) {
|
|
404
|
+
let typeStr = schema.type;
|
|
405
|
+
if (schema.items?.type) {
|
|
406
|
+
typeStr = `${schema.items.type}[]`;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
if (required.has(name)) {
|
|
410
|
+
params.push(`${name}:${typeStr}`);
|
|
411
|
+
} else {
|
|
412
|
+
params.push(`[${name}:${typeStr}]`);
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
console.log(` ${tool.name}(${params.join(', ')})`);
|
|
417
|
+
if (tool.description) {
|
|
418
|
+
console.log(` ${tool.description}`);
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
console.log('');
|
|
423
|
+
console.log('Usage: ah tools ' + config.name + ':<tool> --<param>=<value>');
|
|
424
|
+
|
|
425
|
+
if (config.stateful) {
|
|
426
|
+
console.log('');
|
|
427
|
+
console.log('Session: auto-starts on first call, use --restart for recovery');
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
/**
|
|
432
|
+
* Handle daemon shutdown command.
|
|
433
|
+
*/
|
|
434
|
+
async function handleShutdownDaemon(
|
|
435
|
+
agentId: string,
|
|
436
|
+
json?: boolean
|
|
437
|
+
): Promise<void> {
|
|
438
|
+
const daemonInfo = await getDaemonInfo(agentId);
|
|
439
|
+
|
|
440
|
+
if (!daemonInfo.running) {
|
|
441
|
+
if (json) {
|
|
442
|
+
console.log(JSON.stringify({
|
|
443
|
+
success: true,
|
|
444
|
+
agentId,
|
|
445
|
+
wasRunning: false,
|
|
446
|
+
}, null, 2));
|
|
447
|
+
} else {
|
|
448
|
+
console.log(`Daemon not running (AGENT_ID: ${agentId}).`);
|
|
449
|
+
}
|
|
450
|
+
return;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
await shutdownDaemon(agentId);
|
|
454
|
+
|
|
455
|
+
if (json) {
|
|
456
|
+
console.log(JSON.stringify({
|
|
457
|
+
success: true,
|
|
458
|
+
agentId,
|
|
459
|
+
wasRunning: true,
|
|
460
|
+
pid: daemonInfo.pid,
|
|
461
|
+
}, null, 2));
|
|
462
|
+
} else {
|
|
463
|
+
console.log(`Daemon shutdown (AGENT_ID: ${agentId}, PID: ${daemonInfo.pid}).`);
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
/**
|
|
468
|
+
* Handle session restart command.
|
|
469
|
+
*/
|
|
470
|
+
async function handleSessionRestart(
|
|
471
|
+
config: McpServerConfig,
|
|
472
|
+
agentId: string,
|
|
473
|
+
json?: boolean
|
|
474
|
+
): Promise<void> {
|
|
475
|
+
const result = await restartServer(config, agentId);
|
|
476
|
+
|
|
477
|
+
if (json) {
|
|
478
|
+
console.log(JSON.stringify({
|
|
479
|
+
success: result.success,
|
|
480
|
+
server: config.name,
|
|
481
|
+
agentId,
|
|
482
|
+
pid: result.pid,
|
|
483
|
+
error: result.error,
|
|
484
|
+
}, null, 2));
|
|
485
|
+
return;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
if (result.success) {
|
|
489
|
+
console.log(`Session ${config.name} restarted (AGENT_ID: ${agentId}, MCP PID: ${result.pid ?? 'unknown'}).`);
|
|
490
|
+
} else {
|
|
491
|
+
console.error(`Failed to restart ${config.name}: ${result.error}`);
|
|
492
|
+
process.exit(1);
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
/**
|
|
497
|
+
* Parse tool arguments from command line.
|
|
498
|
+
*
|
|
499
|
+
* Supports:
|
|
500
|
+
* - --param=value
|
|
501
|
+
* - --param value
|
|
502
|
+
* - --flag (boolean true)
|
|
503
|
+
* - JSON string as single argument
|
|
504
|
+
*/
|
|
505
|
+
function parseToolArgs(args: string[]): Record<string, unknown> {
|
|
506
|
+
// Filter out our own flags
|
|
507
|
+
const toolArgs = args.filter((arg) =>
|
|
508
|
+
arg !== '--restart' &&
|
|
509
|
+
arg !== '--json' &&
|
|
510
|
+
arg !== '--help-tool' &&
|
|
511
|
+
arg !== '--list' &&
|
|
512
|
+
arg !== '--sessions' &&
|
|
513
|
+
arg !== '--shutdown-daemon'
|
|
514
|
+
);
|
|
515
|
+
|
|
516
|
+
// Check if first arg is JSON
|
|
517
|
+
if (toolArgs.length === 1 && toolArgs[0].startsWith('{')) {
|
|
518
|
+
try {
|
|
519
|
+
return JSON.parse(toolArgs[0]);
|
|
520
|
+
} catch {
|
|
521
|
+
// Not JSON, parse as flags
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
const params: Record<string, unknown> = {};
|
|
526
|
+
let i = 0;
|
|
527
|
+
|
|
528
|
+
while (i < toolArgs.length) {
|
|
529
|
+
const arg = toolArgs[i];
|
|
530
|
+
|
|
531
|
+
if (arg.startsWith('--')) {
|
|
532
|
+
const withoutDashes = arg.slice(2);
|
|
533
|
+
|
|
534
|
+
if (withoutDashes.includes('=')) {
|
|
535
|
+
// --param=value
|
|
536
|
+
const [key, ...valueParts] = withoutDashes.split('=');
|
|
537
|
+
const value = valueParts.join('=');
|
|
538
|
+
params[key] = parseValue(value);
|
|
539
|
+
} else if (i + 1 < toolArgs.length && !toolArgs[i + 1].startsWith('--')) {
|
|
540
|
+
// --param value
|
|
541
|
+
params[withoutDashes] = parseValue(toolArgs[i + 1]);
|
|
542
|
+
i++;
|
|
543
|
+
} else {
|
|
544
|
+
// --flag (boolean)
|
|
545
|
+
params[withoutDashes] = true;
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
i++;
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
return params;
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
/**
|
|
556
|
+
* Parse a string value to appropriate type.
|
|
557
|
+
*/
|
|
558
|
+
function parseValue(value: string): unknown {
|
|
559
|
+
// Boolean
|
|
560
|
+
if (value === 'true') return true;
|
|
561
|
+
if (value === 'false') return false;
|
|
562
|
+
|
|
563
|
+
// Number
|
|
564
|
+
if (/^-?\d+(\.\d+)?$/.test(value)) {
|
|
565
|
+
return parseFloat(value);
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
// JSON array or object
|
|
569
|
+
if ((value.startsWith('[') && value.endsWith(']')) ||
|
|
570
|
+
(value.startsWith('{') && value.endsWith('}'))) {
|
|
571
|
+
try {
|
|
572
|
+
return JSON.parse(value);
|
|
573
|
+
} catch {
|
|
574
|
+
// Return as string
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
return value;
|
|
579
|
+
}
|